]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
rtla/timerlat: Add --stack-format option
authorTomas Glozar <tglozar@redhat.com>
Mon, 19 Jan 2026 11:52:21 +0000 (12:52 +0100)
committerTomas Glozar <tglozar@redhat.com>
Wed, 4 Mar 2026 14:51:56 +0000 (15:51 +0100)
In the current implementation, the auto-analysis code for printing the
stack captured in the tracefs buffer of the aa instance stops at the
first encountered address that cannot be resolved into a function
symbol.

This is not always the desired behavior on all platforms; sometimes,
there might be resolvable entries after unresolvable ones, and
sometimes, the user might want to inspect the raw pointers for the
unresolvable entries.

Add a new option, --stack-format, with three values:

- truncate: stop at first unresolvable entry. This is the current
  behavior, and is kept as the default.
- skip: skip unresolvable entries, but do not stop on them.
- full: print all entries, including unresolvable ones.

To make this work, the "size" field of the stack entry is now also read
and used as the maximum number of entries to print, capped at 64, since
that is the fixed length of the "caller" field.

Link: https://lore.kernel.org/r/20260119115222.744150-1-tglozar@redhat.com
Signed-off-by: Tomas Glozar <tglozar@redhat.com>
tools/tracing/rtla/src/timerlat.c
tools/tracing/rtla/src/timerlat.h
tools/tracing/rtla/src/timerlat_aa.c
tools/tracing/rtla/src/timerlat_aa.h
tools/tracing/rtla/src/timerlat_hist.c
tools/tracing/rtla/src/timerlat_top.c
tools/tracing/rtla/src/utils.c
tools/tracing/rtla/src/utils.h

index 8f8811f7a13bda84bf4b52267f8f29a6808d3bb4..9e4daed0aafc659527f42bc5ec1893d5119601d1 100644 (file)
@@ -134,7 +134,7 @@ int timerlat_enable(struct osnoise_tool *tool)
                if (!tool->aa)
                        return -1;
 
