st 17. 6. 2026 v 15:18 odesílatel Valentin Schneider
<[email protected]> napsal:
>
> Leverage the ipi_send_cpu and ipi_send_cpumask trace events to record the
> count of IPIs sent to monitored CPUs. These interferences are already
> accounted by the IRQ count, but this split gives a better overall picture.
>
> This uses the newly added -i cmdline option.
>
> Signed-off-by: Valentin Schneider <[email protected]>
> ---
>  tools/tracing/rtla/src/osnoise_top.c | 124 ++++++++++++++++++++++++++-
>  1 file changed, 123 insertions(+), 1 deletion(-)
>

Overall, looks good to me (see small comments below). The reported
numbers make sense:

[tglozar@cs9 rtla]$ sudo ./rtla osnoise top -q -c 0,1 -d 5s --ipi
                                         Operating System Noise
duration:   0 00:00:05 | time is in us
CPU Period       Runtime        Noise  % CPU Aval   Max Noise   Max
Single          HW          NMI          IRQ      Softirq       Thread
         IPI
 0 #4           4000000        28481    99.28797        8977
248        6756            0         4002           18            1
       42
 1 #5           5000000        38025    99.23950        8120
185        8403            0         5260          153          141
       49

(It looks good in the terminal, I'm sure Gmail will garble it...)

I'll compare with trace output on the next patch.

> diff --git a/tools/tracing/rtla/src/osnoise_top.c 
> b/tools/tracing/rtla/src/osnoise_top.c
> index 512a6299cb018..5b462a3543b97 100644
> --- a/tools/tracing/rtla/src/osnoise_top.c
> +++ b/tools/tracing/rtla/src/osnoise_top.c
>
> [truncated]
>
> @@ -70,6 +72,91 @@ static struct osnoise_top_data *osnoise_alloc_top(void)
>  return NULL;
>  }
> +static void account_ipi(struct osnoise_tool *tool,
> +                       unsigned long long src_cpu, unsigned long long 
> dst_cpu)
> +{
> +       struct osnoise_top_cpu *cpu_data;
> +       struct osnoise_top_data *data;
> +       unsigned long long inc = 1;
> +
> +       data = tool->data;
> +       cpu_data = &data->cpu_data[dst_cpu];
> +
> +       update_sum(&cpu_data->ipi_count, &inc);
> +}
> +
> +/*
> + * osnoise_ipi_cpu_handler - this is the handler for single CPU IPI events.
> + */
> +static int
> +osnoise_ipi_cpu_handler(struct trace_seq *s, struct tep_record *record,
> +                    struct tep_event *event, void *context)
> +{
> +       struct osnoise_tool *tool;
> +       struct osnoise_params *params;
> +       unsigned long long src_cpu, dst_cpu;
> +       struct trace_instance *trace = context;
> +
> +       tool = container_of(trace, struct osnoise_tool, trace);
> +       params = to_osnoise_params(tool->params);
> +
> +       src_cpu = record->cpu;
> +       tep_get_field_val(s, event, "cpu", record, &dst_cpu, 1);
> +
> +       if (CPU_ISSET(dst_cpu, &params->common.monitored_cpus))
> +               account_ipi(tool, src_cpu, dst_cpu);

Do we need to retrieve and pass the src_cpu here? I get it if you plan
on using it in the future, but as far as I understand, you are
specifically tracking the destination CPU, not the source CPU. Same
note applies to osnoise_ipi_cpumask_handler() below.

> +
> +       return 0;
> +}
> +
> +static cpu_set_t cpumask_tmp_cpus;
> +
> +/*
> + * osnoise_ipi_cpumask_handler - this is the handler for broadcasted IPI 
> events.
> + */
> +static int
> +osnoise_ipi_cpumask_handler(struct trace_seq *s, struct tep_record *record,
> +                        struct tep_event *event, void *context)
> +{
> +       struct trace_instance *trace = context;
> +       struct osnoise_tool *tool;
> +       struct osnoise_params *params;
> +       struct tep_format_field *field;
> +       unsigned long long src_cpu;
> +       cpu_set_t *event_cpus;
> +       int len;
> +
> +       tool = container_of(trace, struct osnoise_tool, trace);
> +       params = to_osnoise_params(tool->params);
> +
> +       src_cpu = record->cpu;
> +
> +       field = tep_find_field(event, "cpumask");
> +       if (!field)
> +               return 0;
> +
> +       event_cpus = tep_get_field_raw(s, event, "cpumask", record, &len, 1);
> +       if (!event_cpus) {
> +               err_msg("Failed to get cpumask field\n");
> +               return 0;
> +       }
> +
> +       CPU_AND(&cpumask_tmp_cpus, event_cpus, 
> &params->common.monitored_cpus);
> +
> +       /*
> +        * Computing the mask weight is overkill but there is no leaner option
> +        * provided by glibc, e.g cpumask_first() or somesuch.
> +        */
> +       if (CPU_COUNT(&cpumask_tmp_cpus)) {
> +               for (int cpu = 0; cpu < nr_cpus; cpu++) {
> +                       if (CPU_ISSET(cpu, &cpumask_tmp_cpus))
> +                               account_ipi(tool, src_cpu, cpu);
> +               }
> +       }

Technically, the existing code already relies on the glibc cpumask
implementation (cpu_set_t) matching the kernel "cpumask_t" type, as
the "cpumask" field is the latter (per
/sys/kernel/tracing/events/ipi/ipi_send_cpumask/format), not the
former. So I wouldn't worry about the opaqueness of cpu_set_t much.

Not sure how this is handled in other tracing tools that need to use
cpumask, I'd have to look around a bit. It might even make sense to
have a "tools" version of the cpumask functions like cpumask_first(),
I guess, like we already do for e.g. lists and container_of.

> +
> +       return 0;
> +}
> +
>  /*
>   * osnoise_top_handler - this is the handler for osnoise tracer events
>   */

Nit: As this is extra functionality, it'd be more readable to have the
IPI handling after the main top handler, so that someone not familiar
with the source code will see the core logic first. That would also
match IPI being displayed to the right of the other numbers in the top
output.

> @@ -164,6 +251,8 @@ static void osnoise_top_header(struct osnoise_tool *top)
>                 goto eol;
>
>         trace_seq_printf(s, "          IRQ      Softirq       Thread");
> +       if (params->common.ipi)
> +               trace_seq_printf(s, "          IPI");
>
>  eol:
>         if (pretty)
> @@ -218,7 +307,13 @@ static void osnoise_top_print(struct osnoise_tool *tool, 
> int cpu)
>
>         trace_seq_printf(s, "%12llu ", cpu_data->irq_count);
>         trace_seq_printf(s, "%12llu ", cpu_data->softirq_count);
> -       trace_seq_printf(s, "%12llu\n", cpu_data->thread_count);
> +       trace_seq_printf(s, "%12llu", cpu_data->thread_count);
> +       if (!params->common.ipi) {
> +               trace_seq_printf(s, "\n");
> +               return;
> +       }
> +
> +       trace_seq_printf(s, " %12llu\n", cpu_data->ipi_count);

Maybe at this point it is worth it to print the "\n" in a separate
statement, readability-wise:

        trace_seq_printf(s, "%12llu ", cpu_data->irq_count);
        trace_seq_printf(s, "%12llu ", cpu_data->softirq_count);
        trace_seq_printf(s, "%12llu", cpu_data->thread_count);
        if (params->common.ipi)
                trace_seq_printf(s, " %12llu", cpu_data->ipi_count);
        trace_seq_printf(s, "\n");

It would also make diffs nicer when adding new options.

> [truncated]


Tomas


Reply via email to