diff --git a/include/haproxy/cpu_topo-t.h b/include/haproxy/cpu_topo-t.h index cb6e4f005..4d85945eb 100644 --- a/include/haproxy/cpu_topo-t.h +++ b/include/haproxy/cpu_topo-t.h @@ -49,4 +49,19 @@ struct ha_cpu_cluster { uint nb_cpu; /* total CPUs */ }; +/* Description of a CPU selection policy. For now it only associates an option + * name with a callback function that is supposed to adjust the global.nbthread + * and global.nbtgroups based on the policy, the topology, and the constraints + * on the number of threads which must be between tmin and tmax included, and + * the number of thread groups which must be between gmin and gmax included. + * The callback also takes the policy number (cpu_policy) and a pointer to a + * string to write an error to in case of failure (in which case ret must be + * < 0 and the caller will fre the location). More settings might come later. + */ +struct ha_cpu_policy { + const char *name; /* option name in the configuration */ + const char *desc; /* short description for help messages */ + int (*fct)(int policy, int tmin, int tmax, int gmin, int gmax, char **err); +}; + #endif /* _HAPROXY_CPU_TOPO_T_H */ diff --git a/include/haproxy/cpu_topo.h b/include/haproxy/cpu_topo.h index 97a8a783d..959d89b99 100644 --- a/include/haproxy/cpu_topo.h +++ b/include/haproxy/cpu_topo.h @@ -37,6 +37,11 @@ void cpu_compose_clusters(void); /* apply remaining topology-based cpu set restrictions */ void cpu_refine_cpusets(void); +/* apply the chosen CPU policy. Returns < 0 on failure with a message in *err + * that must be freed by the caller if non-null. + */ +int cpu_apply_policy(int tmin, int tmax, int gmin, int gmax, char **err); + /* Detects CPUs that are bound to the current process. Returns the number of * CPUs detected or 0 if the detection failed. */ diff --git a/src/cpu_topo.c b/src/cpu_topo.c index 15d664f6e..1aef23198 100644 --- a/src/cpu_topo.c +++ b/src/cpu_topo.c @@ -46,6 +46,15 @@ struct cpu_set_cfg { struct hap_cpuset drop_threads; } cpu_set_cfg; +/* CPU policy choice */ +static int cpu_policy = 0; + +/* list of CPU policies for "cpu-policy". The default one is the first one. */ +static struct ha_cpu_policy ha_cpu_policy[] = { + { .name = "none", .desc = "use all available CPUs", .fct = NULL }, + { 0 } /* end */ +}; + /* Detects CPUs that are online on the system. It may rely on FS access (e.g. * /sys on Linux). Returns the number of CPUs detected or 0 if the detection * failed. @@ -872,6 +881,29 @@ void cpu_refine_cpusets(void) } } +/* apply the chosen CPU policy if no cpu-map was forced. Returns < 0 on failure + * with a message in *err that must be freed by the caller if non-null. + */ +int cpu_apply_policy(int tmin, int tmax, int gmin, int gmax, char **err) +{ + *err = NULL; + + if (cpu_map_configured()) { + /* nothing to do */ + return 0; + } + + if (!ha_cpu_policy[cpu_policy].fct) { + /* nothing to do */ + return 0; + } + + if (ha_cpu_policy[cpu_policy].fct(cpu_policy, tmin, tmax, gmin, gmax, err) < 0) + return -1; + + return 0; +} + /* CPU topology detection below, OS-specific */ #if defined(__linux__) @@ -1330,6 +1362,36 @@ static int cfg_parse_cpu_set(char **args, int section_type, struct proxy *curpx, return -1; } +/* Parse the "cpu-policy" global directive, which takes the name of one of the + * ha_cpu_policy[] names, and sets the associated index in cpu_policy. + */ +static int cfg_parse_cpu_policy(char **args, int section_type, struct proxy *curpx, + const struct proxy *defpx, const char *file, int line, + char **err) +{ + int i; + + if (too_many_args(1, args, err, NULL)) + return -1; + + for (i = 0; ha_cpu_policy[i].name; i++) { + if (strcmp(args[1], ha_cpu_policy[i].name) == 0) { + cpu_policy = i; + return 0; + } + } + + memprintf(err, "'%s' passed an unknown CPU policy '%s'. Supported values are:", args[0], args[1]); + for (i = 0; ha_cpu_policy[i].name; i++) { + memprintf(err, "%s%s '%s' (%s)%s", *err, + (i > 0 && ha_cpu_policy[i+1].name) ? "" : " and", + ha_cpu_policy[i].name, + ha_cpu_policy[i].desc, + (ha_cpu_policy[i+1].name) ? "," : ".\n"); + } + return -1; +} + /* Allocates everything needed to store CPU topology at boot. * Returns non-zero on success, zero on failure. */ @@ -1403,6 +1465,7 @@ REGISTER_POST_DEINIT(cpu_topo_deinit); /* config keyword parsers */ static struct cfg_kw_list cfg_kws = {ILH, { + { CFG_GLOBAL, "cpu-policy", cfg_parse_cpu_policy, 0 }, { CFG_GLOBAL, "cpu-set", cfg_parse_cpu_set, 0 }, { 0, NULL, NULL } }}; diff --git a/src/thread.c b/src/thread.c index 7a720d827..57925b989 100644 --- a/src/thread.c +++ b/src/thread.c @@ -1549,6 +1549,7 @@ void thread_detect_count(void) int grp_max __maybe_unused; int cpus_avail __maybe_unused; int cpu __maybe_unused; + char *err __maybe_unused; thr_min = 1; thr_max = MAX_THREADS; grp_min = 1; grp_max = MAX_TGROUPS; @@ -1614,6 +1615,13 @@ void thread_detect_count(void) * on the same cluster _capacity_ up to thr_max. */ + if (cpu_apply_policy(thr_min, thr_max, grp_min, grp_max, &err) < 0) { + if (err) + ha_warning("cpu-policy: %s\n", err); + ha_free(&err); + return; + } + /* Let's implement here the automatic binding to the first available * NUMA node when thread count is not set, taskset is not used and * no cpu-map directive is present.