[PATCH 1/5] rtla: Replace get_nprocs_conf() with sysfs possible cpus

Tomas Glozar <[email protected]>
Newsgroups org.kernel.vger.linux-trace-kernel,org.kernel.vger.linux-kernel
Message-ID <[email protected]>
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
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.