rtla uses get_nprocs_conf(), a standard libc routine, to retrieve the total number of CPUs for the purpose of processing per-CPU data.
In some configurations, libc might return a different value than what is seen by the kernel. To cover for this situation, replace the call to get_nprocs_conf() with a newly added helper, get_possible_cpus(). get_possible_cpus() reads /sys/devices/system/cpu/possible and parses the CPU list, identically to what libbpf_num_possible_cpus() does. Additionally, it checks that the possible cpu list is zero-based and has no holes by computing both the cpu count and the maximum cpu number. Systems where /sys/.../cpu/possible is unreadable, is not zero-based, or has holes, now report an error instead of crashing later due to unmet assumptions. Note that parse_cpu_set() cannot be used to parse /sys/devices/system/cpu/possible, because it only supports CPU count of 1024 and lower. Higher CPU counts are not fully supported by rtla; nr_cpus > 1024 is, though, so it has to be covered by the new implementation in order to avoid a regression. A new function, cpu_list_iterate(), is added to utils.c to contain the cpu list parsing logic, which is now shared between the new function get_possible_cpus() and the pre-existing parse_cpu_set(). Assisted-by: Claude:claude-opus-4-6 Signed-off-by: Tomas Glozar <[email protected]> --- tools/tracing/rtla/src/common.c | 16 +++- tools/tracing/rtla/src/utils.c | 165 +++++++++++++++++++++++++++----- tools/tracing/rtla/src/utils.h | 3 + 3 files changed, 156 insertions(+), 28 deletions(-) diff --git a/tools/tracing/rtla/src/common.c b/tools/tracing/rtla/src/common.c index 8c7f5e75b2ec8..20fae1f19cacf 100644 --- a/tools/tracing/rtla/src/common.c +++ b/tools/tracing/rtla/src/common.c @@ -6,7 +6,6 @@ #include <stdlib.h> #include <string.h> #include <unistd.h> -#include <sys/sysinfo.h> #include "common.h" @@ -155,7 +154,20 @@ int run_tool(struct tool_ops *ops, int argc, char *argv[]) bool stopped; int retval; - nr_cpus = get_nprocs_conf(); + nr_cpus = get_possible_cpus(); + if (nr_cpus == -1) { + err_msg("Could not read number of possible cpus\n"); + goto out_exit; + } + if (nr_cpus == -2) { + err_msg("Could not parse number of possible cpus\n"); + goto out_exit; + } + if (nr_cpus == -3) { + err_msg("Unsupported non-contiguous or non-zero-based CPU topology\n"); + goto out_exit; + } + params = ops->parse_args(argc, argv); if (!params) exit(1); diff --git a/tools/tracing/rtla/src/utils.c b/tools/tracing/rtla/src/utils.c index cb187e7d48d1c..d65de511be9e1 100644 --- a/tools/tracing/rtla/src/utils.c +++ b/tools/tracing/rtla/src/utils.c @@ -108,57 +108,170 @@ void get_duration(time_t start_time, char *output, int output_size) } /* - * parse_cpu_set - parse a cpu_list filling cpu_set_t argument + * cpu_list_iterate - parse a cpu list and call a function on each element * - * Receives a cpu list, like 1-3,5 (cpus 1, 2, 3, 5), and then set - * filling cpu_set_t argument. + * If callback returns a non-zero value, the iteration is stopped. * - * Returns 0 on success, 1 otherwise. + * Returns the number of cpus in the list (including duplicates) on success, + * callback return value on break, and -1 on error. */ -int parse_cpu_set(char *cpu_list, cpu_set_t *set) +int cpu_list_iterate(const char *cpu_list, int (*callback)(int, void *), void *data) { const char *p; - int end_cpu; - int cpu; - int i; - - CPU_ZERO(set); + int i, cpu, end_cpu, count = 0, retval; - for (p = cpu_list; *p; ) { + for (p = cpu_list; *p && *p != '\n'; ) { cpu = atoi(p); - if (cpu < 0 || (!cpu && *p != '0') || cpu >= nr_cpus) - goto err; + if (cpu < 0 || (!cpu && *p != '0')) + return -1; while (isdigit(*p)) p++; if (*p == '-') { p++; end_cpu = atoi(p); - if (end_cpu < cpu || (!end_cpu && *p != '0') || end_cpu >= nr_cpus) - goto err; + if (end_cpu < cpu || (!end_cpu && *p != '0')) + return -1; while (isdigit(*p)) p++; } else end_cpu = cpu; - if (cpu == end_cpu) { - debug_msg("cpu_set: adding cpu %d\n", cpu); - CPU_SET(cpu, set); - } else { - for (i = cpu; i <= end_cpu; i++) { - debug_msg("cpu_set: adding cpu %d\n", i); - CPU_SET(i, set); - } + for (i = cpu; i <= end_cpu; i++) { + retval = callback(i, data); + if (retval) + return retval; + ++count; } if (*p == ',') p++; } + return count; +} + +static int max_cpu_callback(int i, void *data) +{ + int *max_cpu = data; + + if (i > *max_cpu) + *max_cpu = i; + + return 0; +} + +static int tmp_cpu_set_callback(int i, void *data) +{ + bool *cpu_set = data; + + cpu_set[i] = true; + + return 0; +} + +/* + * get_possible_cpus - get the number of possible CPUs from sysfs + * + * Parse /sys/devices/system/cpu/possible to determine the number of + * possible CPUs. Only contiguous zero-based CPUs lists are accepted. + * + * Returns the number of possible CPUs, or a negative value on error: + * - -1 if the file is unreadable, + * - -2 if parsing failed, + * - -3 if the cpu list is non-zero-based or non-contiguous. + */ +int get_possible_cpus(void) +{ + char *str = NULL; + size_t len = 0; + int nr_cpus = 0, max_cpu = -1, i; + FILE *fp; + bool *cpu_set; + + fp = fopen("/sys/devices/system/cpu/possible", "r"); + if (!fp) + return -1; + + if (getline(&str, &len, fp) < 1) { + /* cpu string should be at least 1 character */ + if (str) + free(str); + fclose(fp); + return -1; + } + + fclose(fp); + + /* get maximum cpu number */ + if (cpu_list_iterate(str, max_cpu_callback, &max_cpu) < 0) { + free(str); + return -2; + } + + if (max_cpu < 0 || max_cpu == INT_MAX) { + /* empty or bogus cpu list */ + free(str); + return -2; + } + + /* get max cpu using dynamic array, as nr_cpus might be > 1024 */ + cpu_set = calloc(max_cpu + 1, sizeof(bool)); + if (!cpu_set) { + free(str); + return -2; + } + if (cpu_list_iterate(str, tmp_cpu_set_callback, cpu_set) < 0) { + free(str); + free(cpu_set); + return -2; + } + for (i = 0; i <= max_cpu; i++) { + if (cpu_set[i]) + ++nr_cpus; + } + free(cpu_set); + + free(str); + + if (max_cpu >= nr_cpus) + /* rtla assumes cpu < nr_cpus for all cpus */ + return -3; + + return nr_cpus; +} + +static int cpu_set_callback(int i, void *data) +{ + cpu_set_t *set = data; + + if (i >= nr_cpus || i >= CPU_SETSIZE) + return -1; + + debug_msg("cpu_set: adding cpu %d\n", i); + CPU_SET(i, set); + + return 0; +} + +/* + * parse_cpu_set - parse a cpu_list filling cpu_set_t argument + * + * Receives a cpu list, like 1-3,5 (cpus 1, 2, 3, 5), and then set + * filling cpu_set_t argument. + * + * Returns 0 on success, 1 otherwise. + */ +int parse_cpu_set(char *cpu_list, cpu_set_t *set) +{ + CPU_ZERO(set); + + if (cpu_list_iterate(cpu_list, cpu_set_callback, set) < 0) { + debug_msg("Error parsing the cpu set %s\n", cpu_list); + return 1; + } + return 0; -err: - debug_msg("Error parsing the cpu set %s\n", cpu_list); - return 1; } /* diff --git a/tools/tracing/rtla/src/utils.h b/tools/tracing/rtla/src/utils.h index 2ba3333669bb4..c26ba8827947a 100644 --- a/tools/tracing/rtla/src/utils.h +++ b/tools/tracing/rtla/src/utils.h @@ -47,6 +47,9 @@ void fatal(const char *fmt, ...); long parse_seconds_duration(char *val); void get_duration(time_t start_time, char *output, int output_size); +int cpu_list_iterate(const char *cpu_list, int (*callback)(int, void *), void *data); +int get_possible_cpus(void); + long long get_llong_from_str(char *start); static inline void -- 2.55.0
