blob: d8c48a4bdf3513e7b2466f028c6844bbc09f4b9a [file] [log] [blame]
/*****************************************************************************\
* slurm_resource_info.c - Functions to determine number of available resources
*****************************************************************************
* Copyright (C) 2006 Hewlett-Packard Development Company, L.P.
* Written by Susanne M. Balle, <susanne.balle@hp.com>
* CODE-OCEC-09-009. All rights reserved.
*
* 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 <ctype.h>
#include <limits.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include "slurm/slurm.h"
#include "slurm/slurm_errno.h"
#include "src/common/log.h"
#include "src/common/read_config.h"
#include "src/common/slurm_protocol_api.h"
#include "src/common/slurm_resource_info.h"
#include "src/common/xmalloc.h"
#include "src/common/xstring.h"
/*
* First clear all of the bits in "*data" which are set in "clear_mask".
* Then set all of the bits in "*data" that are set in "set_mask".
*/
static void _clear_then_set(int *data, int clear_mask, int set_mask)
{
*data &= ~clear_mask;
*data |= set_mask;
}
/*
* _isvalue
* returns 1 is the argument appears to be a value, 0 otherwise
*/
static int _isvalue(char *arg) {
if (isdigit((int)*arg)) { /* decimal values and 0x... hex values */
return 1;
}
while (isxdigit((int)*arg)) { /* hex values not preceded by 0x */
arg++;
}
if (*arg == ',' || *arg == '\0') { /* end of field or string */
return 1;
}
return 0; /* not a value */
}
/* Expand a list of CPU/memory maps or masks containing multipliers.
* For example, change "1*4,2*4" to "1,1,1,1,2,2,2,2"
* list IN - input mask or map
* type IN - "mask_cpu", "map_mem", used for error messages
* error_code - output SLURM_SUCCESS or SLURM_ERROR on failure
* RET - output mask or map, value must be xfreed */
static char *_expand_mult(char *list, char *type, int *error_code)
{
char *ast, *end_ptr = NULL, *result = NULL, *save_ptr = NULL;
char *sep = "", *tmp, *tok;
int (*isfunc) (int) = isdigit;
bool mask = false;
*error_code = SLURM_SUCCESS;
if (!list) /* Nothing to convert */
return NULL;
tmp = xstrdup(list);
if (!xstrncmp(type, "mask", 4)) {
isfunc = isxdigit;
mask = true;
}
tok = strtok_r(tmp, ",", &save_ptr);
while (tok) {
long int i;
long int count = 1;
if (mask && !xstrncmp(tok, "0x", 2))
tok+=2;
ast = strchr(tok, '*');
if (ast) {
/* Starting from 1 since we know that ast[0] == '*' */
for (int i = 1; ast[i]; i++)
if (!isdigit(ast[i])) {
error("Failed to validate number: %s, the offending character is %c",
ast, ast[i]);
*error_code = SLURM_ERROR;
return 0;
}
count = strtol(ast + 1, &end_ptr, 10);
if ((count <= 0) || (end_ptr[0] != '\0') ||
(count == LONG_MIN) || (count == LONG_MAX)) {
error("Invalid %s multiplier: %s",
type, ast + 1);
xfree(result);
*error_code = SLURM_ERROR;
break;
}
ast[0] = '\0';
}
for (i = 0; tok[i]; i++) {
if (!isfunc(tok[i])) {
error("Failed to validate number: %s, the offending character is %c",
tok, tok[i]);
*error_code = SLURM_ERROR;
return 0;
}
}
for (i = 0; i < count; i++) {
xstrfmtcat(result, "%s%s", sep, tok);
sep = ",";
}
tok = strtok_r(NULL, ",", &save_ptr);
}
xfree(tmp);
if (!result) {
error("Failed to expand list: '%s'", list);
*error_code = SLURM_ERROR;
}
return result;
}
static bool _have_task_affinity(void)
{
if (!xstrcmp(slurm_conf.task_plugin, "task/none"))
return false;
return true;
}
/*
* slurm_sprint_cpu_bind_type
*
* Given a cpu_bind_type, report all flag settings in str
* IN - cpu_bind_type
* OUT - str
*/
void slurm_sprint_cpu_bind_type(char *str, cpu_bind_type_t cpu_bind_type)
{
if (!str)
return;
str[0] = '\0';
if (cpu_bind_type & CPU_BIND_VERBOSE)
strcat(str, "verbose,");
if (cpu_bind_type & CPU_BIND_TO_THREADS)
strcat(str, "threads,");
if (cpu_bind_type & CPU_BIND_TO_CORES)
strcat(str, "cores,");
if (cpu_bind_type & CPU_BIND_TO_SOCKETS)
strcat(str, "sockets,");
if (cpu_bind_type & CPU_BIND_TO_LDOMS)
strcat(str, "ldoms,");
if (cpu_bind_type & CPU_BIND_NONE)
strcat(str, "none,");
if (cpu_bind_type & CPU_BIND_MAP)
strcat(str, "map_cpu,");
if (cpu_bind_type & CPU_BIND_MASK)
strcat(str, "mask_cpu,");
if (cpu_bind_type & CPU_BIND_LDRANK)
strcat(str, "rank_ldom,");
if (cpu_bind_type & CPU_BIND_LDMAP)
strcat(str, "map_ldom,");
if (cpu_bind_type & CPU_BIND_LDMASK)
strcat(str, "mask_ldom,");
if (cpu_bind_type & CPU_BIND_ONE_THREAD_PER_CORE)
strcat(str, "one_thread,");
if (cpu_bind_type & CPU_AUTO_BIND_TO_THREADS)
strcat(str, "autobind=threads,");
if (cpu_bind_type & CPU_AUTO_BIND_TO_CORES)
strcat(str, "autobind=cores,");
if (cpu_bind_type & CPU_AUTO_BIND_TO_SOCKETS)
strcat(str, "autobind=sockets,");
if (cpu_bind_type & CPU_BIND_OFF)
strcat(str, "off,");
if (*str) {
str[strlen(str)-1] = '\0'; /* remove trailing ',' */
} else {
strcat(str, "(null type)"); /* no bits set */
}
}
/*
* slurm_xstr_mem_bind_type
*
* Given a mem_bind_type, report all flag settings in str
* IN - mem_bind_type
* OUT - str
*/
extern char *slurm_xstr_mem_bind_type(mem_bind_type_t mem_bind_type)
{
char *str = NULL;
if (mem_bind_type & MEM_BIND_VERBOSE)
xstrcat(str, "verbose,");
if (mem_bind_type & MEM_BIND_PREFER)
xstrcat(str, "prefer,");
if (mem_bind_type & MEM_BIND_SORT)
xstrcat(str, "sort,");
if (mem_bind_type & MEM_BIND_NONE)
xstrcat(str, "none,");
if (mem_bind_type & MEM_BIND_RANK)
xstrcat(str, "rank,");
if (mem_bind_type & MEM_BIND_LOCAL)
xstrcat(str, "local,");
if (mem_bind_type & MEM_BIND_MAP)
xstrcat(str, "map_mem,");
if (mem_bind_type & MEM_BIND_MASK)
xstrcat(str, "mask_mem,");
if (str) {
str[strlen(str)-1] = '\0'; /* remove trailing ',' */
}
return str;
}
void slurm_print_cpu_bind_help(void)
{
if (!_have_task_affinity()) {
printf("CPU bind options not supported with current "
"configuration\n");
} else {
printf(
"CPU bind options:\n"
" --cpu-bind= Bind tasks to CPUs\n"
" q[uiet] quietly bind before task runs (default)\n"
" v[erbose] verbosely report binding before task runs\n"
" no[ne] don't bind tasks to CPUs (default)\n"
" map_cpu:<list> specify a CPU ID binding for each task\n"
" where <list> is <cpuid1>,<cpuid2>,...<cpuidN>\n"
" mask_cpu:<list> specify a CPU ID binding mask for each task\n"
" where <list> is <mask1>,<mask2>,...<maskN>\n"
" rank_ldom bind task by rank to CPUs in a NUMA locality domain\n"
" map_ldom:<list> specify a NUMA locality domain ID for each task\n"
" where <list> is <ldom1>,<ldom2>,...<ldomN>\n"
" mask_ldom:<list>specify a NUMA locality domain ID mask for each task\n"
" where <list> is <mask1>,<mask2>,...<maskN>\n"
" sockets auto-generated masks bind to sockets\n"
" cores auto-generated masks bind to cores\n"
" threads auto-generated masks bind to threads\n"
" ldoms auto-generated masks bind to NUMA locality domains\n"
" help show this help message\n");
}
}
/*
* verify cpu_bind arguments
*
* we support different launch policy names
* we also allow a verbose setting to be specified
* --cpu-bind=threads
* --cpu-bind=cores
* --cpu-bind=sockets
* --cpu-bind=v
* --cpu-bind=rank,v
* --cpu-bind=rank
* --cpu-bind={MAP_CPU|MASK_CPU}:0,1,2,3,4
*
* arg IN - user task binding option
* cpu_bind OUT - task binding string
* flags OUT OUT - task binding flags
* RET SLURM_SUCCESS, SLURM_ERROR (-1) on failure, 1 for return for "help" arg
*/
extern int slurm_verify_cpu_bind(const char *arg, char **cpu_bind,
cpu_bind_type_t *flags)
{
char *buf, *p, *tok;
int bind_bits, bind_to_bits;
bool have_binding = _have_task_affinity();
bool log_binding = true;
int rc = SLURM_SUCCESS;
bind_bits = CPU_BIND_NONE | CPU_BIND_MAP | CPU_BIND_MASK |
CPU_BIND_LDRANK | CPU_BIND_LDMAP | CPU_BIND_LDMASK;
bind_to_bits = CPU_BIND_TO_SOCKETS | CPU_BIND_TO_CORES |
CPU_BIND_TO_THREADS | CPU_BIND_TO_LDOMS;
buf = xstrdup(arg);
p = buf;
/* change all ',' delimiters not followed by a digit to ';' */
/* simplifies parsing tokens while keeping map/mask together */
while (p[0] != '\0') {
if ((p[0] == ',') && (!_isvalue(&(p[1]))))
p[0] = ';';
p++;
}
p = buf;
while ((rc == SLURM_SUCCESS) && (tok = strsep(&p, ";"))) {
if (xstrcasecmp(tok, "help") == 0) {
slurm_print_cpu_bind_help();
xfree(buf);
return 1;
}
if (!have_binding && log_binding) {
info("cluster configuration lacks support for cpu binding");
log_binding = false;
}
if ((xstrcasecmp(tok, "q") == 0) ||
(xstrcasecmp(tok, "quiet") == 0)) {
*flags &= ~CPU_BIND_VERBOSE;
} else if ((xstrcasecmp(tok, "v") == 0) ||
(xstrcasecmp(tok, "verbose") == 0)) {
*flags |= CPU_BIND_VERBOSE;
} else if ((xstrcasecmp(tok, "one_thread") == 0)) {
*flags |= CPU_BIND_ONE_THREAD_PER_CORE;
} else if ((xstrcasecmp(tok, "no") == 0) ||
(xstrcasecmp(tok, "none") == 0)) {
_clear_then_set((int *)flags, bind_bits, CPU_BIND_NONE);
xfree(*cpu_bind);
} else if (xstrcasecmp(tok, "rank") == 0) {
info("Ignoring --cpu-bind=rank. Rank binding is obsolete.");
xfree(*cpu_bind);
} else if ((xstrncasecmp(tok, "map_cpu", 7) == 0) ||
(xstrncasecmp(tok, "mapcpu", 6) == 0)) {
char *list;
(void) strsep(&tok, ":=");
list = strsep(&tok, ":="); /* THIS IS NOT REDUNDANT */
_clear_then_set((int *)flags, bind_bits, CPU_BIND_MAP);
xfree(*cpu_bind);
if (list && *list) {
*cpu_bind = _expand_mult(list, "map_cpu", &rc);
} else {
error("missing list for \"--cpu-bind=map_cpu:<list>\"");
rc = SLURM_ERROR;
}
} else if ((xstrncasecmp(tok, "mask_cpu", 8) == 0) ||
(xstrncasecmp(tok, "maskcpu", 7) == 0)) {
char *list;
(void) strsep(&tok, ":=");
list = strsep(&tok, ":="); /* THIS IS NOT REDUNDANT */
_clear_then_set((int *)flags, bind_bits, CPU_BIND_MASK);
xfree(*cpu_bind);
if (list && *list) {
*cpu_bind = _expand_mult(list, "mask_cpu", &rc);
} else {
error("missing list for \"--cpu-bind=mask_cpu:<list>\"");
rc = SLURM_ERROR;
}
} else if (xstrcasecmp(tok, "rank_ldom") == 0) {
_clear_then_set((int *)flags, bind_bits,
CPU_BIND_LDRANK);
xfree(*cpu_bind);
} else if ((xstrncasecmp(tok, "map_ldom", 8) == 0) ||
(xstrncasecmp(tok, "mapldom", 7) == 0)) {
char *list;
(void) strsep(&tok, ":=");
list = strsep(&tok, ":="); /* THIS IS NOT REDUNDANT */
_clear_then_set((int *)flags, bind_bits,
CPU_BIND_LDMAP);
xfree(*cpu_bind);
if (list && *list) {
*cpu_bind = _expand_mult(list, "map_ldom", &rc);
} else {
error("missing list for \"--cpu-bind=map_ldom:<list>\"");
rc = SLURM_ERROR;
}
} else if ((xstrncasecmp(tok, "mask_ldom", 9) == 0) ||
(xstrncasecmp(tok, "maskldom", 8) == 0)) {
char *list;
(void) strsep(&tok, ":=");
list = strsep(&tok, ":="); /* THIS IS NOT REDUNDANT */
_clear_then_set((int *)flags, bind_bits,
CPU_BIND_LDMASK);
xfree(*cpu_bind);
if (list && *list) {
*cpu_bind = _expand_mult(list, "mask_ldom",&rc);
} else {
error("missing list for \"--cpu-bind="
"mask_ldom:<list>\"");
rc = SLURM_ERROR;
}
} else if ((xstrcasecmp(tok, "socket") == 0) ||
(xstrcasecmp(tok, "sockets") == 0)) {
_clear_then_set((int *)flags, bind_to_bits,
CPU_BIND_TO_SOCKETS);
} else if ((xstrcasecmp(tok, "core") == 0) ||
(xstrcasecmp(tok, "cores") == 0)) {
_clear_then_set((int *)flags, bind_to_bits,
CPU_BIND_TO_CORES);
} else if ((xstrcasecmp(tok, "thread") == 0) ||
(xstrcasecmp(tok, "threads") == 0)) {
_clear_then_set((int *)flags, bind_to_bits,
CPU_BIND_TO_THREADS);
} else if ((xstrcasecmp(tok, "ldom") == 0) ||
(xstrcasecmp(tok, "ldoms") == 0)) {
_clear_then_set((int *)flags, bind_to_bits,
CPU_BIND_TO_LDOMS);
} else {
error("unrecognized --cpu-bind argument \"%s\"", tok);
rc = SLURM_ERROR;
}
}
xfree(buf);
if (rc)
fatal("Failed to parse --cpu-bind= values.");
return rc;
}
/*
* Translate a CPU bind string to its equivalent numeric value
* cpu_bind_str IN - string to translate
* flags OUT - equlvalent numeric value
* RET SLURM_SUCCESS or SLURM_ERROR
*/
extern int xlate_cpu_bind_str(char *cpu_bind_str, uint32_t *flags)
{
int rc = SLURM_SUCCESS;
char *save_ptr = NULL, *tok, *tmp;
bool have_bind_type = false;
*flags = 0;
if (!cpu_bind_str)
return rc;
tmp = xstrdup(cpu_bind_str);
tok = strtok_r(tmp, ",;", &save_ptr);
while (tok) {
if ((xstrcasecmp(tok, "no") == 0) ||
(xstrcasecmp(tok, "none") == 0)) {
if (have_bind_type) {
rc = SLURM_ERROR;
break;
} else {
*flags |= CPU_BIND_NONE;
have_bind_type = true;
}
} else if ((xstrcasecmp(tok, "socket") == 0) ||
(xstrcasecmp(tok, "sockets") == 0)) {
if (have_bind_type) {
rc = SLURM_ERROR;
break;
} else {
*flags |= CPU_BIND_TO_SOCKETS;
have_bind_type = true;
}
} else if ((xstrcasecmp(tok, "ldom") == 0) ||
(xstrcasecmp(tok, "ldoms") == 0)) {
if (have_bind_type) {
rc = SLURM_ERROR;
break;
} else {
*flags |= CPU_BIND_TO_LDOMS;
have_bind_type = true;
}
} else if ((xstrcasecmp(tok, "core") == 0) ||
(xstrcasecmp(tok, "cores") == 0)) {
if (have_bind_type) {
rc = SLURM_ERROR;
break;
} else {
*flags |= CPU_BIND_TO_CORES;
have_bind_type = true;
}
} else if ((xstrcasecmp(tok, "thread") == 0) ||
(xstrcasecmp(tok, "threads") == 0)) {
if (have_bind_type) {
rc = SLURM_ERROR;
break;
} else {
*flags |= CPU_BIND_TO_THREADS;
have_bind_type = true;
}
} else if (xstrcasecmp(tok, "off") == 0) {
if (have_bind_type) {
rc = SLURM_ERROR;
break;
} else {
*flags |= CPU_BIND_OFF;
have_bind_type = true;
}
} else if ((xstrcasecmp(tok, "v") == 0) ||
(xstrcasecmp(tok, "verbose") == 0)) {
*flags |= CPU_BIND_VERBOSE;
} else {
/* Other options probably not desirable to support */
rc = SLURM_ERROR;
break;
}
tok = strtok_r(NULL, ",;", &save_ptr);
}
xfree(tmp);
return rc;
}
void slurm_print_mem_bind_help(void)
{
printf(
"Memory bind options:\n"
" --mem-bind= Bind memory to locality domains (ldom)\n"
" nosort avoid sorting pages at startup\n"
" sort sort pages at startup\n"
" q[uiet] quietly bind before task runs (default)\n"
" v[erbose] verbosely report binding before task runs\n"
" no[ne] don't bind tasks to memory (default)\n"
" rank bind by task rank\n"
" local bind to memory local to processor\n"
" map_mem:<list> specify a memory binding for each task\n"
" where <list> is <cpuid1>,<cpuid2>,...<cpuidN>\n"
" mask_mem:<list> specify a memory binding mask for each tasks\n"
" where <list> is <mask1>,<mask2>,...<maskN>\n"
" help show this help message\n");
}
/*
* verify mem_bind arguments
*
* we support different memory binding names
* we also allow a verbose setting to be specified
* --mem-bind=v
* --mem-bind=rank,v
* --mem-bind=rank
* --mem-bind={MAP_MEM|MASK_MEM}:0,1,2,3,4
*
* returns -1 on error, 0 otherwise
*/
int slurm_verify_mem_bind(const char *arg, char **mem_bind,
mem_bind_type_t *flags)
{
char *buf, *p, *tok;
int bind_bits = MEM_BIND_NONE|MEM_BIND_RANK|MEM_BIND_LOCAL|
MEM_BIND_MAP|MEM_BIND_MASK;
int rc = SLURM_SUCCESS;
if (arg == NULL) {
return 0;
}
buf = xstrdup(arg);
p = buf;
/* change all ',' delimiters not followed by a digit to ';' */
/* simplifies parsing tokens while keeping map/mask together */
while (p[0] != '\0') {
if ((p[0] == ',') && (!_isvalue(&(p[1]))))
p[0] = ';';
p++;
}
p = buf;
while ((rc == SLURM_SUCCESS) && (tok = strsep(&p, ";"))) {
if (xstrcasecmp(tok, "help") == 0) {
slurm_print_mem_bind_help();
xfree(buf);
return 1;
} else if ((xstrcasecmp(tok, "p") == 0) ||
(xstrcasecmp(tok, "prefer") == 0)) {
*flags |= MEM_BIND_PREFER;
} else if (!xstrcasecmp(tok, "nosort")) {
*flags &= ~MEM_BIND_SORT;
} else if (!xstrcasecmp(tok, "sort")) {
*flags |= MEM_BIND_SORT;
} else if ((xstrcasecmp(tok, "q") == 0) ||
(xstrcasecmp(tok, "quiet") == 0)) {
*flags &= ~MEM_BIND_VERBOSE;
} else if ((xstrcasecmp(tok, "v") == 0) ||
(xstrcasecmp(tok, "verbose") == 0)) {
*flags |= MEM_BIND_VERBOSE;
} else if ((xstrcasecmp(tok, "no") == 0) ||
(xstrcasecmp(tok, "none") == 0)) {
_clear_then_set((int *)flags, bind_bits, MEM_BIND_NONE);
xfree(*mem_bind);
} else if (xstrcasecmp(tok, "rank") == 0) {
_clear_then_set((int *)flags, bind_bits, MEM_BIND_RANK);
xfree(*mem_bind);
} else if (xstrcasecmp(tok, "local") == 0) {
_clear_then_set((int *)flags, bind_bits,MEM_BIND_LOCAL);
xfree(*mem_bind);
} else if ((xstrncasecmp(tok, "map_mem", 7) == 0) ||
(xstrncasecmp(tok, "mapmem", 6) == 0)) {
char *list;
(void) strsep(&tok, ":=");
list = strsep(&tok, ":="); /* THIS IS NOT REDUNDANT */
_clear_then_set((int *)flags, bind_bits, MEM_BIND_MAP);
xfree(*mem_bind);
if (list && *list) {
*mem_bind = _expand_mult(list, "map_mem", &rc);
} else {
error("missing list for \"--mem-bind=map_mem:<list>\"");
rc = SLURM_ERROR;
}
} else if ((xstrncasecmp(tok, "mask_mem", 8) == 0) ||
(xstrncasecmp(tok, "maskmem", 7) == 0)) {
char *list;
(void) strsep(&tok, ":=");
list = strsep(&tok, ":="); /* THIS IS NOT REDUNDANT */
_clear_then_set((int *)flags, bind_bits, MEM_BIND_MASK);
xfree(*mem_bind);
if (list && *list) {
*mem_bind = _expand_mult(list, "mask_mem", &rc);
} else {
error("missing list for \"--mem-bind=mask_mem:<list>\"");
rc = SLURM_ERROR;
}
} else {
error("unrecognized --mem-bind argument \"%s\"", tok);
rc = SLURM_ERROR;
}
}
xfree(buf);
return rc;
}