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