* [PATCH 1/5] rtla: Replace get_nprocs_conf() with sysfs possible cpus
2026-08-14 13:55 [PATCH 0/5] rtla: Implement more robust nr_cpus handling Tomas Glozar
@ 2026-08-14 13:55 ` Tomas Glozar
2026-08-14 13:55 ` [PATCH 2/5] rtla: Discard trace entries with cpu >= nr_cpus Tomas Glozar
` (4 subsequent siblings)
5 siblings, 0 replies; 9+ messages in thread
From: Tomas Glozar @ 2026-08-14 13:55 UTC (permalink / raw)
To: Steven Rostedt, Tomas Glozar
Cc: John Kacur, Luis Goncalves, Crystal Wood, Costa Shulyupin,
Wander Lairson Costa, LKML, linux-trace-kernel
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 <tglozar@redhat.com>
---
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
^ permalink raw reply related [flat|nested] 9+ messages in thread* [PATCH 2/5] rtla: Discard trace entries with cpu >= nr_cpus
2026-08-14 13:55 [PATCH 0/5] rtla: Implement more robust nr_cpus handling Tomas Glozar
2026-08-14 13:55 ` [PATCH 1/5] rtla: Replace get_nprocs_conf() with sysfs possible cpus Tomas Glozar
@ 2026-08-14 13:55 ` Tomas Glozar
2026-08-14 14:02 ` sashiko-bot
2026-08-14 13:55 ` [PATCH 3/5] rtla: Abort on nr_cpus mismatch with tracer Tomas Glozar
` (3 subsequent siblings)
5 siblings, 1 reply; 9+ messages in thread
From: Tomas Glozar @ 2026-08-14 13:55 UTC (permalink / raw)
To: Steven Rostedt, Tomas Glozar
Cc: John Kacur, Luis Goncalves, Crystal Wood, Costa Shulyupin,
Wander Lairson Costa, LKML, linux-trace-kernel
The "cpu" field of trace entries processed by rtla in tracefs mode is
used as an index into an array of size nr_cpus. In case the cpu numbers
mismatch because either the kernel or rtla reporting the number
incorrectly, out-of-bounds read/write may occur.
Guard against this by dropping trace entries with cpu >= nr_cpus in
collect_registered_events(). A new counter, "invalid_events", is added
to struct trace_instance, and printed (if non zero) next to the
pre-existing missed events counter.
Signed-off-by: Tomas Glozar <tglozar@redhat.com>
---
tools/tracing/rtla/src/common.h | 2 --
tools/tracing/rtla/src/osnoise.c | 26 +++++++++++++++++++++++++-
tools/tracing/rtla/src/osnoise.h | 2 ++
tools/tracing/rtla/src/osnoise_hist.c | 1 +
tools/tracing/rtla/src/osnoise_top.c | 1 +
tools/tracing/rtla/src/timerlat_hist.c | 1 +
tools/tracing/rtla/src/timerlat_top.c | 1 +
tools/tracing/rtla/src/trace.c | 7 +++++++
tools/tracing/rtla/src/trace.h | 1 +
tools/tracing/rtla/src/utils.h | 2 ++
10 files changed, 41 insertions(+), 3 deletions(-)
diff --git a/tools/tracing/rtla/src/common.h b/tools/tracing/rtla/src/common.h
index 04b287a03f6d4..051d56182276b 100644
--- a/tools/tracing/rtla/src/common.h
+++ b/tools/tracing/rtla/src/common.h
@@ -114,8 +114,6 @@ struct common_params {
struct timerlat_u_params user;
};
-extern int nr_cpus;
-
#define for_each_monitored_cpu(cpu, common) \
for (cpu = 0; cpu < nr_cpus; cpu++) \
if (!(common)->cpus || CPU_ISSET(cpu, &(common)->monitored_cpus))
diff --git a/tools/tracing/rtla/src/osnoise.c b/tools/tracing/rtla/src/osnoise.c
index 4ff5dad013b10..b9bcbf9ee430c 100644
--- a/tools/tracing/rtla/src/osnoise.c
+++ b/tools/tracing/rtla/src/osnoise.c
@@ -1224,6 +1224,29 @@ bool osnoise_trace_is_off(struct osnoise_tool *tool, struct osnoise_tool *record
return record && !tracefs_trace_is_on(record->trace.inst);
}
+/*
+ * osnoise_report_invalid_events - report number of invalid events
+ */
+void
+osnoise_report_invalid_events(struct osnoise_tool *tool)
+{
+ unsigned long long total_events;
+
+ if (tool->trace.invalid_events > 0) {
+ if (tool->trace.missed_events != UINT64_MAX) {
+ total_events = tool->trace.processed_events + tool->trace.invalid_events +
+ tool->trace.missed_events;
+
+ printf("%lld (%.2f%%) invalid events, results might not be accurate\n",
+ tool->trace.invalid_events,
+ (double) tool->trace.invalid_events / total_events * 100.0);
+ } else {
+ printf("%lld invalid events, results might not be accurate\n",
+ tool->trace.invalid_events);
+ }
+ }
+}
+
/*
* osnoise_report_missed_events - report number of events dropped by trace
* buffer
@@ -1236,7 +1259,8 @@ osnoise_report_missed_events(struct osnoise_tool *tool)
if (tool->trace.missed_events == UINT64_MAX)
printf("unknown number of events missed, results might not be accurate\n");
else if (tool->trace.missed_events > 0) {
- total_events = tool->trace.processed_events + tool->trace.missed_events;
+ total_events = tool->trace.processed_events + tool->trace.invalid_events +
+ tool->trace.missed_events;
printf("%lld (%.2f%%) events missed, results might not be accurate\n",
tool->trace.missed_events,
diff --git a/tools/tracing/rtla/src/osnoise.h b/tools/tracing/rtla/src/osnoise.h
index 340ff5a64e6e4..b54e9ebef7f27 100644
--- a/tools/tracing/rtla/src/osnoise.h
+++ b/tools/tracing/rtla/src/osnoise.h
@@ -56,6 +56,8 @@ void osnoise_restore_timerlat_align_us(struct osnoise_context *context);
int osnoise_set_timerlat_align(struct osnoise_context *context, bool onoff);
int osnoise_set_irq_disable(struct osnoise_context *context, bool onoff);
+
+void osnoise_report_invalid_events(struct osnoise_tool *tool);
void osnoise_report_missed_events(struct osnoise_tool *tool);
int osnoise_apply_config(struct osnoise_tool *tool, struct osnoise_params *params);
diff --git a/tools/tracing/rtla/src/osnoise_hist.c b/tools/tracing/rtla/src/osnoise_hist.c
index dfa91d0681f8f..bad0b8958ddb2 100644
--- a/tools/tracing/rtla/src/osnoise_hist.c
+++ b/tools/tracing/rtla/src/osnoise_hist.c
@@ -397,6 +397,7 @@ osnoise_print_stats(struct osnoise_tool *tool)
trace_seq_reset(trace->seq);
osnoise_print_summary(params, trace, data);
+ osnoise_report_invalid_events(tool);
osnoise_report_missed_events(tool);
}
diff --git a/tools/tracing/rtla/src/osnoise_top.c b/tools/tracing/rtla/src/osnoise_top.c
index 512a6299cb018..3c0ff82a4b5c8 100644
--- a/tools/tracing/rtla/src/osnoise_top.c
+++ b/tools/tracing/rtla/src/osnoise_top.c
@@ -242,6 +242,7 @@ osnoise_print_stats(struct osnoise_tool *top)
trace_seq_do_printf(trace->seq);
trace_seq_reset(trace->seq);
+ osnoise_report_invalid_events(top);
osnoise_report_missed_events(top);
}
diff --git a/tools/tracing/rtla/src/timerlat_hist.c b/tools/tracing/rtla/src/timerlat_hist.c
index df7b1398a966d..b6af5ba11340d 100644
--- a/tools/tracing/rtla/src/timerlat_hist.c
+++ b/tools/tracing/rtla/src/timerlat_hist.c
@@ -682,6 +682,7 @@ timerlat_print_stats(struct osnoise_tool *tool)
timerlat_print_summary(params, trace, data);
timerlat_print_stats_all(params, trace, data);
+ osnoise_report_invalid_events(tool);
osnoise_report_missed_events(tool);
}
diff --git a/tools/tracing/rtla/src/timerlat_top.c b/tools/tracing/rtla/src/timerlat_top.c
index 6206a0a565ad3..2afd619c16059 100644
--- a/tools/tracing/rtla/src/timerlat_top.c
+++ b/tools/tracing/rtla/src/timerlat_top.c
@@ -456,6 +456,7 @@ timerlat_print_stats(struct osnoise_tool *top)
trace_seq_do_printf(trace->seq);
trace_seq_reset(trace->seq);
+ osnoise_report_invalid_events(top);
osnoise_report_missed_events(top);
}
diff --git a/tools/tracing/rtla/src/trace.c b/tools/tracing/rtla/src/trace.c
index e407447773d04..1c3e2b098ba81 100644
--- a/tools/tracing/rtla/src/trace.c
+++ b/tools/tracing/rtla/src/trace.c
@@ -138,6 +138,12 @@ collect_registered_events(struct tep_event *event, struct tep_record *record,
struct trace_instance *trace = context;
struct trace_seq *s = trace->seq;
+ if (cpu >= nr_cpus) {
+ /* Kernel reports event on CPU we don't see, corrupt data? */
+ trace->invalid_events++;
+ return 0;
+ }
+
trace->processed_events++;
if (!event->handler)
@@ -236,6 +242,7 @@ int trace_instance_init(struct trace_instance *trace, char *tool_name)
trace);
trace->processed_events = 0;
+ trace->invalid_events = 0;
return 0;
diff --git a/tools/tracing/rtla/src/trace.h b/tools/tracing/rtla/src/trace.h
index 95b911a2228b2..715a3616fe45a 100644
--- a/tools/tracing/rtla/src/trace.h
+++ b/tools/tracing/rtla/src/trace.h
@@ -18,6 +18,7 @@ struct trace_instance {
struct tep_handle *tep;
struct trace_seq *seq;
unsigned long long missed_events;
+ unsigned long long invalid_events;
unsigned long long processed_events;
};
diff --git a/tools/tracing/rtla/src/utils.h b/tools/tracing/rtla/src/utils.h
index c26ba8827947a..2579e7fa08be6 100644
--- a/tools/tracing/rtla/src/utils.h
+++ b/tools/tracing/rtla/src/utils.h
@@ -40,6 +40,8 @@ static inline bool str_has_prefix(const char *str, const char *prefix)
}
extern bool config_debug;
+extern int nr_cpus;
+
void debug_msg(const char *fmt, ...);
void err_msg(const char *fmt, ...);
void fatal(const char *fmt, ...);
--
2.55.0
^ permalink raw reply related [flat|nested] 9+ messages in thread* Re: [PATCH 2/5] rtla: Discard trace entries with cpu >= nr_cpus
2026-08-14 13:55 ` [PATCH 2/5] rtla: Discard trace entries with cpu >= nr_cpus Tomas Glozar
@ 2026-08-14 14:02 ` sashiko-bot
0 siblings, 0 replies; 9+ messages in thread
From: sashiko-bot @ 2026-08-14 14:02 UTC (permalink / raw)
To: Tomas Glozar; +Cc: linux-trace-kernel
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] The printf format strings incorrectly use `%lld` to print `unsigned long long` variables.
--
--- Patch [2]: [PATCH 2/5] rtla: Discard trace entries with cpu >= nr_cpus ---
commit 2e2ceb9161355aea34f26cdf4de0da20e2aa2e3d
Author: Tomas Glozar <tglozar@redhat.com>
rtla: Discard trace entries with cpu >= nr_cpus
This commit adds a bounds check to rtla to discard trace entries when the
CPU index exceeds the known number of CPUs. It introduces an invalid_events
counter and updates the tool's output to report when such events occur.
> diff --git a/tools/tracing/rtla/src/osnoise.c b/tools/tracing/rtla/src/osnoise.c
> index 4ff5dad013b10..b9bcbf9ee430c 100644
> --- a/tools/tracing/rtla/src/osnoise.c
> +++ b/tools/tracing/rtla/src/osnoise.c
[ ... ]
> +void
> +osnoise_report_invalid_events(struct osnoise_tool *tool)
> +{
> + unsigned long long total_events;
> +
> + if (tool->trace.invalid_events > 0) {
> + if (tool->trace.missed_events != UINT64_MAX) {
> + total_events = tool->trace.processed_events + tool->trace.invalid_events +
> + tool->trace.missed_events;
> +
> + printf("%lld (%.2f%%) invalid events, results might not be accurate\n",
> + tool->trace.invalid_events,
> + (double) tool->trace.invalid_events / total_events * 100.0);
[Severity: Low]
Since invalid_events is declared as an unsigned long long in struct
trace_instance, should this printf format string use %llu instead of %lld?
If the value were to somehow exceed LLONG_MAX, it would print as a negative
number.
> + } else {
> + printf("%lld invalid events, results might not be accurate\n",
> + tool->trace.invalid_events);
[Severity: Low]
Does this format string also need to be updated to %llu to match the
unsigned long long type of invalid_events?
> + }
> + }
> +}
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260814135511.2207190-1-tglozar@redhat.com?part=2
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH 3/5] rtla: Abort on nr_cpus mismatch with tracer
2026-08-14 13:55 [PATCH 0/5] rtla: Implement more robust nr_cpus handling Tomas Glozar
2026-08-14 13:55 ` [PATCH 1/5] rtla: Replace get_nprocs_conf() with sysfs possible cpus Tomas Glozar
2026-08-14 13:55 ` [PATCH 2/5] rtla: Discard trace entries with cpu >= nr_cpus Tomas Glozar
@ 2026-08-14 13:55 ` Tomas Glozar
2026-08-14 13:55 ` [PATCH 4/5] rtla/tests: Add unit test for cpu_list_iterate() Tomas Glozar
` (2 subsequent siblings)
5 siblings, 0 replies; 9+ messages in thread
From: Tomas Glozar @ 2026-08-14 13:55 UTC (permalink / raw)
To: Steven Rostedt, Tomas Glozar
Cc: John Kacur, Luis Goncalves, Crystal Wood, Costa Shulyupin,
Wander Lairson Costa, LKML, linux-trace-kernel
In rare situations, sysfs might report a different number of CPUs than
the kernel sees, for example, when /sys is not real sysfs.
In tracefs mode, RTLA will discard any samples that are reported on CPUs
higher than the maximum in sysfs. However, in BPF mode, the incorrectly
sized BPF_MAP_LOOKUP_ELEM syscall on percpu maps will cause a buffer
overflow.
To cover for this corner case, write "all" to osnoise/cpus, and read
back the real kernel-side list of all possible cpus. If it doesn't match
RTLA's nr_cpus (read from sysfs), abort with an error.
Assisted-by: Claude:claude-opus-4-6
Signed-off-by: Tomas Glozar <tglozar@redhat.com>
---
tools/tracing/rtla/src/common.c | 13 +++++++++-
tools/tracing/rtla/src/common.h | 1 +
tools/tracing/rtla/src/osnoise.c | 44 ++++++++++++++++++++++++++++++++
tools/tracing/rtla/src/utils.c | 16 ++++++++++++
tools/tracing/rtla/src/utils.h | 1 +
5 files changed, 74 insertions(+), 1 deletion(-)
diff --git a/tools/tracing/rtla/src/common.c b/tools/tracing/rtla/src/common.c
index 20fae1f19cacf..2a5333270e035 100644
--- a/tools/tracing/rtla/src/common.c
+++ b/tools/tracing/rtla/src/common.c
@@ -62,11 +62,22 @@ static void unset_signals(struct common_params *params)
int
common_apply_config(struct osnoise_tool *tool, struct common_params *params)
{
- int retval, i;
+ int retval, i, tracer_cpus;
if (!params->sleep_time)
params->sleep_time = 1;
+ tracer_cpus = osnoise_get_cpu_count();
+ if (tracer_cpus < 0) {
+ err_msg("Failed to get tracer cpu count from osnoise/cpus\n");
+ goto out_err;
+ }
+ if (tracer_cpus != nr_cpus) {
+ err_msg("Kernel reports %d cpus but rtla sees %d\n",
+ tracer_cpus, nr_cpus);
+ goto out_err;
+ }
+
retval = osnoise_set_cpus(tool->context, params->cpus ? params->cpus : "all");
if (retval) {
err_msg("Failed to apply CPUs config\n");
diff --git a/tools/tracing/rtla/src/common.h b/tools/tracing/rtla/src/common.h
index 051d56182276b..a7a9063c302b5 100644
--- a/tools/tracing/rtla/src/common.h
+++ b/tools/tracing/rtla/src/common.h
@@ -168,6 +168,7 @@ should_continue_tracing(const struct common_params *params)
int
common_threshold_handler(const struct osnoise_tool *tool);
+int osnoise_get_cpu_count(void);
int osnoise_set_cpus(struct osnoise_context *context, char *cpus);
void osnoise_restore_cpus(struct osnoise_context *context);
diff --git a/tools/tracing/rtla/src/osnoise.c b/tools/tracing/rtla/src/osnoise.c
index b9bcbf9ee430c..5a746bc11f03b 100644
--- a/tools/tracing/rtla/src/osnoise.c
+++ b/tools/tracing/rtla/src/osnoise.c
@@ -124,6 +124,50 @@ void osnoise_put_cpus(struct osnoise_context *context)
context->orig_cpus = NULL;
}
+/*
+ * osnoise_get_cpu_count - get the number of CPUs as seen by the osnoise tracer
+ *
+ * Write "all" to osnoise/cpus and read back the real kernel-side list
+ * of all possible cpus. Restore the original value afterwards.
+ *
+ * Returns the number of CPUs, or -1 on error.
+ */
+int osnoise_get_cpu_count(void)
+{
+ char *orig, *readback;
+ int max_cpu;
+
+ orig = tracefs_instance_file_read(NULL, "osnoise/cpus", NULL);
+ if (!orig)
+ return -1;
+
+ if (tracefs_instance_file_write(NULL, "osnoise/cpus", "all\n") < 0) {
+ free(orig);
+ return -1;
+ }
+
+ readback = tracefs_instance_file_read(NULL, "osnoise/cpus", NULL);
+ if (!readback) {
+ tracefs_instance_file_write(NULL, "osnoise/cpus", orig);
+ free(orig);
+ return -1;
+ }
+
+ max_cpu = get_max_cpu_from_list(readback);
+ free(readback);
+
+ if (tracefs_instance_file_write(NULL, "osnoise/cpus", orig) < 0) {
+ free(orig);
+ return -1;
+ }
+ free(orig);
+
+ if (max_cpu < 0)
+ return -1;
+
+ return max_cpu + 1;
+}
+
/*
* osnoise_read_ll_config - read a long long value from a config
*
diff --git a/tools/tracing/rtla/src/utils.c b/tools/tracing/rtla/src/utils.c
index d65de511be9e1..5d91c9151aa4a 100644
--- a/tools/tracing/rtla/src/utils.c
+++ b/tools/tracing/rtla/src/utils.c
@@ -161,6 +161,22 @@ static int max_cpu_callback(int i, void *data)
return 0;
}
+/*
+ * get_max_cpu_from_list - get the maximum CPU in a CPU list
+ *
+ * Returns maximum CPU number, or -1 on error
+ */
+int get_max_cpu_from_list(const char *cpu_list)
+{
+ int max_cpu = -1;
+
+ if (cpu_list_iterate(cpu_list, max_cpu_callback, &max_cpu) < 0)
+ return -1;
+
+ return max_cpu;
+}
+
+
static int tmp_cpu_set_callback(int i, void *data)
{
bool *cpu_set = data;
diff --git a/tools/tracing/rtla/src/utils.h b/tools/tracing/rtla/src/utils.h
index 2579e7fa08be6..44b450645ffb5 100644
--- a/tools/tracing/rtla/src/utils.h
+++ b/tools/tracing/rtla/src/utils.h
@@ -50,6 +50,7 @@ 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_max_cpu_from_list(const char *cpu_list);
int get_possible_cpus(void);
long long get_llong_from_str(char *start);
--
2.55.0
^ permalink raw reply related [flat|nested] 9+ messages in thread* [PATCH 4/5] rtla/tests: Add unit test for cpu_list_iterate()
2026-08-14 13:55 [PATCH 0/5] rtla: Implement more robust nr_cpus handling Tomas Glozar
` (2 preceding siblings ...)
2026-08-14 13:55 ` [PATCH 3/5] rtla: Abort on nr_cpus mismatch with tracer Tomas Glozar
@ 2026-08-14 13:55 ` Tomas Glozar
2026-08-14 13:55 ` [PATCH 5/5] rtla/tests: Add unit test for get_max_cpu_from_list() Tomas Glozar
2026-08-14 14:30 ` [PATCH 0/5] rtla: Implement more robust nr_cpus handling Steven Rostedt
5 siblings, 0 replies; 9+ messages in thread
From: Tomas Glozar @ 2026-08-14 13:55 UTC (permalink / raw)
To: Steven Rostedt, Tomas Glozar
Cc: John Kacur, Luis Goncalves, Crystal Wood, Costa Shulyupin,
Wander Lairson Costa, LKML, linux-trace-kernel
cpu_list_iterate() was split out of parse_cpu_set() to hold shared code
between it and the newly added function get_possible_cpus().
As its semantics are more complex than parse_cpu_set() - it calls a
callback on each element of the list, with possible abort on failure,
while parse_cpu_set() cares only about the set defined by the list - it
deserves its own test.
Test the callback being called correctly as well as the return value and
early break on different combinations of comma-separated numbers and
ranges.
Signed-off-by: Tomas Glozar <tglozar@redhat.com>
---
tools/tracing/rtla/tests/unit/utils.c | 62 +++++++++++++++++++++++++++
1 file changed, 62 insertions(+)
diff --git a/tools/tracing/rtla/tests/unit/utils.c b/tools/tracing/rtla/tests/unit/utils.c
index ce53cab494575..0cb5e217c56eb 100644
--- a/tools/tracing/rtla/tests/unit/utils.c
+++ b/tools/tracing/rtla/tests/unit/utils.c
@@ -34,6 +34,67 @@ START_TEST(test_strtoi)
}
END_TEST
+struct cpu_list_iterate_cb_data {
+ int index;
+ int *values;
+};
+
+static int cpu_list_iterate_callback(int cpu, void *data)
+{
+ struct cpu_list_iterate_cb_data *cb_data = data;
+
+ ck_assert_int_eq(cpu, cb_data->values[cb_data->index++]);
+
+ return 0;
+}
+
+static int cpu_list_iterate_callback_error(int cpu, void *data)
+{
+ struct cpu_list_iterate_cb_data *cb_data = data;
+
+ if (cpu > 10)
+ return -42;
+
+ ck_assert_int_eq(cpu, cb_data->values[cb_data->index++]);
+
+ return 0;
+}
+
+START_TEST(test_cpu_list_iterate)
+{
+ struct cpu_list_iterate_cb_data cb_data;
+ int test_data_1[] = {1, 2, 3, 4};
+ int test_data_2[] = {1, 2, 10, 11, 12};
+
+ cb_data.index = 0;
+
+ cb_data.values = test_data_1;
+ ck_assert_int_eq(cpu_list_iterate("1,2,3,4", cpu_list_iterate_callback, &cb_data), 4);
+ ck_assert_int_eq(cb_data.index, 4);
+ cb_data.index = 0;
+ ck_assert_int_eq(cpu_list_iterate("1-4", cpu_list_iterate_callback, &cb_data), 4);
+ ck_assert_int_eq(cb_data.index, 4);
+ cb_data.index = 0;
+ ck_assert_int_eq(cpu_list_iterate("1,2-3,4", cpu_list_iterate_callback, &cb_data), 4);
+ ck_assert_int_eq(cb_data.index, 4);
+ cb_data.index = 0;
+ ck_assert_int_eq(cpu_list_iterate("1-3,4", cpu_list_iterate_callback, &cb_data), 4);
+ ck_assert_int_eq(cb_data.index, 4);
+ cb_data.index = 0;
+ ck_assert_int_eq(cpu_list_iterate("1,2-4", cpu_list_iterate_callback, &cb_data), 4);
+ ck_assert_int_eq(cb_data.index, 4);
+
+ cb_data.index = 0;
+ ck_assert_int_eq(cpu_list_iterate("1,2-4", cpu_list_iterate_callback_error, &cb_data), 4);
+ ck_assert_int_eq(cb_data.index, 4);
+ cb_data.index = 0;
+ cb_data.values = test_data_2;
+ ck_assert_int_eq(cpu_list_iterate("1,2,10-12", cpu_list_iterate_callback_error, &cb_data),
+ -42);
+ ck_assert_int_eq(cb_data.index, 3);
+}
+END_TEST
+
START_TEST(test_parse_cpu_set)
{
cpu_set_t set;
@@ -98,6 +159,7 @@ Suite *utils_suite(void)
TCase *tc = tcase_create("core");
tcase_add_test(tc, test_strtoi);
+ tcase_add_test(tc, test_cpu_list_iterate);
tcase_add_test(tc, test_parse_cpu_set);
tcase_add_test(tc, test_parse_prio);
--
2.55.0
^ permalink raw reply related [flat|nested] 9+ messages in thread* [PATCH 5/5] rtla/tests: Add unit test for get_max_cpu_from_list()
2026-08-14 13:55 [PATCH 0/5] rtla: Implement more robust nr_cpus handling Tomas Glozar
` (3 preceding siblings ...)
2026-08-14 13:55 ` [PATCH 4/5] rtla/tests: Add unit test for cpu_list_iterate() Tomas Glozar
@ 2026-08-14 13:55 ` Tomas Glozar
2026-08-14 14:30 ` [PATCH 0/5] rtla: Implement more robust nr_cpus handling Steven Rostedt
5 siblings, 0 replies; 9+ messages in thread
From: Tomas Glozar @ 2026-08-14 13:55 UTC (permalink / raw)
To: Steven Rostedt, Tomas Glozar
Cc: John Kacur, Luis Goncalves, Crystal Wood, Costa Shulyupin,
Wander Lairson Costa, LKML, linux-trace-kernel
The get_max_cpu_from_list() function is used when parsing osnoise cpus
"all" value. Furthermore, the callback used by it is also used in
get_possible_cpus().
Add unit test to cover it.
Signed-off-by: Tomas Glozar <tglozar@redhat.com>
---
tools/tracing/rtla/tests/unit/utils.c | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/tools/tracing/rtla/tests/unit/utils.c b/tools/tracing/rtla/tests/unit/utils.c
index 0cb5e217c56eb..cdd84ff895f86 100644
--- a/tools/tracing/rtla/tests/unit/utils.c
+++ b/tools/tracing/rtla/tests/unit/utils.c
@@ -95,6 +95,14 @@ START_TEST(test_cpu_list_iterate)
}
END_TEST
+START_TEST(test_get_max_cpu_from_list)
+{
+ ck_assert_int_eq(get_max_cpu_from_list("1,2,3,4-60"), 60);
+ ck_assert_int_eq(get_max_cpu_from_list("2,1-7,4"), 7);
+ ck_assert_int_eq(get_max_cpu_from_list("invalid"), -1);
+}
+END_TEST
+
START_TEST(test_parse_cpu_set)
{
cpu_set_t set;
@@ -160,6 +168,7 @@ Suite *utils_suite(void)
tcase_add_test(tc, test_strtoi);
tcase_add_test(tc, test_cpu_list_iterate);
+ tcase_add_test(tc, test_get_max_cpu_from_list);
tcase_add_test(tc, test_parse_cpu_set);
tcase_add_test(tc, test_parse_prio);
--
2.55.0
^ permalink raw reply related [flat|nested] 9+ messages in thread* Re: [PATCH 0/5] rtla: Implement more robust nr_cpus handling
2026-08-14 13:55 [PATCH 0/5] rtla: Implement more robust nr_cpus handling Tomas Glozar
` (4 preceding siblings ...)
2026-08-14 13:55 ` [PATCH 5/5] rtla/tests: Add unit test for get_max_cpu_from_list() Tomas Glozar
@ 2026-08-14 14:30 ` Steven Rostedt
2026-08-14 17:49 ` Tomas Glozar
5 siblings, 1 reply; 9+ messages in thread
From: Steven Rostedt @ 2026-08-14 14:30 UTC (permalink / raw)
To: Tomas Glozar
Cc: John Kacur, Luis Goncalves, Crystal Wood, Costa Shulyupin,
Wander Lairson Costa, LKML, linux-trace-kernel
On Fri, 14 Aug 2026 15:55:06 +0200
Tomas Glozar <tglozar@redhat.com> wrote:
> RTLA is an interface to the kernel osnoise and timerlat tracers, and as
> such, operates on all CPUs seen by the kernel, regardless of any applied
> user space CPU isolation. Currently, it uses libc's get_nprocs_conf()
> function to get the total number of valid (configured) CPUs on the
> system.
>
> The number reported by the function may vary depending on both the
> libc implementation and the environment. This can cause discrepancies,
> including:
>
> - RTLA crashing because of out-of-bounds access and/or memory
> corruption, as a result of reading kernel events with cpu >= nr_cpus
> and using it to index an nr_cpus-length buffer.
> - nr_cpus mismatching the return value of libbpf_num_possible_cpus(),
> which is used in the per-CPU map size check, leading RTLA to fail to
> read BPF data.
>
> To address both issues, this patchset switches RTLA to unconditionally
> use the CPU count exposed through sysfs
> (/sys/devices/system/cpu/possible) - the same interface through which
> the tracers are accessed. As several places in RTLA assume the CPU count
> correlates with the maximum CPU (pre-existing limitation), the new
> mechanism also rejects possible CPU lists with holes or starting with
> non-zero CPU.
>
> As a safety measure, trace events are further validated and rejected
> if their cpu field is greater or equal to nr_cpus. nr_cpus is
> additionally validated for equality against osnoise's "all" cpu setting.
> This is done to catch environments where sysfs is virtualized and
> reports a different number of CPUs than the kernel.
>
> Note: As an alternative approach - reading only the osnoise cpu "all"
> setting and using it for nr_cpus - was considered. While it would make
> patch 3 unnecessary and make RTLA compatible with environments with
> virtualized sysfs, it would require separate handling of userspace and
> kernel CPU number, as nr_cpus is also used to guard -H/--housekeeping
> option argument. That does not appear to me to be worth it just to cover
> for a very rare corner case; if such use case appears in the future, RTLA
> can always be fixed.
>
> Tomas Glozar (5):
> rtla: Replace get_nprocs_conf() with sysfs possible cpus
> rtla: Discard trace entries with cpu >= nr_cpus
> rtla: Abort on nr_cpus mismatch with tracer
> rtla/tests: Add unit test for cpu_list_iterate()
> rtla/tests: Add unit test for get_max_cpu_from_list()
>
> tools/tracing/rtla/src/common.c | 29 +++-
> tools/tracing/rtla/src/common.h | 3 +-
> tools/tracing/rtla/src/osnoise.c | 70 +++++++++-
> tools/tracing/rtla/src/osnoise.h | 2 +
> tools/tracing/rtla/src/osnoise_hist.c | 1 +
> tools/tracing/rtla/src/osnoise_top.c | 1 +
> tools/tracing/rtla/src/timerlat_hist.c | 1 +
> tools/tracing/rtla/src/timerlat_top.c | 1 +
> tools/tracing/rtla/src/trace.c | 7 +
> tools/tracing/rtla/src/trace.h | 1 +
> tools/tracing/rtla/src/utils.c | 181 +++++++++++++++++++++----
> tools/tracing/rtla/src/utils.h | 6 +
> tools/tracing/rtla/tests/unit/utils.c | 71 ++++++++++
> 13 files changed, 342 insertions(+), 32 deletions(-)
>
Note, the merge window will likely open next week (Sunday) (and of course,
I'll be traveling). If you want this in the next release, please send a pull
request today. Otherwise it will likely not be able to go into Linus's tree
until 7.4.
-- Steve
^ permalink raw reply [flat|nested] 9+ messages in thread* Re: [PATCH 0/5] rtla: Implement more robust nr_cpus handling
2026-08-14 14:30 ` [PATCH 0/5] rtla: Implement more robust nr_cpus handling Steven Rostedt
@ 2026-08-14 17:49 ` Tomas Glozar
0 siblings, 0 replies; 9+ messages in thread
From: Tomas Glozar @ 2026-08-14 17:49 UTC (permalink / raw)
To: Steven Rostedt
Cc: John Kacur, Luis Goncalves, Crystal Wood, Costa Shulyupin,
Wander Lairson Costa, LKML, linux-trace-kernel
pá 14. 8. 2026 v 16:30 odesílatel Steven Rostedt <rostedt@goodmis.org> napsal:
>
> Note, the merge window will likely open next week (Sunday) (and of course,
> I'll be traveling). If you want this in the next release, please send a pull
> request today. Otherwise it will likely not be able to go into Linus's tree
> until 7.4.
>
> -- Steve
>
Thanks for the note. This is targeted for 7.4, I generally like for
patches to sit on the mailing list for at least a few days to let
people give feedback, unless they are trivial and/or urgent. This
patchset is neither.
I will try to leave a note next time when sending a patchset for the
next cycle late in the previous one, so that you don't have to worry
about it.
Tomas
^ permalink raw reply [flat|nested] 9+ messages in thread