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