blob: 332b2ac205fc4383a820a4e40b58916a3127e34c [file] [log] [blame]
Daniel Bristot de Oliveira1eceb2f2021-12-10 19:11:23 +01001// SPDX-License-Identifier: GPL-2.0
2/*
3 * Copyright (C) 2021 Red Hat Inc, Daniel Bristot de Oliveira <bristot@kernel.org>
4 */
5
6#include <getopt.h>
7#include <stdlib.h>
8#include <string.h>
9#include <signal.h>
10#include <unistd.h>
11#include <stdio.h>
12#include <time.h>
13
14#include "osnoise.h"
15#include "utils.h"
16
17/*
18 * osnoise top parameters
19 */
20struct osnoise_top_params {
21 char *cpus;
22 char *monitored_cpus;
23 char *trace_output;
24 unsigned long long runtime;
25 unsigned long long period;
26 long long stop_us;
27 long long stop_total_us;
28 int sleep_time;
29 int duration;
30 int quiet;
31 int set_sched;
32 struct sched_attr sched_param;
33};
34
35struct osnoise_top_cpu {
36 unsigned long long sum_runtime;
37 unsigned long long sum_noise;
38 unsigned long long max_noise;
39 unsigned long long max_sample;
40
41 unsigned long long hw_count;
42 unsigned long long nmi_count;
43 unsigned long long irq_count;
44 unsigned long long softirq_count;
45 unsigned long long thread_count;
46
47 int sum_cycles;
48};
49
50struct osnoise_top_data {
51 struct osnoise_top_cpu *cpu_data;
52 int nr_cpus;
53};
54
55/*
56 * osnoise_free_top - free runtime data
57 */
58static void
59osnoise_free_top(struct osnoise_top_data *data)
60{
61 free(data->cpu_data);
62 free(data);
63}
64
65/*
66 * osnoise_alloc_histogram - alloc runtime data
67 */
68static struct osnoise_top_data *osnoise_alloc_top(int nr_cpus)
69{
70 struct osnoise_top_data *data;
71
72 data = calloc(1, sizeof(*data));
73 if (!data)
74 return NULL;
75
76 data->nr_cpus = nr_cpus;
77
78 /* one set of histograms per CPU */
79 data->cpu_data = calloc(1, sizeof(*data->cpu_data) * nr_cpus);
80 if (!data->cpu_data)
81 goto cleanup;
82
83 return data;
84
85cleanup:
86 osnoise_free_top(data);
87 return NULL;
88}
89
90/*
91 * osnoise_top_handler - this is the handler for osnoise tracer events
92 */
93static int
94osnoise_top_handler(struct trace_seq *s, struct tep_record *record,
95 struct tep_event *event, void *context)
96{
97 struct trace_instance *trace = context;
98 struct osnoise_tool *tool;
99 unsigned long long val;
100 struct osnoise_top_cpu *cpu_data;
101 struct osnoise_top_data *data;
102 int cpu = record->cpu;
103
104 tool = container_of(trace, struct osnoise_tool, trace);
105
106 data = tool->data;
107 cpu_data = &data->cpu_data[cpu];
108
109 cpu_data->sum_cycles++;
110
111 tep_get_field_val(s, event, "runtime", record, &val, 1);
112 update_sum(&cpu_data->sum_runtime, &val);
113
114 tep_get_field_val(s, event, "noise", record, &val, 1);
115 update_max(&cpu_data->max_noise, &val);
116 update_sum(&cpu_data->sum_noise, &val);
117
118 tep_get_field_val(s, event, "max_sample", record, &val, 1);
119 update_max(&cpu_data->max_sample, &val);
120
121 tep_get_field_val(s, event, "hw_count", record, &val, 1);
122 update_sum(&cpu_data->hw_count, &val);
123
124 tep_get_field_val(s, event, "nmi_count", record, &val, 1);
125 update_sum(&cpu_data->nmi_count, &val);
126
127 tep_get_field_val(s, event, "irq_count", record, &val, 1);
128 update_sum(&cpu_data->irq_count, &val);
129
130 tep_get_field_val(s, event, "softirq_count", record, &val, 1);
131 update_sum(&cpu_data->softirq_count, &val);
132
133 tep_get_field_val(s, event, "thread_count", record, &val, 1);
134 update_sum(&cpu_data->thread_count, &val);
135
136 return 0;
137}
138
139/*
140 * osnoise_top_header - print the header of the tool output
141 */
142static void osnoise_top_header(struct osnoise_tool *top)
143{
144 struct trace_seq *s = top->trace.seq;
145 char duration[26];
146
147 get_duration(top->start_time, duration, sizeof(duration));
148
149 trace_seq_printf(s, "\033[2;37;40m");
150 trace_seq_printf(s, " Operating System Noise");
151 trace_seq_printf(s, " ");
152 trace_seq_printf(s, " ");
153 trace_seq_printf(s, "\033[0;0;0m");
154 trace_seq_printf(s, "\n");
155
156 trace_seq_printf(s, "duration: %9s | time is in us\n", duration);
157
158 trace_seq_printf(s, "\033[2;30;47m");
159 trace_seq_printf(s, "CPU Period Runtime ");
160 trace_seq_printf(s, " Noise ");
161 trace_seq_printf(s, " %% CPU Aval ");
162 trace_seq_printf(s, " Max Noise Max Single ");
163 trace_seq_printf(s, " HW NMI IRQ Softirq Thread");
164 trace_seq_printf(s, "\033[0;0;0m");
165 trace_seq_printf(s, "\n");
166}
167
168/*
169 * clear_terminal - clears the output terminal
170 */
171static void clear_terminal(struct trace_seq *seq)
172{
173 if (!config_debug)
174 trace_seq_printf(seq, "\033c");
175}
176
177/*
178 * osnoise_top_print - prints the output of a given CPU
179 */
180static void osnoise_top_print(struct osnoise_tool *tool, int cpu)
181{
182 struct trace_seq *s = tool->trace.seq;
183 struct osnoise_top_cpu *cpu_data;
184 struct osnoise_top_data *data;
185 int percentage;
186 int decimal;
187
188 data = tool->data;
189 cpu_data = &data->cpu_data[cpu];
190
191 if (!cpu_data->sum_runtime)
192 return;
193
194 percentage = ((cpu_data->sum_runtime - cpu_data->sum_noise) * 10000000)
195 / cpu_data->sum_runtime;
196 decimal = percentage % 100000;
197 percentage = percentage / 100000;
198
199 trace_seq_printf(s, "%3d #%-6d %12llu ", cpu, cpu_data->sum_cycles, cpu_data->sum_runtime);
200 trace_seq_printf(s, "%12llu ", cpu_data->sum_noise);
201 trace_seq_printf(s, " %3d.%05d", percentage, decimal);
202 trace_seq_printf(s, "%12llu %12llu", cpu_data->max_noise, cpu_data->max_sample);
203
204 trace_seq_printf(s, "%12llu ", cpu_data->hw_count);
205 trace_seq_printf(s, "%12llu ", cpu_data->nmi_count);
206 trace_seq_printf(s, "%12llu ", cpu_data->irq_count);
207 trace_seq_printf(s, "%12llu ", cpu_data->softirq_count);
208 trace_seq_printf(s, "%12llu\n", cpu_data->thread_count);
209}
210
211/*
212 * osnoise_print_stats - print data for all cpus
213 */
214static void
215osnoise_print_stats(struct osnoise_top_params *params, struct osnoise_tool *top)
216{
217 struct trace_instance *trace = &top->trace;
218 static int nr_cpus = -1;
219 int i;
220
221 if (nr_cpus == -1)
222 nr_cpus = sysconf(_SC_NPROCESSORS_CONF);
223
224 if (!params->quiet)
225 clear_terminal(trace->seq);
226
227 osnoise_top_header(top);
228
229 for (i = 0; i < nr_cpus; i++) {
230 if (params->cpus && !params->monitored_cpus[i])
231 continue;
232 osnoise_top_print(top, i);
233 }
234
235 trace_seq_do_printf(trace->seq);
236 trace_seq_reset(trace->seq);
237}
238
239/*
240 * osnoise_top_usage - prints osnoise top usage message
241 */
242void osnoise_top_usage(char *usage)
243{
244 int i;
245
246 static const char * const msg[] = {
247 " usage: rtla osnoise [top] [-h] [-q] [-D] [-d s] [-p us] [-r us] [-s us] [-S us] [-t[=file]] \\",
248 " [-c cpu-list] [-P priority]",
249 "",
250 " -h/--help: print this menu",
251 " -p/--period us: osnoise period in us",
252 " -r/--runtime us: osnoise runtime in us",
253 " -s/--stop us: stop trace if a single sample is higher than the argument in us",
254 " -S/--stop-total us: stop trace if the total sample is higher than the argument in us",
255 " -c/--cpus cpu-list: list of cpus to run osnoise threads",
256 " -d/--duration time[s|m|h|d]: duration of the session",
257 " -D/--debug: print debug info",
258 " -t/--trace[=file]: save the stopped trace to [file|osnoise_trace.txt]",
259 " -q/--quiet print only a summary at the end",
260 " -P/--priority o:prio|r:prio|f:prio|d:runtime:period : set scheduling parameters",
261 " o:prio - use SCHED_OTHER with prio",
262 " r:prio - use SCHED_RR with prio",
263 " f:prio - use SCHED_FIFO with prio",
264 " d:runtime[us|ms|s]:period[us|ms|s] - use SCHED_DEADLINE with runtime and period",
265 " in nanoseconds",
266 NULL,
267 };
268
269 if (usage)
270 fprintf(stderr, "%s\n", usage);
271
272 fprintf(stderr, "rtla osnoise top: a per-cpu summary of the OS noise (version %s)\n",
273 VERSION);
274
275 for (i = 0; msg[i]; i++)
276 fprintf(stderr, "%s\n", msg[i]);
277 exit(1);
278}
279
280/*
281 * osnoise_top_parse_args - allocs, parse and fill the cmd line parameters
282 */
283struct osnoise_top_params *osnoise_top_parse_args(int argc, char **argv)
284{
285 struct osnoise_top_params *params;
286 int retval;
287 int c;
288
289 params = calloc(1, sizeof(*params));
290 if (!params)
291 exit(1);
292
293 while (1) {
294 static struct option long_options[] = {
295 {"cpus", required_argument, 0, 'c'},
296 {"debug", no_argument, 0, 'D'},
297 {"duration", required_argument, 0, 'd'},
298 {"help", no_argument, 0, 'h'},
299 {"period", required_argument, 0, 'p'},
300 {"priority", required_argument, 0, 'P'},
301 {"quiet", no_argument, 0, 'q'},
302 {"runtime", required_argument, 0, 'r'},
303 {"stop", required_argument, 0, 's'},
304 {"stop-total", required_argument, 0, 'S'},
305 {"trace", optional_argument, 0, 't'},
306 {0, 0, 0, 0}
307 };
308
309 /* getopt_long stores the option index here. */
310 int option_index = 0;
311
312 c = getopt_long(argc, argv, "c:d:Dhp:P:qr:s:S:t::",
313 long_options, &option_index);
314
315 /* Detect the end of the options. */
316 if (c == -1)
317 break;
318
319 switch (c) {
320 case 'c':
321 retval = parse_cpu_list(optarg, &params->monitored_cpus);
322 if (retval)
323 osnoise_top_usage("\nInvalid -c cpu list\n");
324 params->cpus = optarg;
325 break;
326 case 'D':
327 config_debug = 1;
328 break;
329 case 'd':
330 params->duration = parse_seconds_duration(optarg);
331 if (!params->duration)
332 osnoise_top_usage("Invalid -D duration\n");
333 break;
334 case 'h':
335 case '?':
336 osnoise_top_usage(NULL);
337 break;
338 case 'p':
339 params->period = get_llong_from_str(optarg);
340 if (params->period > 10000000)
341 osnoise_top_usage("Period longer than 10 s\n");
342 break;
343 case 'P':
344 retval = parse_prio(optarg, &params->sched_param);
345 if (retval == -1)
346 osnoise_top_usage("Invalid -P priority");
347 params->set_sched = 1;
348 break;
349 case 'q':
350 params->quiet = 1;
351 break;
352 case 'r':
353 params->runtime = get_llong_from_str(optarg);
354 if (params->runtime < 100)
355 osnoise_top_usage("Runtime shorter than 100 us\n");
356 break;
357 case 's':
358 params->stop_us = get_llong_from_str(optarg);
359 break;
360 case 'S':
361 params->stop_total_us = get_llong_from_str(optarg);
362 break;
363 case 't':
364 if (optarg)
365 /* skip = */
366 params->trace_output = &optarg[1];
367 else
368 params->trace_output = "osnoise_trace.txt";
369 break;
370 default:
371 osnoise_top_usage("Invalid option");
372 }
373 }
374
375 if (geteuid()) {
376 err_msg("osnoise needs root permission\n");
377 exit(EXIT_FAILURE);
378 }
379
380 return params;
381}
382
383/*
384 * osnoise_top_apply_config - apply the top configs to the initialized tool
385 */
386static int
387osnoise_top_apply_config(struct osnoise_tool *tool, struct osnoise_top_params *params)
388{
389 int retval;
390
391 if (!params->sleep_time)
392 params->sleep_time = 1;
393
394 if (params->cpus) {
395 retval = osnoise_set_cpus(tool->context, params->cpus);
396 if (retval) {
397 err_msg("Failed to apply CPUs config\n");
398 goto out_err;
399 }
400 }
401
402 if (params->runtime || params->period) {
403 retval = osnoise_set_runtime_period(tool->context,
404 params->runtime,
405 params->period);
406 if (retval) {
407 err_msg("Failed to set runtime and/or period\n");
408 goto out_err;
409 }
410 }
411
412 if (params->stop_us) {
413 retval = osnoise_set_stop_us(tool->context, params->stop_us);
414 if (retval) {
415 err_msg("Failed to set stop us\n");
416 goto out_err;
417 }
418 }
419
420 if (params->stop_total_us) {
421 retval = osnoise_set_stop_total_us(tool->context, params->stop_total_us);
422 if (retval) {
423 err_msg("Failed to set stop total us\n");
424 goto out_err;
425 }
426 }
427
428 return 0;
429
430out_err:
431 return -1;
432}
433
434/*
435 * osnoise_init_top - initialize a osnoise top tool with parameters
436 */
437struct osnoise_tool *osnoise_init_top(struct osnoise_top_params *params)
438{
439 struct osnoise_tool *tool;
440 int nr_cpus;
441
442 nr_cpus = sysconf(_SC_NPROCESSORS_CONF);
443
444 tool = osnoise_init_tool("osnoise_top");
445 if (!tool)
446 return NULL;
447
448 tool->data = osnoise_alloc_top(nr_cpus);
449 if (!tool->data)
450 goto out_err;
451
452 tool->params = params;
453
454 tep_register_event_handler(tool->trace.tep, -1, "ftrace", "osnoise",
455 osnoise_top_handler, NULL);
456
457 return tool;
458
459out_err:
460 osnoise_free_top(tool->data);
461 osnoise_destroy_tool(tool);
462 return NULL;
463}
464
465static int stop_tracing;
466static void stop_top(int sig)
467{
468 stop_tracing = 1;
469}
470
471/*
472 * osnoise_top_set_signals - handles the signal to stop the tool
473 */
474static void osnoise_top_set_signals(struct osnoise_top_params *params)
475{
476 signal(SIGINT, stop_top);
477 if (params->duration) {
478 signal(SIGALRM, stop_top);
479 alarm(params->duration);
480 }
481}
482
483int osnoise_top_main(int argc, char **argv)
484{
485 struct osnoise_top_params *params;
486 struct trace_instance *trace;
487 struct osnoise_tool *record;
488 struct osnoise_tool *tool;
489 int return_value = 1;
490 int retval;
491
492 params = osnoise_top_parse_args(argc, argv);
493 if (!params)
494 exit(1);
495
496 tool = osnoise_init_top(params);
497 if (!tool) {
498 err_msg("Could not init osnoise top\n");
499 goto out_exit;
500 }
501
502 retval = osnoise_top_apply_config(tool, params);
503 if (retval) {
504 err_msg("Could not apply config\n");
505 goto out_top;
506 }
507
508 trace = &tool->trace;
509
510 retval = enable_osnoise(trace);
511 if (retval) {
512 err_msg("Failed to enable osnoise tracer\n");
513 goto out_top;
514 }
515
516 if (params->set_sched) {
517 retval = set_comm_sched_attr("osnoise/", &params->sched_param);
518 if (retval) {
519 err_msg("Failed to set sched parameters\n");
520 goto out_top;
521 }
522 }
523
524 trace_instance_start(trace);
525
526 if (params->trace_output) {
527 record = osnoise_init_trace_tool("osnoise");
528 if (!record) {
529 err_msg("Failed to enable the trace instance\n");
530 goto out_top;
531 }
532 trace_instance_start(&record->trace);
533 }
534
535 tool->start_time = time(NULL);
536 osnoise_top_set_signals(params);
537
538 do {
539 sleep(params->sleep_time);
540
541 retval = tracefs_iterate_raw_events(trace->tep,
542 trace->inst,
543 NULL,
544 0,
545 collect_registered_events,
546 trace);
547 if (retval < 0) {
548 err_msg("Error iterating on events\n");
549 goto out_top;
550 }
551
552 if (!params->quiet)
553 osnoise_print_stats(params, tool);
554
555 if (!tracefs_trace_is_on(trace->inst))
556 break;
557
558 } while (!stop_tracing);
559
560 osnoise_print_stats(params, tool);
561
562 return_value = 0;
563
564 if (!tracefs_trace_is_on(trace->inst)) {
565 printf("osnoise hit stop tracing\n");
566 if (params->trace_output) {
567 printf(" Saving trace to %s\n", params->trace_output);
568 save_trace_to_file(record->trace.inst, params->trace_output);
569 }
570 }
571
572out_top:
573 osnoise_free_top(tool->data);
574 osnoise_destroy_tool(tool);
575 if (params->trace_output)
576 osnoise_destroy_tool(record);
577out_exit:
578 exit(return_value);
579}