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 #include <getopt.h> 7 #include <stdlib.h> 8 #include <string.h> 9 #include <signal.h> 10 #include <unistd.h> 11 #include <errno.h> 12 #include <stdio.h> 13 #include <time.h> 14 15 #include "utils.h" 16 #include "osnoise.h" 17 18 struct osnoise_hist_params { 19 char *cpus; 20 char *monitored_cpus; 21 char *trace_output; 22 unsigned long long runtime; 23 unsigned long long period; 24 long long stop_us; 25 long long stop_total_us; 26 int sleep_time; 27 int duration; 28 int set_sched; 29 int output_divisor; 30 struct sched_attr sched_param; 31 32 char no_header; 33 char no_summary; 34 char no_index; 35 char with_zeros; 36 int bucket_size; 37 int entries; 38 }; 39 40 struct osnoise_hist_cpu { 41 int *samples; 42 int count; 43 44 unsigned long long min_sample; 45 unsigned long long sum_sample; 46 unsigned long long max_sample; 47 48 }; 49 50 struct osnoise_hist_data { 51 struct tracefs_hist *trace_hist; 52 struct osnoise_hist_cpu *hist; 53 int entries; 54 int bucket_size; 55 int nr_cpus; 56 }; 57 58 /* 59 * osnoise_free_histogram - free runtime data 60 */ 61 static void 62 osnoise_free_histogram(struct osnoise_hist_data *data) 63 { 64 int cpu; 65 66 /* one histogram for IRQ and one for thread, per CPU */ 67 for (cpu = 0; cpu < data->nr_cpus; cpu++) { 68 if (data->hist[cpu].samples) 69 free(data->hist[cpu].samples); 70 } 71 72 /* one set of histograms per CPU */ 73 if (data->hist) 74 free(data->hist); 75 76 free(data); 77 } 78 79 /* 80 * osnoise_alloc_histogram - alloc runtime data 81 */ 82 static struct osnoise_hist_data 83 *osnoise_alloc_histogram(int nr_cpus, int entries, int bucket_size) 84 { 85 struct osnoise_hist_data *data; 86 int cpu; 87 88 data = calloc(1, sizeof(*data)); 89 if (!data) 90 return NULL; 91 92 data->entries = entries; 93 data->bucket_size = bucket_size; 94 data->nr_cpus = nr_cpus; 95 96 data->hist = calloc(1, sizeof(*data->hist) * nr_cpus); 97 if (!data->hist) 98 goto cleanup; 99 100 for (cpu = 0; cpu < nr_cpus; cpu++) { 101 data->hist[cpu].samples = calloc(1, sizeof(*data->hist->samples) * (entries + 1)); 102 if (!data->hist[cpu].samples) 103 goto cleanup; 104 } 105 106 /* set the min to max */ 107 for (cpu = 0; cpu < nr_cpus; cpu++) 108 data->hist[cpu].min_sample = ~0; 109 110 return data; 111 112 cleanup: 113 osnoise_free_histogram(data); 114 return NULL; 115 } 116 117 static void osnoise_hist_update_multiple(struct osnoise_tool *tool, int cpu, 118 unsigned long long duration, int count) 119 { 120 struct osnoise_hist_params *params = tool->params; 121 struct osnoise_hist_data *data = tool->data; 122 int entries = data->entries; 123 int bucket; 124 int *hist; 125 126 if (params->output_divisor) 127 duration = duration / params->output_divisor; 128 129 if (data->bucket_size) 130 bucket = duration / data->bucket_size; 131 132 hist = data->hist[cpu].samples; 133 data->hist[cpu].count += count; 134 update_min(&data->hist[cpu].min_sample, &duration); 135 update_sum(&data->hist[cpu].sum_sample, &duration); 136 update_max(&data->hist[cpu].max_sample, &duration); 137 138 if (bucket < entries) 139 hist[bucket] += count; 140 else 141 hist[entries] += count; 142 } 143 144 /* 145 * osnoise_destroy_trace_hist - disable events used to collect histogram 146 */ 147 static void osnoise_destroy_trace_hist(struct osnoise_tool *tool) 148 { 149 struct osnoise_hist_data *data = tool->data; 150 151 tracefs_hist_pause(tool->trace.inst, data->trace_hist); 152 tracefs_hist_destroy(tool->trace.inst, data->trace_hist); 153 } 154 155 /* 156 * osnoise_init_trace_hist - enable events used to collect histogram 157 */ 158 static int osnoise_init_trace_hist(struct osnoise_tool *tool) 159 { 160 struct osnoise_hist_params *params = tool->params; 161 struct osnoise_hist_data *data = tool->data; 162 int bucket_size; 163 char buff[128]; 164 int retval = 0; 165 166 /* 167 * Set the size of the bucket. 168 */ 169 bucket_size = params->output_divisor * params->bucket_size; 170 snprintf(buff, sizeof(buff), "duration.buckets=%d", bucket_size); 171 172 data->trace_hist = tracefs_hist_alloc(tool->trace.tep, "osnoise", "sample_threshold", 173 buff, TRACEFS_HIST_KEY_NORMAL); 174 if (!data->trace_hist) 175 return 1; 176 177 retval = tracefs_hist_add_key(data->trace_hist, "cpu", 0); 178 if (retval) 179 goto out_err; 180 181 retval = tracefs_hist_start(tool->trace.inst, data->trace_hist); 182 if (retval) 183 goto out_err; 184 185 return 0; 186 187 out_err: 188 osnoise_destroy_trace_hist(tool); 189 return 1; 190 } 191 192 /* 193 * osnoise_read_trace_hist - parse histogram file and file osnoise histogram 194 */ 195 static void osnoise_read_trace_hist(struct osnoise_tool *tool) 196 { 197 struct osnoise_hist_data *data = tool->data; 198 long long cpu, counter, duration; 199 char *content, *position; 200 201 tracefs_hist_pause(tool->trace.inst, data->trace_hist); 202 203 content = tracefs_event_file_read(tool->trace.inst, "osnoise", 204 "sample_threshold", 205 "hist", NULL); 206 if (!content) 207 return; 208 209 position = content; 210 while (true) { 211 position = strstr(position, "duration: ~"); 212 if (!position) 213 break; 214 position += strlen("duration: ~"); 215 duration = get_llong_from_str(position); 216 if (duration == -1) 217 err_msg("error reading duration from histogram\n"); 218 219 position = strstr(position, "cpu:"); 220 if (!position) 221 break; 222 position += strlen("cpu: "); 223 cpu = get_llong_from_str(position); 224 if (cpu == -1) 225 err_msg("error reading cpu from histogram\n"); 226 227 position = strstr(position, "hitcount:"); 228 if (!position) 229 break; 230 position += strlen("hitcount: "); 231 counter = get_llong_from_str(position); 232 if (counter == -1) 233 err_msg("error reading counter from histogram\n"); 234 235 osnoise_hist_update_multiple(tool, cpu, duration, counter); 236 } 237 free(content); 238 } 239 240 /* 241 * osnoise_hist_header - print the header of the tracer to the output 242 */ 243 static void osnoise_hist_header(struct osnoise_tool *tool) 244 { 245 struct osnoise_hist_params *params = tool->params; 246 struct osnoise_hist_data *data = tool->data; 247 struct trace_seq *s = tool->trace.seq; 248 char duration[26]; 249 int cpu; 250 251 if (params->no_header) 252 return; 253 254 get_duration(tool->start_time, duration, sizeof(duration)); 255 trace_seq_printf(s, "# RTLA osnoise histogram\n"); 256 trace_seq_printf(s, "# Time unit is %s (%s)\n", 257 params->output_divisor == 1 ? "nanoseconds" : "microseconds", 258 params->output_divisor == 1 ? "ns" : "us"); 259 260 trace_seq_printf(s, "# Duration: %s\n", duration); 261 262 if (!params->no_index) 263 trace_seq_printf(s, "Index"); 264 265 for (cpu = 0; cpu < data->nr_cpus; cpu++) { 266 if (params->cpus && !params->monitored_cpus[cpu]) 267 continue; 268 269 if (!data->hist[cpu].count) 270 continue; 271 272 trace_seq_printf(s, " CPU-%03d", cpu); 273 } 274 trace_seq_printf(s, "\n"); 275 276 trace_seq_do_printf(s); 277 trace_seq_reset(s); 278 } 279 280 /* 281 * osnoise_print_summary - print the summary of the hist data to the output 282 */ 283 static void 284 osnoise_print_summary(struct osnoise_hist_params *params, 285 struct trace_instance *trace, 286 struct osnoise_hist_data *data) 287 { 288 int cpu; 289 290 if (params->no_summary) 291 return; 292 293 if (!params->no_index) 294 trace_seq_printf(trace->seq, "count:"); 295 296 for (cpu = 0; cpu < data->nr_cpus; cpu++) { 297 if (params->cpus && !params->monitored_cpus[cpu]) 298 continue; 299 300 if (!data->hist[cpu].count) 301 continue; 302 303 trace_seq_printf(trace->seq, "%9d ", data->hist[cpu].count); 304 } 305 trace_seq_printf(trace->seq, "\n"); 306 307 if (!params->no_index) 308 trace_seq_printf(trace->seq, "min: "); 309 310 for (cpu = 0; cpu < data->nr_cpus; cpu++) { 311 if (params->cpus && !params->monitored_cpus[cpu]) 312 continue; 313 314 if (!data->hist[cpu].count) 315 continue; 316 317 trace_seq_printf(trace->seq, "%9llu ", data->hist[cpu].min_sample); 318 319 } 320 trace_seq_printf(trace->seq, "\n"); 321 322 if (!params->no_index) 323 trace_seq_printf(trace->seq, "avg: "); 324 325 for (cpu = 0; cpu < data->nr_cpus; cpu++) { 326 if (params->cpus && !params->monitored_cpus[cpu]) 327 continue; 328 329 if (!data->hist[cpu].count) 330 continue; 331 332 if (data->hist[cpu].count) 333 trace_seq_printf(trace->seq, "%9llu ", 334 data->hist[cpu].sum_sample / data->hist[cpu].count); 335 else 336 trace_seq_printf(trace->seq, " - "); 337 } 338 trace_seq_printf(trace->seq, "\n"); 339 340 if (!params->no_index) 341 trace_seq_printf(trace->seq, "max: "); 342 343 for (cpu = 0; cpu < data->nr_cpus; cpu++) { 344 if (params->cpus && !params->monitored_cpus[cpu]) 345 continue; 346 347 if (!data->hist[cpu].count) 348 continue; 349 350 trace_seq_printf(trace->seq, "%9llu ", data->hist[cpu].max_sample); 351 352 } 353 trace_seq_printf(trace->seq, "\n"); 354 trace_seq_do_printf(trace->seq); 355 trace_seq_reset(trace->seq); 356 } 357 358 /* 359 * osnoise_print_stats - print data for all CPUs 360 */ 361 static void 362 osnoise_print_stats(struct osnoise_hist_params *params, struct osnoise_tool *tool) 363 { 364 struct osnoise_hist_data *data = tool->data; 365 struct trace_instance *trace = &tool->trace; 366 int bucket, cpu; 367 int total; 368 369 osnoise_hist_header(tool); 370 371 for (bucket = 0; bucket < data->entries; bucket++) { 372 total = 0; 373 374 if (!params->no_index) 375 trace_seq_printf(trace->seq, "%-6d", 376 bucket * data->bucket_size); 377 378 for (cpu = 0; cpu < data->nr_cpus; cpu++) { 379 if (params->cpus && !params->monitored_cpus[cpu]) 380 continue; 381 382 if (!data->hist[cpu].count) 383 continue; 384 385 total += data->hist[cpu].samples[bucket]; 386 trace_seq_printf(trace->seq, "%9d ", data->hist[cpu].samples[bucket]); 387 } 388 389 if (total == 0 && !params->with_zeros) { 390 trace_seq_reset(trace->seq); 391 continue; 392 } 393 394 trace_seq_printf(trace->seq, "\n"); 395 trace_seq_do_printf(trace->seq); 396 trace_seq_reset(trace->seq); 397 } 398 399 if (!params->no_index) 400 trace_seq_printf(trace->seq, "over: "); 401 402 for (cpu = 0; cpu < data->nr_cpus; cpu++) { 403 if (params->cpus && !params->monitored_cpus[cpu]) 404 continue; 405 406 if (!data->hist[cpu].count) 407 continue; 408 409 trace_seq_printf(trace->seq, "%9d ", 410 data->hist[cpu].samples[data->entries]); 411 } 412 trace_seq_printf(trace->seq, "\n"); 413 trace_seq_do_printf(trace->seq); 414 trace_seq_reset(trace->seq); 415 416 osnoise_print_summary(params, trace, data); 417 } 418 419 /* 420 * osnoise_hist_usage - prints osnoise hist usage message 421 */ 422 static void osnoise_hist_usage(char *usage) 423 { 424 int i; 425 426 static const char * const msg[] = { 427 "", 428 " usage: rtla osnoise hist [-h] [-D] [-d s] [-p us] [-r us] [-s us] [-S us] [-t[=file]] \\", 429 " [-c cpu-list] [-P priority] [-b N] [-E N] [--no-header] [--no-summary] \\", 430 " [--no-index] [--with-zeros]", 431 "", 432 " -h/--help: print this menu", 433 " -p/--period us: osnoise period in us", 434 " -r/--runtime us: osnoise runtime in us", 435 " -s/--stop us: stop trace if a single sample is higher than the argument in us", 436 " -S/--stop-total us: stop trace if the total sample is higher than the argument in us", 437 " -c/--cpus cpu-list: list of cpus to run osnoise threads", 438 " -d/--duration time[s|m|h|d]: duration of the session", 439 " -D/--debug: print debug info", 440 " -t/--trace[=file]: save the stopped trace to [file|osnoise_trace.txt]", 441 " -b/--bucket-size N: set the histogram bucket size (default 1)", 442 " -E/--entries N: set the number of entries of the histogram (default 256)", 443 " --no-header: do not print header", 444 " --no-summary: do not print summary", 445 " --no-index: do not print index", 446 " --with-zeros: print zero only entries", 447 " -P/--priority o:prio|r:prio|f:prio|d:runtime:period: set scheduling parameters", 448 " o:prio - use SCHED_OTHER with prio", 449 " r:prio - use SCHED_RR with prio", 450 " f:prio - use SCHED_FIFO with prio", 451 " d:runtime[us|ms|s]:period[us|ms|s] - use SCHED_DEADLINE with runtime and period", 452 " in nanoseconds", 453 NULL, 454 }; 455 456 if (usage) 457 fprintf(stderr, "%s\n", usage); 458 459 fprintf(stderr, "rtla osnoise hist: a per-cpu histogram of the OS noise (version %s)\n", 460 VERSION); 461 462 for (i = 0; msg[i]; i++) 463 fprintf(stderr, "%s\n", msg[i]); 464 exit(1); 465 } 466 467 /* 468 * osnoise_hist_parse_args - allocs, parse and fill the cmd line parameters 469 */ 470 static struct osnoise_hist_params 471 *osnoise_hist_parse_args(int argc, char *argv[]) 472 { 473 struct osnoise_hist_params *params; 474 int retval; 475 int c; 476 477 params = calloc(1, sizeof(*params)); 478 if (!params) 479 exit(1); 480 481 /* display data in microseconds */ 482 params->output_divisor = 1000; 483 params->bucket_size = 1; 484 params->entries = 256; 485 486 while (1) { 487 static struct option long_options[] = { 488 {"bucket-size", required_argument, 0, 'b'}, 489 {"entries", required_argument, 0, 'E'}, 490 {"cpus", required_argument, 0, 'c'}, 491 {"debug", no_argument, 0, 'D'}, 492 {"duration", required_argument, 0, 'd'}, 493 {"help", no_argument, 0, 'h'}, 494 {"period", required_argument, 0, 'p'}, 495 {"priority", required_argument, 0, 'P'}, 496 {"runtime", required_argument, 0, 'r'}, 497 {"stop", required_argument, 0, 's'}, 498 {"stop-total", required_argument, 0, 'S'}, 499 {"trace", optional_argument, 0, 't'}, 500 {"no-header", no_argument, 0, '0'}, 501 {"no-summary", no_argument, 0, '1'}, 502 {"no-index", no_argument, 0, '2'}, 503 {"with-zeros", no_argument, 0, '3'}, 504 {0, 0, 0, 0} 505 }; 506 507 /* getopt_long stores the option index here. */ 508 int option_index = 0; 509 510 c = getopt_long(argc, argv, "c:b:d:E:Dhp:P:r:s:S:t::0123", 511 long_options, &option_index); 512 513 /* detect the end of the options. */ 514 if (c == -1) 515 break; 516 517 switch (c) { 518 case 'b': 519 params->bucket_size = get_llong_from_str(optarg); 520 if ((params->bucket_size == 0) || (params->bucket_size >= 1000000)) 521 osnoise_hist_usage("Bucket size needs to be > 0 and <= 1000000\n"); 522 break; 523 case 'c': 524 retval = parse_cpu_list(optarg, ¶ms->monitored_cpus); 525 if (retval) 526 osnoise_hist_usage("\nInvalid -c cpu list\n"); 527 params->cpus = optarg; 528 break; 529 case 'D': 530 config_debug = 1; 531 break; 532 case 'd': 533 params->duration = parse_seconds_duration(optarg); 534 if (!params->duration) 535 osnoise_hist_usage("Invalid -D duration\n"); 536 break; 537 case 'E': 538 params->entries = get_llong_from_str(optarg); 539 if ((params->entries < 10) || (params->entries > 9999999)) 540 osnoise_hist_usage("Entries must be > 10 and < 9999999\n"); 541 break; 542 case 'h': 543 case '?': 544 osnoise_hist_usage(NULL); 545 break; 546 case 'p': 547 params->period = get_llong_from_str(optarg); 548 if (params->period > 10000000) 549 osnoise_hist_usage("Period longer than 10 s\n"); 550 break; 551 case 'P': 552 retval = parse_prio(optarg, ¶ms->sched_param); 553 if (retval == -1) 554 osnoise_hist_usage("Invalid -P priority"); 555 params->set_sched = 1; 556 break; 557 case 'r': 558 params->runtime = get_llong_from_str(optarg); 559 if (params->runtime < 100) 560 osnoise_hist_usage("Runtime shorter than 100 us\n"); 561 break; 562 case 's': 563 params->stop_us = get_llong_from_str(optarg); 564 break; 565 case 'S': 566 params->stop_total_us = get_llong_from_str(optarg); 567 break; 568 case 't': 569 if (optarg) 570 /* skip = */ 571 params->trace_output = &optarg[1]; 572 else 573 params->trace_output = "osnoise_trace.txt"; 574 break; 575 case '0': /* no header */ 576 params->no_header = 1; 577 break; 578 case '1': /* no summary */ 579 params->no_summary = 1; 580 break; 581 case '2': /* no index */ 582 params->no_index = 1; 583 break; 584 case '3': /* with zeros */ 585 params->with_zeros = 1; 586 break; 587 default: 588 osnoise_hist_usage("Invalid option"); 589 } 590 } 591 592 if (geteuid()) { 593 err_msg("rtla needs root permission\n"); 594 exit(EXIT_FAILURE); 595 } 596 597 if (params->no_index && !params->with_zeros) 598 osnoise_hist_usage("no-index set and with-zeros not set - it does not make sense"); 599 600 return params; 601 } 602 603 /* 604 * osnoise_hist_apply_config - apply the hist configs to the initialized tool 605 */ 606 static int 607 osnoise_hist_apply_config(struct osnoise_tool *tool, struct osnoise_hist_params *params) 608 { 609 int retval; 610 611 if (!params->sleep_time) 612 params->sleep_time = 1; 613 614 if (params->cpus) { 615 retval = osnoise_set_cpus(tool->context, params->cpus); 616 if (retval) { 617 err_msg("Failed to apply CPUs config\n"); 618 goto out_err; 619 } 620 } 621 622 if (params->runtime || params->period) { 623 retval = osnoise_set_runtime_period(tool->context, 624 params->runtime, 625 params->period); 626 if (retval) { 627 err_msg("Failed to set runtime and/or period\n"); 628 goto out_err; 629 } 630 } 631 632 if (params->stop_us) { 633 retval = osnoise_set_stop_us(tool->context, params->stop_us); 634 if (retval) { 635 err_msg("Failed to set stop us\n"); 636 goto out_err; 637 } 638 } 639 640 if (params->stop_total_us) { 641 retval = osnoise_set_stop_total_us(tool->context, params->stop_total_us); 642 if (retval) { 643 err_msg("Failed to set stop total us\n"); 644 goto out_err; 645 } 646 } 647 648 return 0; 649 650 out_err: 651 return -1; 652 } 653 654 /* 655 * osnoise_init_hist - initialize a osnoise hist tool with parameters 656 */ 657 static struct osnoise_tool 658 *osnoise_init_hist(struct osnoise_hist_params *params) 659 { 660 struct osnoise_tool *tool; 661 int nr_cpus; 662 663 nr_cpus = sysconf(_SC_NPROCESSORS_CONF); 664 665 tool = osnoise_init_tool("osnoise_hist"); 666 if (!tool) 667 return NULL; 668 669 tool->data = osnoise_alloc_histogram(nr_cpus, params->entries, params->bucket_size); 670 if (!tool->data) 671 goto out_err; 672 673 tool->params = params; 674 675 return tool; 676 677 out_err: 678 osnoise_destroy_tool(tool); 679 return NULL; 680 } 681 682 static int stop_tracing; 683 static void stop_hist(int sig) 684 { 685 stop_tracing = 1; 686 } 687 688 /* 689 * osnoise_hist_set_signals - handles the signal to stop the tool 690 */ 691 static void 692 osnoise_hist_set_signals(struct osnoise_hist_params *params) 693 { 694 signal(SIGINT, stop_hist); 695 if (params->duration) { 696 signal(SIGALRM, stop_hist); 697 alarm(params->duration); 698 } 699 } 700 701 int osnoise_hist_main(int argc, char *argv[]) 702 { 703 struct osnoise_hist_params *params; 704 struct osnoise_tool *record = NULL; 705 struct osnoise_tool *tool = NULL; 706 struct trace_instance *trace; 707 int return_value = 1; 708 int retval; 709 710 params = osnoise_hist_parse_args(argc, argv); 711 if (!params) 712 exit(1); 713 714 tool = osnoise_init_hist(params); 715 if (!tool) { 716 err_msg("Could not init osnoise hist\n"); 717 goto out_exit; 718 } 719 720 retval = osnoise_hist_apply_config(tool, params); 721 if (retval) { 722 err_msg("Could not apply config\n"); 723 goto out_destroy; 724 } 725 726 trace = &tool->trace; 727 728 retval = enable_osnoise(trace); 729 if (retval) { 730 err_msg("Failed to enable osnoise tracer\n"); 731 goto out_destroy; 732 } 733 734 retval = osnoise_init_trace_hist(tool); 735 if (retval) 736 goto out_destroy; 737 738 if (params->set_sched) { 739 retval = set_comm_sched_attr("osnoise/", ¶ms->sched_param); 740 if (retval) { 741 err_msg("Failed to set sched parameters\n"); 742 goto out_hist; 743 } 744 } 745 746 trace_instance_start(trace); 747 748 if (params->trace_output) { 749 record = osnoise_init_trace_tool("osnoise"); 750 if (!record) { 751 err_msg("Failed to enable the trace instance\n"); 752 goto out_hist; 753 } 754 trace_instance_start(&record->trace); 755 } 756 757 tool->start_time = time(NULL); 758 osnoise_hist_set_signals(params); 759 760 while (!stop_tracing) { 761 sleep(params->sleep_time); 762 763 retval = tracefs_iterate_raw_events(trace->tep, 764 trace->inst, 765 NULL, 766 0, 767 collect_registered_events, 768 trace); 769 if (retval < 0) { 770 err_msg("Error iterating on events\n"); 771 goto out_hist; 772 } 773 774 if (!tracefs_trace_is_on(trace->inst)) 775 break; 776 }; 777 778 osnoise_read_trace_hist(tool); 779 780 osnoise_print_stats(params, tool); 781 782 return_value = 0; 783 784 if (!tracefs_trace_is_on(trace->inst)) { 785 printf("rtla timelat hit stop tracing\n"); 786 if (params->trace_output) { 787 printf(" Saving trace to %s\n", params->trace_output); 788 save_trace_to_file(record->trace.inst, params->trace_output); 789 } 790 } 791 792 out_hist: 793 osnoise_free_histogram(tool->data); 794 out_destroy: 795 osnoise_destroy_tool(record); 796 osnoise_destroy_tool(tool); 797 free(params); 798 out_exit: 799 exit(return_value); 800 } 801