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