| /*****************************************************************************\ |
| * job_test.c - Determine if job can be allocated resources. |
| ***************************************************************************** |
| * Copyright (C) SchedMD LLC. |
| * Derived in large part from select/cons_res plugin |
| * |
| * This file is part of Slurm, a resource management program. |
| * For details, see <https://slurm.schedmd.com/>. |
| * Please also read the included file: DISCLAIMER. |
| * |
| * Slurm is free software; you can redistribute it and/or modify it under |
| * the terms of the GNU General Public License as published by the Free |
| * Software Foundation; either version 2 of the License, or (at your option) |
| * any later version. |
| * |
| * In addition, as a special exception, the copyright holders give permission |
| * to link the code of portions of this program with the OpenSSL library under |
| * certain conditions as described in each individual source file, and |
| * distribute linked combinations including the two. You must obey the GNU |
| * General Public License in all respects for all of the code used other than |
| * OpenSSL. If you modify file(s) with this exception, you may extend this |
| * exception to your version of the file(s), but you are not obligated to do |
| * so. If you do not wish to do so, delete this exception statement from your |
| * version. If you delete this exception statement from all source files in |
| * the program, then also delete it here. |
| * |
| * Slurm is distributed in the hope that it will be useful, but WITHOUT ANY |
| * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
| * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more |
| * details. |
| * |
| * You should have received a copy of the GNU General Public License along |
| * with Slurm; if not, write to the Free Software Foundation, Inc., |
| * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
| \*****************************************************************************/ |
| |
| #include <string.h> |
| #include "select_cons_tres.h" |
| #include "dist_tasks.h" |
| #include "job_test.h" |
| #include "gres_select_filter.h" |
| #include "gres_select_util.h" |
| #include "gres_sock_list.h" |
| |
| #include "src/common/slurm_time.h" |
| |
| #include "src/slurmctld/acct_policy.h" |
| #include "src/slurmctld/licenses.h" |
| |
| typedef struct { |
| int action; |
| list_t *license_list; |
| bitstr_t *node_map; |
| node_use_record_t *node_usage; |
| part_res_record_t *part_record_ptr; |
| int rc; |
| } wrapper_rm_job_args_t; |
| |
| typedef struct { |
| list_t *preemptee_candidates; |
| list_t *cr_job_list; |
| node_use_record_t *future_usage; |
| part_res_record_t *future_part; |
| list_t *future_license_list; |
| list_t *job_license_list; |
| time_t now; |
| bitstr_t *orig_map; |
| bool *qos_preemptor; |
| time_t start; |
| bitstr_t **tmp_bitmap_pptr; |
| int *topology_idx; |
| } cr_job_list_args_t; |
| |
| typedef struct { |
| licenses_id_t *id; |
| uint32_t remaining; |
| uint32_t required; |
| } license_req_t; |
| |
| typedef struct { |
| bitstr_t *effective_nodes; |
| list_t *future_license_list; |
| uint32_t license_cnt; |
| license_req_t *needed_licenses; |
| bitstr_t *selected_nodes; |
| int *topology_idx; |
| } first_relevant_job_arg_t; |
| |
| typedef struct { |
| bitstr_t **efctv_bitmap; |
| bitstr_t *node_bitmap; |
| list_t *preemptee_job_list; |
| int *topology_idx; |
| } will_run_preemptee_arg_t; |
| |
| typedef struct { |
| list_t *licenses_to_preempt; |
| bitstr_t *node_bitmap; |
| list_t *preemptee_job_list; |
| bool *remove_some_jobs; |
| } run_now_preemptee_arg_t; |
| |
| static int _foreach_rm_cores(void *x, void *arg) |
| { |
| job_record_t *job_ptr = x; |
| part_row_data_t *r_ptr = arg; |
| |
| job_res_rm_cores(job_ptr->job_resrcs, r_ptr); |
| |
| return 0; |
| } |
| |
| uint64_t def_cpu_per_gpu = 0; |
| uint64_t def_mem_per_gpu = 0; |
| bool preempt_strict_order = false; |
| bool preempt_for_licenses = false; |
| int preempt_reorder_cnt = 1; |
| bool soft_time_limit = false; |
| bitstr_t **suspend_exempt_cores = NULL; |
| |
| /* Local functions */ |
| static avail_res_t *_allocate(job_record_t *job_ptr, |
| bitstr_t *core_map, |
| bitstr_t *part_core_map, |
| const uint32_t node_i, |
| int *cpu_alloc_size, |
| bitstr_t *req_sock_map, |
| uint16_t cr_type); |
| |
| /* Log avail_res_t information for a given node */ |
| static void _avail_res_log(avail_res_t *avail_res, char *node_name) |
| { |
| int i; |
| char *gres_info = ""; |
| |
| if (!(slurm_conf.debug_flags & DEBUG_FLAG_SELECT_TYPE)) |
| return; |
| |
| if (!avail_res) { |
| log_flag(SELECT_TYPE, "Node:%s No resources", node_name); |
| return; |
| } |
| |
| log_flag(SELECT_TYPE, "Node:%s ResProd:%"PRIu64" Sockets:%u SpecThreads:%u CPUs:Min-Max,Avail:%u-%u,%u ThreadsPerCore:%u", |
| node_name, avail_res->avail_res_prod, avail_res->sock_cnt, avail_res->spec_threads, |
| avail_res->min_cpus, avail_res->max_cpus, |
| avail_res->avail_cpus, avail_res->tpc); |
| gres_info = gres_sock_str(avail_res->sock_gres_list, -1); |
| if (gres_info) { |
| log_flag(SELECT_TYPE, " AnySocket %s", gres_info); |
| xfree(gres_info); |
| } |
| for (i = 0; i < avail_res->sock_cnt; i++) { |
| gres_info = gres_sock_str(avail_res->sock_gres_list, i); |
| if (gres_info) { |
| log_flag(SELECT_TYPE, " Socket[%d] Cores:%u GRES:%s", i, |
| avail_res->avail_cores_per_sock[i], gres_info); |
| xfree(gres_info); |
| } else { |
| log_flag(SELECT_TYPE, " Socket[%d] Cores:%u", i, |
| avail_res->avail_cores_per_sock[i]); |
| } |
| } |
| } |
| |
| /* When any cores on a node are removed from being available for a job, |
| * then remove the entire node from being available. */ |
| static void _block_whole_nodes(bitstr_t *node_bitmap, |
| bitstr_t **orig_core_bitmap, |
| bitstr_t **new_core_bitmap) |
| { |
| int i_node; |
| int first_core, last_core, i_core; |
| node_record_t *node_ptr; |
| bitstr_t *cr_orig_core_bitmap = NULL; |
| bitstr_t *cr_new_core_bitmap = NULL; |
| |
| for (i_node = 0; (node_ptr = next_node_bitmap(node_bitmap, &i_node)); |
| i_node++) { |
| first_core = 0; |
| last_core = node_ptr->tot_cores; |
| cr_orig_core_bitmap = orig_core_bitmap[i_node]; |
| cr_new_core_bitmap = new_core_bitmap[i_node]; |
| |
| for (i_core = first_core; i_core < last_core; i_core++) { |
| if (bit_test(cr_orig_core_bitmap, i_core) && |
| !bit_test(cr_new_core_bitmap, i_core)) { |
| bit_clear(node_bitmap, i_node); |
| break; |
| } |
| } |
| } |
| } |
| |
| static void _block_by_topology(job_record_t *job_ptr, |
| part_res_record_t *p_ptr, |
| bitstr_t *node_bitmap) |
| { |
| static int enable_exclusive_topo = -1; |
| bitstr_t *tmp2_bitmap = NULL; |
| bool whole_topo; |
| |
| if (enable_exclusive_topo == -1) { |
| enable_exclusive_topo = 0; |
| (void) topology_g_get(TOPO_DATA_EXCLUSIVE_TOPO, NULL, |
| &enable_exclusive_topo); |
| } |
| |
| if (!enable_exclusive_topo) |
| return; |
| |
| whole_topo = (IS_JOB_WHOLE_TOPO(job_ptr) && |
| topology_g_whole_topo_enabled(job_ptr->part_ptr |
| ->topology_idx)); |
| |
| for (; p_ptr; p_ptr = p_ptr->next) { |
| bitstr_t *tmp_bitmap = NULL; |
| if (!p_ptr->row) |
| continue; |
| for (int i = 0; i < p_ptr->num_rows; i++) { |
| for (int j = 0; j < p_ptr->row[i].num_jobs; j++) { |
| struct job_resources *job; |
| job = p_ptr->row[i].job_list[j]; |
| |
| if (!job->node_bitmap) |
| continue; |
| if ((job->whole_node & WHOLE_TOPO) || |
| (p_ptr->part_ptr->flags & |
| PART_FLAG_EXCLUSIVE_TOPO)) { |
| if (tmp_bitmap) |
| bit_or(tmp_bitmap, |
| job->node_bitmap); |
| else |
| tmp_bitmap = bit_copy( |
| job->node_bitmap); |
| } else if (whole_topo) { |
| if (tmp2_bitmap) |
| bit_or(tmp2_bitmap, |
| job->node_bitmap); |
| else |
| tmp2_bitmap = bit_copy( |
| job->node_bitmap); |
| } |
| } |
| } |
| if (tmp_bitmap) { |
| topology_g_whole_topo(tmp_bitmap, |
| p_ptr->part_ptr->topology_idx); |
| if (tmp2_bitmap) |
| bit_or(tmp2_bitmap, tmp_bitmap); |
| else |
| tmp2_bitmap = bit_copy(tmp_bitmap); |
| } |
| FREE_NULL_BITMAP(tmp_bitmap); |
| } |
| |
| if (tmp2_bitmap) { |
| if (whole_topo) |
| topology_g_whole_topo(tmp2_bitmap, |
| job_ptr->part_ptr->topology_idx); |
| bit_and_not(node_bitmap, tmp2_bitmap); |
| FREE_NULL_BITMAP(tmp2_bitmap); |
| } |
| |
| return; |
| } |
| |
| static uint16_t _valid_uint16(uint16_t arg) |
| { |
| if ((arg == NO_VAL16) || (arg == INFINITE16)) |
| return 0; |
| return arg; |
| } |
| |
| static gres_mc_data_t *_build_gres_mc_data(job_record_t *job_ptr) |
| { |
| gres_mc_data_t *tres_mc_ptr; |
| |
| tres_mc_ptr = xmalloc(sizeof(gres_mc_data_t)); |
| tres_mc_ptr->cpus_per_task = |
| _valid_uint16(job_ptr->details->cpus_per_task); |
| /* |
| * _copy_job_desc_to_job_record() sets job_ptr->details->cpus_per_task |
| * to 1 if unset or NO_VAL16; INFINITE16 is invalid. Therefore, |
| * tres_mc_ptr->cpus_per_task should always be non-zero. |
| */ |
| xassert(tres_mc_ptr->cpus_per_task); |
| |
| tres_mc_ptr->ntasks_per_job = job_ptr->details->num_tasks; |
| tres_mc_ptr->ntasks_per_node = |
| _valid_uint16(job_ptr->details->ntasks_per_node); |
| tres_mc_ptr->overcommit = job_ptr->details->overcommit; |
| tres_mc_ptr->task_dist = job_ptr->details->task_dist; |
| tres_mc_ptr->whole_node = job_ptr->details->whole_node; |
| if (job_ptr->details->mc_ptr) { |
| multi_core_data_t *job_mc_ptr = job_ptr->details->mc_ptr; |
| tres_mc_ptr->boards_per_node = |
| _valid_uint16(job_mc_ptr->boards_per_node); |
| tres_mc_ptr->sockets_per_board = |
| _valid_uint16(job_mc_ptr->sockets_per_board); |
| tres_mc_ptr->sockets_per_node = |
| _valid_uint16(job_mc_ptr->sockets_per_node); |
| tres_mc_ptr->cores_per_socket = |
| _valid_uint16(job_mc_ptr->cores_per_socket); |
| tres_mc_ptr->threads_per_core = |
| _valid_uint16(job_mc_ptr->threads_per_core); |
| tres_mc_ptr->ntasks_per_board = |
| _valid_uint16(job_mc_ptr->ntasks_per_board); |
| tres_mc_ptr->ntasks_per_socket = |
| _valid_uint16(job_mc_ptr->ntasks_per_socket); |
| tres_mc_ptr->ntasks_per_core = |
| _valid_uint16(job_mc_ptr->ntasks_per_core); |
| } |
| if ((tres_mc_ptr->ntasks_per_core == 0) && |
| (slurm_conf.select_type_param & SELECT_ONE_TASK_PER_CORE)) |
| tres_mc_ptr->ntasks_per_core = 1; |
| |
| return tres_mc_ptr; |
| } |
| |
| static time_t _soft_job_end(job_record_t *job_ptr, time_t now) |
| { |
| time_t end_time; |
| |
| if (!soft_time_limit || !job_ptr->time_min) |
| return job_ptr->end_time; |
| |
| end_time = job_ptr->start_time + job_ptr->time_min * 60; |
| /* |
| * To ensure consistency with the _bf_reserve_running() function. |
| * If over the soft limit, assume the job will use half of the |
| * remaining time until the hard limit. |
| */ |
| if (end_time < now) |
| end_time = now + (job_ptr->end_time - now) / 2; |
| |
| return end_time; |
| } |
| |
| /* list sort function: sort by the job's expected end time */ |
| static int _cr_job_list_sort(void *x, void *y) |
| { |
| job_record_t *job1_ptr = *(job_record_t **) x; |
| job_record_t *job2_ptr = *(job_record_t **) y; |
| time_t end_time1, end_time2; |
| |
| end_time1 = _soft_job_end(job1_ptr, 0); |
| end_time2 = _soft_job_end(job2_ptr, 0); |
| |
| return slurm_sort_time_list_asc(&end_time1, &end_time2); |
| } |
| |
| static int _find_job (void *x, void *key) |
| { |
| job_record_t *job_ptr = (job_record_t *) x; |
| if (job_ptr == (job_record_t *) key) |
| return 1; |
| return 0; |
| } |
| |
| static int _is_job_sharing(void *x, void *key) |
| { |
| job_record_t *job_ptr = x; |
| if ((job_ptr->details->share_res == 1) || |
| (job_ptr->part_ptr->max_share & SHARED_FORCE)) { |
| debug3("%pJ is sharing resources.", job_ptr); |
| return 1; |
| } |
| return 0; |
| } |
| |
| static void _free_avail_res(avail_res_t *avail_res) |
| { |
| if (!avail_res) |
| return; |
| |
| xfree(avail_res->avail_cores_per_sock); |
| FREE_NULL_LIST(avail_res->sock_gres_list); |
| xfree(avail_res); |
| } |
| |
| static void _free_avail_res_array(avail_res_t **avail_res_array) |
| { |
| int n; |
| if (!avail_res_array) |
| return; |
| |
| for (n = 0; next_node(&n); n++) |
| _free_avail_res(avail_res_array[n]); |
| xfree(avail_res_array); |
| } |
| |
| /* Determine the node requirements for the job: |
| * - does the job need exclusive nodes? (NODE_CR_RESERVED) |
| * - can the job run on shared nodes? (NODE_CR_ONE_ROW) |
| * - can the job run on overcommitted resources? (NODE_CR_AVAILABLE) |
| */ |
| static uint16_t _get_job_node_req(job_record_t *job_ptr) |
| { |
| int max_share = job_ptr->part_ptr->max_share; |
| |
| if (max_share == 0) /* Partition OverSubscribe=EXCLUSIVE */ |
| return NODE_CR_RESERVED; |
| |
| /* Partition is OverSubscribe=FORCE */ |
| if (max_share & SHARED_FORCE) |
| return NODE_CR_AVAILABLE; |
| |
| if ((max_share > 1) && (job_ptr->details->share_res == 1)) |
| /* part allows sharing, and the user has requested it */ |
| return NODE_CR_AVAILABLE; |
| |
| return NODE_CR_ONE_ROW; |
| } |
| |
| static void _set_gpu_defaults(job_record_t *job_ptr) |
| { |
| static part_record_t *last_part_ptr = NULL; |
| static uint64_t last_cpu_per_gpu = NO_VAL64; |
| static uint64_t last_mem_per_gpu = NO_VAL64; |
| uint64_t cpu_per_gpu, mem_per_gpu; |
| |
| if (!job_ptr->gres_list_req) |
| return; |
| |
| if (job_ptr->part_ptr != last_part_ptr) { |
| /* Cache data from last partition referenced */ |
| last_part_ptr = job_ptr->part_ptr; |
| last_cpu_per_gpu = slurm_get_def_cpu_per_gpu( |
| last_part_ptr->job_defaults_list); |
| if (last_cpu_per_gpu == NO_VAL64) { |
| last_cpu_per_gpu = slurm_get_def_cpu_per_gpu( |
| slurm_conf.job_defaults_list); |
| } |
| last_mem_per_gpu = slurm_get_def_mem_per_gpu( |
| last_part_ptr->job_defaults_list); |
| /* DefMemPerGPU is mutually exclusive with DefMemPer{CPU,Node} */ |
| if ((last_mem_per_gpu == NO_VAL64) && |
| !last_part_ptr->def_mem_per_cpu) { |
| last_mem_per_gpu = slurm_get_def_mem_per_gpu( |
| slurm_conf.job_defaults_list); |
| } |
| } |
| if ((last_cpu_per_gpu != NO_VAL64) && |
| (job_ptr->details->orig_cpus_per_task == NO_VAL16)) |
| cpu_per_gpu = last_cpu_per_gpu; |
| else if ((def_cpu_per_gpu != NO_VAL64) && |
| (job_ptr->details->orig_cpus_per_task == NO_VAL16)) |
| cpu_per_gpu = def_cpu_per_gpu; |
| else |
| cpu_per_gpu = 0; |
| if (last_mem_per_gpu != NO_VAL64) |
| mem_per_gpu = last_mem_per_gpu; |
| else if (def_mem_per_gpu != NO_VAL64) |
| mem_per_gpu = def_mem_per_gpu; |
| else |
| mem_per_gpu = 0; |
| |
| gres_select_util_job_set_defs(job_ptr->gres_list_req, "gpu", |
| cpu_per_gpu, mem_per_gpu, |
| &job_ptr->cpus_per_tres, |
| &job_ptr->mem_per_tres, |
| &job_ptr->details->cpus_per_task); |
| } |
| |
| /* Determine how many sockets per node this job requires for GRES */ |
| static uint32_t _socks_per_node(job_record_t *job_ptr) |
| { |
| multi_core_data_t *mc_ptr; |
| uint32_t s_p_n = NO_VAL; |
| uint32_t cpu_cnt, cpus_per_node, tasks_per_node; |
| uint32_t min_nodes; |
| |
| if (!job_ptr->details) |
| return s_p_n; |
| |
| cpu_cnt = job_ptr->details->num_tasks * job_ptr->details->cpus_per_task; |
| cpu_cnt = MAX(job_ptr->details->min_cpus, cpu_cnt); |
| min_nodes = MAX(job_ptr->details->min_nodes, 1); |
| cpus_per_node = cpu_cnt / min_nodes; |
| if (cpus_per_node <= 1) |
| return (uint32_t) 1; |
| |
| mc_ptr = job_ptr->details->mc_ptr; |
| if ((mc_ptr->ntasks_per_socket != NO_VAL16) && |
| (mc_ptr->ntasks_per_socket != INFINITE16)) { |
| tasks_per_node = job_ptr->details->num_tasks / min_nodes; |
| s_p_n = ROUNDUP(tasks_per_node, mc_ptr->ntasks_per_socket); |
| return s_p_n; |
| } |
| |
| /* |
| * This logic could be expanded to support additional cases, which may |
| * require information per node information (e.g. threads per core). |
| */ |
| |
| return s_p_n; |
| } |
| |
| /* |
| * _can_job_run_on_node - Given the job requirements, determine which |
| * resources from the given node (if any) can be |
| * allocated to this job. Returns a structure identifying |
| * the resources available for allocation to this job. |
| * NOTE: This process does NOT support overcommitting resources |
| * |
| * IN job_ptr - pointer to job requirements |
| * IN/OUT core_map - per-node bitmap of available cores |
| * IN node_i - index of node to be evaluated |
| * IN s_p_n - Expected sockets_per_node (NO_VAL if not limited) |
| * IN cr_type - Consumable Resource setting |
| * IN test_only - Determine if job could ever run, ignore allocated memory |
| * check |
| * IN will_run - Determining when a pending job can start |
| * IN: part_core_map - per-node bitmap of cores allocated to jobs of this |
| * partition or NULL if don't care |
| * resv_exc_ptr IN - gres that can be included (gres_list_inc) |
| * or excluded (gres_list_exc) |
| * RET Available resources. Call _array() to release memory. |
| * |
| * NOTE: The returned cpu_count may be less than the number of set bits in |
| * core_map for the given node. The cr_dist functions will determine |
| * which bits to de-select from the core_map to match the cpu_count. |
| */ |
| static avail_res_t *_can_job_run_on_node(job_record_t *job_ptr, |
| bitstr_t **core_map, |
| const uint32_t node_i, |
| uint32_t s_p_n, |
| node_use_record_t *node_usage, |
| uint16_t cr_type, |
| bool test_only, bool will_run, |
| bitstr_t **part_core_map, |
| resv_exc_t *resv_exc_ptr) |
| { |
| uint16_t cpus = 0; |
| uint64_t avail_mem = NO_VAL64, req_mem; |
| int cpu_alloc_size, i, rc; |
| node_record_t *node_ptr = node_record_table_ptr[node_i]; |
| bitstr_t *part_core_map_ptr = NULL; |
| avail_res_t *avail_res = NULL; |
| list_t *sock_gres_list = NULL; |
| uint16_t min_cpus_per_node, ntasks_per_node = 1; |
| uint16_t hres_leaf_idx = NO_VAL16; |
| hres_select_t *hres_select = job_ptr->hres_select; |
| gres_sock_list_create_t create_args = { |
| .cores_per_sock = node_ptr->cores, |
| .core_bitmap = NULL, |
| .cr_type = cr_type, |
| .enforce_binding = false, |
| .gpu_spec_bitmap = node_ptr->gpu_spec_bitmap, |
| .job_gres_list = job_ptr->gres_list_req, |
| .need_gpu = false, |
| .node_gres_list = node_usage[node_i].gres_list ? |
| node_usage[node_i].gres_list : |
| node_ptr->gres_list, |
| .node_inx = node_i, |
| .node_name = node_ptr->name, |
| .resv_exc_ptr = resv_exc_ptr, |
| .req_sock_map = NULL, |
| .res_cores_per_gpu = node_ptr->res_cores_per_gpu, |
| .sockets = node_ptr->tot_sockets, |
| .use_total_gres = test_only, |
| }; |
| |
| if (((job_ptr->bit_flags & BACKFILL_TEST) == 0) && |
| !test_only && !will_run && IS_NODE_COMPLETING(node_ptr)) { |
| /* |
| * Do not allocate more jobs to nodes with completing jobs, |
| * backfill scheduler independently handles completing nodes |
| */ |
| log_flag(SELECT_TYPE, "Node %s is in COMPLETING state, skip trying to schedule %pJ on this node.", |
| node_ptr->name, job_ptr); |
| return NULL; |
| } |
| |
| if (part_core_map) |
| part_core_map_ptr = part_core_map[node_i]; |
| |
| if (job_ptr->gres_list_req) { |
| /* Identify available GRES and adjacent cores */ |
| |
| if (job_ptr->bit_flags & GRES_ENFORCE_BIND) |
| create_args.enforce_binding = true; |
| if (!core_map[node_i]) { |
| core_map[node_i] = bit_alloc(node_ptr->tot_cores); |
| bit_set_all(core_map[node_i]); |
| } |
| create_args.core_bitmap = core_map[node_i]; |
| |
| gres_sock_list_create(&create_args); |
| sock_gres_list = create_args.sock_gres_list; |
| create_args.sock_gres_list = NULL; |
| if (!sock_gres_list) { /* GRES requirement fail */ |
| log_flag(SELECT_TYPE, "Test fail on node %s: gres_sock_list_create", |
| node_ptr->name); |
| return NULL; |
| } |
| } |
| |
| if (hres_select) { |
| hres_leaf_idx = hres_select_find_leaf(hres_select, node_i); |
| if (hres_leaf_idx == NO_VAL16) { |
| log_flag(SELECT_TYPE, "Test fail on node %s: hres_select_find_leaf", |
| node_ptr->name); |
| return NULL; |
| } |
| } |
| /* Identify available CPUs */ |
| avail_res = _allocate(job_ptr, core_map[node_i], |
| part_core_map_ptr, node_i, |
| &cpu_alloc_size, create_args.req_sock_map, |
| cr_type); |
| |
| FREE_NULL_BITMAP(create_args.req_sock_map); |
| if (!avail_res || (avail_res->avail_cpus == 0)) { |
| _free_avail_res(avail_res); |
| log_flag(SELECT_TYPE, "Test fail on node %d: _allocate_cores/sockets", |
| node_i); |
| FREE_NULL_LIST(sock_gres_list); |
| return NULL; |
| } |
| |
| /* Check that sufficient CPUs remain to run a task on this node */ |
| if (job_ptr->details->ntasks_per_node) { |
| ntasks_per_node = job_ptr->details->ntasks_per_node; |
| } else if (job_ptr->details->overcommit) { |
| ntasks_per_node = 1; |
| } else if ((job_ptr->details->max_nodes == 1) && |
| (job_ptr->details->num_tasks != 0)) { |
| ntasks_per_node = job_ptr->details->num_tasks; |
| } |
| min_cpus_per_node = ntasks_per_node * job_ptr->details->cpus_per_task; |
| if (avail_res->avail_cpus < min_cpus_per_node) { |
| log_flag(SELECT_TYPE, "Test fail on node %d: avail_cpus < min_cpus_per_node (%u < %u)", |
| node_i, avail_res->avail_cpus, min_cpus_per_node); |
| FREE_NULL_LIST(sock_gres_list); |
| _free_avail_res(avail_res); |
| return NULL; |
| } |
| |
| if (cr_type & SELECT_MEMORY) { |
| avail_mem = node_ptr->real_memory - node_ptr->mem_spec_limit; |
| if (!test_only) |
| avail_mem -= node_usage[node_i].alloc_memory; |
| } |
| |
| if (sock_gres_list) { |
| uint16_t near_gpu_cnt = 0; |
| gres_remove_unused_args_t gres_rm_args = { |
| .avail_mem = avail_mem, |
| .max_cpus = avail_res->avail_cpus, |
| .enforce_binding = create_args.enforce_binding, |
| .core_bitmap = core_map[node_i], |
| .sockets = node_ptr->tot_sockets, |
| .cores_per_sock = node_ptr->cores, |
| .cpus_per_core = node_ptr->tpc, |
| .sock_per_node = s_p_n, |
| .task_per_node = job_ptr->details->ntasks_per_node, |
| .cpus_per_task = job_ptr->details->cpus_per_task, |
| .whole_node = (job_ptr->details->whole_node & |
| WHOLE_NODE_REQUIRED), |
| .avail_gpus = &avail_res->avail_gpus, |
| .near_gpus = &near_gpu_cnt, |
| }; |
| avail_res->sock_gres_list = sock_gres_list; |
| /* Disable GRES that can't be used with remaining cores */ |
| rc = gres_select_filter_remove_unusable(sock_gres_list, |
| &gres_rm_args); |
| if (rc != 0) { |
| log_flag(SELECT_TYPE, "Test fail on node %d: gres_select_filter_remove_unusable", |
| node_i); |
| _free_avail_res(avail_res); |
| return NULL; |
| } |
| |
| /* Favor nodes with more co-located GPUs */ |
| node_ptr->sched_weight = |
| (node_ptr->sched_weight & 0xffffffffffffff00) | |
| (0xff - near_gpu_cnt); |
| } |
| |
| cpus = avail_res->avail_cpus; |
| |
| if (cr_type & SELECT_MEMORY) { |
| /* |
| * Memory Check: check pn_min_memory to see if: |
| * - this node has enough memory (MEM_PER_CPU == 0) |
| * - there are enough free_cores (MEM_PER_CPU == 1) |
| */ |
| req_mem = job_ptr->details->pn_min_memory & ~MEM_PER_CPU; |
| if (job_ptr->details->pn_min_memory & MEM_PER_CPU) { |
| /* memory is per-CPU */ |
| if (!(job_ptr->bit_flags & BF_WHOLE_NODE_TEST) && |
| ((req_mem * cpus) > avail_mem) && |
| (job_ptr->details->whole_node & |
| WHOLE_NODE_REQUIRED)) { |
| cpus = 0; |
| } else if (!(cr_type & SELECT_CPU) && |
| job_ptr->details->mc_ptr && |
| (job_ptr->details->mc_ptr-> |
| ntasks_per_core == 1) && |
| job_ptr->details->cpus_per_task == 1) { |
| /* |
| * In this scenario, CPUs represents cores and |
| * the CPU/core count will be inflated later on |
| * to include all of the threads on a core. So |
| * we need to compare apples to apples and only |
| * remove 1 CPU/core at a time. |
| */ |
| while ((cpus > 0) && |
| ((req_mem * |
| ((uint64_t)cpus * |
| (uint64_t)node_ptr->tpc)) |
| > avail_mem)) |
| cpus -= 1; |
| } else { |
| while ((req_mem * cpus) > avail_mem) { |
| if (cpus >= cpu_alloc_size) { |
| cpus -= cpu_alloc_size; |
| } else { |
| cpus = 0; |
| break; |
| } |
| } |
| } |
| |
| if (job_ptr->details->cpus_per_task > 1) { |
| i = cpus % job_ptr->details->cpus_per_task; |
| cpus -= i; |
| } |
| if (cpus < job_ptr->details->ntasks_per_node) |
| cpus = 0; |
| /* FIXME: Need to recheck min_cores, etc. here */ |
| } else { |
| /* memory is per node */ |
| if (req_mem > avail_mem) |
| cpus = 0; |
| } |
| } |
| |
| if (cpus == 0) { |
| log_flag(SELECT_TYPE, "Test fail on node %d: cpus == 0", |
| node_i); |
| bit_clear_all(core_map[node_i]); |
| } |
| |
| log_flag(SELECT_TYPE, "%u CPUs on %s(state:%d), mem %"PRIu64"/%"PRIu64, |
| cpus, node_ptr->name, |
| node_usage[node_i].node_state, node_usage[node_i].alloc_memory, |
| node_ptr->real_memory); |
| |
| avail_res->avail_cpus = cpus; |
| avail_res->avail_res_prod = cpus; |
| if (create_args.need_gpu) |
| avail_res->avail_res_prod *= avail_res->avail_gpus; |
| |
| avail_res->hres_leaf_idx = hres_leaf_idx; |
| if (hres_select) |
| avail_res->avail_res_prod *= |
| hres_select->leaf[hres_leaf_idx].capacity; |
| _avail_res_log(avail_res, node_ptr->name); |
| |
| return avail_res; |
| } |
| |
| /* |
| * Determine resource availability for pending job |
| * |
| * IN: job_ptr - pointer to the job requesting resources |
| * IN: node_map - bitmap of available nodes |
| * IN/OUT: core_map - per-node bitmaps of available cores |
| * IN: cr_type - resource type |
| * IN: test_only - Determine if job could ever run, ignore allocated memory |
| * check |
| * IN: will_run - Determining when a pending job can start |
| * IN: part_core_map - per-node bitmap of cores allocated to jobs of this |
| * partition or NULL if don't care |
| * resv_exc_ptr IN - gres that can be included (gres_list_inc) |
| * or excluded (gres_list_exc) |
| * |
| * RET array of avail_res_t pointers, free using _free_avail_res_array() |
| */ |
| static avail_res_t **_get_res_avail(job_record_t *job_ptr, |
| bitstr_t *node_map, bitstr_t **core_map, |
| node_use_record_t *node_usage, |
| uint16_t cr_type, bool test_only, |
| bool will_run, bitstr_t **part_core_map, |
| resv_exc_t *resv_exc_ptr) |
| { |
| int i, i_first, i_last; |
| avail_res_t **avail_res_array = NULL; |
| uint32_t s_p_n = _socks_per_node(job_ptr); |
| |
| avail_res_array = xcalloc(node_record_count, sizeof(avail_res_t *)); |
| i_first = bit_ffs(node_map); |
| if (i_first != -1) |
| i_last = bit_fls(node_map); |
| else |
| i_last = -2; |
| for (i = i_first; i <= i_last; i++) { |
| if (bit_test(node_map, i)) |
| avail_res_array[i] = |
| _can_job_run_on_node( |
| job_ptr, core_map, i, |
| s_p_n, node_usage, |
| cr_type, test_only, will_run, |
| part_core_map, resv_exc_ptr); |
| } |
| |
| return avail_res_array; |
| } |
| |
| static int _cmp_node(const void *x, const void *y) |
| { |
| node_record_t *n1 = *(node_record_t **) x; |
| node_record_t *n2 = *(node_record_t **) y; |
| |
| if (n1->sched_weight < n2->sched_weight) |
| return -1; |
| else if (n1->sched_weight > n2->sched_weight) |
| return 1; |
| return 0; |
| } |
| |
| static int _get_one_res(topology_eval_t *topo_eval, |
| node_use_record_t *node_usage, bool test_only, |
| bool will_run, bitstr_t **part_core_map, |
| resv_exc_t *resv_exc_ptr) |
| { |
| avail_res_t **avail_res_array = NULL; |
| node_record_t **sorted_nodes; |
| node_record_t *node_ptr; |
| job_record_t *job_ptr = topo_eval->job_ptr; |
| bitstr_t *req_map = job_ptr->details->req_node_bitmap; |
| uint32_t s_p_n = _socks_per_node(job_ptr); |
| int32_t avail_node_cnt; |
| int rc = SLURM_ERROR; |
| bitstr_t **orig_core_array; |
| |
| if (req_map) |
| bit_and(topo_eval->node_map, req_map); |
| |
| avail_node_cnt = bit_set_count(topo_eval->node_map); |
| |
| if (avail_node_cnt <= 0) |
| return rc; |
| |
| orig_core_array = copy_core_array(topo_eval->avail_core); |
| |
| avail_res_array = xcalloc(node_record_count, sizeof(avail_res_t *)); |
| topo_eval->avail_res_array = avail_res_array; |
| topo_eval->min_nodes = 1; |
| topo_eval->gres_per_job = gres_sched_init(job_ptr->gres_list_req); |
| |
| log_flag(SELECT_TYPE, "begin"); |
| |
| sorted_nodes = xcalloc(avail_node_cnt, sizeof(*sorted_nodes)); |
| |
| for (int i = 0, idx = 0; |
| (node_ptr = next_node_bitmap(topo_eval->node_map, &i)); i++) |
| sorted_nodes[idx++] = node_ptr; |
| |
| qsort(sorted_nodes, avail_node_cnt, sizeof(*sorted_nodes), _cmp_node); |
| |
| check_nodes: |
| for (int idx = 0; idx < avail_node_cnt; idx++) { |
| int i = sorted_nodes[idx]->index; |
| |
| avail_res_array[i] = |
| _can_job_run_on_node(job_ptr, topo_eval->avail_core, i, |
| s_p_n, node_usage, |
| topo_eval->cr_type, test_only, |
| will_run, part_core_map, |
| resv_exc_ptr); |
| |
| if (!avail_res_array[i] || !avail_res_array[i]->avail_cpus) |
| continue; |
| log_flag(SELECT_TYPE, "check %s", sorted_nodes[idx]->name); |
| if (topology_g_eval_node(topo_eval, i)) |
| continue; |
| |
| log_flag(SELECT_TYPE, "chosen:%s", sorted_nodes[idx]->name); |
| rc = SLURM_SUCCESS; |
| break; |
| } |
| if (rc && topo_eval->first_pass) { |
| topo_eval->first_pass = false; |
| topo_eval->gres_per_job = |
| gres_sched_init(job_ptr->gres_list_req); |
| core_array_or(topo_eval->avail_core, orig_core_array); |
| goto check_nodes; |
| } |
| xfree(sorted_nodes); |
| free_core_array(&orig_core_array); |
| return rc; |
| } |
| |
| /* For a given job already past it's end time, guess when it will actually end. |
| * Used for backfill scheduling. */ |
| static time_t _guess_job_end(job_record_t *job_ptr, time_t now) |
| { |
| time_t end_time; |
| uint16_t over_time_limit; |
| |
| if (job_ptr->part_ptr && |
| (job_ptr->part_ptr->over_time_limit != NO_VAL16)) { |
| over_time_limit = job_ptr->part_ptr->over_time_limit; |
| } else { |
| over_time_limit = slurm_conf.over_time_limit; |
| } |
| if (over_time_limit == 0) { |
| end_time = job_ptr->end_time + slurm_conf.kill_wait; |
| } else if (over_time_limit == INFINITE16) { |
| /* No idea when the job might end, this is just a guess */ |
| if (job_ptr->time_limit && (job_ptr->time_limit != NO_VAL) && |
| (job_ptr->time_limit != INFINITE)) { |
| end_time = now + (job_ptr->time_limit * 60); |
| } else { |
| end_time = now + (365 * 24 * 60 * 60); /* one year */ |
| } |
| } else { |
| end_time = job_ptr->end_time + slurm_conf.kill_wait + |
| (over_time_limit * 60); |
| } |
| if (end_time <= now) |
| end_time = now + 1; |
| |
| return end_time; |
| } |
| |
| /* |
| * Test to see if a node already has running jobs for _other_ partitions. |
| * If (sharing_only) then only check sharing partitions. This is because |
| * the job was submitted to a single-row partition which does not share |
| * allocated CPUs with multi-row partitions. |
| */ |
| static int _is_node_busy(part_res_record_t *p_ptr, uint32_t node_i, |
| bool sharing_only, part_record_t *my_part_ptr, |
| bool use_extra_row, list_t *jobs) |
| { |
| uint32_t r; |
| uint16_t num_rows; |
| |
| for (; p_ptr; p_ptr = p_ptr->next) { |
| num_rows = p_ptr->num_rows; |
| if (preempt_by_qos && !use_extra_row) |
| num_rows--; /* Don't use extra row */ |
| if (sharing_only && |
| ((num_rows < 2) || (p_ptr->part_ptr == my_part_ptr))) |
| continue; |
| if (!p_ptr->row) |
| continue; |
| |
| xassert(!p_ptr->rebuild_rows); |
| for (r = 0; r < num_rows; r++) { |
| if (!p_ptr->row[r].row_bitmap) |
| continue; |
| |
| if (!p_ptr->row[r].row_bitmap[node_i]) |
| continue; |
| |
| if (!bit_set_count(p_ptr->row[r].row_bitmap[node_i])) |
| continue; |
| |
| if (jobs && |
| list_find_first(jobs, _is_job_sharing, NULL)) |
| return 1; |
| } |
| } |
| return 0; |
| } |
| |
| static bool _is_preemptable(job_record_t *job_ptr, list_t *preemptee_candidates) |
| { |
| if (!preemptee_candidates) |
| return false; |
| if (list_find_first(preemptee_candidates, _find_job, job_ptr)) |
| return true; |
| return false; |
| } |
| |
| /* |
| * Select the best set of resources for the given job |
| * IN: job_ptr - pointer to the job requesting resources |
| * IN: min_nodes - minimum number of nodes required |
| * IN: max_nodes - maximum number of nodes requested |
| * IN: req_nodes - number of requested nodes |
| * IN/OUT: node_bitmap - bitmap of available nodes / bitmap of selected nodes |
| * IN/OUT: avail_core - available/selected cores |
| * IN: cr_type - resource type |
| * IN: test_only - Determine if job could ever run, ignore allocated memory |
| * check |
| * IN: will_run - Determining when a pending job can start |
| * IN: part_core_map - per-node bitmap of cores allocated to jobs of this |
| * partition or NULL if don't care |
| * IN: prefer_alloc_nodes - select currently allocated nodes first |
| * IN: tres_mc_ptr - job's multi-core options |
| * IN: resv_exc_ptr - gres that can be included (gres_list_inc) |
| * or excluded (gres_list_exc) |
| * IN: select_rc - rc from this function. |
| * RET: array of avail_res_t pointers, free using _free_avail_res_array(). |
| * NULL on error and select_rc set. |
| */ |
| static avail_res_t **_select_nodes(job_record_t *job_ptr, uint32_t min_nodes, |
| uint32_t max_nodes, uint32_t req_nodes, |
| bitstr_t *node_bitmap, bitstr_t **avail_core, |
| node_use_record_t *node_usage, |
| uint16_t cr_type, bool test_only, |
| bool will_run, |
| bitstr_t **part_core_map, |
| bool prefer_alloc_nodes, |
| gres_mc_data_t *tres_mc_ptr, |
| resv_exc_t *resv_exc_ptr, |
| int *select_rc) |
| { |
| int i, rc; |
| job_details_t *details_ptr = job_ptr->details; |
| bitstr_t *req_map = details_ptr->req_node_bitmap; |
| topology_eval_t topo_eval = { |
| .avail_core = avail_core, |
| .avail_cpus = 0, |
| .avail_res_array = NULL, |
| .cr_type = cr_type, |
| .enforce_binding = (job_ptr->gres_list_req && |
| (job_ptr->bit_flags & GRES_ENFORCE_BIND)) ? |
| true : false, |
| .first_pass = true, |
| .job_ptr = job_ptr, |
| .max_nodes = max_nodes, |
| .mc_ptr = tres_mc_ptr, |
| .min_nodes = min_nodes, |
| .node_map = node_bitmap, |
| .prefer_alloc_nodes = prefer_alloc_nodes, |
| .req_nodes = req_nodes, |
| }; |
| |
| if (bit_set_count(topo_eval.node_map) < topo_eval.min_nodes) { |
| #if _DEBUG |
| info("AvailNodes < MinNodes (%u < %u)", |
| bit_set_count(topo_eval.node_map), topo_eval.min_nodes); |
| #endif |
| return NULL; |
| } |
| |
| core_array_log("_select_nodes/enter", |
| topo_eval.node_map, topo_eval.avail_core); |
| |
| if ((topo_eval.max_nodes == 1) && |
| topology_g_allow_one_node(job_ptr->part_ptr->topology_idx)) { |
| rc = _get_one_res(&topo_eval, node_usage, test_only, will_run, |
| part_core_map, resv_exc_ptr); |
| goto sync; |
| } |
| |
| /* Determine resource availability on each node for pending job */ |
| topo_eval.avail_res_array = |
| _get_res_avail(topo_eval.job_ptr, topo_eval.node_map, |
| topo_eval.avail_core, |
| node_usage, topo_eval.cr_type, test_only, |
| will_run, part_core_map, resv_exc_ptr); |
| if (!topo_eval.avail_res_array) |
| return NULL; |
| |
| /* Eliminate nodes that don't have sufficient resources for this job */ |
| for (int n = 0; next_node_bitmap(topo_eval.node_map, &n); n++) { |
| if ((!topo_eval.avail_res_array[n] || |
| !topo_eval.avail_res_array[n]->avail_cpus)) { |
| /* insufficient resources available on this node */ |
| bit_clear(topo_eval.node_map, n); |
| } |
| } |
| if ((bit_set_count(topo_eval.node_map) < topo_eval.min_nodes) || |
| (req_map && !bit_super_set(req_map, topo_eval.node_map))) { |
| rc = SLURM_ERROR; |
| goto fini; |
| } |
| core_array_log("_select_nodes/elim_nodes", |
| topo_eval.node_map, topo_eval.avail_core); |
| |
| /* Select the best nodes for this job */ |
| if (details_ptr->ntasks_per_node && details_ptr->num_tasks) { |
| i = ROUNDUP(details_ptr->num_tasks, |
| details_ptr->ntasks_per_node); |
| topo_eval.min_nodes = MAX(topo_eval.min_nodes, i); |
| } |
| |
| rc = topology_g_eval_nodes(&topo_eval); |
| if (rc != SLURM_SUCCESS) |
| goto fini; |
| sync: |
| core_array_log("_select_nodes/choose_nodes", |
| topo_eval.node_map, topo_eval.avail_core); |
| |
| /* If successful, sync up the avail_core with the node_map */ |
| if (rc == SLURM_SUCCESS) { |
| int n; |
| for (n = 0; n < bit_size(topo_eval.node_map); n++) { |
| if (!topo_eval.avail_res_array[n] || |
| !bit_test(topo_eval.node_map, n)) |
| FREE_NULL_BITMAP(topo_eval.avail_core[n]); |
| } |
| } |
| core_array_log("_select_nodes/sync_cores", |
| topo_eval.node_map, topo_eval.avail_core); |
| |
| fini: if (rc != SLURM_SUCCESS) { |
| *select_rc = rc; |
| _free_avail_res_array(topo_eval.avail_res_array); |
| return NULL; |
| } |
| |
| return topo_eval.avail_res_array; |
| } |
| |
| /* |
| * Sort the usable_node element to put jobs in the correct |
| * preemption order. |
| */ |
| static int _sort_usable_nodes_dec(void *j1, void *j2) |
| { |
| job_record_t *job_a = *(job_record_t **) j1; |
| job_record_t *job_b = *(job_record_t **) j2; |
| |
| if (job_a->details->usable_nodes > job_b->details->usable_nodes) |
| return -1; |
| else if (job_a->details->usable_nodes < job_b->details->usable_nodes) |
| return 1; |
| |
| return 0; |
| } |
| |
| /* |
| * Determine which of these nodes are usable by this job |
| * |
| * Remove nodes from node_bitmap that don't have enough memory or other |
| * resources to support this job. |
| * |
| * Return SLURM_ERROR if a required node can't be used. |
| * |
| * if node_state = NODE_CR_RESERVED, clear node_bitmap (if node is required |
| * then should we return NODE_BUSY!?!) |
| * |
| * if node_state = NODE_CR_ONE_ROW, then this node can only be used by |
| * another NODE_CR_ONE_ROW job |
| * |
| * if node_state = NODE_CR_AVAILABLE AND: |
| * - job_node_req = NODE_CR_RESERVED, then we need idle nodes |
| * - job_node_req = NODE_CR_ONE_ROW, then we need idle or non-sharing nodes |
| */ |
| static int _verify_node_state(part_res_record_t *cr_part_ptr, |
| job_record_t *job_ptr, |
| bitstr_t *node_bitmap, |
| uint16_t cr_type, |
| node_use_record_t *node_usage, |
| enum node_cr_state job_node_req, |
| resv_exc_t *resv_exc_ptr, bool use_extra_row) |
| { |
| node_record_t *node_ptr; |
| uint32_t gres_cpus, gres_cores; |
| uint64_t free_mem, min_mem, avail_mem; |
| list_t *gres_list; |
| |
| if (!(job_ptr->bit_flags & JOB_MEM_SET) && |
| (min_mem = gres_select_util_job_mem_max(job_ptr->gres_list_req))) { |
| /* |
| * Clear default partition or system per-node memory limit. |
| * Rely exclusively upon the per-GRES memory limit. |
| */ |
| job_ptr->details->pn_min_memory = 0; |
| } else if (job_ptr->details->pn_min_memory & MEM_PER_CPU) { |
| uint16_t min_cpus; |
| min_mem = job_ptr->details->pn_min_memory & (~MEM_PER_CPU); |
| min_cpus = MAX(job_ptr->details->ntasks_per_node, |
| job_ptr->details->pn_min_cpus); |
| min_cpus = MAX(min_cpus, job_ptr->details->cpus_per_task); |
| if (min_cpus > 0) |
| min_mem *= min_cpus; |
| } else { |
| min_mem = job_ptr->details->pn_min_memory; |
| } |
| |
| for (int i = 0; (node_ptr = next_node_bitmap(node_bitmap, &i)); i++) { |
| /* node-level memory check */ |
| if (min_mem && (cr_type & SELECT_MEMORY)) { |
| avail_mem = node_ptr->real_memory - |
| node_ptr->mem_spec_limit; |
| if (avail_mem > node_usage[i].alloc_memory) { |
| free_mem = avail_mem - |
| node_usage[i].alloc_memory; |
| } else |
| free_mem = 0; |
| if (free_mem < min_mem) { |
| debug3("Not considering node %s, free_mem < min_mem (%"PRIu64" < %"PRIu64") for %pJ", |
| node_ptr->name, |
| free_mem, min_mem, job_ptr); |
| goto clear_bit; |
| } |
| } else if (cr_type & SELECT_MEMORY) { /* mem=0 for all memory */ |
| if (node_usage[i].alloc_memory) { |
| debug3("Not considering node %s, allocated memory = %"PRIu64" and all memory requested for %pJ", |
| node_ptr->name, |
| node_usage[i].alloc_memory, job_ptr); |
| goto clear_bit; |
| } |
| } |
| |
| /* Exclude nodes with reserved cores */ |
| if ((job_ptr->details->whole_node & WHOLE_NODE_REQUIRED) && |
| resv_exc_ptr->exc_cores) { |
| if (resv_exc_ptr->exc_cores[i] && |
| (bit_ffs(resv_exc_ptr->exc_cores[i]) != -1)) { |
| debug3("node %s exclusive", node_ptr->name); |
| goto clear_bit; |
| } |
| } |
| |
| /* node-level GRES check, assumes all cores usable */ |
| if (node_usage[i].gres_list) |
| gres_list = node_usage[i].gres_list; |
| else |
| gres_list = node_ptr->gres_list; |
| |
| if ((job_ptr->details->whole_node & WHOLE_NODE_REQUIRED) && |
| gres_node_state_list_has_alloc_gres(gres_list)) { |
| debug3("node %s has GRES in use (whole node requested)", |
| node_ptr->name); |
| goto clear_bit; |
| } |
| |
| gres_cores = gres_job_test(job_ptr->gres_list_req, |
| gres_list, true, |
| 0, 0, job_ptr->job_id, |
| node_ptr->name); |
| gres_cpus = gres_cores; |
| if (gres_cpus != NO_VAL) |
| gres_cpus *= node_ptr->tpc; |
| if (gres_cpus == 0) { |
| debug3("node %s lacks GRES", |
| node_ptr->name); |
| goto clear_bit; |
| } |
| |
| /* exclusive node check */ |
| if (node_usage[i].node_state >= NODE_CR_RESERVED) { |
| debug3("node %s in exclusive use", |
| node_ptr->name); |
| goto clear_bit; |
| |
| /* non-resource-sharing node check */ |
| } else if (node_usage[i].node_state >= NODE_CR_ONE_ROW) { |
| if ((job_node_req == NODE_CR_RESERVED) || |
| (job_node_req == NODE_CR_AVAILABLE)) { |
| log_flag(SELECT_TYPE, "node %s is running --exclusive job", |
| node_ptr->name); |
| goto clear_bit; |
| } |
| /* |
| * cannot use this node if it is running jobs |
| * in sharing partitions |
| */ |
| if (_is_node_busy(cr_part_ptr, i, true, |
| job_ptr->part_ptr, use_extra_row, |
| node_usage[i].jobs)) { |
| log_flag(SELECT_TYPE, "node %s is running job that shares resources in other partition", |
| node_ptr->name); |
| goto clear_bit; |
| } |
| |
| /* node is NODE_CR_AVAILABLE - check job request */ |
| } else { |
| if (job_node_req == NODE_CR_RESERVED) { |
| if (_is_node_busy(cr_part_ptr, i, false, |
| job_ptr->part_ptr, |
| use_extra_row, |
| node_usage[i].jobs)) { |
| log_flag(SELECT_TYPE, "node %s is running other jobs, cannot run --exclusive job here", |
| node_ptr->name); |
| goto clear_bit; |
| } |
| } else if (job_node_req == NODE_CR_ONE_ROW) { |
| /* |
| * cannot use this node if it is running jobs |
| * in sharing partitions |
| */ |
| if (_is_node_busy(cr_part_ptr, i, true, |
| job_ptr->part_ptr, |
| use_extra_row, |
| node_usage[i].jobs)) { |
| log_flag(SELECT_TYPE, "node %s is running job that shares resources in other partition", |
| node_ptr->name); |
| goto clear_bit; |
| } |
| } |
| } |
| continue; /* node is usable, test next node */ |
| |
| clear_bit: /* This node is not usable by this job */ |
| bit_clear(node_bitmap, i); |
| if (job_ptr->details->req_node_bitmap && |
| bit_test(job_ptr->details->req_node_bitmap, i)) |
| return SLURM_ERROR; |
| |
| } |
| |
| return SLURM_SUCCESS; |
| } |
| |
| /* |
| * _job_test - does most of the real work for select_p_job_test(), which |
| * includes contiguous selection, load-leveling and max_share logic |
| * |
| * PROCEDURE: |
| * |
| * Step 1: compare nodes in "avail" node_bitmap with current node state data |
| * to find available nodes that match the job request |
| * |
| * Step 2: check resources in "avail" node_bitmap with allocated resources from |
| * higher priority partitions (busy resources are UNavailable) |
| * |
| * Step 3: select resource usage on remaining resources in "avail" node_bitmap |
| * for this job, with the placement influenced by existing |
| * allocations |
| */ |
| static int _job_test(job_record_t *job_ptr, bitstr_t *node_bitmap, |
| uint32_t min_nodes, uint32_t max_nodes, |
| uint32_t req_nodes, int mode, uint16_t cr_type, |
| enum node_cr_state job_node_req, |
| part_res_record_t *cr_part_ptr, |
| node_use_record_t *node_usage, list_t *license_list, |
| resv_exc_t *resv_exc_ptr, bool prefer_alloc_nodes, |
| bool use_extra_row, bool preempt_mode, |
| list_t *qos_preemptees) |
| { |
| int error_code = SLURM_SUCCESS, select_rc = SLURM_SUCCESS; |
| bitstr_t *orig_node_map, **part_core_map = NULL; |
| bitstr_t **free_cores_tmp = NULL, *node_bitmap_tmp = NULL; |
| bitstr_t **free_cores_tmp2 = NULL, *node_bitmap_tmp2 = NULL; |
| bitstr_t **avail_cores, **free_cores, **avail_cores_tmp = NULL; |
| bool test_only = false, will_run = false; |
| bool have_gres_max_tasks = false; |
| uint32_t sockets_per_node = 1; |
| uint32_t c, j, n, c_alloc = 0, c_size, total_cpus; |
| uint32_t *gres_min_cpus; |
| uint64_t save_mem = 0, avail_mem = 0, needed_mem = 0, lowest_mem = 0; |
| int32_t build_cnt; |
| job_resources_t *job_res; |
| job_details_t *details_ptr = job_ptr->details; |
| part_res_record_t *p_ptr, *jp_ptr; |
| uint16_t *cpu_count; |
| int i; |
| avail_res_t **avail_res_array, **avail_res_array_tmp; |
| gres_mc_data_t *tres_mc_ptr = NULL; |
| list_t **node_gres_list = NULL, **sock_gres_list = NULL; |
| uint32_t *gres_task_limit = NULL; |
| char *nodename = NULL; |
| node_record_t *node_ptr; |
| uint32_t orig_min_nodes = min_nodes; |
| uint32_t next_job_size = 0; |
| uint32_t ntasks_per_node; |
| |
| free_job_resources(&job_ptr->job_resrcs); |
| part_data_rebuild_rows(cr_part_ptr); |
| |
| if (mode == SELECT_MODE_TEST_ONLY) |
| test_only = true; |
| else if (mode == SELECT_MODE_WILL_RUN) |
| will_run = true; |
| if (qos_preemptees) { |
| use_extra_row = true; |
| preempt_mode = false; |
| } |
| |
| /* check node_state and update the node_bitmap as necessary */ |
| if (!test_only) { |
| error_code = _verify_node_state( |
| cr_part_ptr, job_ptr, node_bitmap, cr_type, |
| node_usage, job_node_req, resv_exc_ptr, use_extra_row); |
| if (error_code != SLURM_SUCCESS) { |
| return error_code; |
| } |
| } |
| if (details_ptr->job_size_bitmap) { |
| int start; |
| n = bit_set_count(node_bitmap); |
| if (max_nodes < n) |
| n = max_nodes; |
| start = bit_fls_from_bit(details_ptr->job_size_bitmap, n); |
| if (start < 1 || start < orig_min_nodes) |
| return SLURM_ERROR; |
| max_nodes = start; |
| min_nodes = max_nodes; |
| req_nodes = max_nodes; |
| } |
| |
| if (license_list) { |
| /* Ensure job has access to requested licenses */ |
| int license_rc = license_job_test_with_list(job_ptr, time(NULL), |
| true, license_list, |
| true); |
| if (license_rc == SLURM_ERROR) { |
| log_flag(SELECT_TYPE, |
| "test 0 fail: insufficient licenses configured"); |
| return ESLURM_LICENSES_UNAVAILABLE; |
| } |
| if (!test_only && license_rc == EAGAIN) { |
| log_flag(SELECT_TYPE, |
| "test 0 fail: insufficient licenses available"); |
| return ESLURM_LICENSES_UNAVAILABLE; |
| } |
| } |
| |
| /* |
| * Ensure sufficient resources to satisfy thread/core/socket |
| * specifications with -O/--overcommit option. |
| */ |
| if (details_ptr->overcommit && |
| (details_ptr->min_cpus == details_ptr->min_nodes)) { |
| struct multi_core_data *mc_ptr = details_ptr->mc_ptr; |
| |
| if ((mc_ptr->threads_per_core != NO_VAL16) && |
| (mc_ptr->threads_per_core > 1)) |
| details_ptr->min_cpus *= mc_ptr->threads_per_core; |
| if ((mc_ptr->cores_per_socket != NO_VAL16) && |
| (mc_ptr->cores_per_socket > 1)) |
| details_ptr->min_cpus *= mc_ptr->cores_per_socket; |
| if ((mc_ptr->sockets_per_node != NO_VAL16) && |
| (mc_ptr->sockets_per_node > 1)) |
| details_ptr->min_cpus *= mc_ptr->sockets_per_node; |
| } |
| |
| ntasks_per_node = MAX(details_ptr->ntasks_per_node, 1); |
| if (details_ptr->mc_ptr && details_ptr->mc_ptr->sockets_per_node) |
| sockets_per_node = details_ptr->mc_ptr->sockets_per_node; |
| _set_gpu_defaults(job_ptr); |
| if (!job_ptr->gres_list_req_accum) |
| job_ptr->gres_list_req_accum = |
| gres_select_util_create_list_req_accum( |
| job_ptr->gres_list_req); |
| details_ptr->min_gres_cpu = gres_select_util_job_min_cpu_node( |
| sockets_per_node, |
| details_ptr->ntasks_per_node, |
| job_ptr->gres_list_req_accum); |
| details_ptr->min_job_gres_cpu = gres_select_util_job_min_cpus( |
| details_ptr->min_nodes, |
| sockets_per_node, |
| ntasks_per_node * details_ptr->min_nodes, |
| job_ptr->gres_list_req_accum); |
| |
| log_flag(SELECT_TYPE, "evaluating %pJ on %u nodes", |
| job_ptr, bit_set_count(node_bitmap)); |
| |
| orig_node_map = bit_copy(node_bitmap); |
| avail_cores = cons_helpers_mark_avail_cores(node_bitmap, job_ptr); |
| |
| /* |
| * test to make sure that this job can succeed with all avail_cores |
| * if 'no' then return FAIL |
| * if 'yes' then we will seek the optimal placement for this job |
| * within avail_cores |
| */ |
| free_cores = copy_core_array(avail_cores); |
| tres_mc_ptr = _build_gres_mc_data(job_ptr); |
| |
| try_next_nodes_cnt: |
| if (details_ptr->job_size_bitmap) { |
| int next = bit_fls_from_bit(details_ptr->job_size_bitmap, |
| max_nodes - 1); |
| if (next > 0 && next >= orig_min_nodes) |
| next_job_size = next; |
| else |
| next_job_size = 0; |
| } |
| |
| if ((gang_mode == 0) && |
| (job_node_req == NODE_CR_ONE_ROW || |
| job_node_req == NODE_CR_RESERVED) && |
| !test_only) { |
| log_flag(SELECT_TYPE, "test 0 skipped: goto test 1"); |
| goto skip_test0; |
| } |
| |
| avail_res_array = _select_nodes(job_ptr, min_nodes, max_nodes, |
| req_nodes, node_bitmap, free_cores, |
| node_usage, cr_type, test_only, |
| will_run, part_core_map, |
| prefer_alloc_nodes, tres_mc_ptr, |
| resv_exc_ptr, &select_rc); |
| if ((!avail_res_array || !job_ptr->best_switch) && next_job_size) { |
| log_flag(SELECT_TYPE, "test 0 fail: try again"); |
| bit_copybits(node_bitmap, orig_node_map); |
| free_core_array(&free_cores); |
| free_cores = copy_core_array(avail_cores); |
| min_nodes = next_job_size; |
| max_nodes = next_job_size; |
| req_nodes = next_job_size; |
| _free_avail_res_array(avail_res_array); |
| goto try_next_nodes_cnt; |
| } else if (!avail_res_array) { |
| /* job can not fit */ |
| xfree(tres_mc_ptr); |
| FREE_NULL_BITMAP(orig_node_map); |
| free_core_array(&avail_cores); |
| free_core_array(&free_cores); |
| log_flag(SELECT_TYPE, "test 0 fail: insufficient resources"); |
| return select_rc ? select_rc : SLURM_ERROR; |
| } else if (test_only) { |
| xfree(tres_mc_ptr); |
| FREE_NULL_BITMAP(orig_node_map); |
| free_core_array(&avail_cores); |
| free_core_array(&free_cores); |
| _free_avail_res_array(avail_res_array); |
| log_flag(SELECT_TYPE, "test 0 pass: test_only"); |
| return SLURM_SUCCESS; |
| } else if (!job_ptr->best_switch) { |
| xfree(tres_mc_ptr); |
| FREE_NULL_BITMAP(orig_node_map); |
| free_core_array(&avail_cores); |
| free_core_array(&free_cores); |
| _free_avail_res_array(avail_res_array); |
| log_flag(SELECT_TYPE, "test 0 fail: waiting for switches"); |
| return select_rc ? select_rc : SLURM_ERROR; |
| } |
| |
| log_flag(SELECT_TYPE, "test 0 pass - job fits on given resources"); |
| _free_avail_res_array(avail_res_array); |
| |
| /* |
| * now that we know that this job can run with the given resources, |
| * let's factor in the existing allocations and seek the optimal set |
| * of resources for this job. Here is the procedure: |
| * |
| * Step 1: Seek idle CPUs across all partitions. If successful then |
| * place job and exit. If not successful, then continue. Two |
| * related items to note: |
| * 1. Jobs that don't share CPUs finish with step 1. |
| * 2. The remaining steps assume sharing (gang or |
| * preempt/partition_prio + PREEMPT_MODE_SUSPEND). |
| * |
| * Step 2: Remove resources that are in use by higher-priority |
| * partitions, and test that job can still succeed. If not |
| * then exit. |
| * |
| * Step 3: Seek idle nodes among the partitions with the same |
| * priority as the job's partition. If successful then |
| * goto Step 6. If not then continue: |
| * |
| * Step 4: Seek placement within the job's partition. Search |
| * row-by-row. If no placement if found, then exit. If a row |
| * is found, then continue: |
| * |
| * Step 5: Place job and exit. FIXME! Here is where we need a |
| * placement algorithm that recognizes existing job |
| * boundaries and tries to "overlap jobs" as efficiently |
| * as possible. |
| * |
| * Step 6: Place job and exit. FIXME! here is we use a placement |
| * algorithm similar to Step 5 on jobs from lower-priority |
| * partitions. |
| */ |
| |
| /*** Step 1 ***/ |
| bit_copybits(node_bitmap, orig_node_map); |
| |
| free_core_array(&free_cores); |
| free_cores = copy_core_array(avail_cores); |
| |
| skip_test0: |
| if (resv_exc_ptr->exc_cores) { |
| #if _DEBUG |
| core_array_log("exclude reserved cores", |
| NULL, resv_exc_ptr->exc_cores); |
| #endif |
| core_array_and_not(free_cores, resv_exc_ptr->exc_cores); |
| } |
| |
| /* remove all existing allocations from free_cores */ |
| for (p_ptr = cr_part_ptr; p_ptr; p_ptr = p_ptr->next) { |
| if (!p_ptr->row) |
| continue; |
| for (i = 0; i < p_ptr->num_rows; i++) { |
| if (!p_ptr->row[i].row_bitmap) |
| continue; |
| |
| core_array_and_not(free_cores, |
| p_ptr->row[i].row_bitmap); |
| if (p_ptr->part_ptr != job_ptr->part_ptr) |
| continue; |
| if (part_core_map) { |
| core_array_or(part_core_map, |
| p_ptr->row[i].row_bitmap); |
| } else { |
| part_core_map = copy_core_array( |
| p_ptr->row[i].row_bitmap); |
| } |
| } |
| } |
| if (job_ptr->details->whole_node & WHOLE_NODE_REQUIRED) |
| _block_whole_nodes(node_bitmap, avail_cores, free_cores); |
| |
| _block_by_topology(job_ptr, cr_part_ptr, node_bitmap); |
| |
| avail_res_array = _select_nodes(job_ptr, min_nodes, max_nodes, |
| req_nodes, node_bitmap, free_cores, |
| node_usage, cr_type, test_only, |
| will_run, part_core_map, |
| prefer_alloc_nodes, tres_mc_ptr, |
| resv_exc_ptr, &select_rc); |
| if (avail_res_array && job_ptr->best_switch) { |
| /* job fits! We're done. */ |
| log_flag(SELECT_TYPE, "test 1 pass - idle resources found"); |
| goto alloc_job; |
| } |
| _free_avail_res_array(avail_res_array); |
| avail_res_array = NULL; |
| |
| if ((gang_mode == 0) && (job_node_req != NODE_CR_AVAILABLE)) { |
| /* |
| * This job CANNOT share CPUs regardless of priority, |
| * so we fail here. Note that job preemption removes |
| * jobs from simulated resource allocation map before |
| * this point. |
| */ |
| log_flag(SELECT_TYPE, "test 1 fail - no idle resources available"); |
| goto alloc_job; |
| } |
| log_flag(SELECT_TYPE, "test 1 fail - not enough idle resources"); |
| |
| /*** Step 2 ***/ |
| for (jp_ptr = cr_part_ptr; jp_ptr; jp_ptr = jp_ptr->next) { |
| if (jp_ptr->part_ptr == job_ptr->part_ptr) |
| break; |
| } |
| if (!jp_ptr) { |
| error("could not find partition for %pJ", |
| job_ptr); |
| goto alloc_job; |
| } |
| |
| bit_copybits(node_bitmap, orig_node_map); |
| free_core_array(&free_cores); |
| free_cores = copy_core_array(avail_cores); |
| if (resv_exc_ptr->exc_cores) |
| core_array_and_not(free_cores, resv_exc_ptr->exc_cores); |
| |
| /* |
| * Remove cores that are exempt from preemption. Only applicable when |
| * using PreemptMode=suspend,gang and PreemptExemptTime. |
| */ |
| if (suspend_exempt_cores && !test_only) |
| core_array_and_not(free_cores, suspend_exempt_cores); |
| |
| if (preempt_by_part) { |
| /* |
| * Remove from avail_cores resources allocated to jobs which |
| * this job can not preempt |
| */ |
| log_flag(SELECT_TYPE, "looking for higher-priority or PREEMPT_MODE_OFF part's to remove from avail_cores"); |
| |
| for (p_ptr = cr_part_ptr; p_ptr; p_ptr = p_ptr->next) { |
| if ((p_ptr->part_ptr->priority_tier <= |
| jp_ptr->part_ptr->priority_tier) && |
| (p_ptr->part_ptr->preempt_mode != |
| PREEMPT_MODE_OFF)) { |
| log_flag(SELECT_TYPE, "continuing on part: %s", |
| p_ptr->part_ptr->name); |
| continue; |
| } |
| /* |
| * If the partition allows oversubscription we can't |
| * easily determine if the job can start. It |
| * may be that it won't be able to start because of |
| * preemption, but it may be able to start on different |
| * row. |
| */ |
| if ((p_ptr->part_ptr == jp_ptr->part_ptr) && |
| (p_ptr->num_rows > 1)) |
| continue; |
| if (!p_ptr->row) |
| continue; |
| for (i = 0; i < p_ptr->num_rows; i++) { |
| if (!p_ptr->row[i].row_bitmap) |
| continue; |
| core_array_and_not(free_cores, |
| p_ptr->row[i].row_bitmap); |
| } |
| } |
| } |
| |
| if (job_ptr->details->whole_node & WHOLE_NODE_REQUIRED) |
| _block_whole_nodes(node_bitmap, avail_cores, free_cores); |
| |
| _block_by_topology(job_ptr, cr_part_ptr, node_bitmap); |
| |
| /* make these changes permanent */ |
| avail_cores_tmp = avail_cores; |
| avail_cores = copy_core_array(free_cores); |
| bit_copybits(orig_node_map, node_bitmap); |
| |
| avail_res_array = _select_nodes(job_ptr, min_nodes, max_nodes, |
| req_nodes, node_bitmap, free_cores, |
| node_usage, cr_type, test_only, |
| will_run, part_core_map, |
| prefer_alloc_nodes, tres_mc_ptr, |
| resv_exc_ptr, &select_rc); |
| if (!avail_res_array) { |
| /* |
| * job needs resources that are currently in use by |
| * higher-priority jobs, so fail for now |
| */ |
| log_flag(SELECT_TYPE, "test 2 fail - resources busy with higher priority jobs"); |
| goto alloc_job; |
| } |
| _free_avail_res_array(avail_res_array); |
| log_flag(SELECT_TYPE, "test 2 pass - available resources for this priority"); |
| |
| /*** Step 3 ***/ |
| bit_copybits(node_bitmap, orig_node_map); |
| free_core_array(&free_cores); |
| free_cores = copy_core_array(avail_cores); |
| |
| /* |
| * remove existing allocations (jobs) from same-priority partitions |
| * from avail_cores |
| */ |
| for (p_ptr = cr_part_ptr; p_ptr; p_ptr = p_ptr->next) { |
| if (p_ptr->part_ptr->priority_tier != |
| jp_ptr->part_ptr->priority_tier) |
| continue; |
| if (!p_ptr->row) |
| continue; |
| for (i = 0; i < p_ptr->num_rows; i++) { |
| if (!p_ptr->row[i].row_bitmap) |
| continue; |
| core_array_and_not(free_cores, |
| p_ptr->row[i].row_bitmap); |
| } |
| } |
| |
| if (job_ptr->details->whole_node & WHOLE_NODE_REQUIRED) |
| _block_whole_nodes(node_bitmap, avail_cores, free_cores); |
| |
| _block_by_topology(job_ptr, cr_part_ptr, node_bitmap); |
| |
| free_cores_tmp = copy_core_array(free_cores); |
| node_bitmap_tmp = bit_copy(node_bitmap); |
| avail_res_array = _select_nodes(job_ptr, min_nodes, max_nodes, |
| req_nodes, node_bitmap, free_cores, |
| node_usage, cr_type, test_only, |
| will_run, part_core_map, |
| prefer_alloc_nodes, tres_mc_ptr, |
| resv_exc_ptr, &select_rc); |
| if (avail_res_array) { |
| /* |
| * To the extent possible, remove from consideration resources |
| * which are allocated to jobs in lower priority partitions. |
| */ |
| log_flag(SELECT_TYPE, "test 3 pass - found resources"); |
| for (p_ptr = cr_part_ptr; p_ptr; p_ptr = p_ptr->next) { |
| if (p_ptr->part_ptr->priority_tier >= |
| jp_ptr->part_ptr->priority_tier) |
| continue; |
| if (!p_ptr->row) |
| continue; |
| for (i = 0; i < p_ptr->num_rows; i++) { |
| if (!p_ptr->row[i].row_bitmap) |
| continue; |
| core_array_and_not(free_cores_tmp, |
| p_ptr->row[i].row_bitmap); |
| } |
| if (job_ptr->details->whole_node & |
| WHOLE_NODE_REQUIRED) { |
| _block_whole_nodes(node_bitmap_tmp, avail_cores, |
| free_cores_tmp); |
| } |
| |
| _block_by_topology(job_ptr, cr_part_ptr, |
| node_bitmap_tmp); |
| |
| free_cores_tmp2 = copy_core_array(free_cores_tmp); |
| node_bitmap_tmp2 = bit_copy(node_bitmap_tmp); |
| avail_res_array_tmp = _select_nodes( |
| job_ptr, min_nodes, max_nodes, req_nodes, |
| node_bitmap_tmp, free_cores_tmp, node_usage, |
| cr_type, test_only, will_run, part_core_map, |
| prefer_alloc_nodes, tres_mc_ptr, |
| resv_exc_ptr, &select_rc); |
| if (!avail_res_array_tmp) { |
| free_core_array(&free_cores_tmp2); |
| FREE_NULL_BITMAP(node_bitmap_tmp2); |
| break; |
| } |
| log_flag(SELECT_TYPE, "remove low-priority partition %s", |
| p_ptr->part_ptr->name); |
| free_core_array(&free_cores); |
| free_cores = free_cores_tmp; |
| free_cores_tmp = free_cores_tmp2; |
| free_cores_tmp2 = NULL; |
| bit_copybits(node_bitmap, node_bitmap_tmp); |
| FREE_NULL_BITMAP(node_bitmap_tmp); |
| node_bitmap_tmp = node_bitmap_tmp2; |
| node_bitmap_tmp2 = NULL; |
| _free_avail_res_array(avail_res_array); |
| avail_res_array = avail_res_array_tmp; |
| } |
| goto alloc_job; |
| } |
| log_flag(SELECT_TYPE, "test 3 fail - not enough idle resources in same priority"); |
| |
| /*** Step 4 ***/ |
| /* |
| * try to fit the job into an existing row |
| * |
| * free_cores = core_bitmap to be built |
| * avail_cores = static core_bitmap of all available cores |
| */ |
| |
| if (!jp_ptr || !jp_ptr->row) { |
| /* |
| * there's no existing jobs in this partition, so place |
| * the job in avail_cores. FIXME: still need a good |
| * placement algorithm here that optimizes "job overlap" |
| * between this job (in these idle nodes) and existing |
| * jobs in the other partitions with <= priority to |
| * this partition |
| */ |
| free_core_array(&free_cores); |
| free_cores = copy_core_array(avail_cores); |
| bit_copybits(node_bitmap, orig_node_map); |
| avail_res_array = _select_nodes(job_ptr, min_nodes, max_nodes, |
| req_nodes, node_bitmap, |
| free_cores, node_usage, cr_type, |
| test_only, will_run, |
| part_core_map, |
| prefer_alloc_nodes, |
| tres_mc_ptr, resv_exc_ptr, |
| &select_rc); |
| if (avail_res_array) |
| log_flag(SELECT_TYPE, "test 4 pass - first row found"); |
| goto alloc_job; |
| } |
| |
| |
| if ((jp_ptr->num_rows > 1) && (!preempt_by_qos || qos_preemptees)) |
| part_data_sort_res(jp_ptr); /* Preserve row order for QOS */ |
| c = jp_ptr->num_rows; |
| if (preempt_by_qos && !use_extra_row) |
| c--; /* Do not use extra row */ |
| if (qos_preemptees && use_extra_row) { |
| /* |
| * We may be putting the job in extra row. We need to make sure |
| * that extra row allows the use of resources of jobs that we |
| * were allowed to preempt and we already know the job should |
| * fit there. If we just leave the row empty, we will select |
| * the nodes not taking currently running jobs into account |
| */ |
| if (!jp_ptr->row[c - 1].row_bitmap) |
| jp_ptr->row[c - 1].row_bitmap = build_core_array(); |
| for (int i = 0; i < (c - 1); i++) { |
| core_array_or(jp_ptr->row[c - 1].row_bitmap, |
| jp_ptr->row[i].row_bitmap); |
| } |
| |
| list_for_each_ro(qos_preemptees, _foreach_rm_cores, |
| &jp_ptr->row[c - 1]); |
| } |
| |
| |
| if (preempt_by_qos && (job_node_req != NODE_CR_AVAILABLE)) |
| c = 1; |
| for (i = 0; i < c; i++) { |
| if (!jp_ptr->row[i].row_bitmap) |
| break; |
| free_core_array(&free_cores); |
| free_cores = copy_core_array(avail_cores); |
| core_array_and_not(free_cores, jp_ptr->row[i].row_bitmap); |
| bit_copybits(node_bitmap, orig_node_map); |
| if (job_ptr->details->whole_node & WHOLE_NODE_REQUIRED) |
| _block_whole_nodes(node_bitmap, avail_cores,free_cores); |
| |
| _block_by_topology(job_ptr, cr_part_ptr, node_bitmap); |
| |
| avail_res_array = _select_nodes(job_ptr, min_nodes, max_nodes, |
| req_nodes, node_bitmap, |
| free_cores, node_usage, cr_type, |
| test_only, will_run, |
| part_core_map, |
| prefer_alloc_nodes, |
| tres_mc_ptr, resv_exc_ptr, |
| &select_rc); |
| if (avail_res_array) { |
| log_flag(SELECT_TYPE, "test 4 pass - row %i", |
| i); |
| break; |
| } |
| log_flag(SELECT_TYPE, "test 4 fail - row %i", |
| i); |
| } |
| |
| if ((i < c) && !jp_ptr->row[i].row_bitmap) { |
| /* we've found an empty row, so use it */ |
| free_core_array(&free_cores); |
| free_cores = copy_core_array(avail_cores); |
| bit_copybits(node_bitmap, orig_node_map); |
| log_flag(SELECT_TYPE, "test 4 trying empty row %i", |
| i); |
| avail_res_array = _select_nodes(job_ptr, min_nodes, max_nodes, |
| req_nodes, node_bitmap, |
| free_cores, node_usage, cr_type, |
| test_only, will_run, |
| part_core_map, |
| prefer_alloc_nodes, |
| tres_mc_ptr, resv_exc_ptr, |
| &select_rc); |
| } |
| |
| if (!avail_res_array) { |
| /* job can't fit into any row, so exit */ |
| log_flag(SELECT_TYPE, "test 4 fail - busy partition"); |
| goto alloc_job; |
| } |
| |
| /* |
| *** CONSTRUCTION ZONE FOR STEPs 5 AND 6 *** |
| * Note that while the job may have fit into a row, it should |
| * still be run through a good placement algorithm here that |
| * optimizes "job overlap" between this job (in these idle nodes) |
| * and existing jobs in the other partitions with <= priority to |
| * this partition |
| */ |
| |
| alloc_job: |
| /* |
| * at this point we've found a good set of nodes and cores for the job: |
| * - node_bitmap is the set of nodes to allocate |
| * - free_cores is the set of allocated cores |
| * - avail_res_array identifies cores and GRES |
| * |
| * Next steps are to cleanup the worker variables, |
| * create the job_resources struct, |
| * distribute the job on the bits, and exit |
| */ |
| if ((!avail_res_array || !job_ptr->best_switch) && next_job_size) { |
| log_flag(SELECT_TYPE, "no idle resources, try next job size:%u", |
| next_job_size); |
| bit_copybits(node_bitmap, orig_node_map); |
| free_core_array(&free_cores); |
| if (avail_cores_tmp) { |
| free_core_array(&avail_cores); |
| avail_cores = avail_cores_tmp; |
| avail_cores_tmp = NULL; |
| } |
| free_cores = copy_core_array(avail_cores); |
| min_nodes = next_job_size; |
| max_nodes = next_job_size; |
| req_nodes = next_job_size; |
| _free_avail_res_array(avail_res_array); |
| goto try_next_nodes_cnt; |
| } |
| |
| if (avail_cores_tmp) |
| free_core_array(&avail_cores_tmp); |
| |
| FREE_NULL_BITMAP(orig_node_map); |
| free_core_array(&part_core_map); |
| free_core_array(&free_cores_tmp); |
| FREE_NULL_BITMAP(node_bitmap_tmp); |
| if (!avail_res_array || !job_ptr->best_switch) { |
| /* we were sent here to cleanup and exit */ |
| xfree(tres_mc_ptr); |
| free_core_array(&avail_cores); |
| free_core_array(&free_cores); |
| _free_avail_res_array(avail_res_array); |
| log_flag(SELECT_TYPE, "exiting with no allocation select_rc=%d", select_rc); |
| return select_rc ? select_rc : SLURM_ERROR; |
| } |
| |
| if ((mode != SELECT_MODE_WILL_RUN) && (job_ptr->part_ptr == NULL)) |
| error_code = EINVAL; |
| if ((error_code == SLURM_SUCCESS) && (mode == SELECT_MODE_WILL_RUN)) { |
| /* |
| * Set a reasonable value for the number of allocated CPUs. |
| * Without computing task distribution this is only a guess |
| */ |
| job_ptr->total_cpus = MAX(job_ptr->details->min_cpus, |
| job_ptr->details->min_nodes); |
| } |
| |
| /* |
| * Defer checking select mode until we get a correct CPU count. Then |
| * exit if select mode is not SELECT_MODE_RUN_NOW, making sure to free |
| * job_ptr->job_resrcs. |
| */ |
| if (error_code != SLURM_SUCCESS) { |
| xfree(tres_mc_ptr); |
| free_core_array(&avail_cores); |
| free_core_array(&free_cores); |
| _free_avail_res_array(avail_res_array); |
| return error_code; |
| } |
| |
| log_flag(SELECT_TYPE, "distributing %pJ", |
| job_ptr); |
| |
| /** create the struct_job_res **/ |
| n = bit_set_count(node_bitmap); |
| cpu_count = xmalloc(sizeof(uint16_t) * n); |
| for (i = 0, j = 0; next_node_bitmap(node_bitmap, &i); i++) { |
| if (avail_res_array[i]) |
| cpu_count[j++] = avail_res_array[i]->avail_cpus; |
| } |
| if (j != n) { |
| error("problem building cpu_count array (%d != %d)", |
| j, n); |
| } |
| |
| job_res = create_job_resources(); |
| job_res->node_bitmap = bit_copy(node_bitmap); |
| job_res->nodes = bitmap2node_name_sortable(node_bitmap, false); |
| job_res->nhosts = n; |
| job_res->ncpus = job_res->nhosts; |
| job_res->threads_per_core = job_ptr->details->mc_ptr->threads_per_core; |
| job_res->cr_type = cr_type; |
| |
| if (job_ptr->details->ntasks_per_node) |
| job_res->ncpus *= details_ptr->ntasks_per_node; |
| /* See if # of cpus increases with ntasks_per_tres */ |
| i = gres_select_util_job_min_tasks(job_res->nhosts, sockets_per_node, |
| details_ptr->ntasks_per_tres, "gpu", |
| job_ptr->gres_list_req); |
| job_res->ncpus = MAX(job_res->ncpus, i); |
| job_res->ncpus = MAX(job_res->ncpus, |
| details_ptr->min_cpus); |
| job_res->ncpus = MAX(job_res->ncpus, |
| (job_res->nhosts * |
| details_ptr->pn_min_cpus)); |
| if (job_ptr->details->mc_ptr) |
| sockets_per_node = job_ptr->details->mc_ptr->sockets_per_node; |
| if (!job_ptr->gres_list_req_accum) |
| job_ptr->gres_list_req_accum = |
| gres_select_util_create_list_req_accum( |
| job_ptr->gres_list_req); |
| i = gres_select_util_job_min_cpus(job_res->nhosts, sockets_per_node, |
| job_ptr->details->num_tasks, |
| job_ptr->gres_list_req_accum); |
| job_res->ncpus = MAX(job_res->ncpus, i); |
| job_res->node_req = job_node_req; |
| job_res->cpus = cpu_count; /* Per node CPU counts */ |
| job_res->cpus_used = xmalloc(job_res->nhosts * |
| sizeof(uint16_t)); |
| job_res->memory_allocated = xmalloc(job_res->nhosts * |
| sizeof(uint64_t)); |
| job_res->memory_used = xmalloc(job_res->nhosts * |
| sizeof(uint64_t)); |
| job_res->whole_node = job_ptr->details->whole_node; |
| |
| /* store the hardware data for the selected nodes */ |
| error_code = build_job_resources(job_res); |
| if (error_code != SLURM_SUCCESS) { |
| xfree(tres_mc_ptr); |
| _free_avail_res_array(avail_res_array); |
| free_job_resources(&job_res); |
| free_core_array(&avail_cores); |
| free_core_array(&free_cores); |
| return error_code; |
| } |
| |
| /* total up all CPUs and load the core_bitmap */ |
| total_cpus = 0; |
| c = 0; |
| if (job_res->core_bitmap) |
| c_size = bit_size(job_res->core_bitmap); |
| else |
| c_size = 0; |
| gres_min_cpus = xcalloc(job_res->nhosts, sizeof(uint32_t)); |
| for (i = 0, n = 0; (node_ptr = next_node_bitmap(node_bitmap, &i)); |
| i++) { |
| uint32_t gres_min_node_cpus; |
| int first_core = 0, last_core = node_ptr->tot_cores; |
| bitstr_t *use_free_cores = free_cores[i]; |
| |
| /* Set the start lower if any nodes have a lower version */ |
| if (job_ptr->start_protocol_ver > node_ptr->protocol_version) |
| job_ptr->start_protocol_ver = |
| node_ptr->protocol_version; |
| |
| for (j = first_core; j < last_core; j++, c++) { |
| if (!bit_test(use_free_cores, j)) |
| continue; |
| if (c >= c_size) { |
| error("core_bitmap index error on node %s (NODE_INX:%d, C_SIZE:%u)", |
| node_ptr->name, i, c_size); |
| drain_nodes(node_ptr->name, "Bad core count", |
| getuid()); |
| _free_avail_res_array(avail_res_array); |
| free_job_resources(&job_res); |
| free_core_array(&free_cores); |
| xfree(gres_min_cpus); |
| return SLURM_ERROR; |
| } |
| bit_set(job_res->core_bitmap, c); |
| c_alloc++; |
| } |
| if ((gres_min_node_cpus = avail_res_array[i]->gres_min_cpus)) { |
| gres_min_cpus[n] = gres_min_node_cpus; |
| log_flag( |
| SELECT_TYPE, |
| "%pJ: Node=%s: job_res->cpus[%d]=%u, gres_min_cpus[%d]=%u", |
| job_ptr, node_record_table_ptr[i]->name, i, |
| job_res->cpus[n], i, gres_min_cpus[n]); |
| } |
| if (avail_res_array[i]->gres_max_tasks) |
| have_gres_max_tasks = true; |
| total_cpus += job_res->cpus[n]; |
| n++; |
| } |
| |
| /* |
| * When 'srun --overcommit' is used, ncpus is set to a minimum value |
| * in order to allocate the appropriate number of nodes based on the |
| * job request. |
| * For cons_tres, all available logical processors will be allocated on |
| * each allocated node in order to accommodate the overcommit request. |
| */ |
| if (details_ptr->overcommit && details_ptr->num_tasks) |
| job_res->ncpus = MIN(total_cpus, details_ptr->num_tasks); |
| |
| log_flag(SELECT_TYPE, "%pJ ncpus %u cbits %u/%u nbits %u", |
| job_ptr, job_res->ncpus, |
| count_core_array_set(free_cores), c_alloc, job_res->nhosts); |
| free_core_array(&free_cores); |
| |
| /* distribute the tasks, clear unused cores from job_res->core_bitmap */ |
| job_ptr->job_resrcs = job_res; |
| if (job_ptr->gres_list_req && (error_code == SLURM_SUCCESS)) { |
| bool have_gres_per_task, task_limit_set = false; |
| /* |
| * Determine if any job gres_per_task specification here |
| * to avoid calling gres_get_task_limit unless needed |
| */ |
| have_gres_per_task = gres_select_util_job_tres_per_task( |
| job_ptr->gres_list_req); |
| if (have_gres_per_task || have_gres_max_tasks) { |
| gres_task_limit = xcalloc(job_res->nhosts, |
| sizeof(uint32_t)); |
| } |
| node_gres_list = xcalloc(job_res->nhosts, sizeof(list_t *)); |
| sock_gres_list = xcalloc(job_res->nhosts, sizeof(list_t *)); |
| for (i = 0, j = 0; |
| (node_ptr = next_node_bitmap(job_res->node_bitmap, &i)); |
| i++) { |
| if (have_gres_per_task) { |
| gres_task_limit[j] = |
| gres_select_util_get_task_limit( |
| avail_res_array[i]-> |
| sock_gres_list); |
| if (gres_task_limit[j] != NO_VAL) |
| task_limit_set = true; |
| } else if (have_gres_max_tasks) { |
| gres_task_limit[j] = |
| avail_res_array[i]->gres_max_tasks; |
| task_limit_set = true; |
| } |
| node_gres_list[j] = node_ptr->gres_list; |
| sock_gres_list[j] = |
| avail_res_array[i]->sock_gres_list; |
| if (task_limit_set) |
| log_flag(SELECT_TYPE, "%pJ: Node=%s: gres_task_limit[%d]=%u", |
| job_ptr, |
| node_ptr->name, |
| i, gres_task_limit[j]); |
| j++; |
| } |
| |
| if (!task_limit_set) |
| xfree(gres_task_limit); |
| } |
| error_code = dist_tasks(job_ptr, cr_type, preempt_mode, avail_cores, |
| gres_task_limit, gres_min_cpus); |
| xfree(gres_min_cpus); |
| if (job_ptr->gres_list_req && (error_code == SLURM_SUCCESS)) { |
| error_code = gres_select_filter_select_and_set( |
| sock_gres_list, job_ptr, tres_mc_ptr); |
| } |
| xfree(gres_task_limit); |
| xfree(node_gres_list); |
| xfree(sock_gres_list); |
| xfree(tres_mc_ptr); |
| _free_avail_res_array(avail_res_array); |
| free_core_array(&avail_cores); |
| |
| if ((error_code == SLURM_SUCCESS) && (job_res->nhosts > 1) && |
| !(slurm_conf.select_type_param & SELECT_NO_DIST_TOPO_BLOCK)) { |
| uint32_t cnt = 0; |
| error_code = |
| topology_g_get_rank(job_res->node_bitmap, |
| &(job_res->node_ranks), &cnt, |
| job_ptr->part_ptr->topology_idx); |
| if (job_res->node_ranks && (cnt != job_res->nhosts)) |
| error_code = SLURM_ERROR; |
| if (error_code) |
| error("problem building node_ranks array"); |
| } |
| |
| if (error_code != SLURM_SUCCESS) { |
| free_job_resources(&job_ptr->job_resrcs); |
| return error_code; |
| } |
| |
| /* translate job_res->cpus array into format with repetition count */ |
| build_cnt = build_job_resources_cpu_array(job_res); |
| if (job_ptr->details->whole_node & WHOLE_NODE_REQUIRED) { |
| job_ptr->total_cpus = 0; |
| for (i = 0; |
| (node_ptr = next_node_bitmap(job_res->node_bitmap, &i)); |
| i++) { |
| /* |
| * This could make the job_res->cpus incorrect. |
| * Don't use job_res->cpus when allocating |
| * whole nodes as the job is finishing to |
| * subtract from the total cpu count or you |
| * will get an incorrect count. |
| */ |
| job_ptr->total_cpus += node_ptr->cpus_efctv; |
| } |
| } else if (cr_type & SELECT_SOCKET) { |
| int ci = 0; |
| int s, last_s, sock_cnt = 0; |
| |
| job_ptr->total_cpus = 0; |
| for (i = 0; |
| (node_ptr = next_node_bitmap(job_res->node_bitmap, &i)); |
| i++) { |
| |
| sock_cnt = 0; |
| for (s = 0; s < node_ptr->tot_sockets; s++) { |
| last_s = -1; |
| for (c = 0; c < node_ptr->cores; c++) { |
| if (bit_test(job_res->core_bitmap, |
| ci)) { |
| if (s != last_s) { |
| sock_cnt++; |
| last_s = s; |
| } |
| } |
| ci++; |
| } |
| } |
| job_ptr->total_cpus += (sock_cnt * node_ptr->cores * |
| node_ptr->tpc); |
| } |
| } else if (build_cnt >= 0) |
| job_ptr->total_cpus = build_cnt; |
| else |
| job_ptr->total_cpus = total_cpus; /* best guess */ |
| |
| /* |
| * Stop if we aren't trying to start the job right now. We needed to |
| * get to here to have an accurate total_cpus so that accounting limits |
| * checks are accurate later on. |
| */ |
| if (mode != SELECT_MODE_RUN_NOW) { |
| /* |
| * In the cases where we are evaluating a preemptor job, |
| * we need to save a copy of the assigned node_bitmap so |
| * we have enough information to accurately determine |
| * if we are not breaking accounting policy limits later on. |
| */ |
| FREE_NULL_BITMAP(job_ptr->node_bitmap_preempt); |
| job_ptr->node_bitmap_preempt = |
| bit_copy(job_ptr->job_resrcs->node_bitmap); |
| |
| /* |
| * If we are a reservation the job_id == 0, we don't want to |
| * free job_resrcs here. |
| */ |
| if (job_ptr->job_id) |
| free_job_resources(&job_ptr->job_resrcs); |
| return error_code; |
| } |
| |
| if (!(cr_type & SELECT_MEMORY)) |
| return error_code; |
| |
| if (!(job_ptr->bit_flags & JOB_MEM_SET) && |
| gres_select_util_job_mem_set(job_ptr->gres_list_req, job_res)) { |
| debug("%pJ memory set via GRES limit", job_ptr); |
| } else { |
| /* load memory allocated array */ |
| save_mem = details_ptr->pn_min_memory; |
| for (i = 0, j = 0; |
| (node_ptr = next_node_bitmap(job_res->node_bitmap, &i)); |
| i++) { |
| nodename = node_ptr->name; |
| avail_mem = node_ptr->real_memory - |
| node_ptr->mem_spec_limit; |
| if (save_mem & MEM_PER_CPU) { /* Memory per CPU */ |
| /* |
| * If the job requested less threads that we |
| * allocated but requested memory based on cpu |
| * count we would need to adjust that to avoid |
| * getting more memory than we are actually |
| * expecting. |
| */ |
| uint16_t cpu_count = |
| job_resources_get_node_cpu_cnt( |
| job_res, j, i); |
| needed_mem = cpu_count * |
| (save_mem & (~MEM_PER_CPU)); |
| } else if (save_mem) { /* Memory per node */ |
| needed_mem = save_mem; |
| } else { /* Allocate all node memory */ |
| needed_mem = avail_mem; |
| if (node_usage[i].alloc_memory > 0) { |
| log_flag(SELECT_TYPE, "node %s has already alloc_memory=%"PRIu64". %pJ can't allocate all node memory", |
| nodename, |
| node_usage[i].alloc_memory, |
| job_ptr); |
| error_code = SLURM_ERROR; |
| break; |
| } |
| if ((j == 0) || (lowest_mem > avail_mem)) |
| lowest_mem = avail_mem; |
| } |
| if (save_mem) { |
| if (node_usage[i].alloc_memory > avail_mem) { |
| error("node %s memory is already overallocated (%"PRIu64" > %"PRIu64"). %pJ can't allocate any node memory", |
| nodename, |
| node_usage[i].alloc_memory, |
| avail_mem, job_ptr); |
| error_code = SLURM_ERROR; |
| break; |
| } |
| avail_mem -= node_usage[i].alloc_memory; |
| } |
| if (needed_mem > avail_mem) { |
| log_flag(SELECT_TYPE, "%pJ would overallocate node %s memory (%"PRIu64" > %"PRIu64")", |
| job_ptr, nodename, |
| needed_mem, avail_mem); |
| error_code = SLURM_ERROR; |
| break; |
| } |
| job_res->memory_allocated[j] = needed_mem; |
| j++; |
| } |
| } |
| if (error_code == SLURM_ERROR) |
| free_job_resources(&job_ptr->job_resrcs); |
| |
| return error_code; |
| } |
| |
| static uint16_t _setup_cr_type(job_record_t *job_ptr) |
| { |
| uint16_t tmp_cr_type = slurm_conf.select_type_param; |
| |
| if (job_ptr->part_ptr->cr_type) { |
| if ((tmp_cr_type & SELECT_SOCKET) || |
| (tmp_cr_type & SELECT_CORE)) { |
| tmp_cr_type &= |
| ~(SELECT_SOCKET | SELECT_CORE | SELECT_MEMORY); |
| tmp_cr_type |= job_ptr->part_ptr->cr_type; |
| } else |
| info("%s: Can't use Partition SelectType unless using CR_Socket or CR_Core", |
| plugin_type); |
| } |
| |
| return tmp_cr_type; |
| } |
| |
| /* Determine if a job can ever run */ |
| static int _test_only(job_record_t *job_ptr, bitstr_t *node_bitmap, |
| uint32_t min_nodes, uint32_t max_nodes, |
| uint32_t req_nodes, uint16_t job_node_req) |
| { |
| int rc; |
| uint16_t tmp_cr_type = _setup_cr_type(job_ptr); |
| list_t *license_list = cluster_license_copy(); |
| |
| hres_pre_select(job_ptr, true); |
| |
| rc = _job_test(job_ptr, node_bitmap, min_nodes, max_nodes, req_nodes, |
| SELECT_MODE_TEST_ONLY, tmp_cr_type, job_node_req, |
| select_part_record, select_node_usage, license_list, |
| NULL, false, false, false, NULL); |
| FREE_NULL_LIST(license_list); |
| return rc; |
| } |
| |
| static int _wrapper_get_usable_nodes(void *x, void *arg) |
| { |
| job_record_t *job_ptr = (job_record_t *)x; |
| wrapper_rm_job_args_t *wargs = (wrapper_rm_job_args_t *)arg; |
| |
| if ((!IS_JOB_RUNNING(job_ptr) && !IS_JOB_SUSPENDED(job_ptr))) |
| return 0; |
| |
| wargs->rc += bit_overlap(wargs->node_map, job_ptr->node_bitmap); |
| return 0; |
| } |
| |
| static int _get_usable_nodes(bitstr_t *node_map, job_record_t *job_ptr) |
| { |
| wrapper_rm_job_args_t wargs = { |
| .node_map = node_map |
| }; |
| |
| if (!job_ptr->het_job_list) |
| (void)_wrapper_get_usable_nodes(job_ptr, &wargs); |
| else |
| (void)list_for_each_nobreak(job_ptr->het_job_list, |
| _wrapper_get_usable_nodes, |
| &wargs); |
| return wargs.rc; |
| } |
| |
| static int _wrapper_job_res_rm_job(void *x, void *arg) |
| { |
| job_record_t *job_ptr = (job_record_t *)x; |
| wrapper_rm_job_args_t *wargs = (wrapper_rm_job_args_t *)arg; |
| |
| (void)job_res_rm_job(wargs->part_record_ptr, wargs->node_usage, |
| wargs->license_list, job_ptr, wargs->action, |
| wargs->node_map); |
| |
| return 0; |
| } |
| |
| static int _job_res_rm_job(part_res_record_t *part_record_ptr, |
| node_use_record_t *node_usage, list_t *license_list, |
| job_record_t *job_ptr, int action, |
| bitstr_t *node_map) |
| { |
| wrapper_rm_job_args_t wargs = { |
| .action = action, |
| .license_list = license_list, |
| .node_usage = node_usage, |
| .part_record_ptr = part_record_ptr, |
| .node_map = node_map |
| }; |
| |
| if (!job_overlap_and_running(node_map, license_list, job_ptr)) |
| return 1; |
| |
| if (!job_ptr->het_job_list) |
| (void)_wrapper_job_res_rm_job(job_ptr, &wargs); |
| else |
| (void)list_for_each(job_ptr->het_job_list, |
| _wrapper_job_res_rm_job, |
| &wargs); |
| return 0; |
| } |
| |
| static bitstr_t *_select_topo_bitmap(job_record_t *job_ptr, |
| bitstr_t *node_bitmap, |
| bitstr_t **efctv_bitmap, int *topology_idx) |
| { |
| if (IS_JOB_WHOLE_TOPO(job_ptr)) { |
| if (!(*efctv_bitmap)) { |
| *efctv_bitmap = bit_copy(node_bitmap); |
| *topology_idx = job_ptr->part_ptr->topology_idx; |
| topology_g_whole_topo(*efctv_bitmap, |
| job_ptr->part_ptr->topology_idx); |
| } else if (*topology_idx != job_ptr->part_ptr->topology_idx) { |
| *topology_idx = job_ptr->part_ptr->topology_idx; |
| bit_copybits(*efctv_bitmap, node_bitmap); |
| topology_g_whole_topo(*efctv_bitmap, |
| job_ptr->part_ptr->topology_idx); |
| } |
| return *efctv_bitmap; |
| } else |
| return node_bitmap; |
| } |
| |
| static int _build_cr_job_list(void *x, void *arg) |
| { |
| int action; |
| job_record_t *tmp_job_ptr = (job_record_t *)x; |
| job_record_t *job_ptr_preempt = NULL; |
| cr_job_list_args_t *args = (cr_job_list_args_t *)arg; |
| |
| if (!IS_JOB_RUNNING(tmp_job_ptr) && |
| !IS_JOB_SUSPENDED(tmp_job_ptr)) |
| return 0; |
| if (tmp_job_ptr->end_time == 0) { |
| error("Active %pJ has zero end_time", tmp_job_ptr); |
| return 0; |
| } |
| if (tmp_job_ptr->node_bitmap == NULL) { |
| /* |
| * This should indicate a requeued job was cancelled |
| * while NHC was running |
| */ |
| error("%pJ has NULL node_bitmap", tmp_job_ptr); |
| return 0; |
| } |
| /* |
| * For hetjobs, only the leader component is potentially added |
| * to the preemptee_candidates. If the leader is preemptable, |
| * it will be removed in the else statement alongside all of the |
| * rest of the components. For such case, we don't want to |
| * append non-leaders to cr_job_list, otherwise we would be |
| * double deallocating them (once in this else statement and |
| * twice later in the simulation of jobs removal). |
| */ |
| job_ptr_preempt = tmp_job_ptr; |
| if (tmp_job_ptr->het_job_id) { |
| job_ptr_preempt = find_job_record(tmp_job_ptr->het_job_id); |
| if (!job_ptr_preempt) { |
| error("%pJ HetJob leader not found", tmp_job_ptr); |
| return 0; |
| } |
| } |
| |
| if (_soft_job_end(job_ptr_preempt, args->now) <= args->start) { |
| bitstr_t *efctv_bitmap_ptr; |
| efctv_bitmap_ptr = |
| _select_topo_bitmap(tmp_job_ptr, args->orig_map, |
| args->tmp_bitmap_pptr, |
| args->topology_idx); |
| if (bit_overlap_any(efctv_bitmap_ptr, |
| tmp_job_ptr->node_bitmap) || |
| license_list_overlap(tmp_job_ptr->license_list, |
| args->job_license_list)) { |
| job_res_rm_job(args->future_part, args->future_usage, |
| args->future_license_list, tmp_job_ptr, |
| JOB_RES_ACTION_NORMAL, efctv_bitmap_ptr); |
| } |
| } else if (!_is_preemptable(job_ptr_preempt, |
| args->preemptee_candidates)) { |
| /* Queue job for later removal from data structures */ |
| list_append(args->cr_job_list, tmp_job_ptr); |
| } else if (tmp_job_ptr == job_ptr_preempt) { |
| uint16_t mode = slurm_job_preempt_mode(tmp_job_ptr); |
| if (mode == PREEMPT_MODE_OFF) |
| return 0; |
| if (mode == PREEMPT_MODE_SUSPEND) { |
| /* remove cores, keep memory */ |
| action = JOB_RES_ACTION_RESUME; |
| if (preempt_by_qos) |
| *args->qos_preemptor = true; |
| } else { |
| /* remove cores and memory */ |
| action = JOB_RES_ACTION_NORMAL; |
| } |
| /* Remove preemptable job now */ |
| _job_res_rm_job(args->future_part, args->future_usage, |
| args->future_license_list, tmp_job_ptr, action, |
| args->orig_map); |
| } |
| return 0; |
| } |
| |
| /* |
| * Set scheduling weight for node bitmaps -- pre-nodeset scheduling. |
| * |
| * Similar to _set_sched_weight() in node_scheduler.c except this function |
| * shifts by 16 instead of 8 to give room to accommodate extra weighted states |
| * (e.g. completing and rebooting). sched_weight will be rebuilt when scheduling |
| * based off of nodesets in _build_node_list(). |
| * |
| * 0x00000000000## - Reserved for cons_tres, favor nodes with co-located CPU/GPU |
| * 0x000000000##00 - Reserved for completing and rebooting nodes |
| * 0x0########0000 - Node weight |
| * 0x#000000000000 - Reserved for powered down nodes |
| * |
| * 0x0000000000100 - Completing nodes |
| * 0x0000000000200 - Rebooting nodes |
| * 0x2000000000000 - Node powered down |
| */ |
| static void _set_sched_weight(bitstr_t *node_bitmap, bool future) |
| { |
| node_record_t *node_ptr; |
| |
| for (int i = 0; (node_ptr = next_node_bitmap(node_bitmap, &i)); i++) { |
| node_ptr->sched_weight = node_ptr->weight; |
| node_ptr->sched_weight = node_ptr->sched_weight << 16; |
| if (!future && IS_NODE_COMPLETING(node_ptr)) |
| node_ptr->sched_weight |= 0x100; |
| if (IS_NODE_REBOOT_REQUESTED(node_ptr) || |
| IS_NODE_REBOOT_ISSUED(node_ptr)) |
| node_ptr->sched_weight |= 0x200; |
| if (IS_NODE_POWERED_DOWN(node_ptr) || |
| IS_NODE_POWERING_DOWN(node_ptr) || |
| IS_NODE_POWERING_UP(node_ptr)) |
| node_ptr->sched_weight |= 0x2000000000000; |
| } |
| } |
| |
| /* Return true if the removed job's end time can not be safely ignored */ |
| static int _is_job_relevant(void *x, void *key) |
| { |
| job_record_t *running_job_ptr = x; |
| first_relevant_job_arg_t *args = key; |
| bitstr_t *effective_bitmap; |
| licenses_t *match; |
| |
| xassert(args); |
| xassert(args->selected_nodes); |
| |
| effective_bitmap = |
| _select_topo_bitmap(running_job_ptr, args->selected_nodes, |
| &args->effective_nodes, args->topology_idx); |
| |
| /* If nodes overlap assume it is not safe to ignore */ |
| if (bit_overlap_any(effective_bitmap, running_job_ptr->node_bitmap)) |
| return true; |
| |
| /* |
| * Verify there are enough licenses without this job's licenses. |
| * This is only dealing with normal licenses, not hierarchal resources. |
| * see the comment in _set_license_req() for more details. |
| */ |
| if (running_job_ptr->license_list && args->needed_licenses) { |
| for (uint32_t i = 0; i < args->license_cnt; i++) { |
| license_req_t *needed_lic = &args->needed_licenses[i]; |
| match = license_find_rec_by_id(running_job_ptr |
| ->license_list, |
| *(needed_lic->id)); |
| if (!match) |
| continue; |
| |
| /* |
| * At this point, we know that there are enough |
| * licenses in the cluster to run the job request. If |
| * we remove licenses used by running_job, then are |
| * there still enough for the job to run? If not, then |
| * we know running_job is relevant for this job request. |
| */ |
| if (needed_lic->remaining < match->total) |
| needed_lic->remaining = 0; |
| else |
| needed_lic->remaining -= match->total; |
| if (needed_lic->remaining < needed_lic->required) |
| return true; |
| } |
| } |
| |
| if (slurm_conf.debug_flags & DEBUG_FLAG_SELECT_TYPE) |
| verbose("%pJ no longer overlaps after resource selection", |
| running_job_ptr); |
| return false; |
| } |
| |
| static int _set_license_req(void *x, void *arg) |
| { |
| first_relevant_job_arg_t *args = arg; |
| licenses_t *job_license = x; |
| licenses_t *future_license = NULL; |
| |
| /* |
| * Populate needed_licenses with the required # of licenses for the job |
| * and the amount of licenses available to be used. |
| * Hierarchal resource licenses are ignored. This logic is currently |
| * only used by --test-only job option, which does not support checking |
| * hierarchal resources. |
| */ |
| if ((job_license->id.hres_id == NO_VAL16) && |
| (future_license = license_find_rec_by_id(args->future_license_list, |
| job_license->id))) { |
| args->needed_licenses[args->license_cnt].id = |
| &future_license->id; |
| args->needed_licenses[args->license_cnt].required = |
| job_license->total; |
| args->needed_licenses[args->license_cnt].remaining = |
| future_license->total - future_license->used; |
| |
| args->license_cnt++; |
| } |
| |
| return SLURM_SUCCESS; |
| } |
| |
| /* |
| * Returns a pointer to the job with the latest endtime that can't be ignored. |
| * IN job_ptr - job being scheduled |
| * IN node_bitmap - nodes selected for job_ptr |
| * IN removed_jobs - list of jobs whose resources where considered for job_ptr |
| * sorted by latest end time to soonest end time. |
| * IN future_license_list - list of licenses in the state they would be at the |
| * end time of the first job in removed_jobs. |
| * (Note: For Backfill cycles this will be NULL) |
| * IN topology_idx - pointer to topology index |
| */ |
| static job_record_t *_get_last_relevant_job(job_record_t *job_ptr, |
| bitstr_t *node_bitmap, |
| list_t *removed_jobs, |
| list_t *future_license_list, |
| int *topology_idx) |
| { |
| job_record_t *last_relevant_job; |
| first_relevant_job_arg_t relevant_job_args = { |
| .selected_nodes = node_bitmap, |
| .topology_idx = topology_idx, |
| }; |
| |
| xassert(removed_jobs); |
| |
| if (future_license_list && job_ptr->license_list) { |
| /* |
| * Only pass relevant future license to |
| * _is_job_relevant() to reduce looping |
| */ |
| relevant_job_args.needed_licenses = |
| xcalloc(list_count(job_ptr->license_list), |
| sizeof(license_req_t)); |
| relevant_job_args.future_license_list = future_license_list; |
| |
| list_for_each(job_ptr->license_list, _set_license_req, |
| &relevant_job_args); |
| } |
| |
| last_relevant_job = list_find_first(removed_jobs, _is_job_relevant, |
| &relevant_job_args); |
| |
| xfree(relevant_job_args.needed_licenses); |
| FREE_NULL_BITMAP(relevant_job_args.effective_nodes); |
| |
| return last_relevant_job; |
| } |
| |
| static int _future_run_test(job_record_t *job_ptr, bitstr_t *node_bitmap, |
| uint32_t min_nodes, uint32_t max_nodes, |
| uint32_t req_nodes, uint16_t job_node_req, |
| list_t *preemptee_candidates, |
| list_t **preemptee_job_list, |
| resv_exc_t *resv_exc_ptr, |
| will_run_data_t *will_run_ptr, |
| bitstr_t *orig_map) |
| { |
| part_res_record_t *future_part; |
| node_use_record_t *future_usage; |
| list_t *future_license_list = NULL; |
| list_t *cr_job_list; |
| list_t *removed_jobs = NULL; |
| list_itr_t *job_iterator; |
| int rc = SLURM_ERROR; |
| time_t now = time(NULL); |
| uint16_t tmp_cr_type = _setup_cr_type(job_ptr); |
| bool qos_preemptor = false; |
| bitstr_t *efctv_bitmap_ptr, *efctv_bitmap = NULL; |
| cr_job_list_args_t args; |
| int time_window = 30; |
| time_t end_time = 0; |
| bool more_jobs = true; |
| int topology_idx; |
| DEF_TIMERS; |
| |
| if (will_run_ptr && will_run_ptr->start) |
| _set_sched_weight(node_bitmap, true); |
| |
| /* |
| * Job is still pending. Simulate termination of jobs one at a time |
| * to determine when and where the job can start. |
| */ |
| future_part = part_data_dup_res(select_part_record, orig_map); |
| if (future_part == NULL) { |
| return SLURM_ERROR; |
| } |
| future_usage = node_data_dup_use(select_node_usage, orig_map); |
| if (future_usage == NULL) { |
| part_data_destroy_res(future_part); |
| return SLURM_ERROR; |
| } |
| |
| if (!(job_ptr->bit_flags & BACKFILL_TEST)) |
| future_license_list = cluster_license_copy(); |
| |
| /* Build list of running and suspended jobs */ |
| cr_job_list = list_create(NULL); |
| args = (cr_job_list_args_t) { |
| .preemptee_candidates = preemptee_candidates, |
| .cr_job_list = cr_job_list, |
| .future_usage = future_usage, |
| .future_part = future_part, |
| .future_license_list = future_license_list, |
| .job_license_list = job_ptr->license_list, |
| .now = now, |
| .orig_map = orig_map, |
| .qos_preemptor = &qos_preemptor, |
| .start = will_run_ptr ? will_run_ptr->start : 0, |
| .tmp_bitmap_pptr = &efctv_bitmap, |
| .topology_idx = &topology_idx, |
| }; |
| list_for_each(job_list, _build_cr_job_list, &args); |
| |
| /* Test with all preemptable jobs gone */ |
| if (preemptee_candidates || args.start) { |
| bit_or(node_bitmap, orig_map); |
| rc = _job_test(job_ptr, node_bitmap, min_nodes, max_nodes, |
| req_nodes, SELECT_MODE_WILL_RUN, tmp_cr_type, |
| job_node_req, future_part, future_usage, |
| future_license_list, resv_exc_ptr, false, |
| qos_preemptor, true, NULL); |
| if (rc == SLURM_SUCCESS) { |
| /* |
| * Actual start time will actually be later than "now", |
| * but return "now" for backfill scheduler to |
| * initiate preemption. |
| */ |
| job_ptr->start_time = now; |
| goto cleanup; |
| } |
| } |
| |
| /* |
| * Remove the running jobs from exp_node_cr and try scheduling the |
| * pending job after each one (or a few jobs that end close in time). |
| */ |
| list_sort(cr_job_list, _cr_job_list_sort); |
| |
| removed_jobs = list_create(NULL); |
| |
| START_TIMER; |
| /* |
| * This needs to be an iterator since the outer "more_jobs" loop |
| * drives the inner list_next() calls in multiple passes, looking |
| * ahead to find groups of jobs ending at similar times. The |
| * iterator state must persist across outer iterations, which |
| * list_for_each() cannot provide. |
| */ |
| job_iterator = list_iterator_create(cr_job_list); |
| while (more_jobs) { |
| job_record_t *last_job_ptr = NULL; |
| job_record_t *next_job_ptr = NULL; |
| int overlap, rm_job_cnt = 0; |
| time_t last_job_end_time, next_job_end_time; |
| |
| bit_or(node_bitmap, orig_map); |
| while (true) { |
| job_record_t *tmp_job_ptr = list_next(job_iterator); |
| |
| if (!tmp_job_ptr || |
| (will_run_ptr && will_run_ptr->end && |
| _soft_job_end(tmp_job_ptr, now) > |
| will_run_ptr->end)) { |
| more_jobs = false; |
| break; |
| } |
| efctv_bitmap_ptr = |
| _select_topo_bitmap(tmp_job_ptr, node_bitmap, |
| &efctv_bitmap, |
| &topology_idx); |
| if (slurm_conf.debug_flags & |
| DEBUG_FLAG_SELECT_TYPE) { |
| overlap = bit_overlap(efctv_bitmap_ptr, |
| tmp_job_ptr-> |
| node_bitmap); |
| info("%pJ: overlap=%d", tmp_job_ptr, |
| overlap); |
| } else |
| overlap = bit_overlap_any( |
| efctv_bitmap_ptr, |
| tmp_job_ptr-> |
| node_bitmap); |
| if (overlap == 0 && /* job has no usable nodes */ |
| (!future_license_list || |
| !license_list_overlap_non_hres( /* ignore hres */ |
| tmp_job_ptr->license_list, |
| job_ptr->license_list))) { |
| continue; /* skip it */ |
| } |
| if (!end_time) { |
| time_t delta = 0; |
| time_t tmp_job_end_time = |
| _soft_job_end(tmp_job_ptr, now); |
| |
| /* |
| * align all time windows on a |
| * time_window barrier from the original |
| * first job evaluated, this prevents |
| * data in the running set from skewing |
| * changing the results between |
| * scheduling evaluations |
| */ |
| delta = tmp_job_end_time % time_window; |
| end_time = tmp_job_end_time + |
| (time_window - delta); |
| } |
| last_job_ptr = tmp_job_ptr; |
| (void) job_res_rm_job( |
| future_part, future_usage, |
| future_license_list, tmp_job_ptr, 0, |
| efctv_bitmap_ptr); |
| list_push(removed_jobs, tmp_job_ptr); |
| next_job_ptr = list_peek_next(job_iterator); |
| if (!next_job_ptr) { |
| more_jobs = false; |
| break; |
| } else if ((next_job_end_time = |
| _soft_job_end(next_job_ptr, now)) > |
| (end_time + time_window)) { |
| break; |
| } |
| if (rm_job_cnt++ > 200) |
| goto timer_check; |
| } |
| if (!last_job_ptr) /* Should never happen */ |
| break; |
| |
| rc = _job_test(job_ptr, node_bitmap, min_nodes, |
| max_nodes, req_nodes, |
| SELECT_MODE_WILL_RUN, tmp_cr_type, |
| job_node_req, future_part, future_usage, |
| future_license_list, resv_exc_ptr, |
| backfill_busy_nodes, qos_preemptor, |
| true, NULL); |
| if (rc == SLURM_SUCCESS) { |
| job_record_t *last_relevant_job = |
| _get_last_relevant_job(job_ptr, node_bitmap, |
| removed_jobs, |
| future_license_list, |
| &topology_idx); |
| /* |
| * _get_last_relevant_job() should never return NULL |
| * since we know at least one job's resources needed to |
| * be added back in order for _job_test() to succeed. |
| */ |
| xassert(last_relevant_job); /* should never be NULL */ |
| if (last_relevant_job) |
| last_job_ptr = last_relevant_job; |
| last_job_end_time = _soft_job_end(last_job_ptr, now); |
| if (last_job_end_time <= now) { |
| job_ptr->start_time = |
| _guess_job_end(last_job_ptr, |
| now); |
| } else { |
| job_ptr->start_time = last_job_end_time; |
| } |
| break; |
| } |
| do { |
| if (time_window > YEAR_SECONDS) |
| break; |
| if (bf_window_scale) |
| time_window += bf_window_scale; |
| else |
| time_window *= 2; |
| } while (next_job_ptr && |
| next_job_end_time > (end_time + time_window)); |
| timer_check: |
| END_TIMER; |
| if (TIMER_DURATION_USEC() >= 2000000) |
| break; /* Quit after 2 seconds wall time */ |
| } |
| if (end_time && (slurm_conf.debug_flags & DEBUG_FLAG_SELECT_TYPE || |
| ((job_ptr->bit_flags & BACKFILL_TEST) && |
| slurm_conf.debug_flags & DEBUG_FLAG_BACKFILL))) { |
| char time_str[25]; |
| /* |
| * When time_window gets large it could result in |
| * delaying jobs regardless of priority. Setting |
| * bf_window_linear could help mitigate this. |
| */ |
| verbose("%pJ considered resources from running jobs ending within %d seconds of %s", |
| job_ptr, time_window, slurm_ctime2_r(&end_time, |
| time_str)); |
| } |
| |
| list_iterator_destroy(job_iterator); |
| cleanup: |
| FREE_NULL_BITMAP(efctv_bitmap); |
| FREE_NULL_LIST(cr_job_list); |
| part_data_destroy_res(future_part); |
| node_data_destroy(future_usage); |
| FREE_NULL_LIST(future_license_list); |
| FREE_NULL_LIST(removed_jobs); |
| |
| return rc; |
| } |
| |
| static int _foreach_will_run_preemptee(void *x, void *arg) |
| { |
| job_record_t *tmp_job_ptr = x; |
| will_run_preemptee_arg_t *wargs = arg; |
| bitstr_t *efctv_bitmap_ptr; |
| |
| efctv_bitmap_ptr = _select_topo_bitmap(tmp_job_ptr, wargs->node_bitmap, |
| wargs->efctv_bitmap, |
| wargs->topology_idx); |
| if (!bit_overlap_any(efctv_bitmap_ptr, tmp_job_ptr->node_bitmap)) |
| return 0; |
| list_append(wargs->preemptee_job_list, tmp_job_ptr); |
| |
| return 0; |
| } |
| |
| /* |
| * Determine where and when the job at job_ptr can begin execution by updating |
| * a scratch cr_record structure to reflect each job terminating at the |
| * end of its time limit and use this to show where and when the job at job_ptr |
| * will begin execution. Used by Slurm's sched/backfill plugin. |
| */ |
| static int _will_run_test(job_record_t *job_ptr, bitstr_t *node_bitmap, |
| uint32_t min_nodes, uint32_t max_nodes, |
| uint32_t req_nodes, uint16_t job_node_req, |
| list_t *preemptee_candidates, |
| list_t **preemptee_job_list, |
| resv_exc_t *resv_exc_ptr, |
| will_run_data_t *will_run_ptr) |
| { |
| int rc = SLURM_ERROR; |
| time_t now = time(NULL); |
| uint16_t tmp_cr_type = _setup_cr_type(job_ptr); |
| bitstr_t *orig_map; |
| list_t *license_list = NULL; |
| |
| orig_map = bit_copy(node_bitmap); |
| |
| if (will_run_ptr && will_run_ptr->start > now) |
| goto test_future; |
| |
| _set_sched_weight(node_bitmap, false); |
| |
| license_list = cluster_license_copy(); |
| /* Try to run with currently available nodes */ |
| rc = _job_test(job_ptr, node_bitmap, min_nodes, max_nodes, req_nodes, |
| SELECT_MODE_WILL_RUN, tmp_cr_type, job_node_req, |
| select_part_record, select_node_usage, license_list, |
| resv_exc_ptr, false, false, false, NULL); |
| FREE_NULL_LIST(license_list); |
| if (rc == SLURM_SUCCESS) { |
| job_ptr->start_time = now; |
| FREE_NULL_BITMAP(orig_map); |
| return SLURM_SUCCESS; |
| } |
| |
| /* Don't try preempting for licenses if not enabled */ |
| if ((rc == ESLURM_LICENSES_UNAVAILABLE) && !preempt_for_licenses) |
| preemptee_candidates = NULL; |
| |
| if (!preemptee_candidates && (job_ptr->bit_flags & TEST_NOW_ONLY)) { |
| FREE_NULL_BITMAP(orig_map); |
| return SLURM_ERROR; |
| } |
| |
| test_future: |
| /* |
| * Remove the running jobs from exp_node_cr and try scheduling the |
| * pending job after each one (or a few jobs that end close in time). |
| */ |
| if ((rc != SLURM_SUCCESS) && |
| (((job_ptr->bit_flags & TEST_NOW_ONLY) == 0) || |
| preemptee_candidates)) { |
| rc = _future_run_test(job_ptr, node_bitmap, |
| min_nodes, max_nodes, |
| req_nodes, job_node_req, |
| preemptee_candidates, |
| preemptee_job_list, |
| resv_exc_ptr, |
| will_run_ptr, |
| orig_map); |
| } |
| |
| if ((rc == SLURM_SUCCESS) && preemptee_job_list && |
| preemptee_candidates) { |
| bitstr_t *efctv_bitmap = NULL; |
| int topo_idx; |
| will_run_preemptee_arg_t wargs = { |
| .efctv_bitmap = &efctv_bitmap, |
| .node_bitmap = node_bitmap, |
| .topology_idx = &topo_idx, |
| }; |
| /* |
| * Build list of preemptee jobs whose resources are |
| * actually used. list returned even if not killed |
| * in selected plugin, but by Moab or something else. |
| */ |
| if (*preemptee_job_list == NULL) { |
| *preemptee_job_list = list_create(NULL); |
| } |
| wargs.preemptee_job_list = *preemptee_job_list; |
| list_for_each_ro(preemptee_candidates, |
| _foreach_will_run_preemptee, &wargs); |
| FREE_NULL_BITMAP(efctv_bitmap); |
| } |
| |
| FREE_NULL_BITMAP(orig_map); |
| return rc; |
| } |
| |
| static int _foreach_run_now_preemptee(void *x, void *arg) |
| { |
| job_record_t *tmp_job_ptr = x; |
| run_now_preemptee_arg_t *wargs = arg; |
| int mode = slurm_job_preempt_mode(tmp_job_ptr); |
| |
| if ((mode != PREEMPT_MODE_REQUEUE) && (mode != PREEMPT_MODE_CANCEL)) |
| return 0; |
| if (!job_overlap_and_running(wargs->node_bitmap, |
| wargs->licenses_to_preempt, tmp_job_ptr)) |
| return 0; |
| if (tmp_job_ptr->details->usable_nodes) |
| return -1; |
| list_append(wargs->preemptee_job_list, tmp_job_ptr); |
| *wargs->remove_some_jobs = true; |
| |
| return 0; |
| } |
| |
| /* Allocate resources for a job now, if possible */ |
| static int _run_now(job_record_t *job_ptr, bitstr_t *node_bitmap, |
| uint32_t min_nodes, uint32_t max_nodes, |
| uint32_t req_nodes, uint16_t job_node_req, |
| list_t *preemptee_candidates, list_t **preemptee_job_list, |
| resv_exc_t *resv_exc_ptr) |
| { |
| int rc; |
| bitstr_t *orig_node_map = NULL, *save_node_map; |
| job_record_t *tmp_job_ptr = NULL; |
| list_itr_t *job_iterator; |
| part_res_record_t *future_part; |
| node_use_record_t *future_usage; |
| list_t *license_list; |
| bool remove_some_jobs = false; |
| uint16_t pass_count = 0; |
| uint16_t mode = NO_VAL16; |
| uint16_t tmp_cr_type = _setup_cr_type(job_ptr); |
| bool preempt_mode = false; |
| |
| hres_pre_select(job_ptr, false); |
| |
| save_node_map = bit_copy(node_bitmap); |
| top: orig_node_map = bit_copy(save_node_map); |
| |
| license_list = cluster_license_copy(); |
| rc = _job_test(job_ptr, node_bitmap, min_nodes, max_nodes, req_nodes, |
| SELECT_MODE_RUN_NOW, tmp_cr_type, job_node_req, |
| select_part_record, select_node_usage, license_list, |
| resv_exc_ptr, false, false, false, NULL); |
| |
| /* Don't try preempting for licenses if not enabled */ |
| if ((rc == ESLURM_LICENSES_UNAVAILABLE) && |
| (!preempt_for_licenses || (mode == PREEMPT_MODE_SUSPEND))) |
| preemptee_candidates = NULL; |
| |
| if ((rc != SLURM_SUCCESS) && preemptee_candidates && preempt_by_qos) { |
| /* Determine QOS preempt mode of first job */ |
| if ((tmp_job_ptr = list_peek(preemptee_candidates))) { |
| mode = slurm_job_preempt_mode(tmp_job_ptr); |
| } |
| } |
| if ((rc != SLURM_SUCCESS) && preemptee_candidates && preempt_by_qos && |
| (mode == PREEMPT_MODE_SUSPEND) && |
| (job_ptr->priority != 0)) { /* Job can be held by bad allocate */ |
| list_t *preemptees_to_suspend_by_qos = list_create(NULL); |
| |
| future_part = part_data_dup_res(select_part_record, |
| orig_node_map); |
| if (future_part == NULL) { |
| FREE_NULL_BITMAP(orig_node_map); |
| FREE_NULL_BITMAP(save_node_map); |
| return SLURM_ERROR; |
| } |
| future_usage = node_data_dup_use(select_node_usage, |
| orig_node_map); |
| if (future_usage == NULL) { |
| part_data_destroy_res(future_part); |
| FREE_NULL_BITMAP(orig_node_map); |
| FREE_NULL_BITMAP(save_node_map); |
| return SLURM_ERROR; |
| } |
| |
| job_iterator = list_iterator_create(preemptee_candidates); |
| while ((tmp_job_ptr = list_next(job_iterator))) { |
| int mode = slurm_job_preempt_mode(tmp_job_ptr); |
| if (mode != PREEMPT_MODE_SUSPEND) |
| continue; |
| /* |
| * Remove resources used by tmp_job_ptr and check if |
| * the preemptor job can run. |
| */ |
| if (_job_res_rm_job(future_part, future_usage, |
| NULL, tmp_job_ptr, |
| JOB_RES_ACTION_RESUME, |
| orig_node_map)) |
| continue; |
| list_append(preemptees_to_suspend_by_qos, tmp_job_ptr); |
| bit_or(node_bitmap, orig_node_map); |
| rc = _job_test(job_ptr, node_bitmap, min_nodes, |
| max_nodes, req_nodes, |
| SELECT_MODE_WILL_RUN, tmp_cr_type, |
| job_node_req, future_part, future_usage, |
| NULL, resv_exc_ptr, false, false, |
| preempt_mode, NULL); |
| |
| if (rc != SLURM_SUCCESS) |
| continue; |
| |
| /* |
| * We have identified the preemptee jobs that we need |
| * to suspend to run the preemptor job. Try to |
| * schedule it using extra row of core bitmap. |
| */ |
| bit_or(node_bitmap, orig_node_map); |
| rc = _job_test(job_ptr, node_bitmap, min_nodes, |
| max_nodes, req_nodes, |
| SELECT_MODE_RUN_NOW, tmp_cr_type, |
| job_node_req, select_part_record, |
| select_node_usage, NULL, resv_exc_ptr, |
| false, true, preempt_mode, |
| preemptees_to_suspend_by_qos); |
| FREE_NULL_LIST(preemptees_to_suspend_by_qos); |
| |
| FREE_NULL_BITMAP(orig_node_map); |
| FREE_NULL_BITMAP(save_node_map); |
| list_iterator_destroy(job_iterator); |
| part_data_destroy_res(future_part); |
| node_data_destroy(future_usage); |
| FREE_NULL_LIST(license_list); |
| |
| return rc; |
| } |
| FREE_NULL_LIST(preemptees_to_suspend_by_qos); |
| } else if ((rc != SLURM_SUCCESS) && preemptee_candidates && |
| !(job_ptr->bit_flags & NEED_MORE_FEATURES)) { |
| int preemptee_cand_cnt = list_count(preemptee_candidates); |
| /* Remove preemptable jobs from simulated environment */ |
| preempt_mode = true; |
| future_part = part_data_dup_res(select_part_record, |
| orig_node_map); |
| if (future_part == NULL) { |
| FREE_NULL_BITMAP(orig_node_map); |
| FREE_NULL_BITMAP(save_node_map); |
| FREE_NULL_LIST(license_list); |
| return SLURM_ERROR; |
| } |
| future_usage = node_data_dup_use(select_node_usage, |
| orig_node_map); |
| if (future_usage == NULL) { |
| part_data_destroy_res(future_part); |
| FREE_NULL_BITMAP(orig_node_map); |
| FREE_NULL_BITMAP(save_node_map); |
| FREE_NULL_LIST(license_list); |
| return SLURM_ERROR; |
| } |
| |
| /* |
| * This needs to be an iterator since the loop body uses |
| * list_remove(job_iterator), list_iterator_reset(job_iterator), |
| * and nested list_next() on the same iterator to reorder |
| * preemption candidates. None of those are accessible from |
| * list_for_each(). |
| */ |
| job_iterator = list_iterator_create(preemptee_candidates); |
| while ((tmp_job_ptr = list_next(job_iterator))) { |
| mode = slurm_job_preempt_mode(tmp_job_ptr); |
| if ((mode != PREEMPT_MODE_REQUEUE) && |
| (mode != PREEMPT_MODE_CANCEL)) |
| continue; /* can't remove job */ |
| /* Remove preemptable job now */ |
| if (_job_res_rm_job(future_part, future_usage, |
| license_list, tmp_job_ptr, 0, |
| orig_node_map)) |
| continue; |
| bit_or(node_bitmap, orig_node_map); |
| rc = _job_test(job_ptr, node_bitmap, min_nodes, |
| max_nodes, req_nodes, |
| SELECT_MODE_WILL_RUN, tmp_cr_type, |
| job_node_req, future_part, future_usage, |
| license_list, resv_exc_ptr, false, false, |
| preempt_mode, NULL); |
| tmp_job_ptr->details->usable_nodes = 0; |
| if (rc != SLURM_SUCCESS) |
| continue; |
| |
| if ((pass_count++ > preempt_reorder_cnt) || |
| (preemptee_cand_cnt <= pass_count)) { |
| /* |
| * Ignore remaining jobs, but keep in the list |
| * since the code can get called multiple times |
| * for different node/feature sets -- |
| * _get_req_features(). |
| */ |
| while ((tmp_job_ptr = list_next(job_iterator))) { |
| tmp_job_ptr->details->usable_nodes = 1; |
| } |
| break; |
| } |
| |
| /* |
| * Reorder preemption candidates to minimize number |
| * of preempted jobs and their priorities. |
| */ |
| if (preempt_strict_order) { |
| /* |
| * Move last preempted job to top of preemption |
| * candidate list, preserving order of other |
| * jobs. |
| */ |
| tmp_job_ptr = list_remove(job_iterator); |
| list_prepend(preemptee_candidates, tmp_job_ptr); |
| } else { |
| /* |
| * Set the last job's usable count to a large |
| * value and re-sort preempted jobs. usable_nodes |
| * count set to zero above to eliminate values |
| * previously set to 99999. Note: usable_count |
| * is only used for sorting purposes. |
| */ |
| tmp_job_ptr->details->usable_nodes = 99999; |
| list_iterator_reset(job_iterator); |
| while ((tmp_job_ptr = list_next(job_iterator))) { |
| if (tmp_job_ptr->details->usable_nodes |
| == 99999) |
| break; |
| tmp_job_ptr->details->usable_nodes = |
| _get_usable_nodes(node_bitmap, |
| tmp_job_ptr); |
| } |
| while ((tmp_job_ptr = list_next(job_iterator))) { |
| tmp_job_ptr->details->usable_nodes = 0; |
| } |
| list_sort(preemptee_candidates, |
| (ListCmpF)_sort_usable_nodes_dec); |
| } |
| FREE_NULL_BITMAP(orig_node_map); |
| list_iterator_destroy(job_iterator); |
| part_data_destroy_res(future_part); |
| node_data_destroy(future_usage); |
| FREE_NULL_LIST(license_list); |
| goto top; |
| } |
| list_iterator_destroy(job_iterator); |
| |
| if ((rc == SLURM_SUCCESS) && preemptee_job_list && |
| preemptee_candidates) { |
| run_now_preemptee_arg_t wargs = { |
| .licenses_to_preempt = |
| job_ptr->licenses_to_preempt, |
| .node_bitmap = node_bitmap, |
| .remove_some_jobs = &remove_some_jobs, |
| }; |
| /* |
| * Build list of preemptee jobs whose resources are |
| * actually used |
| */ |
| if (*preemptee_job_list == NULL) { |
| *preemptee_job_list = list_create(NULL); |
| } |
| wargs.preemptee_job_list = *preemptee_job_list; |
| list_for_each_ro(preemptee_candidates, |
| _foreach_run_now_preemptee, &wargs); |
| if (!remove_some_jobs) { |
| FREE_NULL_LIST(*preemptee_job_list); |
| } |
| } |
| |
| part_data_destroy_res(future_part); |
| node_data_destroy(future_usage); |
| } |
| FREE_NULL_LIST(license_list); |
| FREE_NULL_BITMAP(orig_node_map); |
| FREE_NULL_BITMAP(save_node_map); |
| |
| return rc; |
| } |
| |
| static bool _check_ntasks_per_sock(uint16_t core, uint16_t socket, |
| uint16_t threads_per_core, uint16_t cps, |
| uint16_t *cpu_cnt, bitstr_t * core_map) |
| { |
| if (!cpu_cnt[socket]) { /* Start use of next socket */ |
| cpu_cnt[socket] = threads_per_core; |
| } else { /* Continued use of same socket */ |
| if (cpu_cnt[socket] >= cps) { |
| /* do not allocate this core */ |
| bit_clear(core_map, core); |
| return true; |
| } |
| cpu_cnt[socket] += threads_per_core; |
| } |
| return false; |
| } |
| |
| static void _count_used_cpus(uint16_t threads_per_core, uint16_t cpus_per_task, |
| uint16_t ntasks_per_core, bool use_tpc, |
| int *remain_cpt, uint16_t *avail_cpus, |
| uint16_t *cpu_count) |
| { |
| if (*avail_cpus >= threads_per_core) { |
| int used; |
| if (use_tpc) { |
| used = threads_per_core; |
| } else if ((ntasks_per_core == 1) && |
| (cpus_per_task > threads_per_core)) { |
| used = MIN(*remain_cpt, threads_per_core); |
| } else |
| used = threads_per_core; |
| *avail_cpus -= used; |
| *cpu_count += used; |
| if (*remain_cpt <= used) |
| *remain_cpt = cpus_per_task; |
| else |
| *remain_cpt -= used; |
| } else { |
| *cpu_count += *avail_cpus; |
| *avail_cpus = 0; |
| } |
| } |
| |
| /* |
| * _allocate_sc - Given the job requirements, determine which CPUs/cores |
| * from the given node can be allocated (if any) to this |
| * job. Returns structure identifying the usable resources and |
| * a bitmap of the available cores. |
| * |
| * IN job_ptr - pointer to job requirements |
| * IN/OUT core_map - core_bitmap of available cores on this node |
| * IN part_core_map - bitmap of cores already allocated on this partition/node |
| * IN node_i - index of node to be evaluated |
| * IN/OUT cpu_alloc_size - minimum allocation size, in CPUs |
| * IN entire_sockets_only - if true, allocate cores only on sockets that |
| * have no other allocated cores. |
| * IN req_sock_map - OPTIONAL bitmap of required sockets |
| * RET resource availability structure, call _free_avail_res() to free |
| */ |
| static avail_res_t *_allocate_sc(job_record_t *job_ptr, bitstr_t *core_map, |
| bitstr_t *part_core_map, const uint32_t node_i, |
| int *cpu_alloc_size, bool entire_sockets_only, |
| bitstr_t *req_sock_map) |
| { |
| uint16_t cpu_count = 0, part_cpu_limit = INFINITE16; |
| uint16_t cps, avail_cpus = 0, num_tasks = 0; |
| uint16_t req_sock_cpus = 0; |
| uint32_t c; |
| job_details_t *details_ptr = job_ptr->details; |
| uint16_t cpus_per_task = details_ptr->cpus_per_task; |
| uint16_t free_core_count = 0, spec_threads = 0; |
| uint16_t i, j; |
| node_record_t *node_ptr = node_record_table_ptr[node_i]; |
| uint16_t sockets = node_ptr->tot_sockets; |
| uint16_t cores_per_socket = node_ptr->cores; |
| uint16_t threads_per_core = node_ptr->tpc; |
| uint16_t min_cores = 1, min_sockets = 1, ntasks_per_socket = 0; |
| uint16_t ncpus_per_core = INFINITE16; /* Usable CPUs per core */ |
| uint16_t ntasks_per_core = INFINITE16; |
| uint32_t free_cpu_count = 0, used_cpu_count = 0; |
| int tmp_cpt = 0; /* cpus_per_task */ |
| uint16_t free_cores[sockets]; |
| uint16_t used_cores[sockets]; |
| uint16_t cpu_cnt[sockets]; |
| uint16_t max_cpu_per_req_sock = INFINITE16; |
| avail_res_t *avail_res = xmalloc(sizeof(avail_res_t)); |
| bitstr_t *tmp_core = NULL; |
| bool use_tpc = false; |
| uint32_t socket_begin; |
| uint32_t socket_end; |
| |
| memset(free_cores, 0, sockets * sizeof(uint16_t)); |
| memset(used_cores, 0, sockets * sizeof(uint16_t)); |
| memset(cpu_cnt, 0, sockets * sizeof(uint16_t)); |
| |
| if ((details_ptr->whole_node & WHOLE_NODE_REQUIRED) && |
| entire_sockets_only && (details_ptr->core_spec != NO_VAL16)) { |
| /* Ignore specialized cores when allocating "entire" socket */ |
| entire_sockets_only = false; |
| } |
| if (details_ptr->mc_ptr) { |
| uint32_t threads_per_socket; |
| multi_core_data_t *mc_ptr = details_ptr->mc_ptr; |
| if (mc_ptr->cores_per_socket != NO_VAL16) { |
| min_cores = mc_ptr->cores_per_socket; |
| } |
| if (mc_ptr->sockets_per_node != NO_VAL16) { |
| min_sockets = mc_ptr->sockets_per_node; |
| } |
| if ((mc_ptr->ntasks_per_core != INFINITE16) && |
| (mc_ptr->ntasks_per_core)) { |
| ntasks_per_core = mc_ptr->ntasks_per_core; |
| ncpus_per_core = MIN(threads_per_core, |
| (ntasks_per_core * cpus_per_task)); |
| } |
| if ((mc_ptr->threads_per_core != NO_VAL16) && |
| (mc_ptr->threads_per_core < ncpus_per_core)) { |
| ncpus_per_core = mc_ptr->threads_per_core; |
| } |
| *cpu_alloc_size = MIN(*cpu_alloc_size, ncpus_per_core); |
| ntasks_per_socket = mc_ptr->ntasks_per_socket; |
| |
| if ((ncpus_per_core != INFINITE16) && |
| (ncpus_per_core > threads_per_core)) { |
| goto fini; |
| } |
| threads_per_socket = threads_per_core * cores_per_socket; |
| if ((ntasks_per_socket != INFINITE16) && |
| (ntasks_per_socket > threads_per_socket)) { |
| goto fini; |
| } |
| } |
| |
| /* |
| * These are the job parameters that we must respect: |
| * |
| * details_ptr->mc_ptr->cores_per_socket (cr_core|cr_socket) |
| * - min # of cores per socket to allocate to this job |
| * details_ptr->mc_ptr->sockets_per_node (cr_core|cr_socket) |
| * - min # of sockets per node to allocate to this job |
| * details_ptr->mc_ptr->ntasks_per_core (cr_core|cr_socket) |
| * - number of tasks to launch per core |
| * details_ptr->mc_ptr->ntasks_per_socket (cr_core|cr_socket) |
| * - number of tasks to launch per socket |
| * |
| * details_ptr->ntasks_per_node (all cr_types) |
| * - total number of tasks to launch on this node |
| * details_ptr->cpus_per_task (all cr_types) |
| * - number of cpus to allocate per task |
| * |
| * These are the hardware constraints: |
| * cpus = sockets * cores_per_socket * threads_per_core |
| * |
| * These are the cores/sockets that are available: core_map |
| * |
| * NOTE: currently we only allocate at the socket level, the core |
| * level, or the cpu level. When hyperthreading is enabled |
| * in the BIOS, then there can be more than one thread/cpu |
| * per physical core. |
| * |
| * PROCEDURE: |
| * |
| * Step 1: Determine the current usage data: used_cores[], |
| * used_core_count, free_cores[], free_core_count |
| * |
| * Step 2: For core-level and socket-level: apply sockets_per_node |
| * and cores_per_socket to the "free" cores. |
| * |
| * Step 3: Compute task-related data: ncpus_per_core, |
| * ntasks_per_socket, ntasks_per_node and cpus_per_task |
| * and determine the number of tasks to run on this node |
| * |
| * Step 4: Mark the allocated resources in the job_cores bitmap |
| * and return "num_tasks" from Step 3. |
| * |
| * |
| * For socket and core counts, start by assuming that all available |
| * resources will be given to the job. Check min_* to ensure that |
| * there's enough resources. Reduce the resource count to match max_* |
| * (if necessary). Also reduce resource count (if necessary) to |
| * match ntasks_per_resource. |
| */ |
| |
| /* |
| * Step 1: create and compute core-count-per-socket |
| * arrays and total core counts |
| */ |
| if (part_core_map) { |
| tmp_core = bit_copy(part_core_map); |
| bit_and_not(tmp_core, core_map); |
| } |
| |
| socket_begin = 0; |
| socket_end = cores_per_socket; |
| for (i = 0; i < sockets; i++) { |
| uint16_t used_cpus; |
| |
| free_cores[i] = bit_set_count_range(core_map, socket_begin, |
| socket_end); |
| if (!tmp_core) |
| used_cores[i] = (cores_per_socket - free_cores[i]); |
| else |
| used_cores[i] = bit_set_count_range(tmp_core, |
| socket_begin, |
| socket_end); |
| used_cpus = used_cores[i] * threads_per_core; |
| |
| socket_begin = socket_end; |
| socket_end += cores_per_socket; |
| /* |
| * Socket CPUs restrictions: |
| * 1. Partially allocated socket, but entire_sockets_only: |
| * Enabled when SELECT_SOCKET. This mode counts unusable CPUs as |
| * allocated, so it also counts them against MaxCpusPerNode. |
| * 2. Partially allocated socket, up/beyond MaxCPUsPerSocket: |
| * This mode does not count unusable CPUs as allocated, nor |
| * against MaxCpusPerNode. |
| * 3. Free/partially allocated socket, MaxCPUsPerSocket enabled: |
| * We can still use CPUs, but up to MaxCPUsPerSocket. |
| * This mode does not count unusable CPUs as allocated, nor |
| * against MaxCpusPerNode. |
| */ |
| if (entire_sockets_only && used_cpus) { |
| used_cores[i] += free_cores[i]; |
| used_cpus = used_cores[i] * threads_per_core; |
| free_cores[i] = 0; |
| } else if (tmp_core && |
| (used_cpus >= |
| job_ptr->part_ptr->max_cpus_per_socket)) { |
| log_flag(SELECT_TYPE, "MaxCpusPerSocket: %u, CPUs already used on socket[%d]: %u - won't use the socket.", |
| job_ptr->part_ptr->max_cpus_per_socket, |
| i, |
| used_cpus); |
| free_cores[i] = 0; |
| } else if (job_ptr->part_ptr->max_cpus_per_socket != INFINITE) { |
| free_cores[i] = |
| MIN(free_cores[i], |
| (job_ptr->part_ptr->max_cpus_per_socket / |
| threads_per_core)); |
| } |
| free_core_count += free_cores[i]; |
| if (tmp_core) |
| used_cpu_count += used_cpus; |
| } |
| free_cpu_count = free_core_count * threads_per_core; |
| avail_res->max_cpus = free_cpu_count; |
| FREE_NULL_BITMAP(tmp_core); |
| |
| /* Enforce partition CPU limit, but do not pick specific cores yet */ |
| if ((job_ptr->part_ptr->max_cpus_per_node != INFINITE) && |
| (free_cpu_count + used_cpu_count > |
| job_ptr->part_ptr->max_cpus_per_node)) { |
| |
| if (job_ptr->details->whole_node & WHOLE_NODE_REQUIRED) { |
| log_flag(SELECT_TYPE, "Total cpu count greater than max_cpus_per_node on exclusive job. (%d > %d)", |
| free_cpu_count + used_cpu_count, |
| job_ptr->part_ptr->max_cpus_per_node); |
| num_tasks = 0; |
| goto fini; |
| } |
| |
| if (used_cpu_count >= job_ptr->part_ptr->max_cpus_per_node) { |
| /* no available CPUs on this node */ |
| num_tasks = 0; |
| goto fini; |
| } |
| part_cpu_limit = job_ptr->part_ptr->max_cpus_per_node - |
| used_cpu_count; |
| if ((part_cpu_limit == 1) && |
| (((ntasks_per_core != INFINITE16) && |
| (ntasks_per_core > part_cpu_limit)) || |
| ((ntasks_per_socket != INFINITE16) && |
| (ntasks_per_socket > part_cpu_limit)) || |
| ((ncpus_per_core != INFINITE16) && |
| (ncpus_per_core > part_cpu_limit)) || |
| (cpus_per_task > part_cpu_limit))) { |
| /* insufficient available CPUs on this node */ |
| num_tasks = 0; |
| goto fini; |
| } |
| } |
| |
| /* Step 2: check min_cores per socket and min_sockets per node */ |
| j = 0; |
| for (i = 0; i < sockets; i++) { |
| if (free_cores[i] < min_cores) { |
| /* cannot use this socket */ |
| free_core_count -= free_cores[i]; |
| free_cores[i] = 0; |
| continue; |
| } |
| /* count this socket as usable */ |
| j++; |
| } |
| if (j < min_sockets) { |
| /* cannot use this node */ |
| num_tasks = 0; |
| goto fini; |
| } |
| |
| if (free_core_count < 1) { |
| /* no available resources on this node */ |
| num_tasks = 0; |
| goto fini; |
| } |
| |
| /* |
| * Step 3: Compute task-related data: |
| * ntasks_per_socket, ntasks_per_node and cpus_per_task |
| * to determine the number of tasks to run on this node |
| * |
| * Note: cpus_per_task and ncpus_per_core need to play nice |
| * 2 tasks_per_core vs. 2 cpus_per_task |
| */ |
| avail_cpus = 0; |
| num_tasks = 0; |
| threads_per_core = job_mgr_determine_cpus_per_core(details_ptr, node_i); |
| |
| /* Are enough CPUs available compared with required ones? */ |
| if ((free_core_count * threads_per_core) < details_ptr->pn_min_cpus) { |
| num_tasks = 0; |
| goto fini; |
| } |
| |
| for (i = 0; i < sockets; i++) { |
| uint16_t tmp = free_cores[i] * threads_per_core; |
| if (req_sock_map && bit_test(req_sock_map, i)) { |
| if (tmp == 0) { |
| /* no available resources on required socket */ |
| num_tasks = 0; |
| goto fini; |
| } |
| req_sock_cpus += tmp; |
| } |
| avail_cpus += tmp; |
| if (ntasks_per_socket) |
| num_tasks += MIN(tmp, ntasks_per_socket); |
| else |
| num_tasks += tmp; |
| } |
| |
| /* |
| * If job requested exclusive rights to the node don't do the min |
| * here since it will make it so we don't allocate the entire node. |
| * Don't min num_tasks if cpus_per_tres given, since number of |
| * CPUs in that case is not determined by tasks. |
| */ |
| if (details_ptr->ntasks_per_node && details_ptr->share_res && |
| !job_ptr->cpus_per_tres) |
| num_tasks = MIN(num_tasks, details_ptr->ntasks_per_node); |
| |
| /* |
| * If the job requests gres, then do not limit avail_cpus here. |
| * avail_cpus will be limited later by gres_select_filter_sock_core. |
| */ |
| if (!job_ptr->gres_list_req) { |
| if (cpus_per_task < 2) { |
| avail_cpus = num_tasks; |
| } else if ((ntasks_per_core == 1) && |
| (cpus_per_task > threads_per_core)) { |
| /* find out how many cores a task will use */ |
| int task_cores = |
| ROUNDUP(cpus_per_task, threads_per_core); |
| int task_cpus = task_cores * threads_per_core; |
| /* find out how many tasks can fit on a node */ |
| int tasks = avail_cpus / task_cpus; |
| /* how many cpus the job would use on the node */ |
| avail_cpus = tasks * task_cpus; |
| /* subtract out the extra cpus. */ |
| avail_cpus -= (tasks * (task_cpus - cpus_per_task)); |
| } else { |
| j = avail_cpus / cpus_per_task; |
| num_tasks = MIN(num_tasks, j); |
| avail_cpus = num_tasks * cpus_per_task; |
| } |
| } |
| |
| /* |
| * If there's an auto adjustment then use the max between the required |
| * CPUs according to required task number, or to the autoadjustment. |
| */ |
| if (details_ptr->pn_min_cpus > details_ptr->orig_pn_min_cpus) { |
| avail_cpus = MAX(details_ptr->pn_min_cpus, avail_cpus); |
| } |
| |
| if ((details_ptr->ntasks_per_node && |
| (num_tasks < details_ptr->ntasks_per_node) && |
| (details_ptr->overcommit == 0)) || |
| (details_ptr->pn_min_cpus && |
| (avail_cpus < details_ptr->pn_min_cpus))) { |
| /* insufficient resources on this node */ |
| num_tasks = 0; |
| goto fini; |
| } |
| |
| /* |
| * Step 4 - make sure that ntasks_per_socket is enforced when |
| * allocating cores |
| */ |
| if ((ntasks_per_socket != INFINITE16) && |
| (ntasks_per_socket >= 1)) { |
| cps = ntasks_per_socket; |
| if (cpus_per_task > 1) |
| cps *= cpus_per_task; |
| } else |
| cps = cores_per_socket * threads_per_core; |
| |
| tmp_cpt = cpus_per_task; |
| if (req_sock_map && (i = bit_set_count(req_sock_map))) { |
| tmp_core = bit_alloc(bit_size(core_map)); |
| if (req_sock_cpus > avail_cpus) { |
| max_cpu_per_req_sock = avail_cpus / i; |
| } |
| i = 0; |
| } else { |
| i = sockets; |
| } |
| |
| if ((slurm_conf.select_type_param & SELECT_ONE_TASK_PER_CORE) && |
| (details_ptr->min_gres_cpu > 0)) { |
| use_tpc = true; |
| } |
| for ( ; ((i < sockets) && (avail_cpus > 0)); i++) { |
| if (bit_test(req_sock_map, i)) { |
| for (j = 0; j < cores_per_socket && |
| free_cores[i]; j++) { |
| c = (i * cores_per_socket) + j; |
| if (!bit_test(core_map, c)) |
| continue; |
| /* |
| * this socket has free cores, but make sure we don't |
| * use more than are needed for ntasks_per_socket |
| */ |
| if (_check_ntasks_per_sock(c, i, |
| threads_per_core, |
| cps, cpu_cnt, |
| core_map)) { |
| continue; |
| } |
| free_cores[i]--; |
| /* |
| * we have to ensure that cpu_count is not bigger than |
| * avail_cpus due to hyperthreading or this would break |
| * the selection logic providing more CPUs than allowed |
| * after task-related data processing of stage 3 |
| */ |
| _count_used_cpus(threads_per_core, |
| cpus_per_task, ntasks_per_core, |
| use_tpc, &tmp_cpt, &avail_cpus, |
| &cpu_count); |
| |
| bit_set(tmp_core, c); |
| if (cpu_cnt[i] > max_cpu_per_req_sock) |
| break; |
| } |
| } |
| } |
| for (c = 0; c < node_ptr->tot_cores; c++) { |
| if (!bit_test(core_map, c) || (tmp_core && |
| bit_test(tmp_core, c))) |
| continue; |
| |
| /* Socket index */ |
| i = (uint16_t) (c / cores_per_socket); |
| if (free_cores[i] > 0 && (avail_cpus > 0)) { |
| /* |
| * this socket has free cores, but make sure we don't |
| * use more than are needed for ntasks_per_socket |
| */ |
| if (_check_ntasks_per_sock(c, i, threads_per_core, cps, |
| cpu_cnt, core_map)) |
| continue; |
| |
| free_cores[i]--; |
| /* |
| * we have to ensure that cpu_count is not bigger than |
| * avail_cpus due to hyperthreading or this would break |
| * the selection logic providing more CPUs than allowed |
| * after task-related data processing of stage 3 |
| */ |
| _count_used_cpus(threads_per_core, cpus_per_task, |
| ntasks_per_core, use_tpc, &tmp_cpt, |
| &avail_cpus, &cpu_count); |
| } else |
| bit_clear(core_map, c); |
| } |
| |
| fini: |
| /* if num_tasks == 0 then clear all bits on this node */ |
| if (num_tasks == 0) { |
| bit_clear_all(core_map); |
| cpu_count = 0; |
| } |
| |
| if ((details_ptr->core_spec != NO_VAL16) && |
| (details_ptr->core_spec & CORE_SPEC_THREAD) && |
| ((node_ptr->threads == 1) || |
| (node_ptr->threads == node_ptr->tpc))) { |
| /* |
| * NOTE: Currently does not support the situation when Slurm |
| * allocates by core, the thread specialization count occupies |
| * a full core |
| */ |
| c = details_ptr->core_spec & (~CORE_SPEC_THREAD); |
| if (((cpu_count + c) <= node_ptr->cpus)) |
| ; |
| else if (cpu_count > c) |
| spec_threads = c; |
| else |
| spec_threads = cpu_count; |
| } |
| cpu_count -= spec_threads; |
| |
| avail_res->avail_cpus = MIN(cpu_count, part_cpu_limit); |
| |
| avail_res->min_cpus = *cpu_alloc_size; |
| avail_res->avail_cores_per_sock = xcalloc(sockets, sizeof(uint16_t)); |
| socket_begin = 0; |
| socket_end = cores_per_socket; |
| for (i = 0; i < sockets; i++) { |
| avail_res->avail_cores_per_sock[i] = |
| bit_set_count_range(core_map, socket_begin, socket_end); |
| socket_begin = socket_end; |
| socket_end += cores_per_socket; |
| } |
| avail_res->sock_cnt = sockets; |
| avail_res->spec_threads = spec_threads; |
| avail_res->tpc = node_ptr->tpc; |
| |
| FREE_NULL_BITMAP(tmp_core); |
| |
| return avail_res; |
| } |
| |
| /* |
| * _allocate - Given the job requirements, determine which resources |
| * from the given node can be allocated (if any) to this |
| * job. Returns the number of cpus that can be used by |
| * this node AND a bitmap of the selected cores|sockets. |
| * |
| * IN job_ptr - pointer to job requirements |
| * IN/OUT core_map - core_bitmap of available cores on this node |
| * IN part_core_map - bitmap of cores already allocated on this partition/node |
| * IN node_i - index of node to be evaluated |
| * OUT cpu_alloc_size - minimum allocation size, in CPUs |
| * IN req_sock_map - OPTIONAL bitmap of required sockets |
| * IN cr_type - Consumable Resource setting |
| * RET resource availability structure, call _free_avail_res() to free |
| */ |
| static avail_res_t *_allocate(job_record_t *job_ptr, |
| bitstr_t *core_map, |
| bitstr_t *part_core_map, |
| const uint32_t node_i, |
| int *cpu_alloc_size, |
| bitstr_t *req_sock_map, |
| uint16_t cr_type) |
| { |
| bool alloc_sockets; |
| |
| if (cr_type & SELECT_SOCKET) { |
| /* cpu_alloc_size = CPUs per socket */ |
| alloc_sockets = true; |
| *cpu_alloc_size = node_record_table_ptr[node_i]->cores * |
| node_record_table_ptr[node_i]->tpc; |
| } else { |
| /* cpu_alloc_size = # of CPUs per core || 1 individual CPU */ |
| alloc_sockets = false; |
| *cpu_alloc_size = (cr_type & SELECT_CORE) ? |
| node_record_table_ptr[node_i]->tpc : 1; |
| } |
| |
| return _allocate_sc(job_ptr, core_map, part_core_map, node_i, |
| cpu_alloc_size, alloc_sockets, req_sock_map); |
| } |
| |
| static int _add_exempt_cores(void *x, void *arg) |
| { |
| job_record_t *job_ptr = (job_record_t *) x; |
| job_resources_t *job_resrcs; |
| node_record_t *node_ptr; |
| int core_cnt = 0; |
| |
| if (!IS_JOB_RUNNING(job_ptr)) |
| return 0; |
| job_resrcs = job_ptr->job_resrcs; |
| if (!job_resrcs || !job_resrcs->core_bitmap || !job_resrcs->node_bitmap) |
| return 0; |
| if (slurm_job_preempt_mode(job_ptr) != PREEMPT_MODE_SUSPEND) |
| return 0; |
| if (!acct_policy_is_job_preempt_exempt(job_ptr)) |
| return 0; |
| |
| log_flag(SELECT_TYPE, "%pJ is exempt from SUSPEND preemption via PreemptExemptTime, adding cores to suspend_exempt_cores", |
| job_ptr); |
| |
| /* |
| * For each node in the job, create/set a corresponding core bitmap in |
| * suspend_exempt_cores that will be used later to prevent pending jobs |
| * from SUSPEND preempting this job. |
| * |
| * See build_job_resources() for navigating job_resrcs bitmaps. |
| */ |
| for (int i = 0; |
| (node_ptr = next_node_bitmap(job_resrcs->node_bitmap, &i)); i++) { |
| if (!suspend_exempt_cores[i]) |
| suspend_exempt_cores[i] = |
| bit_alloc(node_ptr->tot_cores); |
| |
| if (job_resrcs->whole_node == 1) { |
| bit_set_all(suspend_exempt_cores[i]); |
| } else { |
| for (int c = 0; c < node_ptr->tot_cores; c++) { |
| if (bit_test(job_resrcs->core_bitmap, |
| core_cnt + c)) |
| bit_set(suspend_exempt_cores[i], c); |
| } |
| } |
| core_cnt += node_ptr->tot_cores; |
| } |
| |
| return 0; |
| } |
| |
| /* |
| * Rebuild array of cores currently exempt from preemption. Caches the result |
| * and skips the rebuild if already called within the same second. |
| * PreemptExemptTime has one-second granularity so this is safe, and it avoids |
| * iterating over job_list for every job_test() called. |
| */ |
| static void _rebuild_suspend_exempt_cores(void) |
| { |
| static time_t last_rebuild = 0; |
| time_t now = time(NULL); |
| |
| /* Only rebuild the core array at most once per second */ |
| if (last_rebuild == now) |
| return; |
| last_rebuild = now; |
| |
| if (!suspend_exempt_cores) |
| suspend_exempt_cores = build_core_array(); |
| else |
| clear_core_array(suspend_exempt_cores); |
| |
| list_for_each(job_list, _add_exempt_cores, NULL); |
| |
| core_array_log("suspend_exempt_cores", NULL, suspend_exempt_cores); |
| } |
| |
| /* |
| * job_test - Given a specification of scheduling requirements, |
| * identify the nodes which "best" satisfy the request. |
| * "best" is defined as either a minimal number of consecutive nodes |
| * or if sharing resources then sharing them with a job of similar size. |
| * IN/OUT job_ptr - pointer to job being considered for initiation, |
| * set's start_time when job expected to start |
| * IN/OUT bitmap - usable nodes are set on input, nodes not required to |
| * satisfy the request are cleared, other left set |
| * IN min_nodes - minimum count of nodes |
| * IN req_nodes - requested (or desired) count of nodes |
| * IN max_nodes - maximum count of nodes (0==don't care) |
| * IN mode - SELECT_MODE_RUN_NOW (0): try to schedule job now |
| * SELECT_MODE_TEST_ONLY (1): test if job can ever run |
| * SELECT_MODE_WILL_RUN (2): determine when and where job can run |
| * IN preemptee_candidates - list of pointers to jobs which can be preempted. |
| * IN/OUT preemptee_job_list - Pointer to list of job pointers. These are the |
| * jobs to be preempted to initiate the pending job. Not set |
| * if mode=SELECT_MODE_TEST_ONLY or input pointer is NULL. |
| * IN resv_exc_ptr - Various TRES which the job can NOT use. |
| * IN will_run_ptr - Pointer to data specific to WILL_RUN mode |
| * RET zero on success, EINVAL otherwise |
| * globals (passed via select_p_node_init): |
| * node_record_count - count of nodes configured |
| * node_record_table_ptr - pointer to global node table |
| * NOTE: the job information that is considered for scheduling includes: |
| * req_node_bitmap: bitmap of specific nodes required by the job |
| * contiguous: allocated nodes must be sequentially located |
| * num_cpus: minimum number of processors required by the job |
| * NOTE: bitmap must be a superset of req_nodes at the time that |
| * select_p_job_test is called |
| */ |
| extern int job_test(job_record_t *job_ptr, bitstr_t *node_bitmap, |
| uint32_t min_nodes, uint32_t max_nodes, |
| uint32_t req_nodes, uint16_t mode, |
| list_t *preemptee_candidates, |
| list_t **preemptee_job_list, |
| resv_exc_t *resv_exc_ptr, |
| will_run_data_t *will_run_ptr) |
| { |
| int rc = EINVAL; |
| uint16_t job_node_req; |
| |
| if (!(slurm_conf.conf_flags & CONF_FLAG_ASRU)) |
| job_ptr->details->core_spec = NO_VAL16; |
| if ((job_ptr->details->core_spec != NO_VAL16) && |
| !(job_ptr->details->whole_node & WHOLE_NODE_REQUIRED)) { |
| info("Setting Exclusive mode for %pJ with CoreSpec=%u", |
| job_ptr, |
| job_ptr->details->core_spec); |
| job_ptr->details->whole_node |= WHOLE_NODE_REQUIRED; |
| } |
| |
| if (!job_ptr->details->mc_ptr) |
| job_ptr->details->mc_ptr = job_record_create_mc(); |
| job_node_req = _get_job_node_req(job_ptr); |
| |
| if (slurm_conf.debug_flags & DEBUG_FLAG_SELECT_TYPE) { |
| char *node_mode = "Unknown", *alloc_mode = "Unknown"; |
| if (job_node_req == NODE_CR_RESERVED) |
| node_mode = "Exclusive"; |
| else if (job_node_req == NODE_CR_AVAILABLE) |
| node_mode = "OverCommit"; |
| else if (job_node_req == NODE_CR_ONE_ROW) |
| node_mode = "Normal"; |
| if (mode == SELECT_MODE_WILL_RUN) |
| alloc_mode = "Will_Run"; |
| else if (mode == SELECT_MODE_TEST_ONLY) |
| alloc_mode = "Test_Only"; |
| else if (mode == SELECT_MODE_RUN_NOW) |
| alloc_mode = "Run_Now"; |
| verbose("%pJ node_mode:%s alloc_mode:%s", |
| job_ptr, node_mode, alloc_mode); |
| |
| core_array_log("node_list & exc_cores", |
| node_bitmap, resv_exc_ptr->exc_cores); |
| |
| verbose("nodes: min:%u max:%u requested:%u avail:%u", |
| min_nodes, max_nodes, req_nodes, |
| bit_set_count(node_bitmap)); |
| node_data_dump(); |
| } |
| |
| /* |
| * If using PreemptMode=suspend,gang and PreemptExemptTime is set, build |
| * core array of all cores that are exempt from suspend preemption |
| * because the jobs using them have not been running for long enough |
| * yet. (haven't run for PreemptExemptTime seconds yet) |
| */ |
| if (gang_mode && (slurm_conf.preempt_exempt_time != INFINITE) && |
| slurm_conf.preempt_exempt_time && (mode != SELECT_MODE_TEST_ONLY)) |
| _rebuild_suspend_exempt_cores(); |
| |
| if (mode == SELECT_MODE_WILL_RUN) { |
| rc = _will_run_test(job_ptr, node_bitmap, min_nodes, |
| max_nodes, |
| req_nodes, job_node_req, |
| preemptee_candidates, |
| preemptee_job_list, |
| resv_exc_ptr, |
| will_run_ptr); |
| } else if (mode == SELECT_MODE_TEST_ONLY) { |
| rc = _test_only(job_ptr, node_bitmap, min_nodes, |
| max_nodes, req_nodes, job_node_req); |
| } else if (mode == SELECT_MODE_RUN_NOW) { |
| rc = _run_now(job_ptr, node_bitmap, min_nodes, max_nodes, |
| req_nodes, job_node_req, |
| preemptee_candidates, |
| preemptee_job_list, resv_exc_ptr); |
| } else { |
| /* Should never get here */ |
| error("Mode %d is invalid", |
| mode); |
| return EINVAL; |
| } |
| |
| FREE_NULL_LIST(job_ptr->licenses_to_preempt); |
| |
| if ((slurm_conf.debug_flags & DEBUG_FLAG_CPU_BIND) || |
| (slurm_conf.debug_flags & DEBUG_FLAG_SELECT_TYPE)) { |
| if (job_ptr->job_resrcs) { |
| verbose("Test returned:%s", slurm_strerror(rc)); |
| log_job_resources(job_ptr); |
| gres_job_state_log(job_ptr->gres_list_req, |
| job_ptr->job_id); |
| } else { |
| verbose("no job_resources info for %pJ rc=%d", |
| job_ptr, rc); |
| } |
| } |
| |
| return rc; |
| } |