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