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 const char *tool, *mode, *desc; 261 262 static const char * const msg_start[] = { 263 "[-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 NULL, 267 }; 268 269 static const char * const msg_opts[] = { 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 (params->mode == MODE_OSNOISE) { 300 tool = "osnoise"; 301 mode = "top"; 302 desc = "a per-cpu summary of the OS noise"; 303 } else { 304 tool = "hwnoise"; 305 mode = ""; 306 desc = "a summary of hardware-related noise"; 307 } 308 309 common_usage(tool, mode, desc, msg_start, msg_opts); 310 } 311 312 /* 313 * osnoise_top_parse_args - allocs, parse and fill the cmd line parameters 314 */ 315 struct common_params *osnoise_top_parse_args(int argc, char **argv) 316 { 317 struct osnoise_params *params; 318 struct trace_events *tevent; 319 int retval; 320 int c; 321 char *trace_output = NULL; 322 323 params = calloc(1, sizeof(*params)); 324 if (!params) 325 exit(1); 326 327 actions_init(¶ms->common.threshold_actions); 328 actions_init(¶ms->common.end_actions); 329 330 if (strcmp(argv[0], "hwnoise") == 0) { 331 params->mode = MODE_HWNOISE; 332 /* 333 * Reduce CPU usage for 75% to avoid killing the system. 334 */ 335 params->runtime = 750000; 336 params->period = 1000000; 337 } 338 339 while (1) { 340 static struct option long_options[] = { 341 {"auto", required_argument, 0, 'a'}, 342 {"cpus", required_argument, 0, 'c'}, 343 {"cgroup", optional_argument, 0, 'C'}, 344 {"debug", no_argument, 0, 'D'}, 345 {"duration", required_argument, 0, 'd'}, 346 {"event", required_argument, 0, 'e'}, 347 {"house-keeping", required_argument, 0, 'H'}, 348 {"help", no_argument, 0, 'h'}, 349 {"period", required_argument, 0, 'p'}, 350 {"priority", required_argument, 0, 'P'}, 351 {"quiet", no_argument, 0, 'q'}, 352 {"runtime", required_argument, 0, 'r'}, 353 {"stop", required_argument, 0, 's'}, 354 {"stop-total", required_argument, 0, 'S'}, 355 {"threshold", required_argument, 0, 'T'}, 356 {"trace", optional_argument, 0, 't'}, 357 {"trigger", required_argument, 0, '0'}, 358 {"filter", required_argument, 0, '1'}, 359 {"warm-up", required_argument, 0, '2'}, 360 {"trace-buffer-size", required_argument, 0, '3'}, 361 {"on-threshold", required_argument, 0, '4'}, 362 {"on-end", required_argument, 0, '5'}, 363 {0, 0, 0, 0} 364 }; 365 366 c = getopt_long(argc, argv, "a:c:C::d:De:hH:p:P:qr:s:S:t::T:0:1:2:3:", 367 long_options, NULL); 368 369 /* Detect the end of the options. */ 370 if (c == -1) 371 break; 372 373 switch (c) { 374 case 'a': 375 /* set sample stop to auto_thresh */ 376 params->common.stop_us = get_llong_from_str(optarg); 377 378 /* set sample threshold to 1 */ 379 params->threshold = 1; 380 381 /* set trace */ 382 if (!trace_output) 383 trace_output = "osnoise_trace.txt"; 384 385 break; 386 case 'c': 387 retval = parse_cpu_set(optarg, ¶ms->common.monitored_cpus); 388 if (retval) 389 fatal("Invalid -c cpu list"); 390 params->common.cpus = optarg; 391 break; 392 case 'C': 393 params->common.cgroup = 1; 394 params->common.cgroup_name = parse_optional_arg(argc, argv); 395 break; 396 case 'D': 397 config_debug = 1; 398 break; 399 case 'd': 400 params->common.duration = parse_seconds_duration(optarg); 401 if (!params->common.duration) 402 fatal("Invalid -d duration"); 403 break; 404 case 'e': 405 tevent = trace_event_alloc(optarg); 406 if (!tevent) 407 fatal("Error alloc trace event"); 408 409 if (params->common.events) 410 tevent->next = params->common.events; 411 params->common.events = tevent; 412 413 break; 414 case 'h': 415 case '?': 416 osnoise_top_usage(params); 417 break; 418 case 'H': 419 params->common.hk_cpus = 1; 420 retval = parse_cpu_set(optarg, ¶ms->common.hk_cpu_set); 421 if (retval) 422 fatal("Error parsing house keeping CPUs"); 423 break; 424 case 'p': 425 params->period = get_llong_from_str(optarg); 426 if (params->period > 10000000) 427 fatal("Period longer than 10 s"); 428 break; 429 case 'P': 430 retval = parse_prio(optarg, ¶ms->common.sched_param); 431 if (retval == -1) 432 fatal("Invalid -P priority"); 433 params->common.set_sched = 1; 434 break; 435 case 'q': 436 params->common.quiet = 1; 437 break; 438 case 'r': 439 params->runtime = get_llong_from_str(optarg); 440 if (params->runtime < 100) 441 fatal("Runtime shorter than 100 us"); 442 break; 443 case 's': 444 params->common.stop_us = get_llong_from_str(optarg); 445 break; 446 case 'S': 447 params->common.stop_total_us = get_llong_from_str(optarg); 448 break; 449 case 't': 450 trace_output = parse_optional_arg(argc, argv); 451 if (!trace_output) 452 trace_output = "osnoise_trace.txt"; 453 break; 454 case 'T': 455 params->threshold = get_llong_from_str(optarg); 456 break; 457 case '0': /* trigger */ 458 if (params->common.events) { 459 retval = trace_event_add_trigger(params->common.events, optarg); 460 if (retval) 461 fatal("Error adding trigger %s", optarg); 462 } else { 463 fatal("--trigger requires a previous -e"); 464 } 465 break; 466 case '1': /* filter */ 467 if (params->common.events) { 468 retval = trace_event_add_filter(params->common.events, optarg); 469 if (retval) 470 fatal("Error adding filter %s", optarg); 471 } else { 472 fatal("--filter requires a previous -e"); 473 } 474 break; 475 case '2': 476 params->common.warmup = get_llong_from_str(optarg); 477 break; 478 case '3': 479 params->common.buffer_size = get_llong_from_str(optarg); 480 break; 481 case '4': 482 retval = actions_parse(¶ms->common.threshold_actions, optarg, 483 "osnoise_trace.txt"); 484 if (retval) 485 fatal("Invalid action %s", optarg); 486 break; 487 case '5': 488 retval = actions_parse(¶ms->common.end_actions, optarg, 489 "osnoise_trace.txt"); 490 if (retval) 491 fatal("Invalid action %s", optarg); 492 break; 493 default: 494 fatal("Invalid option"); 495 } 496 } 497 498 if (trace_output) 499 actions_add_trace_output(¶ms->common.threshold_actions, trace_output); 500 501 if (geteuid()) 502 fatal("osnoise needs root permission"); 503 504 return ¶ms->common; 505 } 506 507 /* 508 * osnoise_top_apply_config - apply the top configs to the initialized tool 509 */ 510 static int 511 osnoise_top_apply_config(struct osnoise_tool *tool) 512 { 513 struct osnoise_params *params = to_osnoise_params(tool->params); 514 int retval; 515 516 retval = osnoise_apply_config(tool, params); 517 if (retval) 518 goto out_err; 519 520 if (params->mode == MODE_HWNOISE) { 521 retval = osnoise_set_irq_disable(tool->context, 1); 522 if (retval) { 523 err_msg("Failed to set OSNOISE_IRQ_DISABLE option\n"); 524 goto out_err; 525 } 526 } 527 528 if (isatty(STDOUT_FILENO) && !params->common.quiet) 529 params->common.pretty_output = 1; 530 531 return 0; 532 533 out_err: 534 return -1; 535 } 536 537 /* 538 * osnoise_init_top - initialize a osnoise top tool with parameters 539 */ 540 struct osnoise_tool *osnoise_init_top(struct common_params *params) 541 { 542 struct osnoise_tool *tool; 543 int nr_cpus; 544 545 nr_cpus = sysconf(_SC_NPROCESSORS_CONF); 546 547 tool = osnoise_init_tool("osnoise_top"); 548 if (!tool) 549 return NULL; 550 551 tool->data = osnoise_alloc_top(nr_cpus); 552 if (!tool->data) { 553 osnoise_destroy_tool(tool); 554 return NULL; 555 } 556 557 tep_register_event_handler(tool->trace.tep, -1, "ftrace", "osnoise", 558 osnoise_top_handler, NULL); 559 560 return tool; 561 } 562 563 struct tool_ops osnoise_top_ops = { 564 .tracer = "osnoise", 565 .comm_prefix = "osnoise/", 566 .parse_args = osnoise_top_parse_args, 567 .init_tool = osnoise_init_top, 568 .apply_config = osnoise_top_apply_config, 569 .enable = osnoise_enable, 570 .main = top_main_loop, 571 .print_stats = osnoise_print_stats, 572 .free = osnoise_free_top_tool, 573 }; 574