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 #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, ¶ms->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, ¶ms->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, ¶ms->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, ¶ms->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/", ¶ms->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