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_each_monitored_cpu(i, nr_cpus, ¶ms->common) { 247 osnoise_top_print(top, i); 248 } 249 250 trace_seq_do_printf(trace->seq); 251 trace_seq_reset(trace->seq); 252 osnoise_report_missed_events(top); 253 } 254 255 /* 256 * osnoise_top_usage - prints osnoise top usage message 257 */ 258 static void osnoise_top_usage(struct osnoise_params *params) 259 { 260 int i; 261 262 static const char * const msg[] = { 263 " [-h] [-q] [-D] [-d s] [-a us] [-p us] [-r us] [-s us] [-S us] \\", 264 " [-T us] [-t [file]] [-e sys[:event]] [--filter <filter>] [--trigger <trigger>] \\", 265 " [-c cpu-list] [-H cpu-list] [-P priority] [-C [cgroup_name]] [--warm-up s]", 266 "", 267 " -h/--help: print this menu", 268 " -a/--auto: set automatic trace mode, stopping the session if argument in us sample is hit", 269 " -p/--period us: osnoise period in us", 270 " -r/--runtime us: osnoise runtime in us", 271 " -s/--stop us: stop trace if a single sample is higher than the argument in us", 272 " -S/--stop-total us: stop trace if the total sample is higher than the argument in us", 273 " -T/--threshold us: the minimum delta to be considered a noise", 274 " -c/--cpus cpu-list: list of cpus to run osnoise threads", 275 " -H/--house-keeping cpus: run rtla control threads only on the given cpus", 276 " -C/--cgroup [cgroup_name]: set cgroup, if no cgroup_name is passed, the rtla's cgroup will be inherited", 277 " -d/--duration time[s|m|h|d]: duration of the session", 278 " -D/--debug: print debug info", 279 " -t/--trace [file]: save the stopped trace to [file|osnoise_trace.txt]", 280 " -e/--event <sys:event>: enable the <sys:event> in the trace instance, multiple -e are allowed", 281 " --filter <filter>: enable a trace event filter to the previous -e event", 282 " --trigger <trigger>: enable a trace event trigger to the previous -e event", 283 " -q/--quiet print only a summary at the end", 284 " -P/--priority o:prio|r:prio|f:prio|d:runtime:period : set scheduling parameters", 285 " o:prio - use SCHED_OTHER with prio", 286 " r:prio - use SCHED_RR with prio", 287 " f:prio - use SCHED_FIFO with prio", 288 " d:runtime[us|ms|s]:period[us|ms|s] - use SCHED_DEADLINE with runtime and period", 289 " in nanoseconds", 290 " --warm-up s: let the workload run for s seconds before collecting data", 291 " --trace-buffer-size kB: set the per-cpu trace buffer size in kB", 292 " --on-threshold <action>: define action to be executed at stop-total threshold, multiple are allowed", 293 " --on-end: define action to be executed at measurement end, multiple are allowed", 294 NULL, 295 }; 296 297 if (params->mode == MODE_OSNOISE) { 298 fprintf(stderr, 299 "rtla osnoise top: a per-cpu summary of the OS noise (version %s)\n", 300 VERSION); 301 302 fprintf(stderr, " usage: rtla osnoise [top]"); 303 } 304 305 if (params->mode == MODE_HWNOISE) { 306 fprintf(stderr, 307 "rtla hwnoise: a summary of hardware-related noise (version %s)\n", 308 VERSION); 309 310 fprintf(stderr, " usage: rtla hwnoise"); 311 } 312 313 for (i = 0; msg[i]; i++) 314 fprintf(stderr, "%s\n", msg[i]); 315 316 exit(EXIT_SUCCESS); 317 } 318 319 /* 320 * osnoise_top_parse_args - allocs, parse and fill the cmd line parameters 321 */ 322 struct common_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 char *trace_output = NULL; 329 330 params = calloc(1, sizeof(*params)); 331 if (!params) 332 exit(1); 333 334 actions_init(¶ms->common.threshold_actions); 335 actions_init(¶ms->common.end_actions); 336 337 if (strcmp(argv[0], "hwnoise") == 0) { 338 params->mode = MODE_HWNOISE; 339 /* 340 * Reduce CPU usage for 75% to avoid killing the system. 341 */ 342 params->runtime = 750000; 343 params->period = 1000000; 344 } 345 346 while (1) { 347 static struct option long_options[] = { 348 {"auto", required_argument, 0, 'a'}, 349 {"cpus", required_argument, 0, 'c'}, 350 {"cgroup", optional_argument, 0, 'C'}, 351 {"debug", no_argument, 0, 'D'}, 352 {"duration", required_argument, 0, 'd'}, 353 {"event", required_argument, 0, 'e'}, 354 {"house-keeping", required_argument, 0, 'H'}, 355 {"help", no_argument, 0, 'h'}, 356 {"period", required_argument, 0, 'p'}, 357 {"priority", required_argument, 0, 'P'}, 358 {"quiet", no_argument, 0, 'q'}, 359 {"runtime", required_argument, 0, 'r'}, 360 {"stop", required_argument, 0, 's'}, 361 {"stop-total", required_argument, 0, 'S'}, 362 {"threshold", required_argument, 0, 'T'}, 363 {"trace", optional_argument, 0, 't'}, 364 {"trigger", required_argument, 0, '0'}, 365 {"filter", required_argument, 0, '1'}, 366 {"warm-up", required_argument, 0, '2'}, 367 {"trace-buffer-size", required_argument, 0, '3'}, 368 {"on-threshold", required_argument, 0, '4'}, 369 {"on-end", required_argument, 0, '5'}, 370 {0, 0, 0, 0} 371 }; 372 373 c = getopt_long(argc, argv, "a:c:C::d:De:hH:p:P:qr:s:S:t::T:0:1:2:3:", 374 long_options, NULL); 375 376 /* Detect the end of the options. */ 377 if (c == -1) 378 break; 379 380 switch (c) { 381 case 'a': 382 /* set sample stop to auto_thresh */ 383 params->common.stop_us = get_llong_from_str(optarg); 384 385 /* set sample threshold to 1 */ 386 params->threshold = 1; 387 388 /* set trace */ 389 if (!trace_output) 390 trace_output = "osnoise_trace.txt"; 391 392 break; 393 case 'c': 394 retval = parse_cpu_set(optarg, ¶ms->common.monitored_cpus); 395 if (retval) 396 fatal("Invalid -c cpu list"); 397 params->common.cpus = optarg; 398 break; 399 case 'C': 400 params->common.cgroup = 1; 401 params->common.cgroup_name = parse_optional_arg(argc, argv); 402 break; 403 case 'D': 404 config_debug = 1; 405 break; 406 case 'd': 407 params->common.duration = parse_seconds_duration(optarg); 408 if (!params->common.duration) 409 fatal("Invalid -d duration"); 410 break; 411 case 'e': 412 tevent = trace_event_alloc(optarg); 413 if (!tevent) 414 fatal("Error alloc trace event"); 415 416 if (params->common.events) 417 tevent->next = params->common.events; 418 params->common.events = tevent; 419 420 break; 421 case 'h': 422 case '?': 423 osnoise_top_usage(params); 424 break; 425 case 'H': 426 params->common.hk_cpus = 1; 427 retval = parse_cpu_set(optarg, ¶ms->common.hk_cpu_set); 428 if (retval) 429 fatal("Error parsing house keeping CPUs"); 430 break; 431 case 'p': 432 params->period = get_llong_from_str(optarg); 433 if (params->period > 10000000) 434 fatal("Period longer than 10 s"); 435 break; 436 case 'P': 437 retval = parse_prio(optarg, ¶ms->common.sched_param); 438 if (retval == -1) 439 fatal("Invalid -P priority"); 440 params->common.set_sched = 1; 441 break; 442 case 'q': 443 params->common.quiet = 1; 444 break; 445 case 'r': 446 params->runtime = get_llong_from_str(optarg); 447 if (params->runtime < 100) 448 fatal("Runtime shorter than 100 us"); 449 break; 450 case 's': 451 params->common.stop_us = get_llong_from_str(optarg); 452 break; 453 case 'S': 454 params->common.stop_total_us = get_llong_from_str(optarg); 455 break; 456 case 't': 457 trace_output = parse_optional_arg(argc, argv); 458 if (!trace_output) 459 trace_output = "osnoise_trace.txt"; 460 break; 461 case 'T': 462 params->threshold = get_llong_from_str(optarg); 463 break; 464 case '0': /* trigger */ 465 if (params->common.events) { 466 retval = trace_event_add_trigger(params->common.events, optarg); 467 if (retval) 468 fatal("Error adding trigger %s", optarg); 469 } else { 470 fatal("--trigger requires a previous -e"); 471 } 472 break; 473 case '1': /* filter */ 474 if (params->common.events) { 475 retval = trace_event_add_filter(params->common.events, optarg); 476 if (retval) 477 fatal("Error adding filter %s", optarg); 478 } else { 479 fatal("--filter requires a previous -e"); 480 } 481 break; 482 case '2': 483 params->common.warmup = get_llong_from_str(optarg); 484 break; 485 case '3': 486 params->common.buffer_size = get_llong_from_str(optarg); 487 break; 488 case '4': 489 retval = actions_parse(¶ms->common.threshold_actions, optarg, 490 "osnoise_trace.txt"); 491 if (retval) 492 fatal("Invalid action %s", optarg); 493 break; 494 case '5': 495 retval = actions_parse(¶ms->common.end_actions, optarg, 496 "osnoise_trace.txt"); 497 if (retval) 498 fatal("Invalid action %s", optarg); 499 break; 500 default: 501 fatal("Invalid option"); 502 } 503 } 504 505 if (trace_output) 506 actions_add_trace_output(¶ms->common.threshold_actions, trace_output); 507 508 if (geteuid()) 509 fatal("osnoise needs root permission"); 510 511 return ¶ms->common; 512 } 513 514 /* 515 * osnoise_top_apply_config - apply the top configs to the initialized tool 516 */ 517 static int 518 osnoise_top_apply_config(struct osnoise_tool *tool) 519 { 520 struct osnoise_params *params = to_osnoise_params(tool->params); 521 int retval; 522 523 retval = osnoise_apply_config(tool, params); 524 if (retval) 525 goto out_err; 526 527 if (params->mode == MODE_HWNOISE) { 528 retval = osnoise_set_irq_disable(tool->context, 1); 529 if (retval) { 530 err_msg("Failed to set OSNOISE_IRQ_DISABLE option\n"); 531 goto out_err; 532 } 533 } 534 535 if (isatty(STDOUT_FILENO) && !params->common.quiet) 536 params->common.pretty_output = 1; 537 538 return 0; 539 540 out_err: 541 return -1; 542 } 543 544 /* 545 * osnoise_init_top - initialize a osnoise top tool with parameters 546 */ 547 struct osnoise_tool *osnoise_init_top(struct common_params *params) 548 { 549 struct osnoise_tool *tool; 550 int nr_cpus; 551 552 nr_cpus = sysconf(_SC_NPROCESSORS_CONF); 553 554 tool = osnoise_init_tool("osnoise_top"); 555 if (!tool) 556 return NULL; 557 558 tool->data = osnoise_alloc_top(nr_cpus); 559 if (!tool->data) { 560 osnoise_destroy_tool(tool); 561 return NULL; 562 } 563 564 tep_register_event_handler(tool->trace.tep, -1, "ftrace", "osnoise", 565 osnoise_top_handler, NULL); 566 567 return tool; 568 } 569 570 struct tool_ops osnoise_top_ops = { 571 .tracer = "osnoise", 572 .comm_prefix = "osnoise/", 573 .parse_args = osnoise_top_parse_args, 574 .init_tool = osnoise_init_top, 575 .apply_config = osnoise_top_apply_config, 576 .enable = osnoise_enable, 577 .main = top_main_loop, 578 .print_stats = osnoise_print_stats, 579 .free = osnoise_free_top_tool, 580 }; 581