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