1 // SPDX-License-Identifier: GPL-2.0
3 * Copyright (C) 2021 Red Hat Inc, Daniel Bristot de Oliveira <bristot@kernel.org>
18 struct timerlat_hist_params {
22 unsigned long long runtime;
24 long long stop_total_us;
25 long long timerlat_period_us;
26 long long print_stack;
31 struct sched_attr sched_param;
43 struct timerlat_hist_cpu {
50 unsigned long long min_irq;
51 unsigned long long sum_irq;
52 unsigned long long max_irq;
54 unsigned long long min_thread;
55 unsigned long long sum_thread;
56 unsigned long long max_thread;
59 struct timerlat_hist_data {
60 struct timerlat_hist_cpu *hist;
67 * timerlat_free_histogram - free runtime data
70 timerlat_free_histogram(struct timerlat_hist_data *data)
74 /* one histogram for IRQ and one for thread, per CPU */
75 for (cpu = 0; cpu < data->nr_cpus; cpu++) {
76 if (data->hist[cpu].irq)
77 free(data->hist[cpu].irq);
79 if (data->hist[cpu].thread)
80 free(data->hist[cpu].thread);
83 /* one set of histograms per CPU */
91 * timerlat_alloc_histogram - alloc runtime data
93 static struct timerlat_hist_data
94 *timerlat_alloc_histogram(int nr_cpus, int entries, int bucket_size)
96 struct timerlat_hist_data *data;
99 data = calloc(1, sizeof(*data));
103 data->entries = entries;
104 data->bucket_size = bucket_size;
105 data->nr_cpus = nr_cpus;
107 /* one set of histograms per CPU */
108 data->hist = calloc(1, sizeof(*data->hist) * nr_cpus);
112 /* one histogram for IRQ and one for thread, per cpu */
113 for (cpu = 0; cpu < nr_cpus; cpu++) {
114 data->hist[cpu].irq = calloc(1, sizeof(*data->hist->irq) * (entries + 1));
115 if (!data->hist[cpu].irq)
117 data->hist[cpu].thread = calloc(1, sizeof(*data->hist->thread) * (entries + 1));
118 if (!data->hist[cpu].thread)
122 /* set the min to max */
123 for (cpu = 0; cpu < nr_cpus; cpu++) {
124 data->hist[cpu].min_irq = ~0;
125 data->hist[cpu].min_thread = ~0;
131 timerlat_free_histogram(data);
136 * timerlat_hist_update - record a new timerlat occurent on cpu, updating data
139 timerlat_hist_update(struct osnoise_tool *tool, int cpu,
140 unsigned long long thread,
141 unsigned long long latency)
143 struct timerlat_hist_params *params = tool->params;
144 struct timerlat_hist_data *data = tool->data;
145 int entries = data->entries;
149 if (params->output_divisor)
150 latency = latency / params->output_divisor;
152 if (data->bucket_size)
153 bucket = latency / data->bucket_size;
156 hist = data->hist[cpu].irq;
157 data->hist[cpu].irq_count++;
158 update_min(&data->hist[cpu].min_irq, &latency);
159 update_sum(&data->hist[cpu].sum_irq, &latency);
160 update_max(&data->hist[cpu].max_irq, &latency);
162 hist = data->hist[cpu].thread;
163 data->hist[cpu].thread_count++;
164 update_min(&data->hist[cpu].min_thread, &latency);
165 update_sum(&data->hist[cpu].sum_thread, &latency);
166 update_max(&data->hist[cpu].max_thread, &latency);
169 if (bucket < entries)
176 * timerlat_hist_handler - this is the handler for timerlat tracer events
179 timerlat_hist_handler(struct trace_seq *s, struct tep_record *record,
180 struct tep_event *event, void *data)
182 struct trace_instance *trace = data;
183 unsigned long long thread, latency;
184 struct osnoise_tool *tool;
185 int cpu = record->cpu;
187 tool = container_of(trace, struct osnoise_tool, trace);
189 tep_get_field_val(s, event, "context", record, &thread, 1);
190 tep_get_field_val(s, event, "timer_latency", record, &latency, 1);
192 timerlat_hist_update(tool, cpu, thread, latency);
198 * timerlat_hist_header - print the header of the tracer to the output
200 static void timerlat_hist_header(struct osnoise_tool *tool)
202 struct timerlat_hist_params *params = tool->params;
203 struct timerlat_hist_data *data = tool->data;
204 struct trace_seq *s = tool->trace.seq;
208 if (params->no_header)
211 get_duration(tool->start_time, duration, sizeof(duration));
212 trace_seq_printf(s, "# RTLA timerlat histogram\n");
213 trace_seq_printf(s, "# Time unit is %s (%s)\n",
214 params->output_divisor == 1 ? "nanoseconds" : "microseconds",
215 params->output_divisor == 1 ? "ns" : "us");
217 trace_seq_printf(s, "# Duration: %s\n", duration);
219 if (!params->no_index)
220 trace_seq_printf(s, "Index");
222 for (cpu = 0; cpu < data->nr_cpus; cpu++) {
223 if (params->cpus && !params->monitored_cpus[cpu])
226 if (!data->hist[cpu].irq_count && !data->hist[cpu].thread_count)
230 trace_seq_printf(s, " IRQ-%03d", cpu);
232 if (!params->no_thread)
233 trace_seq_printf(s, " Thr-%03d", cpu);
235 trace_seq_printf(s, "\n");
238 trace_seq_do_printf(s);
243 * timerlat_print_summary - print the summary of the hist data to the output
246 timerlat_print_summary(struct timerlat_hist_params *params,
247 struct trace_instance *trace,
248 struct timerlat_hist_data *data)
252 if (params->no_summary)
255 if (!params->no_index)
256 trace_seq_printf(trace->seq, "count:");
258 for (cpu = 0; cpu < data->nr_cpus; cpu++) {
259 if (params->cpus && !params->monitored_cpus[cpu])
262 if (!data->hist[cpu].irq_count && !data->hist[cpu].thread_count)
266 trace_seq_printf(trace->seq, "%9d ",
267 data->hist[cpu].irq_count);
269 if (!params->no_thread)
270 trace_seq_printf(trace->seq, "%9d ",
271 data->hist[cpu].thread_count);
273 trace_seq_printf(trace->seq, "\n");
275 if (!params->no_index)
276 trace_seq_printf(trace->seq, "min: ");
278 for (cpu = 0; cpu < data->nr_cpus; cpu++) {
279 if (params->cpus && !params->monitored_cpus[cpu])
282 if (!data->hist[cpu].irq_count && !data->hist[cpu].thread_count)
286 trace_seq_printf(trace->seq, "%9llu ",
287 data->hist[cpu].min_irq);
289 if (!params->no_thread)
290 trace_seq_printf(trace->seq, "%9llu ",
291 data->hist[cpu].min_thread);
293 trace_seq_printf(trace->seq, "\n");
295 if (!params->no_index)
296 trace_seq_printf(trace->seq, "avg: ");
298 for (cpu = 0; cpu < data->nr_cpus; cpu++) {
299 if (params->cpus && !params->monitored_cpus[cpu])
302 if (!data->hist[cpu].irq_count && !data->hist[cpu].thread_count)
305 if (!params->no_irq) {
306 if (data->hist[cpu].irq_count)
307 trace_seq_printf(trace->seq, "%9llu ",
308 data->hist[cpu].sum_irq / data->hist[cpu].irq_count);
310 trace_seq_printf(trace->seq, " - ");
313 if (!params->no_thread) {
314 if (data->hist[cpu].thread_count)
315 trace_seq_printf(trace->seq, "%9llu ",
316 data->hist[cpu].sum_thread / data->hist[cpu].thread_count);
318 trace_seq_printf(trace->seq, " - ");
321 trace_seq_printf(trace->seq, "\n");
323 if (!params->no_index)
324 trace_seq_printf(trace->seq, "max: ");
326 for (cpu = 0; cpu < data->nr_cpus; cpu++) {
327 if (params->cpus && !params->monitored_cpus[cpu])
330 if (!data->hist[cpu].irq_count && !data->hist[cpu].thread_count)
334 trace_seq_printf(trace->seq, "%9llu ",
335 data->hist[cpu].max_irq);
337 if (!params->no_thread)
338 trace_seq_printf(trace->seq, "%9llu ",
339 data->hist[cpu].max_thread);
341 trace_seq_printf(trace->seq, "\n");
342 trace_seq_do_printf(trace->seq);
343 trace_seq_reset(trace->seq);
347 * timerlat_print_stats - print data for all CPUs
350 timerlat_print_stats(struct timerlat_hist_params *params, struct osnoise_tool *tool)
352 struct timerlat_hist_data *data = tool->data;
353 struct trace_instance *trace = &tool->trace;
357 timerlat_hist_header(tool);
359 for (bucket = 0; bucket < data->entries; bucket++) {
362 if (!params->no_index)
363 trace_seq_printf(trace->seq, "%-6d",
364 bucket * data->bucket_size);
366 for (cpu = 0; cpu < data->nr_cpus; cpu++) {
367 if (params->cpus && !params->monitored_cpus[cpu])
370 if (!data->hist[cpu].irq_count && !data->hist[cpu].thread_count)
373 if (!params->no_irq) {
374 total += data->hist[cpu].irq[bucket];
375 trace_seq_printf(trace->seq, "%9d ",
376 data->hist[cpu].irq[bucket]);
379 if (!params->no_thread) {
380 total += data->hist[cpu].thread[bucket];
381 trace_seq_printf(trace->seq, "%9d ",
382 data->hist[cpu].thread[bucket]);
387 if (total == 0 && !params->with_zeros) {
388 trace_seq_reset(trace->seq);
392 trace_seq_printf(trace->seq, "\n");
393 trace_seq_do_printf(trace->seq);
394 trace_seq_reset(trace->seq);
397 if (!params->no_index)
398 trace_seq_printf(trace->seq, "over: ");
400 for (cpu = 0; cpu < data->nr_cpus; cpu++) {
401 if (params->cpus && !params->monitored_cpus[cpu])
404 if (!data->hist[cpu].irq_count && !data->hist[cpu].thread_count)
408 trace_seq_printf(trace->seq, "%9d ",
409 data->hist[cpu].irq[data->entries]);
411 if (!params->no_thread)
412 trace_seq_printf(trace->seq, "%9d ",
413 data->hist[cpu].thread[data->entries]);
415 trace_seq_printf(trace->seq, "\n");
416 trace_seq_do_printf(trace->seq);
417 trace_seq_reset(trace->seq);
419 timerlat_print_summary(params, trace, data);
423 * timerlat_hist_usage - prints timerlat top usage message
425 static void timerlat_hist_usage(char *usage)
431 " usage: [rtla] timerlat hist [-h] [-q] [-d s] [-D] [-n] [-p us] [-i us] [-T us] [-s us] [-t[=file]] \\",
432 " [-c cpu-list] [-P priority] [-e N] [-b N] [--no-irq] [--no-thread] [--no-header] [--no-summary] \\",
433 " [--no-index] [--with-zeros]",
435 " -h/--help: print this menu",
436 " -p/--period us: timerlat period in us",
437 " -i/--irq us: stop trace if the irq latency is higher than the argument in us",
438 " -T/--thread us: stop trace if the thread latency is higher than the argument in us",
439 " -s/--stack us: save the stack trace at the IRQ if a thread latency is higher than the argument in us",
440 " -c/--cpus cpus: run the tracer only on the given cpus",
441 " -d/--duration time[m|h|d]: duration of the session in seconds",
442 " -D/--debug: print debug info",
443 " -T/--trace[=file]: save the stopped trace to [file|timerlat_trace.txt]",
444 " -n/--nano: display data in nanoseconds",
445 " -b/--bucket-size N: set the histogram bucket size (default 1)",
446 " -e/--entries N: set the number of entries of the histogram (default 256)",
447 " --no-irq: ignore IRQ latencies",
448 " --no-thread: ignore thread latencies",
449 " --no-header: do not print header",
450 " --no-summary: do not print summary",
451 " --no-index: do not print index",
452 " --with-zeros: print zero only entries",
453 " -P/--priority o:prio|r:prio|f:prio|d:runtime:period : set scheduling parameters",
454 " o:prio - use SCHED_OTHER with prio",
455 " r:prio - use SCHED_RR with prio",
456 " f:prio - use SCHED_FIFO with prio",
457 " d:runtime[us|ms|s]:period[us|ms|s] - use SCHED_DEADLINE with runtime and period",
463 fprintf(stderr, "%s\n", usage);
465 fprintf(stderr, "rtla timerlat hist: a per-cpu histogram of the timer latency (version %s)\n",
468 for (i = 0; msg[i]; i++)
469 fprintf(stderr, "%s\n", msg[i]);
474 * timerlat_hist_parse_args - allocs, parse and fill the cmd line parameters
476 static struct timerlat_hist_params
477 *timerlat_hist_parse_args(int argc, char *argv[])
479 struct timerlat_hist_params *params;
483 params = calloc(1, sizeof(*params));
487 /* display data in microseconds */
488 params->output_divisor = 1000;
489 params->bucket_size = 1;
490 params->entries = 256;
493 static struct option long_options[] = {
494 {"cpus", required_argument, 0, 'c'},
495 {"bucket-size", required_argument, 0, 'b'},
496 {"debug", no_argument, 0, 'D'},
497 {"entries", required_argument, 0, 'e'},
498 {"duration", required_argument, 0, 'd'},
499 {"help", no_argument, 0, 'h'},
500 {"irq", required_argument, 0, 'i'},
501 {"nano", no_argument, 0, 'n'},
502 {"period", required_argument, 0, 'p'},
503 {"priority", required_argument, 0, 'P'},
504 {"stack", required_argument, 0, 's'},
505 {"thread", required_argument, 0, 'T'},
506 {"trace", optional_argument, 0, 't'},
507 {"no-irq", no_argument, 0, '0'},
508 {"no-thread", no_argument, 0, '1'},
509 {"no-header", no_argument, 0, '2'},
510 {"no-summary", no_argument, 0, '3'},
511 {"no-index", no_argument, 0, '4'},
512 {"with-zeros", no_argument, 0, '5'},
516 /* getopt_long stores the option index here. */
517 int option_index = 0;
519 c = getopt_long(argc, argv, "c:b:d:e:Dhi:np:P:s:t::T:012345",
520 long_options, &option_index);
522 /* detect the end of the options. */
528 retval = parse_cpu_list(optarg, ¶ms->monitored_cpus);
530 timerlat_hist_usage("\nInvalid -c cpu list\n");
531 params->cpus = optarg;
534 params->bucket_size = get_llong_from_str(optarg);
535 if ((params->bucket_size == 0) || (params->bucket_size >= 1000000))
536 timerlat_hist_usage("Bucket size needs to be > 0 and <= 1000000\n");
542 params->duration = parse_seconds_duration(optarg);
543 if (!params->duration)
544 timerlat_hist_usage("Invalid -D duration\n");
547 params->entries = get_llong_from_str(optarg);
548 if ((params->entries < 10) || (params->entries > 9999999))
549 timerlat_hist_usage("Entries must be > 10 and < 9999999\n");
553 timerlat_hist_usage(NULL);
556 params->stop_us = get_llong_from_str(optarg);
559 params->output_divisor = 1;
562 params->timerlat_period_us = get_llong_from_str(optarg);
563 if (params->timerlat_period_us > 1000000)
564 timerlat_hist_usage("Period longer than 1 s\n");
567 retval = parse_prio(optarg, ¶ms->sched_param);
569 timerlat_hist_usage("Invalid -P priority");
570 params->set_sched = 1;
573 params->print_stack = get_llong_from_str(optarg);
576 params->stop_total_us = get_llong_from_str(optarg);
581 params->trace_output = &optarg[1];
583 params->trace_output = "timerlat_trace.txt";
585 case '0': /* no irq */
588 case '1': /* no thread */
589 params->no_thread = 1;
591 case '2': /* no header */
592 params->no_header = 1;
594 case '3': /* no summary */
595 params->no_summary = 1;
597 case '4': /* no index */
598 params->no_index = 1;
600 case '5': /* with zeros */
601 params->with_zeros = 1;
604 timerlat_hist_usage("Invalid option");
609 err_msg("rtla needs root permission\n");
613 if (params->no_irq && params->no_thread)
614 timerlat_hist_usage("no-irq and no-thread set, there is nothing to do here");
616 if (params->no_index && !params->with_zeros)
617 timerlat_hist_usage("no-index set with with-zeros is not set - it does not make sense");
623 * timerlat_hist_apply_config - apply the hist configs to the initialized tool
626 timerlat_hist_apply_config(struct osnoise_tool *tool, struct timerlat_hist_params *params)
630 if (!params->sleep_time)
631 params->sleep_time = 1;
634 retval = osnoise_set_cpus(tool->context, params->cpus);
636 err_msg("Failed to apply CPUs config\n");
641 if (params->stop_us) {
642 retval = osnoise_set_stop_us(tool->context, params->stop_us);
644 err_msg("Failed to set stop us\n");
649 if (params->stop_total_us) {
650 retval = osnoise_set_stop_total_us(tool->context, params->stop_total_us);
652 err_msg("Failed to set stop total us\n");
657 if (params->timerlat_period_us) {
658 retval = osnoise_set_timerlat_period_us(tool->context, params->timerlat_period_us);
660 err_msg("Failed to set timerlat period\n");
665 if (params->print_stack) {
666 retval = osnoise_set_print_stack(tool->context, params->print_stack);
668 err_msg("Failed to set print stack\n");
680 * timerlat_init_hist - initialize a timerlat hist tool with parameters
682 static struct osnoise_tool
683 *timerlat_init_hist(struct timerlat_hist_params *params)
685 struct osnoise_tool *tool;
688 nr_cpus = sysconf(_SC_NPROCESSORS_CONF);
690 tool = osnoise_init_tool("timerlat_hist");
694 tool->data = timerlat_alloc_histogram(nr_cpus, params->entries, params->bucket_size);
698 tool->params = params;
700 tep_register_event_handler(tool->trace.tep, -1, "ftrace", "timerlat",
701 timerlat_hist_handler, tool);
706 osnoise_destroy_tool(tool);
710 static int stop_tracing;
711 static void stop_hist(int sig)
717 * timerlat_hist_set_signals - handles the signal to stop the tool
720 timerlat_hist_set_signals(struct timerlat_hist_params *params)
722 signal(SIGINT, stop_hist);
723 if (params->duration) {
724 signal(SIGALRM, stop_hist);
725 alarm(params->duration);
729 int timerlat_hist_main(int argc, char *argv[])
731 struct timerlat_hist_params *params;
732 struct osnoise_tool *record = NULL;
733 struct osnoise_tool *tool = NULL;
734 struct trace_instance *trace;
735 int return_value = 1;
738 params = timerlat_hist_parse_args(argc, argv);
742 tool = timerlat_init_hist(params);
744 err_msg("Could not init osnoise hist\n");
748 retval = timerlat_hist_apply_config(tool, params);
750 err_msg("Could not apply config\n");
754 trace = &tool->trace;
756 retval = enable_timerlat(trace);
758 err_msg("Failed to enable timerlat tracer\n");
762 if (params->set_sched) {
763 retval = set_comm_sched_attr("timerlat/", ¶ms->sched_param);
765 err_msg("Failed to set sched parameters\n");
770 trace_instance_start(trace);
772 if (params->trace_output) {
773 record = osnoise_init_trace_tool("timerlat");
775 err_msg("Failed to enable the trace instance\n");
778 trace_instance_start(&record->trace);
781 tool->start_time = time(NULL);
782 timerlat_hist_set_signals(params);
784 while (!stop_tracing) {
785 sleep(params->sleep_time);
787 retval = tracefs_iterate_raw_events(trace->tep,
791 collect_registered_events,
794 err_msg("Error iterating on events\n");
798 if (!tracefs_trace_is_on(trace->inst))
802 timerlat_print_stats(params, tool);
806 if (!tracefs_trace_is_on(trace->inst)) {
807 printf("rtla timelat hit stop tracing\n");
808 if (params->trace_output) {
809 printf(" Saving trace to %s\n", params->trace_output);
810 save_trace_to_file(record->trace.inst, params->trace_output);
815 timerlat_free_histogram(tool->data);
816 osnoise_destroy_tool(record);
817 osnoise_destroy_tool(tool);