/* SPDX-License-Identifier: GPL-2.0 */ #pragma once #ifndef RTLA_ALLOW_CLI_P_H #error "Private header file included outside of cli.c module" #endif #include #include #include #include #include "cli.h" #include "osnoise.h" #include "timerlat.h" struct osnoise_cb_data { struct osnoise_params *params; char *trace_output; }; struct timerlat_cb_data { struct timerlat_params *params; char *trace_output; }; /* * Non-zero default values for parameters */ static const int default_dma_latency = -1; /* -1 = unset */ static const int default_deepest_idle_state = -2; /* -1 = disable all, -2 = unset */ static const int default_output_divisor = 1000; static const int default_bucket_size = 1; static const int default_entries = 256; static const enum stack_format default_stack_format = STACK_FORMAT_TRUNCATE; /* * Range checking for long long and int option callbacks. * * Pass a pointer to a const struct as opt->data to enable range checking. * If opt->data is NULL, no range check is performed. */ struct llong_range { long long min; long long max; }; struct int_range { int min; int max; }; #define LLONG_RANGE(lo, hi) \ (&(const struct llong_range){ .min = (lo), .max = (hi) }) #define INT_RANGE(lo, hi) \ (&(const struct int_range){ .min = (lo), .max = (hi) }) static int check_llong_range(const struct option *opt, long long value) { const struct llong_range *range = opt->data; if (!range) return 0; if (value < range->min || value > range->max) { fprintf(stderr, " Error: --%s value %lld is out of range [%lld, %lld]\n", opt->long_name, value, range->min, range->max); return -1; } return 0; } static int check_int_range(const struct option *opt, int value) { const struct int_range *range = opt->data; if (!range) return 0; if (value < range->min || value > range->max) { fprintf(stderr, " Error: --%s value %d is out of range [%d, %d]\n", opt->long_name, value, range->min, range->max); return -1; } return 0; } /* * OPT_CALLBACK variant that populates .data (for range checking). */ #define RTLA_OPT_CALLBACK_DATA(s, l, v, a, h, f, d) \ { .type = OPTION_CALLBACK, .short_name = (s), .long_name = (l), \ .value = (v), .argh = (a), .help = (h), .callback = (f), \ .data = (void *)(d) } #define RTLA_OPT_CALLBACK_DATA_DEFVAL(s, l, v, a, h, f, d, dv) \ { .type = OPTION_CALLBACK, .short_name = (s), .long_name = (l), \ .value = (v), .argh = (a), .help = (h), .callback = (f), \ .data = (void *)(d), .defval = (intptr_t)(dv) } /* * Shorthand macros for integer/long long command line options using * opt_int_callback/opt_llong_callback, with variants that set defval * and/or data (for range checking). * * Note: defval's type is intptr_t. opt_int_callback interprets it directly as * an int, opt_llong_callback interprets it as a pointer to a long long, as * long long does not fit into intptr_t on 32-bit architectures. */ #define RTLA_OPT_LLONG(s, l, v, a, h) \ OPT_CALLBACK(s, l, v, a, h, opt_llong_callback) #define RTLA_OPT_LLONG_DEFVAL(s, l, v, a, h, d) { .type = OPTION_CALLBACK, \ .short_name = (s), .long_name = (l), .value = (v), .argh = (a), \ .help = (h), .callback = opt_llong_callback, .defval = (intptr_t)(d) } #define RTLA_OPT_LLONG_DATA(s, l, v, a, h, d) { .type = OPTION_CALLBACK, \ .short_name = (s), .long_name = (l), .value = (v), .argh = (a), \ .help = (h), .callback = opt_llong_callback, .data = (void *)(d) } #define RTLA_OPT_INT(s, l, v, a, h) \ OPT_CALLBACK(s, l, v, a, h, opt_int_callback) #define RTLA_OPT_INT_DEFVAL(s, l, v, a, h, d) { .type = OPTION_CALLBACK, \ .short_name = (s), .long_name = (l), .value = (v), .argh = (a), \ .help = (h), .callback = opt_int_callback, .defval = (intptr_t)(d) } #define RTLA_OPT_INT_DATA_DEFVAL(s, l, v, a, h, d, dv) { .type = OPTION_CALLBACK, \ .short_name = (s), .long_name = (l), .value = (v), .argh = (a), \ .help = (h), .callback = opt_int_callback, \ .data = (void *)(d), .defval = (intptr_t)(dv) } /* * Macros for command line options common to all tools * * Note: Some of the options are common to both timerlat and osnoise, but * have a slightly different meaning. Such options take additional arguments * that have to be provided by the *_parse_args() function of the corresponding * tool. * * All macros defined here assume the presence of a params variable of * the corresponding tool type (i.e struct timerlat_params or struct osnoise_params) * and a cb_data variable of the matching type. */ #define RTLA_OPT_STOP(short, long, name) OPT_CALLBACK_FLAG(short, long, \ ¶ms->common.stop_us, \ "us", \ "stop trace if " name " is higher than the argument in us", \ opt_llong_callback, PARSE_OPT_NOAUTONEG) #define RTLA_OPT_STOP_TOTAL(short, long, name) OPT_CALLBACK_FLAG(short, long, \ ¶ms->common.stop_total_us, \ "us", \ "stop trace if " name " is higher than the argument in us", \ opt_llong_callback, PARSE_OPT_NOAUTONEG) #define RTLA_OPT_TRACE_OUTPUT(tracer, cb) OPT_CALLBACK_OPTARG('t', "trace", \ (const char **)&cb_data.trace_output, \ tracer "_trace.txt", \ "[file]", \ "save the stopped trace to [file|" tracer "_trace.txt]", \ cb) #define RTLA_OPT_CPUS OPT_CALLBACK('c', "cpus", ¶ms->common, \ "cpu-list", \ "run the tracer only on the given cpus", \ opt_cpus_cb) #define RTLA_OPT_CGROUP OPT_CALLBACK_OPTARG('C', "cgroup", ¶ms->common, \ "[cgroup_name]", NULL, \ "set cgroup, no argument means rtla's cgroup will be inherited", \ opt_cgroup_cb) #define RTLA_OPT_USER_THREADS OPT_CALLBACK_NOOPT('u', "user-threads", params, NULL, \ "use rtla user-space threads instead of kernel-space timerlat threads", \ opt_user_threads_cb) #define RTLA_OPT_KERNEL_THREADS OPT_BOOLEAN('k', "kernel-threads", \ ¶ms->common.kernel_workload, \ "use timerlat kernel-space threads instead of rtla user-space threads") #define RTLA_OPT_USER_LOAD OPT_BOOLEAN('U', "user-load", ¶ms->common.user_data, \ "enable timerlat for user-defined user-space workload") #define RTLA_OPT_DURATION OPT_CALLBACK('d', "duration", ¶ms->common, \ "time[s|m|h|d]", \ "set the duration of the session", \ opt_duration_cb) #define RTLA_OPT_EVENT OPT_CALLBACK('e', "event", ¶ms->common.events, \ "sys:event", \ "enable the in the trace instance, multiple -e are allowed", \ opt_event_cb) #define RTLA_OPT_HOUSEKEEPING OPT_CALLBACK('H', "house-keeping", ¶ms->common, \ "cpu-list", \ "run rtla control threads only on the given cpus", \ opt_housekeeping_cb) #define RTLA_OPT_PRIORITY OPT_CALLBACK('P', "priority", ¶ms->common, \ "o:prio|r:prio|f:prio|d:runtime:period", \ "set scheduling parameters", \ opt_priority_cb) #define RTLA_OPT_TRIGGER OPT_CALLBACK(0, "trigger", ¶ms->common.events, \ "trigger", \ "enable a trace event trigger to the previous -e event", \ opt_trigger_cb) #define RTLA_OPT_FILTER OPT_CALLBACK(0, "filter", ¶ms->common.events, \ "filter", \ "enable a trace event filter to the previous -e event", \ opt_filter_cb) #define RTLA_OPT_QUIET OPT_BOOLEAN('q', "quiet", ¶ms->common.quiet, \ "print only a summary at the end") #define RTLA_OPT_TRACE_BUFFER_SIZE RTLA_OPT_INT(0, "trace-buffer-size", \ ¶ms->common.buffer_size, "kB", \ "set the per-cpu trace buffer size in kB") #define RTLA_OPT_WARM_UP RTLA_OPT_INT(0, "warm-up", ¶ms->common.warmup, "s", \ "let the workload run for s seconds before collecting data") #define RTLA_OPT_AUTO(cb) OPT_CALLBACK('a', "auto", &cb_data, "us", \ "set automatic trace mode, stopping the session if argument in us sample is hit", \ cb) #define RTLA_OPT_ON_THRESHOLD(threshold, cb) OPT_CALLBACK(0, "on-threshold", \ ¶ms->common.threshold_actions, \ "action", \ "define action to be executed at " threshold " threshold, multiple are allowed", \ cb) #define RTLA_OPT_ON_END(cb) OPT_CALLBACK(0, "on-end", ¶ms->common.end_actions, \ "action", \ "define action to be executed at measurement end, multiple are allowed", \ cb) #define RTLA_OPT_DEBUG OPT_BOOLEAN('D', "debug", &config_debug, \ "print debug info") /* * Helper functions for parsing numeric option arguments. */ static void opt_err(const struct option *opt, const char *arg, const char *msg) { fprintf(stderr, " Error: --%s: '%s' %s\n", opt->long_name, arg, msg); } static int strtoll_safe(const struct option *opt, const char *arg, long long *value) { long long tmp; char *end; errno = 0; tmp = strtoll(arg, &end, 10); if (errno || *end || end == arg) { opt_err(opt, arg, "is not a valid number"); return -1; } *value = tmp; return 0; } static int strtoi_safe(const struct option *opt, const char *arg, int *value) { int tmp; if (strtoi(arg, &tmp)) { opt_err(opt, arg, "is not a valid number"); return -1; } *value = tmp; return 0; } /* * Common callback functions for command line options */ static int opt_llong_callback(const struct option *opt, const char *arg, int unset) { long long *value = opt->value; if (unset) { *value = opt->defval ? *(long long *)opt->defval : 0; return 0; } if (!arg) return -1; if (strtoll_safe(opt, arg, value)) return -1; if (check_llong_range(opt, *value)) return -1; return 0; } static int opt_int_callback(const struct option *opt, const char *arg, int unset) { int *value = opt->value; if (unset) { *value = (int)opt->defval; return 0; } if (!arg) return -1; if (strtoi_safe(opt, arg, value)) return -1; if (check_int_range(opt, *value)) return -1; return 0; } static int opt_cpus_cb(const struct option *opt, const char *arg, int unset) { struct common_params *params = opt->value; int retval; if (unset) { CPU_ZERO(¶ms->monitored_cpus); params->cpus = NULL; return 0; } if (!arg) return -1; retval = parse_cpu_set((char *)arg, ¶ms->monitored_cpus); if (retval) { opt_err(opt, arg, "is not a valid cpu set"); return -1; } params->cpus = (char *)arg; return 0; } static int opt_cgroup_cb(const struct option *opt, const char *arg, int unset) { struct common_params *params = opt->value; if (unset) { params->cgroup = 0; params->cgroup_name = NULL; return 0; } params->cgroup = 1; params->cgroup_name = (char *)arg; if (params->cgroup_name && params->cgroup_name[0] == '=') /* Allow -C= next to -C[ ] */ ++params->cgroup_name; return 0; } static int opt_duration_cb(const struct option *opt, const char *arg, int unset) { struct common_params *params = opt->value; if (unset) { params->duration = 0; return 0; } if (!arg) return -1; params->duration = parse_seconds_duration((char *)arg); if (!params->duration) { opt_err(opt, arg, "is not a valid duration"); return -1; } return 0; } static int opt_event_cb(const struct option *opt, const char *arg, int unset) { struct trace_events **events = opt->value; struct trace_events *tevent; if (unset || !arg) return -1; tevent = trace_event_alloc((char *)arg); if (!tevent) fatal("Error alloc trace event"); if (*events) tevent->next = *events; *events = tevent; return 0; } static int opt_housekeeping_cb(const struct option *opt, const char *arg, int unset) { struct common_params *params = opt->value; int retval; if (unset) { params->hk_cpus = 0; CPU_ZERO(¶ms->hk_cpu_set); return 0; } if (!arg) return -1; params->hk_cpus = 1; retval = parse_cpu_set((char *)arg, ¶ms->hk_cpu_set); if (retval) { opt_err(opt, arg, "is not a valid cpu set"); return -1; } return 0; } static int opt_priority_cb(const struct option *opt, const char *arg, int unset) { struct common_params *params = opt->value; int retval; if (unset) { memset(¶ms->sched_param, 0, sizeof(params->sched_param)); params->set_sched = 0; return 0; } if (!arg) return -1; retval = parse_prio((char *)arg, ¶ms->sched_param); if (retval == -1) { opt_err(opt, arg, "is not a valid priority"); return -1; } params->set_sched = 1; return 0; } static int opt_trigger_cb(const struct option *opt, const char *arg, int unset) { struct trace_events **events = opt->value; if (unset || !arg) return -1; if (!*events) { opt_err(opt, arg, "has no previous event to apply to"); return -1; } trace_event_add_trigger(*events, (char *)arg); return 0; } static int opt_filter_cb(const struct option *opt, const char *arg, int unset) { struct trace_events **events = opt->value; if (unset || !arg) return -1; if (!*events) { opt_err(opt, arg, "has no previous event to apply to"); return -1; } trace_event_add_filter(*events, (char *)arg); return 0; } /* * Macros for command line options specific to osnoise */ #define OSNOISE_OPT_PERIOD RTLA_OPT_LLONG_DATA('p', "period", ¶ms->period, "us", \ "osnoise period in us", \ LLONG_RANGE(1, 10000000)) #define OSNOISE_OPT_RUNTIME RTLA_OPT_LLONG_DATA('r', "runtime", ¶ms->runtime, "us", \ "osnoise runtime in us", \ LLONG_RANGE(100, LLONG_MAX)) #define OSNOISE_OPT_THRESHOLD RTLA_OPT_LLONG('T', "threshold", ¶ms->threshold, "us", \ "the minimum delta to be considered a noise") /* * Callback functions for command line options for osnoise tools */ static int opt_osnoise_auto_cb(const struct option *opt, const char *arg, int unset) { struct osnoise_cb_data *cb_data = opt->value; struct osnoise_params *params = cb_data->params; long long auto_thresh; if (unset) { params->common.stop_us = 0; params->threshold = 0; cb_data->trace_output = NULL; return 0; } if (!arg) return -1; if (strtoll_safe(opt, arg, &auto_thresh)) return -1; params->common.stop_us = auto_thresh; params->threshold = 1; if (!cb_data->trace_output) cb_data->trace_output = "osnoise_trace.txt"; return 0; } static int opt_osnoise_trace_output_cb(const struct option *opt, const char *arg, int unset) { const char **trace_output = opt->value; if (unset) { *trace_output = NULL; return 0; } if (!arg) { *trace_output = "osnoise_trace.txt"; } else { *trace_output = (char *)arg; if (*trace_output && (*trace_output)[0] == '=') /* Allow -t= next to -t[ ] */ ++*trace_output; } return 0; } static int opt_osnoise_on_threshold_cb(const struct option *opt, const char *arg, int unset) { struct actions *actions = opt->value; int retval; if (unset || !arg) return -1; retval = actions_parse(actions, (char *)arg, "osnoise_trace.txt"); if (retval) { opt_err(opt, arg, "is not a valid action"); return -1; } return 0; } static int opt_osnoise_on_end_cb(const struct option *opt, const char *arg, int unset) { struct actions *actions = opt->value; int retval; if (unset || !arg) return -1; retval = actions_parse(actions, (char *)arg, "osnoise_trace.txt"); if (retval) { opt_err(opt, arg, "is not a valid action"); return -1; } return 0; } /* * Macros for command line options specific to timerlat */ #define TIMERLAT_OPT_PERIOD RTLA_OPT_LLONG_DATA('p', "period", ¶ms->timerlat_period_us, "us", \ "timerlat period in us", \ LLONG_RANGE(100, 1000000)) #define TIMERLAT_OPT_STACK RTLA_OPT_LLONG('s', "stack", ¶ms->print_stack, "us", \ "save the stack trace at the IRQ if a thread latency is higher than the argument in us") #define TIMERLAT_OPT_NANO OPT_CALLBACK_NOOPT('n', "nano", params, NULL, \ "display data in nanoseconds", \ opt_nano_cb) #define TIMERLAT_OPT_DMA_LATENCY RTLA_OPT_INT_DATA_DEFVAL(0, "dma-latency", \ ¶ms->dma_latency, "us", \ "set /dev/cpu_dma_latency latency to reduce exit from idle latency", \ INT_RANGE(0, 10000), default_dma_latency) #define TIMERLAT_OPT_DEEPEST_IDLE_STATE RTLA_OPT_INT_DATA_DEFVAL(0, "deepest-idle-state", \ ¶ms->deepest_idle_state, "n", \ "only go down to idle state n on cpus used by timerlat to reduce exit from idle latency", \ INT_RANGE(-1, INT_MAX), default_deepest_idle_state) #define TIMERLAT_OPT_AA_ONLY OPT_CALLBACK(0, "aa-only", params, "us", \ "stop if latency is hit, only printing the auto analysis (reduces CPU usage)", \ opt_aa_only_cb) #define TIMERLAT_OPT_NO_AA OPT_BOOLEAN(0, "no-aa", ¶ms->no_aa, \ "disable auto-analysis, reducing rtla timerlat cpu usage") #define TIMERLAT_OPT_DUMPS_TASKS OPT_BOOLEAN(0, "dump-tasks", ¶ms->dump_tasks, \ "prints the task running on all CPUs if stop conditions are met (depends on !--no-aa)") #define TIMERLAT_OPT_BPF_ACTION OPT_STRING(0, "bpf-action", ¶ms->bpf_action_program, \ "program", \ "load and execute BPF program when latency threshold is exceeded") #define TIMERLAT_OPT_STACK_FORMAT OPT_CALLBACK(0, "stack-format", ¶ms->stack_format, "format", \ "set the stack format (truncate, skip, full)", \ opt_stack_format_cb) #define TIMERLAT_OPT_ALIGNED RTLA_OPT_CALLBACK_DATA('A', "aligned", params, "us", \ "align thread wakeups to a specific offset", \ opt_timerlat_align_cb, LLONG_RANGE(0, LLONG_MAX)) /* * Callback functions for command line options for timerlat tools */ static int opt_timerlat_auto_cb(const struct option *opt, const char *arg, int unset) { struct timerlat_cb_data *cb_data = opt->value; struct timerlat_params *params = cb_data->params; long long auto_thresh; if (unset) { params->common.stop_total_us = 0; params->common.stop_us = 0; params->print_stack = 0; cb_data->trace_output = NULL; return 0; } if (!arg) return -1; if (strtoll_safe(opt, arg, &auto_thresh)) return -1; params->common.stop_total_us = auto_thresh; params->common.stop_us = auto_thresh; params->print_stack = auto_thresh; if (!cb_data->trace_output) cb_data->trace_output = "timerlat_trace.txt"; return 0; } static int opt_aa_only_cb(const struct option *opt, const char *arg, int unset) { struct timerlat_params *params = opt->value; long long auto_thresh; if (unset) { params->common.stop_total_us = 0; params->common.stop_us = 0; params->print_stack = 0; params->common.aa_only = 0; return 0; } if (!arg) return -1; if (strtoll_safe(opt, arg, &auto_thresh)) return -1; params->common.stop_total_us = auto_thresh; params->common.stop_us = auto_thresh; params->print_stack = auto_thresh; params->common.aa_only = 1; return 0; } static int opt_timerlat_trace_output_cb(const struct option *opt, const char *arg, int unset) { const char **trace_output = opt->value; if (unset) { *trace_output = NULL; return 0; } if (!arg) { *trace_output = "timerlat_trace.txt"; } else { *trace_output = (char *)arg; if (*trace_output && (*trace_output)[0] == '=') /* Allow -t= next to -t[ ] */ ++*trace_output; } return 0; } static int opt_timerlat_on_threshold_cb(const struct option *opt, const char *arg, int unset) { struct actions *actions = opt->value; int retval; if (unset || !arg) return -1; retval = actions_parse(actions, (char *)arg, "timerlat_trace.txt"); if (retval) { opt_err(opt, arg, "is not a valid action"); return -1; } return 0; } static int opt_timerlat_on_end_cb(const struct option *opt, const char *arg, int unset) { struct actions *actions = opt->value; int retval; if (unset || !arg) return -1; retval = actions_parse(actions, (char *)arg, "timerlat_trace.txt"); if (retval) { opt_err(opt, arg, "is not a valid action"); return -1; } return 0; } static int opt_user_threads_cb(const struct option *opt, const char *arg, int unset) { struct timerlat_params *params = opt->value; if (unset) { params->common.user_workload = false; params->common.user_data = false; return 0; } params->common.user_workload = true; params->common.user_data = true; return 0; } static int opt_nano_cb(const struct option *opt, const char *arg, int unset) { struct timerlat_params *params = opt->value; if (unset) { params->common.output_divisor = default_output_divisor; return 0; } params->common.output_divisor = 1; return 0; } static int opt_stack_format_cb(const struct option *opt, const char *arg, int unset) { int *format = opt->value; if (unset) { *format = default_stack_format; return 0; } if (!arg) return -1; *format = parse_stack_format((char *)arg); if (*format == -1) { opt_err(opt, arg, "is not a valid stack format"); return -1; } return 0; } static int opt_timerlat_align_cb(const struct option *opt, const char *arg, int unset) { struct timerlat_params *params = opt->value; long long val; if (unset) { params->timerlat_align = false; params->timerlat_align_us = 0; return 0; } if (!arg) return -1; if (strtoll_safe(opt, arg, &val)) return -1; if (check_llong_range(opt, val)) return -1; params->timerlat_align = true; params->timerlat_align_us = val; return 0; } /* * Macros for command line options specific to histogram-based tools */ #define HIST_OPT_BUCKET_SIZE RTLA_OPT_INT_DATA_DEFVAL('b', "bucket-size", \ ¶ms->common.hist.bucket_size, "N", \ "set the histogram bucket size (default 1)", \ INT_RANGE(1, 999999), default_bucket_size) #define HIST_OPT_ENTRIES RTLA_OPT_INT_DATA_DEFVAL('E', "entries", \ ¶ms->common.hist.entries, "N", \ "set the number of entries of the histogram (default 256)", \ INT_RANGE(10, 9999999), default_entries) #define HIST_OPT_NO_IRQ OPT_BOOLEAN_FLAG(0, "no-irq", ¶ms->common.hist.no_irq, \ "ignore IRQ latencies", PARSE_OPT_NOAUTONEG) #define HIST_OPT_NO_THREAD OPT_BOOLEAN_FLAG(0, "no-thread", ¶ms->common.hist.no_thread, \ "ignore thread latencies", PARSE_OPT_NOAUTONEG) #define HIST_OPT_NO_HEADER OPT_BOOLEAN(0, "no-header", ¶ms->common.hist.no_header, \ "do not print header") #define HIST_OPT_NO_SUMMARY OPT_BOOLEAN(0, "no-summary", ¶ms->common.hist.no_summary, \ "do not print summary") #define HIST_OPT_NO_INDEX OPT_BOOLEAN(0, "no-index", ¶ms->common.hist.no_index, \ "do not print index") #define HIST_OPT_WITH_ZEROS OPT_BOOLEAN(0, "with-zeros", ¶ms->common.hist.with_zeros, \ "print zero only entries")