Slurm管理和使用集群节点资源主要分为四个环节:分别是初始化节点资源、更新节点资源、测试节点资源可用、实际分配节点资源。
slurmctld初始化时解析节点配置文件,借助几个全局数据结构(select插件中也有几个数据结构):
node_record_table_ptr
节点数组,保存所有节点描述符
node_hash_table
节点哈希表,保存所有节点描述符,加快查找
node_record_count
全部节点计数
config_list
节点配置链表,对应配置文件NodeName一条记录,包含相同配置所有节点位图。(节点描述符struct node_record)
part_list
分区配置链表,对应配置文件PartitionName一条记录,包含分区所有节点位图。(分区描述符struct part_record)
last_node_update
节点信息更新时间戳
last_part_update
分区信息更新时间戳
和全局节点位图:
avail_node_bitmap
bitmap of available nodes
booting_node_bitmap
bitmap of booting nodes
cg_node_bitmap
bitmap of completing nodes
future_node_bitmap
bitmap of FUTURE nodes
idle_node_bitmap
bitmap of idle nodes
power_node_bitmap
bitmap of powered down node
share_node_bitmap
bitmap of sharable nodes
up_node_bitmap
bitmap of non-down nodes
rs_node_bitmap
bitmap of resuming nodes
//节点描述符,记录单个节点配置 struct node_record { uint32_t magic; /* magic cookie 用于数据完整性 */ char *name; /* 节点名. NULL==defunct */ uint32_t next_state; /* 重启后状态 */ char *node_hostname; /* 节点hostname */ uint32_t node_state; /* 枚举节点状态 node_states, ORed with * NODE_STATE_NO_RESPOND if not * responding */ bool not_responding; /* 设置该值如果没有响应,日志记录后清除 */ time_t boot_req_time; /* 节点启动请求的时间 */ time_t boot_time; /* 节点启动时间,由up_time计算 */ uint32_t cpu_bind; /* 默认 CPU 绑定类型 */ time_t slurmd_start_time; /* slurmd启动时间 */ time_t last_response; /* 节点最后响应时间 */ time_t last_idle; /* 节点最后变空闲时间 */ uint16_t cpus; /* 节点上的处理器数 */ uint16_t boards; /* 配置的boards数 */ uint16_t sockets; /* 每个节点sockets数 */ uint16_t cores; /* 每个socket的核心数 */ char *cpu_spec_list; /* node's specialized cpus 节点的专用cpu*/ uint16_t core_spec_cnt; /* number of specialized cores on node节点的专用cpu数量*/ uint16_t threads; /* 每个核心线程数 */ uint64_t real_memory; /* MB real memory on the node */ uint64_t mem_spec_limit; /* MB memory limit for specialization */ uint32_t tmp_disk; /* MB total disk in TMP_FS */ uint32_t up_time; /* 节点启动后的秒数 */ struct config_record *config_ptr; /* 配置规格指针,指向节点所属的节点配置文件里面相应行对应的节点配置描述符 */ uint16_t part_cnt; /* 相关分区的数量 */ struct part_record **part_pptr; /* 指向与此节点关联的分区的指针数组 */ char *comm_name; /* 节点的通信路径名 */ uint16_t port; /* slurmd的TCP端口号 */ slurm_addr_t slurm_addr; /* 网络地址 */ uint16_t comp_job_cnt; /* count of jobs completing on node */ uint16_t run_job_cnt; /* 节点上运行的作业数 */ uint16_t sus_job_cnt; /* 节点上挂起的作业数 */ uint16_t no_share_job_cnt; /* 运行中不共享节点的作业数 */ char *reason; /* 节点DOWN或者DRAINING的原因 */ time_t reason_time; /* 设置原因时的时间戳,如果未设置原因,则忽略 */ uint32_t reason_uid; /* 设置原因的用户,如果没有设置原因,则忽略 */ char *features; /* 节点的可用功能仅用于状态保存/还原,不用于调度目的 */ char *features_act; /* 节点的活动特性仅用于状态保存/恢复,不用于调度目的 */ char *gres; /* 节点的通用资源仅用于状态保存/恢复,不用于调度目的 */ List gres_list; /* 由插件管理的gres状态信息列表 */ uint64_t sched_weight; /* 用于调度目的的节点权重。cons_tres使用 */ uint32_t weight; /* 原始权值,仅用于状态保存/恢复,不用于调度目的。*/ char *arch; /* 计算机体系结构 */ char *os; /* 正在运行的操作系统 */ struct node_record *node_next; /* 下一个具有相同哈希索引的项 */ uint32_t node_rank; /* 基于节点名称的希尔伯特数,或用于按位置对节点排序的其他序列号,不需要保存/恢复 */ #ifdef HAVE_ALPS_CRAY uint32_t basil_node_id; /* Cray-XT BASIL node ID, * no need to save/restore */ time_t down_time; /* When first set to DOWN state */ #endif /* HAVE_ALPS_CRAY */ acct_gather_energy_t *energy; /* 能耗数据 */ ext_sensors_data_t *ext_sensors; /* 外部传感器数据 */ power_mgmt_data_t *power; /* 电源管理数据 */ dynamic_plugin_data_t *select_nodeinfo; /* 不透明的数据结构,使用select_g_get_nodeinfo()来访问内容 */ uint32_t cpu_load; /* CPU 负载 * 100 */ time_t cpu_load_time; /* cpu_load最后设置的时间 */ uint64_t free_mem; /* 空闲内存 in MiB */ time_t free_mem_time; /* 上次设置free_mem的时间 */ uint16_t protocol_version; /* Slurm版本号 */ char *version; /* Slurm版本 */ bitstr_t *node_spec_bitmap; /* node cpu specialization bitmap */ uint32_t owner; /* 允许使用节点的用户或者NO_VAL */ uint16_t owner_job_cnt; /* "owner"的独占作业数 */ char *tres_str; /* 该节点有的tres, 1=2,2=3690,5=2*/ char *tres_fmt_str; /* 该节点有的tres, cpu=2,mem=3690M,billing=2*/ uint64_t *tres_cnt; /* tres this node has. NO_PACK*/ char *mcs_label; /* mcs_label if mcs plugin in use */ }; //分区描述符,记录单个分区配置 struct part_record { uint32_t magic; /* 测试数据完整性的magic cookie,不要用字母表示*/ char *allow_accounts; /* 逗号分隔的帐户列表,NULL表示全部 */ char **allow_account_array; /* NULL terminated list of allowed accounts */ char *allow_alloc_nodes;/* 逗号分隔的允许分配节点列表,NULL表示全部*/ char *allow_groups; /* 逗号分隔的组列表,NULL表示全部 */ uid_t *allow_uids; /* zero terminated list of allowed user IDs */ char *allow_qos; /* 逗号分隔的qos列表,NULL表示全部 */ bitstr_t *allow_qos_bitstr; /* (DON'T PACK) assocaited with * char *allow_qos but used internally */ char *alternate; /* 备用分区名称 */ double *billing_weights; /* array of TRES billing weights */ char *billing_weights_str;/* per TRES billing weight string */ uint32_t cpu_bind; /* 默认CPU绑定类型 */ uint64_t def_mem_per_cpu; /* default MB memory per allocated CPU */ uint32_t default_time; /* minutes, NO_VAL or INFINITE */ char *deny_accounts; /* comma delimited list of denied accounts */ char **deny_account_array; /* NULL terminated list of denied accounts */ char *deny_qos; /* comma delimited list of denied qos */ bitstr_t *deny_qos_bitstr; /* (DON'T PACK) associated with * char *deny_qos but used internallly */ uint16_t flags; /* see PART_FLAG_* in slurm.h分区标志位*/ uint32_t grace_time; /* default preempt grace time in seconds */ List job_defaults_list; /* List of job_defaults_t elements */ uint32_t max_cpus_per_node; /* maximum allocated CPUs per node */ uint64_t max_mem_per_cpu; /* maximum MB memory per allocated CPU */ uint32_t max_nodes; /* per job or INFINITE */ uint32_t max_nodes_orig;/* unscaled value (c-nodes on BlueGene) */ uint16_t max_share; /* number of jobs to gang schedule,分区配置项OverSubscribe */ uint32_t max_time; /* minutes or INFINITE */ uint32_t min_nodes; /* per job */ uint32_t min_nodes_orig;/* unscaled value (c-nodes on BlueGene) */ char *name; /* 分区名 */ bitstr_t *node_bitmap; /* 分区中的节点位图 */ char *nodes; /* 逗号分隔的节点列表名称 */ double norm_priority; /* 作业的归一化调度优先级(DON'T PACK) */ uint16_t over_time_limit; /* job's time limit can be exceeded by this * number of minutes before cancellation */ uint16_t preempt_mode; /* See PREEMPT_MODE_* in slurm/slurm.h抢占模式*/ uint16_t priority_job_factor; /* 作业优先级权重因子 */ uint16_t priority_tier; /* 用于调度和抢占的层 */ char *qos_char; /* 请求的QOS来自 slurm.conf */ slurmdb_qos_rec_t *qos_ptr; /* pointer to the quality of * service record attached to this * partition confirm the value before use */ uint16_t state_up; /* See PARTITION_* states in slurm.h 分区状态*/ uint32_t total_nodes; /* 分区中的节点总数 */ uint32_t total_cpus; /* 分区中的cpu总数 */ uint32_t max_cpu_cnt; /* 分区单个节点上的最大cpus数 */ uint32_t max_core_cnt; /* 分区单个节点上的最大核心数 */ uint16_t cr_type; /* Custom CR values for partition (if supported by select plugin) */ uint64_t *tres_cnt; /* array of total TRES in partition. NO_PACK */ char *tres_fmt_str; /* str of configured TRES in partition,类似cpu=4,mem=2G,node=2,billing=4*/ };slurmctld初始化过程中初始化节点配置过程:
main(src\slurmctld\controller.c) read_slurm_conf(recover, false) _build_all_nodeline_info//初始化节点配置 build_all_nodeline_info create_config_record//每个config_record对应于slurm.conf文件中的NodeName一行,通常描述大量节点的配置。添加进全局配置链表config_list _build_single_nodeline_info//遍历NodeName一行所有节点 create_node_record//(src\common\node_conf.c)初始化单个node_ptr,同时更新全局节点链表node_record_table_ptr和节点哈希表node_hash_table _build_all_partitionline_info//初始化分区配置 _build_single_partitionline_info create_part_record//解析PartitionName行数据,逐个初始化分区配置part_record,添加到全局part_list rehash_node//重建全局节点哈希表node_hash_table _build_bitmaps_pre_select//设置分区链表和节点配置链表中对应节点位图 select_g_node_init通过资源选择select插件重新/初始化#全局#节点记录数据结构 select_p_node_init(src\plugins\select\cons_res\select_cons_res.c) cr_init_global_core_data//初始化cpu核心相关全局数据 _build_bitmaps//构建avail_node_bitmap/idle_node_bitmap/share_node_bitmap等全局节点位图slurmd向slurmctld注册时会更新节点部分数据。
slurmctld_req(src\slurmctld\proc_req.c) _slurm_rpc_node_registration//MESSAGE_NODE_REGISTRATION_STATUS validate_node_specs//1.获取节点描述符;2.逐项更新提交作业时,会调用资源选择select插件,测试能否为作业分配节点但并不实际分配节点。
slurmctld_req(src\slurmctld\proc_req.c) _slurm_rpc_submit_batch_job//REQUEST_SUBMIT_BATCH_JOB job_allocate(src\slurmctld\job_mgr.c) _job_create//创建作业对象 set_job_prio//设置作业优先级 slurm_sched_g_initial_priority//调用调度插件 slurm_sched_p_initial_priority(src\plugins\sched\backfill\backfill_wrapper.c) priority_g_set//调用优先级插件 priority_p_set(src\plugins\priority\multifactor\priority_multifactor.c) _get_priority_internal//实际计算作业优先级,输出作业第一条日志 _select_nodes_parts//为作业选定运行节点 select_nodes(test_only为true)//重要,调度过程还会调用该函数 _build_node_list//1.构建节点集 _get_req_features//2.选择最合适节点。可用节点位图由select_bitmap带回来,可用节点数保存在job_ptr→node_cnt_wag _pick_best_nodes//输出第二条日志 _resolve_shared_status//确定作业是否可以共享节点。返回值1共享0独占 select_g_job_test//初始化select插件,SelectType=select/cons_res select_p_job_test(src\plugins\select\cons_res\select_cons_res.c,输出第三条日志) _test_only(三分支mode == SELECT_MODE_TEST_ONLY) cr_job_test//作业创建时_select_nodes可以返回节点数,但是到这就释放并返回了,其实只是判断了下后面啥也没干。作业调度时会走里面六个步骤。 allocate_nodes//3.实际分配节点,创建作业时由于设置了test_only跳过该步。 sched_debug2以上可以看出在创建作业过程中测试了可以获得作业可用节点的数量,作业可用节点位图由_get_req_features函数的参数select_bitmap带回来,位图转换成的节点的数量保存在job_ptr→node_cnt_wag。
这个过程中并没有为作业成功分配具体节点。需要继续分析后台调度和backfill调度代码。
作业调度执行时,会具体分配节点。 使用gdb跟踪代码,提交并创建作业结束后,可能执行schedule后台调度代码:
也可能执行backfill回填调度代码,
执行完调度代码后,作业会变成运行状态,说明被调度执行。
从上面三张截图可以看出来,调度的过程中还是会调用select_nodes函数为作业分配节点。
后台调度代码调用过程:
main(src\slurmctld\controller.c) _slurmctld_background schedule _schedule/_sched_agent select_nodes _get_req_features _pick_best_nodes select_g_job_test select_p_job_test _run_now(mode == SELECT_MODE_RUN_NOW) cr_job_test回填调度代码调用过程:
init(src\plugins\sched\backfill\backfill_wrapper.c) slurm_thread_create(&backfill_thread, backfill_agent, NULL);//创建回填调度代理线程 backfill_agent _attempt_backfill _try_sched//对应日志 backfill:entering _try_sched for JobId=11901178 select_g_job_test select_p_job_test _will_run_test(mode == SELECT_MODE_WILL_RUN) cr_job_test _start_job select_nodes _get_req_features _pick_best_nodes//对应日志 _pick_best_nodes: JobId=11901178 idle_nodes 5 share_nodes 1528 select_g_job_test select_p_job_test _run_now(mode == SELECT_MODE_RUN_NOW) cr_job_test调度作业过程与创建作业过程不同,由于select_mode为false,_pick_best_nodes内部设置select_node为SELECT_MODE_RUN_NOW,select_p_job_test会走三个分支中的_run_now分支,分配成功的节点位图由select_bitmap带回。
在select_nodes函数中select_bitmap被保存在作业结构体的job_ptr->node_bitmap中,然后设置作业状态为RUNNING。之后调用allocate_nodes等函数为作业分配节点,然后继续走调度剩余流程。
作业调度过程中为作业分配节点资源时最终会调用cr_job_test:
cr_job_test _verify_node_state//作用不大 _select_nodes//第一个是测试用,第二个实际选择资源 _get_res_usage//1.从每个可用节点获取此作业的资源使用情况,cpu_cnt重要 _can_job_run_on_node//返回此节点可以使用的cpu的数量以及可用于分配的资源的位图,问题大概率出在这个函数上 _allocate_cores _allocate_sc//重要 //2.中间有一步根据_get_res_usage返回结果过滤掉所有资源不足的节点 _choose_nodes//3.为作业选择最佳节点 _eval_nodes(src/plugins/select/cons_res/job_test.c)//分配节点核心步骤 _log_select_maps//该步骤可以保存节点和核心位图到日志_can_job_run_on_node返回此节点可以使用的cpu的数量以及可用于分配的资源的位图,可以在通过调试,看这个函数返回值。
比如已经为一个作业分配一些节点,并且部分节点占满全部32个核心的情况下,在下一次提交作业并调度执行该作业时会调用_can_job_run_on_node函数,此时可以观察遍历上述占满核心的节点时的返回值是否正常。
PS:
Slurm China社区,群里有很多大牛,感兴趣的同学可以加入。