Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
torvalds
GitHub Repository: torvalds/linux
Path: blob/master/tools/tracing/rtla/src/osnoise_top.c
26285 views
1
// SPDX-License-Identifier: GPL-2.0
2
/*
3
* Copyright (C) 2021 Red Hat Inc, Daniel Bristot de Oliveira <[email protected]>
4
*/
5
6
#define _GNU_SOURCE
7
#include <getopt.h>
8
#include <stdlib.h>
9
#include <string.h>
10
#include <signal.h>
11
#include <unistd.h>
12
#include <stdio.h>
13
#include <time.h>
14
15
#include "osnoise.h"
16
17
struct osnoise_top_cpu {
18
unsigned long long sum_runtime;
19
unsigned long long sum_noise;
20
unsigned long long max_noise;
21
unsigned long long max_sample;
22
23
unsigned long long hw_count;
24
unsigned long long nmi_count;
25
unsigned long long irq_count;
26
unsigned long long softirq_count;
27
unsigned long long thread_count;
28
29
int sum_cycles;
30
};
31
32
struct osnoise_top_data {
33
struct osnoise_top_cpu *cpu_data;
34
int nr_cpus;
35
};
36
37
/*
38
* osnoise_free_top - free runtime data
39
*/
40
static void
41
osnoise_free_top(struct osnoise_top_data *data)
42
{
43
free(data->cpu_data);
44
free(data);
45
}
46
47
/*
48
* osnoise_alloc_histogram - alloc runtime data
49
*/
50
static struct osnoise_top_data *osnoise_alloc_top(int nr_cpus)
51
{
52
struct osnoise_top_data *data;
53
54
data = calloc(1, sizeof(*data));
55
if (!data)
56
return NULL;
57
58
data->nr_cpus = nr_cpus;
59
60
/* one set of histograms per CPU */
61
data->cpu_data = calloc(1, sizeof(*data->cpu_data) * nr_cpus);
62
if (!data->cpu_data)
63
goto cleanup;
64
65
return data;
66
67
cleanup:
68
osnoise_free_top(data);
69
return NULL;
70
}
71
72
/*
73
* osnoise_top_handler - this is the handler for osnoise tracer events
74
*/
75
static int
76
osnoise_top_handler(struct trace_seq *s, struct tep_record *record,
77
struct tep_event *event, void *context)
78
{
79
struct trace_instance *trace = context;
80
struct osnoise_tool *tool;
81
unsigned long long val;
82
struct osnoise_top_cpu *cpu_data;
83
struct osnoise_top_data *data;
84
int cpu = record->cpu;
85
86
tool = container_of(trace, struct osnoise_tool, trace);
87
88
data = tool->data;
89
cpu_data = &data->cpu_data[cpu];
90
91
cpu_data->sum_cycles++;
92
93
tep_get_field_val(s, event, "runtime", record, &val, 1);
94
update_sum(&cpu_data->sum_runtime, &val);
95
96
tep_get_field_val(s, event, "noise", record, &val, 1);
97
update_max(&cpu_data->max_noise, &val);
98
update_sum(&cpu_data->sum_noise, &val);
99
100
tep_get_field_val(s, event, "max_sample", record, &val, 1);
101
update_max(&cpu_data->max_sample, &val);
102
103
tep_get_field_val(s, event, "hw_count", record, &val, 1);
104
update_sum(&cpu_data->hw_count, &val);
105
106
tep_get_field_val(s, event, "nmi_count", record, &val, 1);
107
update_sum(&cpu_data->nmi_count, &val);
108
109
tep_get_field_val(s, event, "irq_count", record, &val, 1);
110
update_sum(&cpu_data->irq_count, &val);
111
112
tep_get_field_val(s, event, "softirq_count", record, &val, 1);
113
update_sum(&cpu_data->softirq_count, &val);
114
115
tep_get_field_val(s, event, "thread_count", record, &val, 1);
116
update_sum(&cpu_data->thread_count, &val);
117
118
return 0;
119
}
120
121
/*
122
* osnoise_top_header - print the header of the tool output
123
*/
124
static void osnoise_top_header(struct osnoise_tool *top)
125
{
126
struct osnoise_params *params = top->params;
127
struct trace_seq *s = top->trace.seq;
128
char duration[26];
129
130
get_duration(top->start_time, duration, sizeof(duration));
131
132
if (params->pretty_output)
133
trace_seq_printf(s, "\033[2;37;40m");
134
135
trace_seq_printf(s, " ");
136
137
if (params->mode == MODE_OSNOISE) {
138
trace_seq_printf(s, "Operating System Noise");
139
trace_seq_printf(s, " ");
140
} else if (params->mode == MODE_HWNOISE) {
141
trace_seq_printf(s, "Hardware-related Noise");
142
}
143
144
trace_seq_printf(s, " ");
145
146
if (params->pretty_output)
147
trace_seq_printf(s, "\033[0;0;0m");
148
trace_seq_printf(s, "\n");
149
150
trace_seq_printf(s, "duration: %9s | time is in us\n", duration);
151
152
if (params->pretty_output)
153
trace_seq_printf(s, "\033[2;30;47m");
154
155
trace_seq_printf(s, "CPU Period Runtime ");
156
trace_seq_printf(s, " Noise ");
157
trace_seq_printf(s, " %% CPU Aval ");
158
trace_seq_printf(s, " Max Noise Max Single ");
159
trace_seq_printf(s, " HW NMI");
160
161
if (params->mode == MODE_HWNOISE)
162
goto eol;
163
164
trace_seq_printf(s, " IRQ Softirq Thread");
165
166
eol:
167
if (params->pretty_output)
168
trace_seq_printf(s, "\033[0;0;0m");
169
trace_seq_printf(s, "\n");
170
}
171
172
/*
173
* clear_terminal - clears the output terminal
174
*/
175
static void clear_terminal(struct trace_seq *seq)
176
{
177
if (!config_debug)
178
trace_seq_printf(seq, "\033c");
179
}
180
181
/*
182
* osnoise_top_print - prints the output of a given CPU
183
*/
184
static void osnoise_top_print(struct osnoise_tool *tool, int cpu)
185
{
186
struct osnoise_params *params = tool->params;
187
struct trace_seq *s = tool->trace.seq;
188
struct osnoise_top_cpu *cpu_data;
189
struct osnoise_top_data *data;
190
int percentage;
191
int decimal;
192
193
data = tool->data;
194
cpu_data = &data->cpu_data[cpu];
195
196
if (!cpu_data->sum_runtime)
197
return;
198
199
percentage = ((cpu_data->sum_runtime - cpu_data->sum_noise) * 10000000)
200
/ cpu_data->sum_runtime;
201
decimal = percentage % 100000;
202
percentage = percentage / 100000;
203
204
trace_seq_printf(s, "%3d #%-6d %12llu ", cpu, cpu_data->sum_cycles, cpu_data->sum_runtime);
205
trace_seq_printf(s, "%12llu ", cpu_data->sum_noise);
206
trace_seq_printf(s, " %3d.%05d", percentage, decimal);
207
trace_seq_printf(s, "%12llu %12llu", cpu_data->max_noise, cpu_data->max_sample);
208
209
trace_seq_printf(s, "%12llu ", cpu_data->hw_count);
210
trace_seq_printf(s, "%12llu ", cpu_data->nmi_count);
211
212
if (params->mode == MODE_HWNOISE) {
213
trace_seq_printf(s, "\n");
214
return;
215
}
216
217
trace_seq_printf(s, "%12llu ", cpu_data->irq_count);
218
trace_seq_printf(s, "%12llu ", cpu_data->softirq_count);
219
trace_seq_printf(s, "%12llu\n", cpu_data->thread_count);
220
}
221
222
/*
223
* osnoise_print_stats - print data for all cpus
224
*/
225
static void
226
osnoise_print_stats(struct osnoise_params *params, struct osnoise_tool *top)
227
{
228
struct trace_instance *trace = &top->trace;
229
static int nr_cpus = -1;
230
int i;
231
232
if (nr_cpus == -1)
233
nr_cpus = sysconf(_SC_NPROCESSORS_CONF);
234
235
if (!params->quiet)
236
clear_terminal(trace->seq);
237
238
osnoise_top_header(top);
239
240
for (i = 0; i < nr_cpus; i++) {
241
if (params->cpus && !CPU_ISSET(i, &params->monitored_cpus))
242
continue;
243
osnoise_top_print(top, i);
244
}
245
246
trace_seq_do_printf(trace->seq);
247
trace_seq_reset(trace->seq);
248
osnoise_report_missed_events(top);
249
}
250
251
/*
252
* osnoise_top_usage - prints osnoise top usage message
253
*/
254
static void osnoise_top_usage(struct osnoise_params *params, char *usage)
255
{
256
int i;
257
258
static const char * const msg[] = {
259
" [-h] [-q] [-D] [-d s] [-a us] [-p us] [-r us] [-s us] [-S us] \\",
260
" [-T us] [-t[file]] [-e sys[:event]] [--filter <filter>] [--trigger <trigger>] \\",
261
" [-c cpu-list] [-H cpu-list] [-P priority] [-C[=cgroup_name]] [--warm-up s]",
262
"",
263
" -h/--help: print this menu",
264
" -a/--auto: set automatic trace mode, stopping the session if argument in us sample is hit",
265
" -p/--period us: osnoise period in us",
266
" -r/--runtime us: osnoise runtime in us",
267
" -s/--stop us: stop trace if a single sample is higher than the argument in us",
268
" -S/--stop-total us: stop trace if the total sample is higher than the argument in us",
269
" -T/--threshold us: the minimum delta to be considered a noise",
270
" -c/--cpus cpu-list: list of cpus to run osnoise threads",
271
" -H/--house-keeping cpus: run rtla control threads only on the given cpus",
272
" -C/--cgroup[=cgroup_name]: set cgroup, if no cgroup_name is passed, the rtla's cgroup will be inherited",
273
" -d/--duration time[s|m|h|d]: duration of the session",
274
" -D/--debug: print debug info",
275
" -t/--trace[file]: save the stopped trace to [file|osnoise_trace.txt]",
276
" -e/--event <sys:event>: enable the <sys:event> in the trace instance, multiple -e are allowed",
277
" --filter <filter>: enable a trace event filter to the previous -e event",
278
" --trigger <trigger>: enable a trace event trigger to the previous -e event",
279
" -q/--quiet print only a summary at the end",
280
" -P/--priority o:prio|r:prio|f:prio|d:runtime:period : set scheduling parameters",
281
" o:prio - use SCHED_OTHER with prio",
282
" r:prio - use SCHED_RR with prio",
283
" f:prio - use SCHED_FIFO with prio",
284
" d:runtime[us|ms|s]:period[us|ms|s] - use SCHED_DEADLINE with runtime and period",
285
" in nanoseconds",
286
" --warm-up s: let the workload run for s seconds before collecting data",
287
" --trace-buffer-size kB: set the per-cpu trace buffer size in kB",
288
NULL,
289
};
290
291
if (usage)
292
fprintf(stderr, "%s\n", usage);
293
294
if (params->mode == MODE_OSNOISE) {
295
fprintf(stderr,
296
"rtla osnoise top: a per-cpu summary of the OS noise (version %s)\n",
297
VERSION);
298
299
fprintf(stderr, " usage: rtla osnoise [top]");
300
}
301
302
if (params->mode == MODE_HWNOISE) {
303
fprintf(stderr,
304
"rtla hwnoise: a summary of hardware-related noise (version %s)\n",
305
VERSION);
306
307
fprintf(stderr, " usage: rtla hwnoise");
308
}
309
310
for (i = 0; msg[i]; i++)
311
fprintf(stderr, "%s\n", msg[i]);
312
313
if (usage)
314
exit(EXIT_FAILURE);
315
316
exit(EXIT_SUCCESS);
317
}
318
319
/*
320
* osnoise_top_parse_args - allocs, parse and fill the cmd line parameters
321
*/
322
struct osnoise_params *osnoise_top_parse_args(int argc, char **argv)
323
{
324
struct osnoise_params *params;
325
struct trace_events *tevent;
326
int retval;
327
int c;
328
329
params = calloc(1, sizeof(*params));
330
if (!params)
331
exit(1);
332
333
if (strcmp(argv[0], "hwnoise") == 0) {
334
params->mode = MODE_HWNOISE;
335
/*
336
* Reduce CPU usage for 75% to avoid killing the system.
337
*/
338
params->runtime = 750000;
339
params->period = 1000000;
340
}
341
342
while (1) {
343
static struct option long_options[] = {
344
{"auto", required_argument, 0, 'a'},
345
{"cpus", required_argument, 0, 'c'},
346
{"cgroup", optional_argument, 0, 'C'},
347
{"debug", no_argument, 0, 'D'},
348
{"duration", required_argument, 0, 'd'},
349
{"event", required_argument, 0, 'e'},
350
{"house-keeping", required_argument, 0, 'H'},
351
{"help", no_argument, 0, 'h'},
352
{"period", required_argument, 0, 'p'},
353
{"priority", required_argument, 0, 'P'},
354
{"quiet", no_argument, 0, 'q'},
355
{"runtime", required_argument, 0, 'r'},
356
{"stop", required_argument, 0, 's'},
357
{"stop-total", required_argument, 0, 'S'},
358
{"threshold", required_argument, 0, 'T'},
359
{"trace", optional_argument, 0, 't'},
360
{"trigger", required_argument, 0, '0'},
361
{"filter", required_argument, 0, '1'},
362
{"warm-up", required_argument, 0, '2'},
363
{"trace-buffer-size", required_argument, 0, '3'},
364
{0, 0, 0, 0}
365
};
366
367
/* getopt_long stores the option index here. */
368
int option_index = 0;
369
370
c = getopt_long(argc, argv, "a:c:C::d:De:hH:p:P:qr:s:S:t::T:0:1:2:3:",
371
long_options, &option_index);
372
373
/* Detect the end of the options. */
374
if (c == -1)
375
break;
376
377
switch (c) {
378
case 'a':
379
/* set sample stop to auto_thresh */
380
params->stop_us = get_llong_from_str(optarg);
381
382
/* set sample threshold to 1 */
383
params->threshold = 1;
384
385
/* set trace */
386
params->trace_output = "osnoise_trace.txt";
387
388
break;
389
case 'c':
390
retval = parse_cpu_set(optarg, &params->monitored_cpus);
391
if (retval)
392
osnoise_top_usage(params, "\nInvalid -c cpu list\n");
393
params->cpus = optarg;
394
break;
395
case 'C':
396
params->cgroup = 1;
397
if (!optarg) {
398
/* will inherit this cgroup */
399
params->cgroup_name = NULL;
400
} else if (*optarg == '=') {
401
/* skip the = */
402
params->cgroup_name = ++optarg;
403
}
404
break;
405
case 'D':
406
config_debug = 1;
407
break;
408
case 'd':
409
params->duration = parse_seconds_duration(optarg);
410
if (!params->duration)
411
osnoise_top_usage(params, "Invalid -d duration\n");
412
break;
413
case 'e':
414
tevent = trace_event_alloc(optarg);
415
if (!tevent) {
416
err_msg("Error alloc trace event");
417
exit(EXIT_FAILURE);
418
}
419
420
if (params->events)
421
tevent->next = params->events;
422
params->events = tevent;
423
424
break;
425
case 'h':
426
case '?':
427
osnoise_top_usage(params, NULL);
428
break;
429
case 'H':
430
params->hk_cpus = 1;
431
retval = parse_cpu_set(optarg, &params->hk_cpu_set);
432
if (retval) {
433
err_msg("Error parsing house keeping CPUs\n");
434
exit(EXIT_FAILURE);
435
}
436
break;
437
case 'p':
438
params->period = get_llong_from_str(optarg);
439
if (params->period > 10000000)
440
osnoise_top_usage(params, "Period longer than 10 s\n");
441
break;
442
case 'P':
443
retval = parse_prio(optarg, &params->sched_param);
444
if (retval == -1)
445
osnoise_top_usage(params, "Invalid -P priority");
446
params->set_sched = 1;
447
break;
448
case 'q':
449
params->quiet = 1;
450
break;
451
case 'r':
452
params->runtime = get_llong_from_str(optarg);
453
if (params->runtime < 100)
454
osnoise_top_usage(params, "Runtime shorter than 100 us\n");
455
break;
456
case 's':
457
params->stop_us = get_llong_from_str(optarg);
458
break;
459
case 'S':
460
params->stop_total_us = get_llong_from_str(optarg);
461
break;
462
case 't':
463
if (optarg) {
464
if (optarg[0] == '=')
465
params->trace_output = &optarg[1];
466
else
467
params->trace_output = &optarg[0];
468
} else if (optind < argc && argv[optind][0] != '-')
469
params->trace_output = argv[optind];
470
else
471
params->trace_output = "osnoise_trace.txt";
472
break;
473
case 'T':
474
params->threshold = get_llong_from_str(optarg);
475
break;
476
case '0': /* trigger */
477
if (params->events) {
478
retval = trace_event_add_trigger(params->events, optarg);
479
if (retval) {
480
err_msg("Error adding trigger %s\n", optarg);
481
exit(EXIT_FAILURE);
482
}
483
} else {
484
osnoise_top_usage(params, "--trigger requires a previous -e\n");
485
}
486
break;
487
case '1': /* filter */
488
if (params->events) {
489
retval = trace_event_add_filter(params->events, optarg);
490
if (retval) {
491
err_msg("Error adding filter %s\n", optarg);
492
exit(EXIT_FAILURE);
493
}
494
} else {
495
osnoise_top_usage(params, "--filter requires a previous -e\n");
496
}
497
break;
498
case '2':
499
params->warmup = get_llong_from_str(optarg);
500
break;
501
case '3':
502
params->buffer_size = get_llong_from_str(optarg);
503
break;
504
default:
505
osnoise_top_usage(params, "Invalid option");
506
}
507
}
508
509
if (geteuid()) {
510
err_msg("osnoise needs root permission\n");
511
exit(EXIT_FAILURE);
512
}
513
514
return params;
515
}
516
517
/*
518
* osnoise_top_apply_config - apply the top configs to the initialized tool
519
*/
520
static int
521
osnoise_top_apply_config(struct osnoise_tool *tool, struct osnoise_params *params)
522
{
523
int retval;
524
525
retval = osnoise_apply_config(tool, params);
526
if (retval)
527
goto out_err;
528
529
if (params->mode == MODE_HWNOISE) {
530
retval = osnoise_set_irq_disable(tool->context, 1);
531
if (retval) {
532
err_msg("Failed to set OSNOISE_IRQ_DISABLE option\n");
533
goto out_err;
534
}
535
}
536
537
if (isatty(STDOUT_FILENO) && !params->quiet)
538
params->pretty_output = 1;
539
540
return 0;
541
542
out_err:
543
return -1;
544
}
545
546
/*
547
* osnoise_init_top - initialize a osnoise top tool with parameters
548
*/
549
struct osnoise_tool *osnoise_init_top(struct osnoise_params *params)
550
{
551
struct osnoise_tool *tool;
552
int nr_cpus;
553
554
nr_cpus = sysconf(_SC_NPROCESSORS_CONF);
555
556
tool = osnoise_init_tool("osnoise_top");
557
if (!tool)
558
return NULL;
559
560
tool->data = osnoise_alloc_top(nr_cpus);
561
if (!tool->data) {
562
osnoise_destroy_tool(tool);
563
return NULL;
564
}
565
566
tool->params = params;
567
568
tep_register_event_handler(tool->trace.tep, -1, "ftrace", "osnoise",
569
osnoise_top_handler, NULL);
570
571
return tool;
572
}
573
574
static int stop_tracing;
575
static void stop_top(int sig)
576
{
577
stop_tracing = 1;
578
}
579
580
/*
581
* osnoise_top_set_signals - handles the signal to stop the tool
582
*/
583
static void osnoise_top_set_signals(struct osnoise_params *params)
584
{
585
signal(SIGINT, stop_top);
586
if (params->duration) {
587
signal(SIGALRM, stop_top);
588
alarm(params->duration);
589
}
590
}
591
592
int osnoise_top_main(int argc, char **argv)
593
{
594
struct osnoise_params *params;
595
struct osnoise_tool *record = NULL;
596
struct osnoise_tool *tool = NULL;
597
enum result return_value = ERROR;
598
struct trace_instance *trace;
599
int retval;
600
601
params = osnoise_top_parse_args(argc, argv);
602
if (!params)
603
exit(1);
604
605
tool = osnoise_init_top(params);
606
if (!tool) {
607
err_msg("Could not init osnoise top\n");
608
goto out_exit;
609
}
610
611
retval = osnoise_top_apply_config(tool, params);
612
if (retval) {
613
err_msg("Could not apply config\n");
614
goto out_free;
615
}
616
617
trace = &tool->trace;
618
619
retval = enable_osnoise(trace);
620
if (retval) {
621
err_msg("Failed to enable osnoise tracer\n");
622
goto out_free;
623
}
624
625
if (params->set_sched) {
626
retval = set_comm_sched_attr("osnoise/", &params->sched_param);
627
if (retval) {
628
err_msg("Failed to set sched parameters\n");
629
goto out_free;
630
}
631
}
632
633
if (params->cgroup) {
634
retval = set_comm_cgroup("osnoise/", params->cgroup_name);
635
if (!retval) {
636
err_msg("Failed to move threads to cgroup\n");
637
goto out_free;
638
}
639
}
640
641
if (params->trace_output) {
642
record = osnoise_init_trace_tool("osnoise");
643
if (!record) {
644
err_msg("Failed to enable the trace instance\n");
645
goto out_free;
646
}
647
648
if (params->events) {
649
retval = trace_events_enable(&record->trace, params->events);
650
if (retval)
651
goto out_top;
652
}
653
654
if (params->buffer_size > 0) {
655
retval = trace_set_buffer_size(&record->trace, params->buffer_size);
656
if (retval)
657
goto out_top;
658
}
659
}
660
661
/*
662
* Start the tracer here, after having set all instances.
663
*
664
* Let the trace instance start first for the case of hitting a stop
665
* tracing while enabling other instances. The trace instance is the
666
* one with most valuable information.
667
*/
668
if (params->trace_output)
669
trace_instance_start(&record->trace);
670
trace_instance_start(trace);
671
672
if (params->warmup > 0) {
673
debug_msg("Warming up for %d seconds\n", params->warmup);
674
sleep(params->warmup);
675
if (stop_tracing)
676
goto out_top;
677
678
/*
679
* Clean up the buffer. The osnoise workload do not run
680
* with tracing off to avoid creating a performance penalty
681
* when not needed.
682
*/
683
retval = tracefs_instance_file_write(trace->inst, "trace", "");
684
if (retval < 0) {
685
debug_msg("Error cleaning up the buffer");
686
goto out_top;
687
}
688
689
}
690
691
tool->start_time = time(NULL);
692
osnoise_top_set_signals(params);
693
694
while (!stop_tracing) {
695
sleep(params->sleep_time);
696
697
retval = tracefs_iterate_raw_events(trace->tep,
698
trace->inst,
699
NULL,
700
0,
701
collect_registered_events,
702
trace);
703
if (retval < 0) {
704
err_msg("Error iterating on events\n");
705
goto out_top;
706
}
707
708
if (!params->quiet)
709
osnoise_print_stats(params, tool);
710
711
if (osnoise_trace_is_off(tool, record))
712
break;
713
714
}
715
716
osnoise_print_stats(params, tool);
717
718
return_value = PASSED;
719
720
if (osnoise_trace_is_off(tool, record)) {
721
printf("osnoise hit stop tracing\n");
722
save_trace_to_file(record ? record->trace.inst : NULL,
723
params->trace_output);
724
return_value = FAILED;
725
}
726
727
out_top:
728
trace_events_destroy(&record->trace, params->events);
729
params->events = NULL;
730
out_free:
731
osnoise_free_top(tool->data);
732
osnoise_destroy_tool(record);
733
osnoise_destroy_tool(tool);
734
free(params);
735
out_exit:
736
exit(return_value);
737
}
738
739