-               retval = timerlat_aa_init(tool->aa, params->dump_tasks);
+               retval = timerlat_aa_init(tool->aa, params->dump_tasks, params->stack_format);
                if (retval) {
                        err_msg("Failed to enable the auto analysis instance\n");
                        return retval;
index 8dd5d134ce08ce5fd9af65f89b9ecf4be2d595e5..364203a29abd6e448edcd6a322cf3154a6a93341 100644 (file)
@@ -28,6 +28,7 @@ struct timerlat_params {
        int                     deepest_idle_state;
        enum timerlat_tracing_mode mode;
        const char              *bpf_action_program;
+       enum stack_format       stack_format;
 };
 
 #define to_timerlat_params(ptr) container_of(ptr, struct timerlat_params, common)
index 31e66ea2b144c24168c475b1eb39cb14aa6c9466..178de60dcef907f486a0e021b1ec109d1e32f667 100644 (file)
@@ -104,6 +104,7 @@ struct timerlat_aa_data {
 struct timerlat_aa_context {
        int nr_cpus;
        int dump_tasks;
+       enum stack_format stack_format;
 
        /* per CPU data */
        struct timerlat_aa_data *taa_data;
@@ -481,23 +482,43 @@ static int timerlat_aa_stack_handler(struct trace_seq *s, struct tep_record *rec
 {
        struct timerlat_aa_context *taa_ctx = timerlat_aa_get_ctx();
        struct timerlat_aa_data *taa_data = timerlat_aa_get_data(taa_ctx, record->cpu);
+       enum stack_format stack_format = taa_ctx->stack_format;
        unsigned long *caller;
        const char *function;
-       int val, i;
+       int val;
+       unsigned long long i;
 
        trace_seq_reset(taa_data->stack_seq);
 
        trace_seq_printf(taa_data->stack_seq, "    Blocking thread stack trace\n");
        caller = tep_get_field_raw(s, event, "caller", record, &val, 1);
+
        if (caller) {
-               for (i = 0; ; i++) {
+               unsigned long long size;
+               unsigned long long max_entries;
+
+               if (tep_get_field_val(s, event, "size", record, &size, 1) == 0)
+                       max_entries = size < 64 ? size : 64;
+               else
+                       max_entries = 64;
+
+               for (i = 0; i < max_entries; i++) {
                        function = tep_find_function(taa_ctx->tool->trace.tep, caller[i]);
-                       if (!function)
-                               break;
-                       trace_seq_printf(taa_data->stack_seq, " %.*s -> %s\n",
-                                        14, spaces, function);
+                       if (!function) {
+                               if (stack_format == STACK_FORMAT_TRUNCATE)
+                                       break;
+                               else if (stack_format == STACK_FORMAT_SKIP)
+                                       continue;
+                               else if (stack_format == STACK_FORMAT_FULL)
+                                       trace_seq_printf(taa_data->stack_seq, " %.*s -> 0x%lx\n",
+                                                    14, spaces, caller[i]);
+                       } else {
+                               trace_seq_printf(taa_data->stack_seq, " %.*s -> %s\n",
+                                                14, spaces, function);
+                       }
                }
        }
+
        return 0;
 }
 
@@ -1020,7 +1041,7 @@ out_ctx:
  *
  * Returns 0 on success, -1 otherwise.
  */
-int timerlat_aa_init(struct osnoise_tool *tool, int dump_tasks)
+int timerlat_aa_init(struct osnoise_tool *tool, int dump_tasks, enum stack_format stack_format)
 {
        int nr_cpus = sysconf(_SC_NPROCESSORS_CONF);
        struct timerlat_aa_context *taa_ctx;
@@ -1035,6 +1056,7 @@ int timerlat_aa_init(struct osnoise_tool *tool, int dump_tasks)
        taa_ctx->nr_cpus = nr_cpus;
        taa_ctx->tool = tool;
        taa_ctx->dump_tasks = dump_tasks;
+       taa_ctx->stack_format = stack_format;
 
        taa_ctx->taa_data = calloc(nr_cpus, sizeof(*taa_ctx->taa_data));
        if (!taa_ctx->taa_data)
index cea4bb1531a89d0b9daa9a6c680884d340caf33a..a11b5f30cdce8bca048635065a7e97795fa42b4a 100644 (file)
@@ -3,7 +3,7 @@
  * Copyright (C) 2023 Red Hat Inc, Daniel Bristot de Oliveira <bristot@kernel.org>
  */
 
-int timerlat_aa_init(struct osnoise_tool *tool, int dump_task);
+int timerlat_aa_init(struct osnoise_tool *tool, int dump_task, enum stack_format stack_format);
 void timerlat_aa_destroy(void);
 
 void timerlat_auto_analysis(int irq_thresh, int thread_thresh);
index 096de8ba3efbb6914884cb9bd2a3737b6eff084a..88211e54bc9d7652fa96539fe60080d447bcdb5d 100644 (file)
@@ -747,6 +747,7 @@ static void timerlat_hist_usage(void)
                "            --on-threshold <action>: define action to be executed at latency threshold, multiple are allowed",
                "            --on-end <action>: define action to be executed at measurement end, multiple are allowed",
                "            --bpf-action <program>: load and execute BPF program when latency threshold is exceeded",
+               "            --stack-format <format>: set the stack format (truncate, skip, full)",
                NULL,
        };
 
@@ -787,6 +788,9 @@ static struct common_params
        /* default to BPF mode */
        params->mode = TRACING_MODE_BPF;
 
+       /* default to truncate stack format */
+       params->stack_format = STACK_FORMAT_TRUNCATE;
+
        while (1) {
                static struct option long_options[] = {
                        {"auto",                required_argument,      0, 'a'},
@@ -819,6 +823,7 @@ static struct common_params
                        {"on-threshold",        required_argument,      0, '\5'},
                        {"on-end",              required_argument,      0, '\6'},
                        {"bpf-action",          required_argument,      0, '\7'},
+                       {"stack-format",        required_argument,      0, '\10'},
                        {0, 0, 0, 0}
                };
 
@@ -965,6 +970,11 @@ static struct common_params
                case '\7':
                        params->bpf_action_program = optarg;
                        break;
+               case '\10':
+                       params->stack_format = parse_stack_format(optarg);
+                       if (params->stack_format == -1)
+                               fatal("Invalid --stack-format option");
+                       break;
                default:
                        fatal("Invalid option");
                }
index 27c14aa71a8bc85ef30aa3719779dd6fa82f1f9e..7a00f3844f56ca59015a86af8760f22bee68f130 100644 (file)
@@ -518,6 +518,7 @@ static void timerlat_top_usage(void)
                "            --on-threshold <action>: define action to be executed at latency threshold, multiple are allowed",
                "            --on-end: define action to be executed at measurement end, multiple are allowed",
                "            --bpf-action <program>: load and execute BPF program when latency threshold is exceeded",
+               "            --stack-format <format>: set the stack format (truncate, skip, full)",
                NULL,
        };
 
@@ -556,6 +557,9 @@ static struct common_params
        /* default to BPF mode */
        params->mode = TRACING_MODE_BPF;
 
+       /* default to truncate stack format */
+       params->stack_format = STACK_FORMAT_TRUNCATE;
+
        while (1) {
                static struct option long_options[] = {
                        {"auto",                required_argument,      0, 'a'},
@@ -582,6 +586,7 @@ static struct common_params
                        {"on-threshold",        required_argument,      0, '9'},
                        {"on-end",              required_argument,      0, '\1'},
                        {"bpf-action",          required_argument,      0, '\2'},
+                       {"stack-format",        required_argument,      0, '\3'},
                        {0, 0, 0, 0}
                };
 
@@ -715,6 +720,11 @@ static struct common_params
                case '\2':
                        params->bpf_action_program = optarg;
                        break;
+               case '\3':
+                       params->stack_format = parse_stack_format(optarg);
+                       if (params->stack_format == -1)
+                               fatal("Invalid --stack-format option");
+                       break;
                default:
                        fatal("Invalid option");
                }
index 0da3b2470c31797613407f9e8f7ab4f5c4da3381..d979159f6b70f92aa189adeab85bd0d73d3fdffa 100644 (file)
@@ -164,6 +164,24 @@ err:
        return 1;
 }
 
+/*
+ * parse_stack_format - parse the stack format
+ *
+ * Return: the stack format on success, -1 otherwise.
+ */
+int parse_stack_format(char *arg)
+{
+       if (!strcmp(arg, "truncate"))
+               return STACK_FORMAT_TRUNCATE;
+       if (!strcmp(arg, "skip"))
+               return STACK_FORMAT_SKIP;
+       if (!strcmp(arg, "full"))
+               return STACK_FORMAT_FULL;
+
+       debug_msg("Error parsing the stack format %s\n", arg);
+       return -1;
+}
+
 /*
  * parse_duration - parse duration with s/m/h/d suffix converting it to seconds
  */
index f7c2a52a0ab546e951e625bbf358847987825d6b..80d5ec0cf93488c20e8ec3f9d257238585536338 100644 (file)
@@ -62,8 +62,15 @@ struct sched_attr {
 };
 #endif /* SCHED_ATTR_SIZE_VER0 */
 
+enum stack_format {
+       STACK_FORMAT_TRUNCATE,
+       STACK_FORMAT_SKIP,
+       STACK_FORMAT_FULL
+};
+
 int parse_prio(char *arg, struct sched_attr *sched_param);
 int parse_cpu_set(char *cpu_list, cpu_set_t *set);
+int parse_stack_format(char *arg);
 int __set_sched_attr(int pid, struct sched_attr *attr);
 int set_comm_sched_attr(const char *comm_prefix, struct sched_attr *attr);
 int set_comm_cgroup(const char *comm_prefix, const char *cgroup);