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