rtla: Replace get_nprocs_conf() with sysfs possible cpus

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
Link: https://lore.kernel.org/r/20260814135511.2207190-2-tglozar@redhat.com
Signed-off-by: Tomas Glozar <tglozar@redhat.com>
This commit is contained in:
Tomas Glozar 2026-08-14 15:55:07 +02:00
parent c6bafe09bf
commit b3dc9905d7
3 changed files with 167 additions and 39 deletions

View File

@ -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);

View File

@ -107,6 +107,153 @@ void get_duration(time_t start_time, char *output, int output_size)
tm_info->tm_sec);
}
/*
* cpu_list_iterate - parse a cpu list and call a function on each element
*
* If callback returns a non-zero value, the iteration is stopped.
*
* Returns the number of cpus in the list (including duplicates) on success,
* callback return value on break, and -1 on error.
*/
int cpu_list_iterate(const char *cpu_list, int (*callback)(int, void *), void *data)
{
const char *p;
int i, cpu, end_cpu, count = 0, retval;
for (p = cpu_list; *p && *p != '\n'; ) {
cpu = atoi(p);
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'))
return -1;
while (isdigit(*p))
p++;
} else
end_cpu = cpu;
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
*
@ -117,48 +264,14 @@ void get_duration(time_t start_time, char *output, int output_size)
*/
int parse_cpu_set(char *cpu_list, cpu_set_t *set)
{
const char *p;
int end_cpu;
int cpu;
int i;
CPU_ZERO(set);
for (p = cpu_list; *p; ) {
cpu = atoi(p);
if (cpu < 0 || (!cpu && *p != '0') || cpu >= nr_cpus)
goto err;
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;
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);
}
}
if (*p == ',')
p++;
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;
}
/*

View File

@ -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