diff options
| author | Tomas Glozar <tglozar@redhat.com> | 2026-05-28 12:32:52 +0200 |
|---|---|---|
| committer | Tomas Glozar <tglozar@redhat.com> | 2026-05-28 13:02:48 +0200 |
| commit | 5d9af63e80b5a202e69ce5bcf54af320e46f397a (patch) | |
| tree | 8ffd88dbda98a7f2303d739d043459f83f2d203f /tools/tracing/rtla/src/cli.c | |
| parent | da62fc345846211442d01feeae34b376e4242c89 (diff) | |
rtla: Parse cmdline using libsubcmd
Instead of using getopt_long() directly to parse the command line
arguments given to an RTLA tool, use libsubcmd's parse_options().
Utilizing libsubcmd for parsing command line arguments has several
benefits:
- A help message is automatically generated by libsubcmd from the
specification, removing the need of writing it by hand.
- Options are sorted into groups based on which part of tracing (CPU,
thread, auto-analysis, tuning, histogram) they relate to.
- Common parsing patterns for numerical and boolean values now share
code, with the target variable being stored in the option array.
To avoid duplication of the option parsing logic, RTLA-specific
macros defining struct option values are created:
- RTLA_OPT_* for options common to all tools
- OSNOISE_OPT_* and TIMERLAT_OPT_* for options specific to
osnoise/timerlat tools
- HIST_OPT_* macros for options specific to histogram-based tools.
Individual *_parse_args() functions then construct an array out of
these macros that is then passed to libsubcmd's parse_options().
All code specific to command line options parsing is moved out of the
individual tool files into a new file, cli.c, which also contains the
contents of the rtla.c file. A private header, cli_p.h, is added
alongside the public header cli.h, so that unit tests are able to test
statically declared option callbacks.
Minor changes:
- The return value of tool-level help option changes to 129, as this is
the value set by libsubcmd; this is reflected in affected test cases.
The implementation of help for command-level and tracer-level help
is set to 129 as well for consistency, and the change is reflected in
exit value documentation.
- Related to the above, {rtla,osnoise,timerlat}_usage() are marked
__noreturn and exit() is removed from after they are called for
cleaner code.
- The error messages for invalid argument for options --dma-latency and
-E/--entries were corrected, fixing off-by-one in the limits.
Note that unsetting options (using --no-<opt> syntax) is currently not
implemented for options that use custom callbacks. For --irq and
--thread, it will never be implemented, as they conflict with already
existing --no-irq and --no-thread with a different meaning.
Assisted-by: Composer:composer-1.5
Link: https://lore.kernel.org/r/20260528103254.2990068-5-tglozar@redhat.com
Signed-off-by: Tomas Glozar <tglozar@redhat.com>
Diffstat (limited to 'tools/tracing/rtla/src/cli.c')
| -rw-r--r-- | tools/tracing/rtla/src/cli.c | 537 |
1 files changed, 537 insertions, 0 deletions
diff --git a/tools/tracing/rtla/src/cli.c b/tools/tracing/rtla/src/cli.c new file mode 100644 index 000000000000..7f531519df44 --- /dev/null +++ b/tools/tracing/rtla/src/cli.c @@ -0,0 +1,537 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2021 Red Hat Inc, Daniel Bristot de Oliveira <bristot@kernel.org> + */ + +#define _GNU_SOURCE +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <unistd.h> +#include <sys/types.h> + +#include <linux/compiler.h> + +#define RTLA_ALLOW_CLI_P_H +#include "cli_p.h" + +static const char * const osnoise_top_usage[] = { + "rtla osnoise [top] [<options>] [-h|--help]", + NULL, +}; + +static const char * const osnoise_hist_usage[] = { + "rtla osnoise hist [<options>] [-h|--help]", + NULL, +}; + +static const char * const timerlat_top_usage[] = { + "rtla timerlat [top] [<options>] [-h|--help]", + NULL, +}; + +static const char * const timerlat_hist_usage[] = { + "rtla timerlat hist [<options>] [-h|--help]", + NULL, +}; + +static const char * const hwnoise_usage[] = { + "rtla hwnoise [<options>] [-h|--help]", + NULL, +}; + +static const int common_parse_options_flags = PARSE_OPT_OPTARG_ALLOW_NEXT; + +bool in_unit_test; + +/* + * osnoise_top_parse_args - allocs, parse and fill the cmd line parameters + */ +struct common_params *osnoise_top_parse_args(int argc, char **argv) +{ + struct osnoise_params *params; + struct osnoise_cb_data cb_data; + const char * const *usage; + + params = calloc_fatal(1, sizeof(*params)); + + cb_data.params = params; + cb_data.trace_output = NULL; + + if (strcmp(argv[0], "hwnoise") == 0) { + params->mode = MODE_HWNOISE; + /* + * Reduce CPU usage for 75% to avoid killing the system. + */ + params->runtime = 750000; + params->period = 1000000; + usage = hwnoise_usage; + } else { + usage = osnoise_top_usage; + } + + const struct option osnoise_top_options[] = { + OPT_GROUP("Tracing Options:"), + OSNOISE_OPT_PERIOD, + OSNOISE_OPT_RUNTIME, + RTLA_OPT_STOP('s', "stop", "single sample"), + RTLA_OPT_STOP_TOTAL('S', "stop-total", "total sample"), + OSNOISE_OPT_THRESHOLD, + RTLA_OPT_TRACE_OUTPUT("osnoise", opt_osnoise_trace_output_cb), + + OPT_GROUP("Event Configuration:"), + RTLA_OPT_EVENT, + RTLA_OPT_FILTER, + RTLA_OPT_TRIGGER, + + OPT_GROUP("CPU Configuration:"), + RTLA_OPT_CPUS, + RTLA_OPT_HOUSEKEEPING, + + OPT_GROUP("Thread Configuration:"), + RTLA_OPT_PRIORITY, + RTLA_OPT_CGROUP, + + OPT_GROUP("Output:"), + RTLA_OPT_QUIET, + + OPT_GROUP("System Tuning:"), + RTLA_OPT_TRACE_BUFFER_SIZE, + RTLA_OPT_WARM_UP, + + OPT_GROUP("Auto Analysis and Actions:"), + RTLA_OPT_AUTO(opt_osnoise_auto_cb), + RTLA_OPT_ON_THRESHOLD("stop-total", opt_osnoise_on_threshold_cb), + RTLA_OPT_ON_END(opt_osnoise_on_end_cb), + + OPT_GROUP("General:"), + RTLA_OPT_DURATION, + RTLA_OPT_DEBUG, + + OPT_END(), + }; + + actions_init(¶ms->common.threshold_actions); + actions_init(¶ms->common.end_actions); + + argc = parse_options(argc, (const char **)argv, + osnoise_top_options, + usage, + common_parse_options_flags); + if (argc < 0) + return NULL; + + if (cb_data.trace_output) + actions_add_trace_output(¶ms->common.threshold_actions, cb_data.trace_output); + + if (geteuid()) + fatal("osnoise needs root permission"); + + return ¶ms->common; +} + +/* + * osnoise_hist_parse_args - allocs, parse and fill the cmd line parameters + */ +struct common_params *osnoise_hist_parse_args(int argc, char **argv) +{ + struct osnoise_params *params; + struct osnoise_cb_data cb_data; + + params = calloc_fatal(1, sizeof(*params)); + + cb_data.params = params; + cb_data.trace_output = NULL; + + const struct option osnoise_hist_options[] = { + OPT_GROUP("Tracing Options:"), + OSNOISE_OPT_PERIOD, + OSNOISE_OPT_RUNTIME, + RTLA_OPT_STOP('s', "stop", "single sample"), + RTLA_OPT_STOP_TOTAL('S', "stop-total", "total sample"), + OSNOISE_OPT_THRESHOLD, + RTLA_OPT_TRACE_OUTPUT("osnoise", opt_osnoise_trace_output_cb), + + OPT_GROUP("Event Configuration:"), + RTLA_OPT_EVENT, + RTLA_OPT_FILTER, + RTLA_OPT_TRIGGER, + + OPT_GROUP("CPU Configuration:"), + RTLA_OPT_CPUS, + RTLA_OPT_HOUSEKEEPING, + + OPT_GROUP("Thread Configuration:"), + RTLA_OPT_PRIORITY, + RTLA_OPT_CGROUP, + + OPT_GROUP("Histogram Options:"), + HIST_OPT_BUCKET_SIZE, + HIST_OPT_ENTRIES, + HIST_OPT_NO_HEADER, + HIST_OPT_NO_SUMMARY, + HIST_OPT_NO_INDEX, + HIST_OPT_WITH_ZEROS, + + OPT_GROUP("System Tuning:"), + RTLA_OPT_TRACE_BUFFER_SIZE, + RTLA_OPT_WARM_UP, + + OPT_GROUP("Auto Analysis and Actions:"), + RTLA_OPT_AUTO(opt_osnoise_auto_cb), + RTLA_OPT_ON_THRESHOLD("stop-total", opt_osnoise_on_threshold_cb), + RTLA_OPT_ON_END(opt_osnoise_on_end_cb), + + OPT_GROUP("General:"), + RTLA_OPT_DURATION, + RTLA_OPT_DEBUG, + + OPT_END(), + }; + + actions_init(¶ms->common.threshold_actions); + actions_init(¶ms->common.end_actions); + + /* display data in microseconds */ + params->common.output_divisor = 1000; + params->common.hist.bucket_size = 1; + params->common.hist.entries = 256; + + argc = parse_options(argc, (const char **)argv, + osnoise_hist_options, osnoise_hist_usage, + common_parse_options_flags); + if (argc < 0) + return NULL; + + if (cb_data.trace_output) + actions_add_trace_output(¶ms->common.threshold_actions, cb_data.trace_output); + + if (geteuid()) + fatal("rtla needs root permission"); + + if (params->common.hist.no_index && !params->common.hist.with_zeros) + fatal("no-index set and with-zeros not set - it does not make sense"); + + return ¶ms->common; +} + +struct common_params *timerlat_top_parse_args(int argc, char **argv) +{ + struct timerlat_params *params; + struct timerlat_cb_data cb_data; + + params = calloc_fatal(1, sizeof(*params)); + + cb_data.params = params; + cb_data.trace_output = NULL; + + const struct option timerlat_top_options[] = { + OPT_GROUP("Tracing Options:"), + TIMERLAT_OPT_PERIOD, + RTLA_OPT_STOP('i', "irq", "irq latency"), + RTLA_OPT_STOP_TOTAL('T', "thread", "thread latency"), + TIMERLAT_OPT_STACK, + RTLA_OPT_TRACE_OUTPUT("timerlat", opt_timerlat_trace_output_cb), + + OPT_GROUP("Event Configuration:"), + RTLA_OPT_EVENT, + RTLA_OPT_FILTER, + RTLA_OPT_TRIGGER, + + OPT_GROUP("CPU Configuration:"), + RTLA_OPT_CPUS, + RTLA_OPT_HOUSEKEEPING, + + OPT_GROUP("Thread Configuration:"), + RTLA_OPT_PRIORITY, + RTLA_OPT_CGROUP, + RTLA_OPT_USER_THREADS, + RTLA_OPT_KERNEL_THREADS, + RTLA_OPT_USER_LOAD, + + OPT_GROUP("Output:"), + TIMERLAT_OPT_NANO, + RTLA_OPT_QUIET, + + OPT_GROUP("System Tuning:"), + TIMERLAT_OPT_DMA_LATENCY, + TIMERLAT_OPT_DEEPEST_IDLE_STATE, + RTLA_OPT_TRACE_BUFFER_SIZE, + RTLA_OPT_WARM_UP, + + OPT_GROUP("Auto Analysis and Actions:"), + RTLA_OPT_AUTO(opt_timerlat_auto_cb), + TIMERLAT_OPT_AA_ONLY, + TIMERLAT_OPT_NO_AA, + TIMERLAT_OPT_DUMPS_TASKS, + RTLA_OPT_ON_THRESHOLD("latency", opt_timerlat_on_threshold_cb), + RTLA_OPT_ON_END(opt_timerlat_on_end_cb), + TIMERLAT_OPT_BPF_ACTION, + TIMERLAT_OPT_STACK_FORMAT, + + OPT_GROUP("General:"), + RTLA_OPT_DURATION, + RTLA_OPT_DEBUG, + + OPT_END(), + }; + + actions_init(¶ms->common.threshold_actions); + actions_init(¶ms->common.end_actions); + + /* disabled by default */ + params->dma_latency = -1; + params->deepest_idle_state = -2; + + /* display data in microseconds */ + params->common.output_divisor = 1000; + + /* default to BPF mode */ + params->mode = TRACING_MODE_BPF; + + /* default to truncate stack format */ + params->stack_format = STACK_FORMAT_TRUNCATE; + + argc = parse_options(argc, (const char **)argv, + timerlat_top_options, timerlat_top_usage, + common_parse_options_flags); + if (argc < 0) + return NULL; + + if (cb_data.trace_output) + actions_add_trace_output(¶ms->common.threshold_actions, cb_data.trace_output); + + if (geteuid()) + fatal("rtla needs root permission"); + + /* + * Auto analysis only happens if stop tracing, thus: + */ + if (!params->common.stop_us && !params->common.stop_total_us) + params->no_aa = 1; + + if (params->no_aa && params->common.aa_only) + fatal("--no-aa and --aa-only are mutually exclusive!"); + + if (params->common.kernel_workload && params->common.user_workload) + fatal("--kernel-threads and --user-threads are mutually exclusive!"); + + /* + * If auto-analysis or trace output is enabled, switch from BPF mode to + * mixed mode + */ + if (params->mode == TRACING_MODE_BPF && + (params->common.threshold_actions.present[ACTION_TRACE_OUTPUT] || + params->common.end_actions.present[ACTION_TRACE_OUTPUT] || + !params->no_aa)) + params->mode = TRACING_MODE_MIXED; + + return ¶ms->common; +} + +struct common_params *timerlat_hist_parse_args(int argc, char **argv) +{ + struct timerlat_params *params; + struct timerlat_cb_data cb_data; + + params = calloc_fatal(1, sizeof(*params)); + + cb_data.params = params; + cb_data.trace_output = NULL; + + const struct option timerlat_hist_options[] = { + OPT_GROUP("Tracing Options:"), + TIMERLAT_OPT_PERIOD, + RTLA_OPT_STOP('i', "irq", "irq latency"), + RTLA_OPT_STOP_TOTAL('T', "thread", "thread latency"), + TIMERLAT_OPT_STACK, + RTLA_OPT_TRACE_OUTPUT("timerlat", opt_timerlat_trace_output_cb), + + OPT_GROUP("Event Configuration:"), + RTLA_OPT_EVENT, + RTLA_OPT_FILTER, + RTLA_OPT_TRIGGER, + + OPT_GROUP("CPU Configuration:"), + RTLA_OPT_CPUS, + RTLA_OPT_HOUSEKEEPING, + + OPT_GROUP("Thread Configuration:"), + RTLA_OPT_PRIORITY, + RTLA_OPT_CGROUP, + RTLA_OPT_USER_THREADS, + RTLA_OPT_KERNEL_THREADS, + RTLA_OPT_USER_LOAD, + + OPT_GROUP("Histogram Options:"), + HIST_OPT_BUCKET_SIZE, + HIST_OPT_ENTRIES, + HIST_OPT_NO_IRQ, + HIST_OPT_NO_THREAD, + HIST_OPT_NO_HEADER, + HIST_OPT_NO_SUMMARY, + HIST_OPT_NO_INDEX, + HIST_OPT_WITH_ZEROS, + + OPT_GROUP("Output:"), + TIMERLAT_OPT_NANO, + + OPT_GROUP("System Tuning:"), + TIMERLAT_OPT_DMA_LATENCY, + TIMERLAT_OPT_DEEPEST_IDLE_STATE, + RTLA_OPT_TRACE_BUFFER_SIZE, + RTLA_OPT_WARM_UP, + + OPT_GROUP("Auto Analysis and Actions:"), + RTLA_OPT_AUTO(opt_timerlat_auto_cb), + TIMERLAT_OPT_NO_AA, + TIMERLAT_OPT_DUMPS_TASKS, + RTLA_OPT_ON_THRESHOLD("latency", opt_timerlat_on_threshold_cb), + RTLA_OPT_ON_END(opt_timerlat_on_end_cb), + TIMERLAT_OPT_BPF_ACTION, + TIMERLAT_OPT_STACK_FORMAT, + + OPT_GROUP("General:"), + RTLA_OPT_DURATION, + RTLA_OPT_DEBUG, + + OPT_END(), + }; + + actions_init(¶ms->common.threshold_actions); + actions_init(¶ms->common.end_actions); + + /* disabled by default */ + params->dma_latency = -1; + + /* disabled by default */ + params->deepest_idle_state = -2; + + /* display data in microseconds */ + params->common.output_divisor = 1000; + params->common.hist.bucket_size = 1; + params->common.hist.entries = 256; + + /* default to BPF mode */ + params->mode = TRACING_MODE_BPF; + + /* default to truncate stack format */ + params->stack_format = STACK_FORMAT_TRUNCATE; + + argc = parse_options(argc, (const char **)argv, + timerlat_hist_options, timerlat_hist_usage, + common_parse_options_flags); + if (argc < 0) + return NULL; + + if (cb_data.trace_output) + actions_add_trace_output(¶ms->common.threshold_actions, cb_data.trace_output); + + if (geteuid()) + fatal("rtla needs root permission"); + + if (params->common.hist.no_irq && params->common.hist.no_thread) + fatal("no-irq and no-thread set, there is nothing to do here"); + + if (params->common.hist.no_index && !params->common.hist.with_zeros) + fatal("no-index set with with-zeros is not set - it does not make sense"); + + /* + * Auto analysis only happens if stop tracing, thus: + */ + if (!params->common.stop_us && !params->common.stop_total_us) + params->no_aa = 1; + + if (params->common.kernel_workload && params->common.user_workload) + fatal("--kernel-threads and --user-threads are mutually exclusive!"); + + /* + * If auto-analysis or trace output is enabled, switch from BPF mode to + * mixed mode + */ + if (params->mode == TRACING_MODE_BPF && + (params->common.threshold_actions.present[ACTION_TRACE_OUTPUT] || + params->common.end_actions.present[ACTION_TRACE_OUTPUT] || + !params->no_aa)) + params->mode = TRACING_MODE_MIXED; + + return ¶ms->common; +} + +/* + * rtla_usage - print rtla usage + */ +__noreturn static void rtla_usage(int err) +{ + int i; + + static const char * const msg[] = { + "", + "rtla version " VERSION, + "", + " usage: rtla COMMAND ...", + "", + " commands:", + " osnoise - gives information about the operating system noise (osnoise)", + " hwnoise - gives information about hardware-related noise", + " timerlat - measures the timer irq and thread latency", + "", + NULL, + }; + + for (i = 0; msg[i]; i++) + fprintf(stderr, "%s\n", msg[i]); + exit(err); +} + +/* + * run_tool_command - try to run a rtla tool command + * + * It returns 0 if it fails. The tool's main will generally not + * return as they should call exit(). + */ +int run_tool_command(int argc, char **argv, int start_position) +{ + if (strcmp(argv[start_position], "osnoise") == 0) { + osnoise_main(argc-start_position, &argv[start_position]); + goto ran; + } else if (strcmp(argv[start_position], "hwnoise") == 0) { + hwnoise_main(argc-start_position, &argv[start_position]); + goto ran; + } else if (strcmp(argv[start_position], "timerlat") == 0) { + timerlat_main(argc-start_position, &argv[start_position]); + goto ran; + } + + return 0; +ran: + return 1; +} + +/* Set main as weak to allow overriding it for building unit test binary */ +#pragma weak main + +int main(int argc, char *argv[]) +{ + int retval; + + /* is it an alias? */ + retval = run_tool_command(argc, argv, 0); + if (retval) + exit(0); + + if (argc < 2) + goto usage; + + if (strcmp(argv[1], "-h") == 0) + rtla_usage(129); + else if (strcmp(argv[1], "--help") == 0) + rtla_usage(129); + + retval = run_tool_command(argc, argv, 1); + if (retval) + exit(0); + +usage: + rtla_usage(129); +} |
