Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0
0002 /*
0003  * Copyright (C) 2021 Red Hat Inc, Daniel Bristot de Oliveira <bristot@kernel.org>
0004  */
0005 
0006 #include <getopt.h>
0007 #include <stdlib.h>
0008 #include <string.h>
0009 #include <signal.h>
0010 #include <unistd.h>
0011 #include <stdio.h>
0012 #include <time.h>
0013 
0014 #include "osnoise.h"
0015 #include "utils.h"
0016 
0017 /*
0018  * osnoise top parameters
0019  */
0020 struct osnoise_top_params {
0021     char            *cpus;
0022     char            *monitored_cpus;
0023     char            *trace_output;
0024     unsigned long long  runtime;
0025     unsigned long long  period;
0026     long long       threshold;
0027     long long       stop_us;
0028     long long       stop_total_us;
0029     int         sleep_time;
0030     int         duration;
0031     int         quiet;
0032     int         set_sched;
0033     struct sched_attr   sched_param;
0034     struct trace_events *events;
0035 };
0036 
0037 struct osnoise_top_cpu {
0038     unsigned long long  sum_runtime;
0039     unsigned long long  sum_noise;
0040     unsigned long long  max_noise;
0041     unsigned long long  max_sample;
0042 
0043     unsigned long long  hw_count;
0044     unsigned long long  nmi_count;
0045     unsigned long long  irq_count;
0046     unsigned long long  softirq_count;
0047     unsigned long long  thread_count;
0048 
0049     int         sum_cycles;
0050 };
0051 
0052 struct osnoise_top_data {
0053     struct osnoise_top_cpu  *cpu_data;
0054     int         nr_cpus;
0055 };
0056 
0057 /*
0058  * osnoise_free_top - free runtime data
0059  */
0060 static void
0061 osnoise_free_top(struct osnoise_top_data *data)
0062 {
0063     free(data->cpu_data);
0064     free(data);
0065 }
0066 
0067 /*
0068  * osnoise_alloc_histogram - alloc runtime data
0069  */
0070 static struct osnoise_top_data *osnoise_alloc_top(int nr_cpus)
0071 {
0072     struct osnoise_top_data *data;
0073 
0074     data = calloc(1, sizeof(*data));
0075     if (!data)
0076         return NULL;
0077 
0078     data->nr_cpus = nr_cpus;
0079 
0080     /* one set of histograms per CPU */
0081     data->cpu_data = calloc(1, sizeof(*data->cpu_data) * nr_cpus);
0082     if (!data->cpu_data)
0083         goto cleanup;
0084 
0085     return data;
0086 
0087 cleanup:
0088     osnoise_free_top(data);
0089     return NULL;
0090 }
0091 
0092 /*
0093  * osnoise_top_handler - this is the handler for osnoise tracer events
0094  */
0095 static int
0096 osnoise_top_handler(struct trace_seq *s, struct tep_record *record,
0097             struct tep_event *event, void *context)
0098 {
0099     struct trace_instance *trace = context;
0100     struct osnoise_tool *tool;
0101     unsigned long long val;
0102     struct osnoise_top_cpu *cpu_data;
0103     struct osnoise_top_data *data;
0104     int cpu = record->cpu;
0105 
0106     tool = container_of(trace, struct osnoise_tool, trace);
0107 
0108     data = tool->data;
0109     cpu_data = &data->cpu_data[cpu];
0110 
0111     cpu_data->sum_cycles++;
0112 
0113     tep_get_field_val(s, event, "runtime", record, &val, 1);
0114     update_sum(&cpu_data->sum_runtime, &val);
0115 
0116     tep_get_field_val(s, event, "noise", record, &val, 1);
0117     update_max(&cpu_data->max_noise, &val);
0118     update_sum(&cpu_data->sum_noise, &val);
0119 
0120     tep_get_field_val(s, event, "max_sample", record, &val, 1);
0121     update_max(&cpu_data->max_sample, &val);
0122 
0123     tep_get_field_val(s, event, "hw_count", record, &val, 1);
0124     update_sum(&cpu_data->hw_count, &val);
0125 
0126     tep_get_field_val(s, event, "nmi_count", record, &val, 1);
0127     update_sum(&cpu_data->nmi_count, &val);
0128 
0129     tep_get_field_val(s, event, "irq_count", record, &val, 1);
0130     update_sum(&cpu_data->irq_count, &val);
0131 
0132     tep_get_field_val(s, event, "softirq_count", record, &val, 1);
0133     update_sum(&cpu_data->softirq_count, &val);
0134 
0135     tep_get_field_val(s, event, "thread_count", record, &val, 1);
0136     update_sum(&cpu_data->thread_count, &val);
0137 
0138     return 0;
0139 }
0140 
0141 /*
0142  * osnoise_top_header - print the header of the tool output
0143  */
0144 static void osnoise_top_header(struct osnoise_tool *top)
0145 {
0146     struct trace_seq *s = top->trace.seq;
0147     char duration[26];
0148 
0149     get_duration(top->start_time, duration, sizeof(duration));
0150 
0151     trace_seq_printf(s, "\033[2;37;40m");
0152     trace_seq_printf(s, "                                          Operating System Noise");
0153     trace_seq_printf(s, "                                     ");
0154     trace_seq_printf(s, "                                     ");
0155     trace_seq_printf(s, "\033[0;0;0m");
0156     trace_seq_printf(s, "\n");
0157 
0158     trace_seq_printf(s, "duration: %9s | time is in us\n", duration);
0159 
0160     trace_seq_printf(s, "\033[2;30;47m");
0161     trace_seq_printf(s, "CPU Period       Runtime ");
0162     trace_seq_printf(s, "       Noise ");
0163     trace_seq_printf(s, " %% CPU Aval ");
0164     trace_seq_printf(s, "  Max Noise   Max Single ");
0165     trace_seq_printf(s, "         HW          NMI          IRQ      Softirq       Thread");
0166     trace_seq_printf(s, "\033[0;0;0m");
0167     trace_seq_printf(s, "\n");
0168 }
0169 
0170 /*
0171  * clear_terminal - clears the output terminal
0172  */
0173 static void clear_terminal(struct trace_seq *seq)
0174 {
0175     if (!config_debug)
0176         trace_seq_printf(seq, "\033c");
0177 }
0178 
0179 /*
0180  * osnoise_top_print - prints the output of a given CPU
0181  */
0182 static void osnoise_top_print(struct osnoise_tool *tool, int cpu)
0183 {
0184     struct trace_seq *s = tool->trace.seq;
0185     struct osnoise_top_cpu *cpu_data;
0186     struct osnoise_top_data *data;
0187     int percentage;
0188     int decimal;
0189 
0190     data = tool->data;
0191     cpu_data = &data->cpu_data[cpu];
0192 
0193     if (!cpu_data->sum_runtime)
0194         return;
0195 
0196     percentage = ((cpu_data->sum_runtime - cpu_data->sum_noise) * 10000000)
0197             / cpu_data->sum_runtime;
0198     decimal = percentage % 100000;
0199     percentage = percentage / 100000;
0200 
0201     trace_seq_printf(s, "%3d #%-6d %12llu ", cpu, cpu_data->sum_cycles, cpu_data->sum_runtime);
0202     trace_seq_printf(s, "%12llu ", cpu_data->sum_noise);
0203     trace_seq_printf(s, "  %3d.%05d", percentage, decimal);
0204     trace_seq_printf(s, "%12llu %12llu", cpu_data->max_noise, cpu_data->max_sample);
0205 
0206     trace_seq_printf(s, "%12llu ", cpu_data->hw_count);
0207     trace_seq_printf(s, "%12llu ", cpu_data->nmi_count);
0208     trace_seq_printf(s, "%12llu ", cpu_data->irq_count);
0209     trace_seq_printf(s, "%12llu ", cpu_data->softirq_count);
0210     trace_seq_printf(s, "%12llu\n", cpu_data->thread_count);
0211 }
0212 
0213 /*
0214  * osnoise_print_stats - print data for all cpus
0215  */
0216 static void
0217 osnoise_print_stats(struct osnoise_top_params *params, struct osnoise_tool *top)
0218 {
0219     struct trace_instance *trace = &top->trace;
0220     static int nr_cpus = -1;
0221     int i;
0222 
0223     if (nr_cpus == -1)
0224         nr_cpus = sysconf(_SC_NPROCESSORS_CONF);
0225 
0226     if (!params->quiet)
0227         clear_terminal(trace->seq);
0228 
0229     osnoise_top_header(top);
0230 
0231     for (i = 0; i < nr_cpus; i++) {
0232         if (params->cpus && !params->monitored_cpus[i])
0233             continue;
0234         osnoise_top_print(top, i);
0235     }
0236 
0237     trace_seq_do_printf(trace->seq);
0238     trace_seq_reset(trace->seq);
0239 }
0240 
0241 /*
0242  * osnoise_top_usage - prints osnoise top usage message
0243  */
0244 void osnoise_top_usage(char *usage)
0245 {
0246     int i;
0247 
0248     static const char * const msg[] = {
0249         "  usage: rtla osnoise [top] [-h] [-q] [-D] [-d s] [-a us] [-p us] [-r us] [-s us] [-S us] \\",
0250         "     [-T us] [-t[=file]] [-e sys[:event]] [--filter <filter>] [--trigger <trigger>] \\",
0251         "     [-c cpu-list] [-P priority]",
0252         "",
0253         "     -h/--help: print this menu",
0254         "     -a/--auto: set automatic trace mode, stopping the session if argument in us sample is hit",
0255         "     -p/--period us: osnoise period in us",
0256         "     -r/--runtime us: osnoise runtime in us",
0257         "     -s/--stop us: stop trace if a single sample is higher than the argument in us",
0258         "     -S/--stop-total us: stop trace if the total sample is higher than the argument in us",
0259         "     -T/--threshold us: the minimum delta to be considered a noise",
0260         "     -c/--cpus cpu-list: list of cpus to run osnoise threads",
0261         "     -d/--duration time[s|m|h|d]: duration of the session",
0262         "     -D/--debug: print debug info",
0263         "     -t/--trace[=file]: save the stopped trace to [file|osnoise_trace.txt]",
0264         "     -e/--event <sys:event>: enable the <sys:event> in the trace instance, multiple -e are allowed",
0265         "        --filter <filter>: enable a trace event filter to the previous -e event",
0266         "        --trigger <trigger>: enable a trace event trigger to the previous -e event",
0267         "     -q/--quiet print only a summary at the end",
0268         "     -P/--priority o:prio|r:prio|f:prio|d:runtime:period : set scheduling parameters",
0269         "       o:prio - use SCHED_OTHER with prio",
0270         "       r:prio - use SCHED_RR with prio",
0271         "       f:prio - use SCHED_FIFO with prio",
0272         "       d:runtime[us|ms|s]:period[us|ms|s] - use SCHED_DEADLINE with runtime and period",
0273         "                              in nanoseconds",
0274         NULL,
0275     };
0276 
0277     if (usage)
0278         fprintf(stderr, "%s\n", usage);
0279 
0280     fprintf(stderr, "rtla osnoise top: a per-cpu summary of the OS noise (version %s)\n",
0281             VERSION);
0282 
0283     for (i = 0; msg[i]; i++)
0284         fprintf(stderr, "%s\n", msg[i]);
0285     exit(1);
0286 }
0287 
0288 /*
0289  * osnoise_top_parse_args - allocs, parse and fill the cmd line parameters
0290  */
0291 struct osnoise_top_params *osnoise_top_parse_args(int argc, char **argv)
0292 {
0293     struct osnoise_top_params *params;
0294     struct trace_events *tevent;
0295     int retval;
0296     int c;
0297 
0298     params = calloc(1, sizeof(*params));
0299     if (!params)
0300         exit(1);
0301 
0302     while (1) {
0303         static struct option long_options[] = {
0304             {"auto",        required_argument,  0, 'a'},
0305             {"cpus",        required_argument,  0, 'c'},
0306             {"debug",       no_argument,        0, 'D'},
0307             {"duration",        required_argument,  0, 'd'},
0308             {"event",       required_argument,  0, 'e'},
0309             {"help",        no_argument,        0, 'h'},
0310             {"period",      required_argument,  0, 'p'},
0311             {"priority",        required_argument,  0, 'P'},
0312             {"quiet",       no_argument,        0, 'q'},
0313             {"runtime",     required_argument,  0, 'r'},
0314             {"stop",        required_argument,  0, 's'},
0315             {"stop-total",      required_argument,  0, 'S'},
0316             {"threshold",       required_argument,  0, 'T'},
0317             {"trace",       optional_argument,  0, 't'},
0318             {"trigger",     required_argument,  0, '0'},
0319             {"filter",      required_argument,  0, '1'},
0320             {0, 0, 0, 0}
0321         };
0322 
0323         /* getopt_long stores the option index here. */
0324         int option_index = 0;
0325 
0326         c = getopt_long(argc, argv, "a:c:d:De:hp:P:qr:s:S:t::T:0:1:",
0327                  long_options, &option_index);
0328 
0329         /* Detect the end of the options. */
0330         if (c == -1)
0331             break;
0332 
0333         switch (c) {
0334         case 'a':
0335             /* set sample stop to auto_thresh */
0336             params->stop_us = get_llong_from_str(optarg);
0337 
0338             /* set sample threshold to 1 */
0339             params->threshold = 1;
0340 
0341             /* set trace */
0342             params->trace_output = "osnoise_trace.txt";
0343 
0344             break;
0345         case 'c':
0346             retval = parse_cpu_list(optarg, &params->monitored_cpus);
0347             if (retval)
0348                 osnoise_top_usage("\nInvalid -c cpu list\n");
0349             params->cpus = optarg;
0350             break;
0351         case 'D':
0352             config_debug = 1;
0353             break;
0354         case 'd':
0355             params->duration = parse_seconds_duration(optarg);
0356             if (!params->duration)
0357                 osnoise_top_usage("Invalid -D duration\n");
0358             break;
0359         case 'e':
0360             tevent = trace_event_alloc(optarg);
0361             if (!tevent) {
0362                 err_msg("Error alloc trace event");
0363                 exit(EXIT_FAILURE);
0364             }
0365 
0366             if (params->events)
0367                 tevent->next = params->events;
0368             params->events = tevent;
0369 
0370             break;
0371         case 'h':
0372         case '?':
0373             osnoise_top_usage(NULL);
0374             break;
0375         case 'p':
0376             params->period = get_llong_from_str(optarg);
0377             if (params->period > 10000000)
0378                 osnoise_top_usage("Period longer than 10 s\n");
0379             break;
0380         case 'P':
0381             retval = parse_prio(optarg, &params->sched_param);
0382             if (retval == -1)
0383                 osnoise_top_usage("Invalid -P priority");
0384             params->set_sched = 1;
0385             break;
0386         case 'q':
0387             params->quiet = 1;
0388             break;
0389         case 'r':
0390             params->runtime = get_llong_from_str(optarg);
0391             if (params->runtime < 100)
0392                 osnoise_top_usage("Runtime shorter than 100 us\n");
0393             break;
0394         case 's':
0395             params->stop_us = get_llong_from_str(optarg);
0396             break;
0397         case 'S':
0398             params->stop_total_us = get_llong_from_str(optarg);
0399             break;
0400         case 't':
0401             if (optarg)
0402                 /* skip = */
0403                 params->trace_output = &optarg[1];
0404             else
0405                 params->trace_output = "osnoise_trace.txt";
0406             break;
0407         case 'T':
0408             params->threshold = get_llong_from_str(optarg);
0409             break;
0410         case '0': /* trigger */
0411             if (params->events) {
0412                 retval = trace_event_add_trigger(params->events, optarg);
0413                 if (retval) {
0414                     err_msg("Error adding trigger %s\n", optarg);
0415                     exit(EXIT_FAILURE);
0416                 }
0417             } else {
0418                 osnoise_top_usage("--trigger requires a previous -e\n");
0419             }
0420             break;
0421         case '1': /* filter */
0422             if (params->events) {
0423                 retval = trace_event_add_filter(params->events, optarg);
0424                 if (retval) {
0425                     err_msg("Error adding filter %s\n", optarg);
0426                     exit(EXIT_FAILURE);
0427                 }
0428             } else {
0429                 osnoise_top_usage("--filter requires a previous -e\n");
0430             }
0431             break;
0432         default:
0433             osnoise_top_usage("Invalid option");
0434         }
0435     }
0436 
0437     if (geteuid()) {
0438         err_msg("osnoise needs root permission\n");
0439         exit(EXIT_FAILURE);
0440     }
0441 
0442     return params;
0443 }
0444 
0445 /*
0446  * osnoise_top_apply_config - apply the top configs to the initialized tool
0447  */
0448 static int
0449 osnoise_top_apply_config(struct osnoise_tool *tool, struct osnoise_top_params *params)
0450 {
0451     int retval;
0452 
0453     if (!params->sleep_time)
0454         params->sleep_time = 1;
0455 
0456     if (params->cpus) {
0457         retval = osnoise_set_cpus(tool->context, params->cpus);
0458         if (retval) {
0459             err_msg("Failed to apply CPUs config\n");
0460             goto out_err;
0461         }
0462     }
0463 
0464     if (params->runtime || params->period) {
0465         retval = osnoise_set_runtime_period(tool->context,
0466                             params->runtime,
0467                             params->period);
0468         if (retval) {
0469             err_msg("Failed to set runtime and/or period\n");
0470             goto out_err;
0471         }
0472     }
0473 
0474     if (params->stop_us) {
0475         retval = osnoise_set_stop_us(tool->context, params->stop_us);
0476         if (retval) {
0477             err_msg("Failed to set stop us\n");
0478             goto out_err;
0479         }
0480     }
0481 
0482     if (params->stop_total_us) {
0483         retval = osnoise_set_stop_total_us(tool->context, params->stop_total_us);
0484         if (retval) {
0485             err_msg("Failed to set stop total us\n");
0486             goto out_err;
0487         }
0488     }
0489 
0490     if (params->threshold) {
0491         retval = osnoise_set_tracing_thresh(tool->context, params->threshold);
0492         if (retval) {
0493             err_msg("Failed to set tracing_thresh\n");
0494             goto out_err;
0495         }
0496     }
0497 
0498     return 0;
0499 
0500 out_err:
0501     return -1;
0502 }
0503 
0504 /*
0505  * osnoise_init_top - initialize a osnoise top tool with parameters
0506  */
0507 struct osnoise_tool *osnoise_init_top(struct osnoise_top_params *params)
0508 {
0509     struct osnoise_tool *tool;
0510     int nr_cpus;
0511 
0512     nr_cpus = sysconf(_SC_NPROCESSORS_CONF);
0513 
0514     tool = osnoise_init_tool("osnoise_top");
0515     if (!tool)
0516         return NULL;
0517 
0518     tool->data = osnoise_alloc_top(nr_cpus);
0519     if (!tool->data)
0520         goto out_err;
0521 
0522     tool->params = params;
0523 
0524     tep_register_event_handler(tool->trace.tep, -1, "ftrace", "osnoise",
0525                    osnoise_top_handler, NULL);
0526 
0527     return tool;
0528 
0529 out_err:
0530     osnoise_free_top(tool->data);
0531     osnoise_destroy_tool(tool);
0532     return NULL;
0533 }
0534 
0535 static int stop_tracing;
0536 static void stop_top(int sig)
0537 {
0538     stop_tracing = 1;
0539 }
0540 
0541 /*
0542  * osnoise_top_set_signals - handles the signal to stop the tool
0543  */
0544 static void osnoise_top_set_signals(struct osnoise_top_params *params)
0545 {
0546     signal(SIGINT, stop_top);
0547     if (params->duration) {
0548         signal(SIGALRM, stop_top);
0549         alarm(params->duration);
0550     }
0551 }
0552 
0553 int osnoise_top_main(int argc, char **argv)
0554 {
0555     struct osnoise_top_params *params;
0556     struct osnoise_tool *record = NULL;
0557     struct osnoise_tool *tool = NULL;
0558     struct trace_instance *trace;
0559     int return_value = 1;
0560     int retval;
0561 
0562     params = osnoise_top_parse_args(argc, argv);
0563     if (!params)
0564         exit(1);
0565 
0566     tool = osnoise_init_top(params);
0567     if (!tool) {
0568         err_msg("Could not init osnoise top\n");
0569         goto out_exit;
0570     }
0571 
0572     retval = osnoise_top_apply_config(tool, params);
0573     if (retval) {
0574         err_msg("Could not apply config\n");
0575         goto out_free;
0576     }
0577 
0578     trace = &tool->trace;
0579 
0580     retval = enable_osnoise(trace);
0581     if (retval) {
0582         err_msg("Failed to enable osnoise tracer\n");
0583         goto out_free;
0584     }
0585 
0586     if (params->set_sched) {
0587         retval = set_comm_sched_attr("osnoise/", &params->sched_param);
0588         if (retval) {
0589             err_msg("Failed to set sched parameters\n");
0590             goto out_free;
0591         }
0592     }
0593 
0594     trace_instance_start(trace);
0595 
0596     if (params->trace_output) {
0597         record = osnoise_init_trace_tool("osnoise");
0598         if (!record) {
0599             err_msg("Failed to enable the trace instance\n");
0600             goto out_free;
0601         }
0602 
0603         if (params->events) {
0604             retval = trace_events_enable(&record->trace, params->events);
0605             if (retval)
0606                 goto out_top;
0607         }
0608 
0609         trace_instance_start(&record->trace);
0610     }
0611 
0612     tool->start_time = time(NULL);
0613     osnoise_top_set_signals(params);
0614 
0615     while (!stop_tracing) {
0616         sleep(params->sleep_time);
0617 
0618         retval = tracefs_iterate_raw_events(trace->tep,
0619                             trace->inst,
0620                             NULL,
0621                             0,
0622                             collect_registered_events,
0623                             trace);
0624         if (retval < 0) {
0625             err_msg("Error iterating on events\n");
0626             goto out_top;
0627         }
0628 
0629         if (!params->quiet)
0630             osnoise_print_stats(params, tool);
0631 
0632         if (trace_is_off(&tool->trace, &record->trace))
0633             break;
0634 
0635     }
0636 
0637     osnoise_print_stats(params, tool);
0638 
0639     return_value = 0;
0640 
0641     if (trace_is_off(&tool->trace, &record->trace)) {
0642         printf("osnoise hit stop tracing\n");
0643         if (params->trace_output) {
0644             printf("  Saving trace to %s\n", params->trace_output);
0645             save_trace_to_file(record->trace.inst, params->trace_output);
0646         }
0647     }
0648 
0649 out_top:
0650     trace_events_destroy(&record->trace, params->events);
0651     params->events = NULL;
0652 out_free:
0653     osnoise_free_top(tool->data);
0654     osnoise_destroy_tool(record);
0655     osnoise_destroy_tool(tool);
0656     free(params);
0657 out_exit:
0658     exit(return_value);
0659 }