summaryrefslogtreecommitdiff
path: root/tools/tracing/rtla
diff options
context:
space:
mode:
Diffstat (limited to 'tools/tracing/rtla')
-rw-r--r--tools/tracing/rtla/.gitignore6
-rw-r--r--tools/tracing/rtla/Build1
-rw-r--r--tools/tracing/rtla/Makefile96
-rw-r--r--tools/tracing/rtla/Makefile.config8
-rw-r--r--tools/tracing/rtla/README.txt31
-rw-r--r--tools/tracing/rtla/example/timerlat_bpf_action.c16
-rw-r--r--tools/tracing/rtla/example/timerlat_load.py (renamed from tools/tracing/rtla/sample/timerlat_load.py)0
-rw-r--r--tools/tracing/rtla/src/Build2
-rw-r--r--tools/tracing/rtla/src/actions.c116
-rw-r--r--tools/tracing/rtla/src/actions.h13
-rw-r--r--tools/tracing/rtla/src/cli.c529
-rw-r--r--tools/tracing/rtla/src/cli.h9
-rw-r--r--tools/tracing/rtla/src/cli_p.h854
-rw-r--r--tools/tracing/rtla/src/common.c167
-rw-r--r--tools/tracing/rtla/src/common.h64
-rw-r--r--tools/tracing/rtla/src/osnoise.c201
-rw-r--r--tools/tracing/rtla/src/osnoise.h14
-rw-r--r--tools/tracing/rtla/src/osnoise_hist.c308
-rw-r--r--tools/tracing/rtla/src/osnoise_top.c277
-rw-r--r--tools/tracing/rtla/src/rtla.c89
-rw-r--r--tools/tracing/rtla/src/timerlat.bpf.c25
-rw-r--r--tools/tracing/rtla/src/timerlat.c74
-rw-r--r--tools/tracing/rtla/src/timerlat.h11
-rw-r--r--tools/tracing/rtla/src/timerlat_aa.c51
-rw-r--r--tools/tracing/rtla/src/timerlat_aa.h2
-rw-r--r--tools/tracing/rtla/src/timerlat_bpf.c85
-rw-r--r--tools/tracing/rtla/src/timerlat_bpf.h18
-rw-r--r--tools/tracing/rtla/src/timerlat_hist.c443
-rw-r--r--tools/tracing/rtla/src/timerlat_top.c411
-rw-r--r--tools/tracing/rtla/src/timerlat_u.c13
-rw-r--r--tools/tracing/rtla/src/timerlat_u.h1
-rw-r--r--tools/tracing/rtla/src/trace.c103
-rw-r--r--tools/tracing/rtla/src/trace.h4
-rw-r--r--tools/tracing/rtla/src/utils.c229
-rw-r--r--tools/tracing/rtla/src/utils.h50
-rw-r--r--tools/tracing/rtla/tests/bpf/bpf_action_map.c25
-rw-r--r--tools/tracing/rtla/tests/engine.sh69
-rw-r--r--tools/tracing/rtla/tests/hwnoise.t5
-rw-r--r--tools/tracing/rtla/tests/osnoise.t116
-rwxr-xr-xtools/tracing/rtla/tests/scripts/check-cgroup-match.sh17
-rwxr-xr-xtools/tracing/rtla/tests/scripts/check-cpus.sh9
-rwxr-xr-xtools/tracing/rtla/tests/scripts/check-housekeeping-cpus.sh4
-rwxr-xr-xtools/tracing/rtla/tests/scripts/check-osnoise-option.sh16
-rwxr-xr-xtools/tracing/rtla/tests/scripts/check-priority.sh8
-rwxr-xr-xtools/tracing/rtla/tests/scripts/check-tracefs-value.sh11
-rwxr-xr-xtools/tracing/rtla/tests/scripts/check-user-kernel-threads.sh16
-rw-r--r--tools/tracing/rtla/tests/scripts/lib/get_workload_pids.sh11
-rw-r--r--tools/tracing/rtla/tests/timerlat.t162
-rw-r--r--tools/tracing/rtla/tests/unit/Build8
-rw-r--r--tools/tracing/rtla/tests/unit/Makefile.unit17
-rw-r--r--tools/tracing/rtla/tests/unit/actions.c393
-rw-r--r--tools/tracing/rtla/tests/unit/cli_opt_callback.c912
-rw-r--r--tools/tracing/rtla/tests/unit/cli_params_assert.h68
-rw-r--r--tools/tracing/rtla/tests/unit/osnoise_hist_cli.c575
-rw-r--r--tools/tracing/rtla/tests/unit/osnoise_top_cli.c521
-rw-r--r--tools/tracing/rtla/tests/unit/timerlat_hist_cli.c740
-rw-r--r--tools/tracing/rtla/tests/unit/timerlat_top_cli.c672
-rw-r--r--tools/tracing/rtla/tests/unit/unit_tests.c39
-rw-r--r--tools/tracing/rtla/tests/unit/utils.c106
59 files changed, 6936 insertions, 1905 deletions
diff --git a/tools/tracing/rtla/.gitignore b/tools/tracing/rtla/.gitignore
index 1a394ad26cc1..2a1c99ddd1bf 100644
--- a/tools/tracing/rtla/.gitignore
+++ b/tools/tracing/rtla/.gitignore
@@ -1,7 +1,9 @@
# SPDX-License-Identifier: GPL-2.0-only
-rtla
-rtla-static
+/rtla
+/rtla-static
+/unit_tests
fixdep
feature
FEATURE-DUMP
*.skel.h
+/libsubcmd/
diff --git a/tools/tracing/rtla/Build b/tools/tracing/rtla/Build
index 6c9d5b36a315..3ce2e0f567fd 100644
--- a/tools/tracing/rtla/Build
+++ b/tools/tracing/rtla/Build
@@ -1 +1,2 @@
rtla-y += src/
+unit_tests-y += tests/unit/
diff --git a/tools/tracing/rtla/Makefile b/tools/tracing/rtla/Makefile
index 746ccf2f5808..387bc6cc18f0 100644
--- a/tools/tracing/rtla/Makefile
+++ b/tools/tracing/rtla/Makefile
@@ -27,18 +27,47 @@ endif
RTLA := $(OUTPUT)rtla
RTLA_IN := $(RTLA)-in.o
+LIBSUBCMD_DIR = $(srctree)/tools/lib/subcmd/
+ifneq ($(OUTPUT),)
+ LIBSUBCMD_OUTPUT = $(abspath $(OUTPUT))/libsubcmd
+else
+ LIBSUBCMD_OUTPUT = $(CURDIR)/libsubcmd
+endif
+LIBSUBCMD = $(LIBSUBCMD_OUTPUT)/libsubcmd.a
+LIBSUBCMD_INCLUDES = $(LIBSUBCMD_OUTPUT)/include
+LIBSUBCMD_MAKEFLAGS = O=$(LIBSUBCMD_OUTPUT) DESTDIR=$(LIBSUBCMD_OUTPUT) prefix= subdir=
+
+TOOLS_INCLUDES = -I$(srctree)/tools/include
+
+ifneq ($(OUTPUT),)
+ LIB_OUTPUT = $(abspath $(OUTPUT))/lib
+else
+ LIB_OUTPUT = $(CURDIR)/lib
+endif
+
+LIB_CTYPE = $(LIB_OUTPUT)/ctype.o
+LIB_CTYPE_SRC = $(srctree)/tools/lib/ctype.c
+
+LIB_STRING = $(LIB_OUTPUT)/string.o
+LIB_STRING_SRC = $(srctree)/tools/lib/string.c
+
+LIB_STR_ERROR_R = $(LIB_OUTPUT)/str_error_r.o
+LIB_STR_ERROR_R_SRC = $(srctree)/tools/lib/str_error_r.c
+
VERSION := $(shell sh -c "make -sC ../../.. kernelversion | grep -v make")
DOCSRC := ../../../Documentation/tools/rtla/
FEATURE_TESTS := libtraceevent
FEATURE_TESTS += libtracefs
FEATURE_TESTS += libcpupower
+FEATURE_TESTS += libcheck
FEATURE_TESTS += libbpf
FEATURE_TESTS += clang-bpf-co-re
FEATURE_TESTS += bpftool-skeletons
FEATURE_DISPLAY := libtraceevent
FEATURE_DISPLAY += libtracefs
FEATURE_DISPLAY += libcpupower
+FEATURE_DISPLAY += libcheck
FEATURE_DISPLAY += libbpf
FEATURE_DISPLAY += clang-bpf-co-re
FEATURE_DISPLAY += bpftool-skeletons
@@ -47,6 +76,7 @@ all: $(RTLA)
include $(srctree)/tools/build/Makefile.include
include Makefile.rtla
+include tests/unit/Makefile.unit
# check for dependencies only on required targets
NON_CONFIG_TARGETS := clean install tarball doc doc_clean doc_install
@@ -63,7 +93,7 @@ ifeq ($(config),1)
include Makefile.config
endif
-CFLAGS += $(INCLUDES) $(LIB_INCLUDES)
+CFLAGS += $(INCLUDES) $(LIB_INCLUDES) $(TOOLS_INCLUDES) -I$(LIBSUBCMD_INCLUDES)
export CFLAGS OUTPUT srctree
@@ -73,30 +103,76 @@ src/timerlat.bpf.o: src/timerlat.bpf.c
src/timerlat.skel.h: src/timerlat.bpf.o
$(QUIET_GENSKEL)$(SYSTEM_BPFTOOL) gen skeleton $< > $@
+
+example/timerlat_bpf_action.o: example/timerlat_bpf_action.c
+ $(QUIET_CLANG)$(CLANG) -g -O2 -target bpf -c $(filter %.c,$^) -o $@
+
+tests/bpf/bpf_action_map.o: tests/bpf/bpf_action_map.c
+ $(QUIET_CLANG)$(CLANG) -g -O2 -target bpf -c $(filter %.c,$^) -o $@
else
src/timerlat.skel.h:
$(Q)echo '/* BPF skeleton is disabled */' > src/timerlat.skel.h
+
+example/timerlat_bpf_action.o: example/timerlat_bpf_action.c
+ $(Q)echo "BPF skeleton support is disabled, skipping example/timerlat_bpf_action.o"
+
+tests/bpf/bpf_action_map.o: tests/bpf/bpf_action_map.c
+ $(Q)echo "BPF skeleton support is disabled, skipping tests/bpf/bpf_action_map.o"
endif
-$(RTLA): $(RTLA_IN)
- $(QUIET_LINK)$(CC) $(LDFLAGS) -o $(RTLA) $(RTLA_IN) $(EXTLIBS)
+$(RTLA): $(RTLA_IN) $(LIBSUBCMD) $(LIB_CTYPE) $(LIB_STRING) $(LIB_STR_ERROR_R)
+ $(QUIET_LINK)$(CC) $(LDFLAGS) -o $(RTLA) $(RTLA_IN) $(LIBSUBCMD) $(LIB_CTYPE) $(LIB_STRING) $(LIB_STR_ERROR_R) $(EXTLIBS)
-static: $(RTLA_IN)
+static: $(RTLA_IN) $(LIBSUBCMD) $(LIB_CTYPE) $(LIB_STRING) $(LIB_STR_ERROR_R)
$(eval LDFLAGS += -static)
- $(QUIET_LINK)$(CC) -static $(LDFLAGS) -o $(RTLA)-static $(RTLA_IN) $(EXTLIBS)
+ $(QUIET_LINK)$(CC) -static $(LDFLAGS) -o $(RTLA)-static $(RTLA_IN) $(LIBSUBCMD) $(LIB_CTYPE) $(LIB_STRING) $(LIB_STR_ERROR_R) $(EXTLIBS)
rtla.%: fixdep FORCE
make -f $(srctree)/tools/build/Makefile.build dir=. $@
-$(RTLA_IN): fixdep FORCE src/timerlat.skel.h
+$(RTLA_IN): fixdep FORCE src/timerlat.skel.h $(LIBSUBCMD_INCLUDES)
make $(build)=rtla
-clean: doc_clean fixdep-clean
+$(LIBSUBCMD_OUTPUT):
+ $(Q)$(MKDIR) -p $@
+
+$(LIBSUBCMD_INCLUDES): FORCE | $(LIBSUBCMD_OUTPUT)
+ $(Q)$(MAKE) -C $(LIBSUBCMD_DIR) $(LIBSUBCMD_MAKEFLAGS) \
+ install_headers
+
+$(LIBSUBCMD): FORCE | $(LIBSUBCMD_OUTPUT)
+ $(Q)$(MAKE) -C $(LIBSUBCMD_DIR) $(LIBSUBCMD_MAKEFLAGS) \
+ $@
+
+$(LIB_OUTPUT):
+ $(Q)$(MKDIR) -p $@
+
+$(LIB_STR_ERROR_R): $(LIB_STR_ERROR_R_SRC) | $(LIB_OUTPUT)
+ $(QUIET_CC)$(CC) $(CFLAGS) -c -o $@ $<
+
+$(LIB_STRING): $(LIB_STRING_SRC) | $(LIB_OUTPUT)
+ $(QUIET_CC)$(CC) $(CFLAGS) -c -o $@ $<
+
+$(LIB_CTYPE): $(LIB_CTYPE_SRC) | $(LIB_OUTPUT)
+ $(QUIET_CC)$(CC) $(CFLAGS) -c -o $@ $<
+
+libsubcmd-clean:
+ $(call QUIET_CLEAN, libsubcmd)
+ $(Q)$(RM) -r -- $(LIBSUBCMD_OUTPUT)
+
+lib-clean:
+ $(call QUIET_CLEAN, lib)
+ $(Q)$(RM) -r -- $(LIB_OUTPUT)
+
+clean: doc_clean fixdep-clean libsubcmd-clean lib-clean
$(call QUIET_CLEAN, rtla)
$(Q)find . -name '*.o' -delete -o -name '\.*.cmd' -delete -o -name '\.*.d' -delete
$(Q)rm -f rtla rtla-static fixdep FEATURE-DUMP rtla-*
$(Q)rm -rf feature
- $(Q)rm -f src/timerlat.bpf.o src/timerlat.skel.h
-check: $(RTLA)
- RTLA=$(RTLA) prove -o -f tests/
+ $(Q)rm -f src/timerlat.bpf.o src/timerlat.skel.h example/timerlat_bpf_action.o
+ $(Q)rm -f $(UNIT_TESTS)
+
+check: $(RTLA) tests/bpf/bpf_action_map.o
+ RTLA=$(RTLA) BPFTOOL=$(SYSTEM_BPFTOOL) prove -o -f -v tests/
+examples: example/timerlat_bpf_action.o
.PHONY: FORCE clean check
diff --git a/tools/tracing/rtla/Makefile.config b/tools/tracing/rtla/Makefile.config
index 07ff5e8f3006..0bdd258b76de 100644
--- a/tools/tracing/rtla/Makefile.config
+++ b/tools/tracing/rtla/Makefile.config
@@ -61,6 +61,14 @@ else
$(info Please install libcpupower-dev/kernel-tools-libs-devel)
endif
+$(call feature_check,libcheck)
+ifeq ($(feature-libcheck), 1)
+ $(call detected,CONFIG_LIBCHECK)
+else
+ $(info libcheck is missing, building without unit tests support.)
+ $(info Please install check-devel/check)
+endif
+
ifndef BUILD_BPF_SKEL
# BPF skeletons are used to implement improved sample collection, enable
# them by default.
diff --git a/tools/tracing/rtla/README.txt b/tools/tracing/rtla/README.txt
index 43e98311d10f..13b4a798b487 100644
--- a/tools/tracing/rtla/README.txt
+++ b/tools/tracing/rtla/README.txt
@@ -12,6 +12,7 @@ RTLA depends on the following libraries and tools:
- libtracefs
- libtraceevent
- libcpupower (optional, for --deepest-idle-state)
+ - libcheck (optional, for unit tests)
For BPF sample collection support, the following extra dependencies are
required:
@@ -41,4 +42,34 @@ For development, we suggest the following steps for compiling rtla:
$ make
$ sudo make install
+Running tests
+
+RTLA has two test suites: a runtime test suite and a unit test suite.
+
+The runtime test suite is available as "make check" (root required) and has
+the following dependencies, in addition to RTLA build dependencies:
+
+- Perl
+- Test::Harness (libtest-harness-perl on Debian/Ubuntu, perl-Test-Harness on Fedora/RHEL)
+- bash
+- coreutils
+- ldd
+- util-linux
+- procps(-ng)
+- bpftool (if rtla is built against libbpf)
+
+as well as the following required system configuration:
+
+- CONFIG_OSNOISE_TRACER=y
+- CONFIG_TIMERLAT_TRACER=y
+- tracefs mounted and readable at /sys/kernel/tracing
+
+The unit test suite is available as "make unit-tests" and has the following
+dependencies:
+
+- libcheck
+
+Unlike the runtime test suite, root is not required to run unit tests, nor is
+a tracefs/osnoise/timerlat-capable kernel required.
+
For further information, please refer to the rtla man page.
diff --git a/tools/tracing/rtla/example/timerlat_bpf_action.c b/tools/tracing/rtla/example/timerlat_bpf_action.c
new file mode 100644
index 000000000000..ac1be049a848
--- /dev/null
+++ b/tools/tracing/rtla/example/timerlat_bpf_action.c
@@ -0,0 +1,16 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/bpf.h>
+#include <bpf/bpf_tracing.h>
+
+char LICENSE[] SEC("license") = "GPL";
+
+struct trace_event_raw_timerlat_sample {
+ unsigned long long timer_latency;
+} __attribute__((preserve_access_index));
+
+SEC("tp/timerlat_action")
+int action_handler(struct trace_event_raw_timerlat_sample *tp_args)
+{
+ bpf_printk("Latency: %lld\n", tp_args->timer_latency);
+ return 0;
+}
diff --git a/tools/tracing/rtla/sample/timerlat_load.py b/tools/tracing/rtla/example/timerlat_load.py
index a819c3588073..a819c3588073 100644
--- a/tools/tracing/rtla/sample/timerlat_load.py
+++ b/tools/tracing/rtla/example/timerlat_load.py
diff --git a/tools/tracing/rtla/src/Build b/tools/tracing/rtla/src/Build
index 329e24a40cf7..a1f3ab927207 100644
--- a/tools/tracing/rtla/src/Build
+++ b/tools/tracing/rtla/src/Build
@@ -11,4 +11,4 @@ rtla-y += timerlat_hist.o
rtla-y += timerlat_u.o
rtla-y += timerlat_aa.o
rtla-y += timerlat_bpf.o
-rtla-y += rtla.o
+rtla-y += cli.o
diff --git a/tools/tracing/rtla/src/actions.c b/tools/tracing/rtla/src/actions.c
index 8945aee58d51..bf13d9d68f16 100644
--- a/tools/tracing/rtla/src/actions.c
+++ b/tools/tracing/rtla/src/actions.c
@@ -15,12 +15,10 @@ void
actions_init(struct actions *self)
{
self->size = action_default_size;
- self->list = calloc(self->size, sizeof(struct action));
+ self->list = calloc_fatal(self->size, sizeof(struct action));
self->len = 0;
self->continue_flag = false;
- memset(&self->present, 0, sizeof(self->present));
-
/* This has to be set by the user */
self->trace_output_inst = NULL;
}
@@ -32,7 +30,9 @@ void
actions_destroy(struct actions *self)
{
/* Free any action-specific data */
- for (struct action *action = self->list; action < self->list + self->len; action++) {
+ struct action *action;
+
+ for_each_action(self, action) {
if (action->type == ACTION_SHELL)
free(action->command);
if (action->type == ACTION_TRACE_OUTPUT)
@@ -50,8 +50,10 @@ static struct action *
actions_new(struct actions *self)
{
if (self->len >= self->size) {
- self->size *= 2;
- self->list = realloc(self->list, self->size * sizeof(struct action));
+ const size_t new_size = self->size * 2;
+
+ self->list = reallocarray_fatal(self->list, new_size, sizeof(struct action));
+ self->size = new_size;
}
return &self->list[self->len++];
@@ -60,25 +62,20 @@ actions_new(struct actions *self)
/*
* actions_add_trace_output - add an action to output trace
*/
-int
+void
actions_add_trace_output(struct actions *self, const char *trace_output)
{
struct action *action = actions_new(self);
self->present[ACTION_TRACE_OUTPUT] = true;
action->type = ACTION_TRACE_OUTPUT;
- action->trace_output = calloc(strlen(trace_output) + 1, sizeof(char));
- if (!action->trace_output)
- return -1;
- strcpy(action->trace_output, trace_output);
-
- return 0;
+ action->trace_output = strdup_fatal(trace_output);
}
/*
* actions_add_trace_output - add an action to send signal to a process
*/
-int
+void
actions_add_signal(struct actions *self, int signal, int pid)
{
struct action *action = actions_new(self);
@@ -87,43 +84,57 @@ actions_add_signal(struct actions *self, int signal, int pid)
action->type = ACTION_SIGNAL;
action->signal = signal;
action->pid = pid;
-
- return 0;
}
/*
* actions_add_shell - add an action to execute a shell command
*/
-int
+void
actions_add_shell(struct actions *self, const char *command)
{
struct action *action = actions_new(self);
self->present[ACTION_SHELL] = true;
action->type = ACTION_SHELL;
- action->command = calloc(strlen(command) + 1, sizeof(char));
- if (!action->command)
- return -1;
- strcpy(action->command, command);
-
- return 0;
+ action->command = strdup_fatal(command);
}
/*
* actions_add_continue - add an action to resume measurement
*/
-int
+void
actions_add_continue(struct actions *self)
{
struct action *action = actions_new(self);
self->present[ACTION_CONTINUE] = true;
action->type = ACTION_CONTINUE;
+}
- return 0;
+static inline const char *__extract_arg(const char *token, const char *opt, size_t opt_len)
+{
+ const size_t tok_len = strlen(token);
+
+ if (tok_len <= opt_len)
+ return NULL;
+
+ if (strncmp(token, opt, opt_len))
+ return NULL;
+
+ return token + opt_len;
}
/*
+ * extract_arg - extract argument value from option token
+ * @token: option token (e.g., "file=trace.txt")
+ * @opt: option name to match (e.g., "file")
+ *
+ * Returns pointer to argument value after "=" if token matches "opt=",
+ * otherwise returns NULL.
+ */
+#define extract_arg(token, opt) __extract_arg(token, opt "=", STRING_LENGTH(opt "="))
+
+/*
* actions_parse - add an action based on text specification
*/
int
@@ -132,6 +143,7 @@ actions_parse(struct actions *self, const char *trigger, const char *tracefn)
enum action_type type = ACTION_NONE;
const char *token;
char trigger_c[strlen(trigger) + 1];
+ const char *arg_value;
/* For ACTION_SIGNAL */
int signal = 0, pid = 0;
@@ -141,6 +153,8 @@ actions_parse(struct actions *self, const char *trigger, const char *tracefn)
strcpy(trigger_c, trigger);
token = strtok(trigger_c, ",");
+ if (!token)
+ return -1;
if (strcmp(token, "trace") == 0)
type = ACTION_TRACE_OUTPUT;
@@ -162,32 +176,36 @@ actions_parse(struct actions *self, const char *trigger, const char *tracefn)
if (token == NULL)
trace_output = tracefn;
else {
- if (strlen(token) > 5 && strncmp(token, "file=", 5) == 0) {
- trace_output = token + 5;
- } else {
+ trace_output = extract_arg(token, "file");
+ if (!trace_output)
/* Invalid argument */
return -1;
- }
token = strtok(NULL, ",");
if (token != NULL)
/* Only one argument allowed */
return -1;
}
- return actions_add_trace_output(self, trace_output);
+ actions_add_trace_output(self, trace_output);
+ break;
case ACTION_SIGNAL:
/* Takes two arguments, num (signal) and pid */
while (token != NULL) {
- if (strlen(token) > 4 && strncmp(token, "num=", 4) == 0) {
- signal = atoi(token + 4);
- } else if (strlen(token) > 4 && strncmp(token, "pid=", 4) == 0) {
- if (strncmp(token + 4, "parent", 7) == 0)
- pid = -1;
- else
- pid = atoi(token + 4);
+ arg_value = extract_arg(token, "num");
+ if (arg_value) {
+ if (strtoi(arg_value, &signal))
+ return -1;
} else {
- /* Invalid argument */
- return -1;
+ arg_value = extract_arg(token, "pid");
+ if (arg_value) {
+ if (strncmp_static(arg_value, "parent") == 0)
+ pid = -1;
+ else if (strtoi(arg_value, &pid))
+ return -1;
+ } else {
+ /* Invalid argument */
+ return -1;
+ }
}
token = strtok(NULL, ",");
@@ -197,21 +215,27 @@ actions_parse(struct actions *self, const char *trigger, const char *tracefn)
/* Missing argument */
return -1;
- return actions_add_signal(self, signal, pid);
+ actions_add_signal(self, signal, pid);
+ break;
case ACTION_SHELL:
if (token == NULL)
return -1;
- if (strlen(token) > 8 && strncmp(token, "command=", 8) == 0)
- return actions_add_shell(self, token + 8);
- return -1;
+ arg_value = extract_arg(token, "command");
+ if (!arg_value)
+ return -1;
+ actions_add_shell(self, arg_value);
+ break;
case ACTION_CONTINUE:
/* Takes no argument */
if (token != NULL)
return -1;
- return actions_add_continue(self);
+ actions_add_continue(self);
+ break;
default:
return -1;
}
+
+ return 0;
}
/*
@@ -223,7 +247,9 @@ actions_perform(struct actions *self)
int pid, retval;
const struct action *action;
- for (action = self->list; action < self->list + self->len; action++) {
+ self->continue_flag = false;
+
+ for_each_action(self, action) {
switch (action->type) {
case ACTION_TRACE_OUTPUT:
retval = save_trace_to_file(self->trace_output_inst, action->trace_output);
diff --git a/tools/tracing/rtla/src/actions.h b/tools/tracing/rtla/src/actions.h
index a4f9b570775b..034048682fef 100644
--- a/tools/tracing/rtla/src/actions.h
+++ b/tools/tracing/rtla/src/actions.h
@@ -42,11 +42,16 @@ struct actions {
struct tracefs_instance *trace_output_inst;
};
+#define for_each_action(actions, action) \
+ for ((action) = (actions)->list; \
+ (action) < (actions)->list + (actions)->len; \
+ (action)++)
+
void actions_init(struct actions *self);
void actions_destroy(struct actions *self);
-int actions_add_trace_output(struct actions *self, const char *trace_output);
-int actions_add_signal(struct actions *self, int signal, int pid);
-int actions_add_shell(struct actions *self, const char *command);
-int actions_add_continue(struct actions *self);
+void actions_add_trace_output(struct actions *self, const char *trace_output);
+void actions_add_signal(struct actions *self, int signal, int pid);
+void actions_add_shell(struct actions *self, const char *command);
+void actions_add_continue(struct actions *self);
int actions_parse(struct actions *self, const char *trigger, const char *tracefn);
int actions_perform(struct actions *self);
diff --git a/tools/tracing/rtla/src/cli.c b/tools/tracing/rtla/src/cli.c
new file mode 100644
index 000000000000..fb8c972c0746
--- /dev/null
+++ b/tools/tracing/rtla/src/cli.c
@@ -0,0 +1,529 @@
+// 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(&params->common.threshold_actions);
+ actions_init(&params->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(&params->common.threshold_actions, cb_data.trace_output);
+
+ if (geteuid() && !in_unit_test)
+ fatal("osnoise needs root permission");
+
+ return &params->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(&params->common.threshold_actions);
+ actions_init(&params->common.end_actions);
+
+ /* set default values */
+ params->common.output_divisor = default_output_divisor;
+ params->common.hist.bucket_size = default_bucket_size;
+ params->common.hist.entries = default_entries;
+
+ 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(&params->common.threshold_actions, cb_data.trace_output);
+
+ if (geteuid() && !in_unit_test)
+ 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 &params->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,
+ TIMERLAT_OPT_ALIGNED,
+
+ 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(&params->common.threshold_actions);
+ actions_init(&params->common.end_actions);
+
+ /* set default values */
+ params->dma_latency = default_dma_latency;
+ params->deepest_idle_state = default_deepest_idle_state;
+ params->common.output_divisor = default_output_divisor;
+ params->stack_format = default_stack_format;
+
+ /* default to BPF mode */
+ params->mode = TRACING_MODE_BPF;
+
+ 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(&params->common.threshold_actions, cb_data.trace_output);
+
+ if (geteuid() && !in_unit_test)
+ 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 &params->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,
+ TIMERLAT_OPT_ALIGNED,
+
+ 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(&params->common.threshold_actions);
+ actions_init(&params->common.end_actions);
+
+ /* set default values */
+ params->dma_latency = default_dma_latency;
+ params->deepest_idle_state = default_deepest_idle_state;
+ params->common.output_divisor = default_output_divisor;
+ params->common.hist.bucket_size = default_bucket_size;
+ params->common.hist.entries = default_entries;
+ params->stack_format = default_stack_format;
+
+ /* default to BPF mode */
+ params->mode = TRACING_MODE_BPF;
+
+ 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(&params->common.threshold_actions, cb_data.trace_output);
+
+ if (geteuid() && !in_unit_test)
+ 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 &params->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);
+}
diff --git a/tools/tracing/rtla/src/cli.h b/tools/tracing/rtla/src/cli.h
new file mode 100644
index 000000000000..633a2322cf89
--- /dev/null
+++ b/tools/tracing/rtla/src/cli.h
@@ -0,0 +1,9 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#pragma once
+
+struct common_params *osnoise_top_parse_args(int argc, char **argv);
+struct common_params *osnoise_hist_parse_args(int argc, char **argv);
+struct common_params *timerlat_top_parse_args(int argc, char **argv);
+struct common_params *timerlat_hist_parse_args(int argc, char **argv);
+
+extern bool in_unit_test;
diff --git a/tools/tracing/rtla/src/cli_p.h b/tools/tracing/rtla/src/cli_p.h
new file mode 100644
index 000000000000..661240d44ad9
--- /dev/null
+++ b/tools/tracing/rtla/src/cli_p.h
@@ -0,0 +1,854 @@
+/* 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 <errno.h>
+#include <limits.h>
+#include <linux/kernel.h>
+#include <subcmd/parse-options.h>
+
+#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, \
+ &params->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, \
+ &params->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", &params->common, \
+ "cpu-list", \
+ "run the tracer only on the given cpus", \
+ opt_cpus_cb)
+
+#define RTLA_OPT_CGROUP OPT_CALLBACK_OPTARG('C', "cgroup", &params->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", \
+ &params->common.kernel_workload, \
+ "use timerlat kernel-space threads instead of rtla user-space threads")
+
+#define RTLA_OPT_USER_LOAD OPT_BOOLEAN('U', "user-load", &params->common.user_data, \
+ "enable timerlat for user-defined user-space workload")
+
+#define RTLA_OPT_DURATION OPT_CALLBACK('d', "duration", &params->common, \
+ "time[s|m|h|d]", \
+ "set the duration of the session", \
+ opt_duration_cb)
+
+#define RTLA_OPT_EVENT OPT_CALLBACK('e', "event", &params->common.events, \
+ "sys:event", \
+ "enable the <sys:event> in the trace instance, multiple -e are allowed", \
+ opt_event_cb)
+
+#define RTLA_OPT_HOUSEKEEPING OPT_CALLBACK('H', "house-keeping", &params->common, \
+ "cpu-list", \
+ "run rtla control threads only on the given cpus", \
+ opt_housekeeping_cb)
+
+#define RTLA_OPT_PRIORITY OPT_CALLBACK('P', "priority", &params->common, \
+ "o:prio|r:prio|f:prio|d:runtime:period", \
+ "set scheduling parameters", \
+ opt_priority_cb)
+
+#define RTLA_OPT_TRIGGER OPT_CALLBACK(0, "trigger", &params->common.events, \
+ "trigger", \
+ "enable a trace event trigger to the previous -e event", \
+ opt_trigger_cb)
+
+#define RTLA_OPT_FILTER OPT_CALLBACK(0, "filter", &params->common.events, \
+ "filter", \
+ "enable a trace event filter to the previous -e event", \
+ opt_filter_cb)
+
+#define RTLA_OPT_QUIET OPT_BOOLEAN('q', "quiet", &params->common.quiet, \
+ "print only a summary at the end")
+
+#define RTLA_OPT_TRACE_BUFFER_SIZE RTLA_OPT_INT(0, "trace-buffer-size", \
+ &params->common.buffer_size, "kB", \
+ "set the per-cpu trace buffer size in kB")
+
+#define RTLA_OPT_WARM_UP RTLA_OPT_INT(0, "warm-up", &params->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", \
+ &params->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", &params->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(&params->monitored_cpus);
+ params->cpus = NULL;
+ return 0;
+ }
+
+ if (!arg)
+ return -1;
+
+ retval = parse_cpu_set((char *)arg, &params->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=<cgroup_name> next to -C[ ]<cgroup_name> */
+ ++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(&params->hk_cpu_set);
+ return 0;
+ }
+
+ if (!arg)
+ return -1;
+
+ params->hk_cpus = 1;
+ retval = parse_cpu_set((char *)arg, &params->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(&params->sched_param, 0, sizeof(params->sched_param));
+ params->set_sched = 0;
+ return 0;
+ }
+
+ if (!arg)
+ return -1;
+
+ retval = parse_prio((char *)arg, &params->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", &params->period, "us", \
+ "osnoise period in us", \
+ LLONG_RANGE(1, 10000000))
+
+#define OSNOISE_OPT_RUNTIME RTLA_OPT_LLONG_DATA('r', "runtime", &params->runtime, "us", \
+ "osnoise runtime in us", \
+ LLONG_RANGE(100, LLONG_MAX))
+
+#define OSNOISE_OPT_THRESHOLD RTLA_OPT_LLONG('T', "threshold", &params->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=<trace_output> next to -t[ ]<trace_output> */
+ ++*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", &params->timerlat_period_us, "us", \
+ "timerlat period in us", \
+ LLONG_RANGE(100, 1000000))
+
+#define TIMERLAT_OPT_STACK RTLA_OPT_LLONG('s', "stack", &params->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", \
+ &params->dma_latency, "us", \
+ "set /dev/cpu_dma_latency latency <us> 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", \
+ &params->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 <us> 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", &params->no_aa, \
+ "disable auto-analysis, reducing rtla timerlat cpu usage")
+
+#define TIMERLAT_OPT_DUMPS_TASKS OPT_BOOLEAN(0, "dump-tasks", &params->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", &params->bpf_action_program, \
+ "program", \
+ "load and execute BPF program when latency threshold is exceeded")
+
+#define TIMERLAT_OPT_STACK_FORMAT OPT_CALLBACK(0, "stack-format", &params->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=<trace_output> next to -t[ ]<trace_output> */
+ ++*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", \
+ &params->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", \
+ &params->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", &params->common.hist.no_irq, \
+ "ignore IRQ latencies", PARSE_OPT_NOAUTONEG)
+
+#define HIST_OPT_NO_THREAD OPT_BOOLEAN_FLAG(0, "no-thread", &params->common.hist.no_thread, \
+ "ignore thread latencies", PARSE_OPT_NOAUTONEG)
+
+#define HIST_OPT_NO_HEADER OPT_BOOLEAN(0, "no-header", &params->common.hist.no_header, \
+ "do not print header")
+
+#define HIST_OPT_NO_SUMMARY OPT_BOOLEAN(0, "no-summary", &params->common.hist.no_summary, \
+ "do not print summary")
+
+#define HIST_OPT_NO_INDEX OPT_BOOLEAN(0, "no-index", &params->common.hist.no_index, \
+ "do not print index")
+
+#define HIST_OPT_WITH_ZEROS OPT_BOOLEAN(0, "with-zeros", &params->common.hist.with_zeros, \
+ "print zero only entries")
+
diff --git a/tools/tracing/rtla/src/common.c b/tools/tracing/rtla/src/common.c
index b197037fc58b..8c7f5e75b2ec 100644
--- a/tools/tracing/rtla/src/common.c
+++ b/tools/tracing/rtla/src/common.c
@@ -4,11 +4,15 @@
#include <pthread.h>
#include <signal.h>
#include <stdlib.h>
+#include <string.h>
#include <unistd.h>
+#include <sys/sysinfo.h>
+
#include "common.h"
-struct trace_instance *trace_inst;
-int stop_tracing;
+struct osnoise_tool *trace_tool;
+volatile int stop_tracing;
+int nr_cpus;
static void stop_trace(int sig)
{
@@ -17,12 +21,16 @@ static void stop_trace(int sig)
* Stop requested twice in a row; abort event processing and
* exit immediately
*/
- tracefs_iterate_stop(trace_inst->inst);
+ if (trace_tool)
+ tracefs_iterate_stop(trace_tool->trace.inst);
return;
}
stop_tracing = 1;
- if (trace_inst)
- trace_instance_stop(trace_inst);
+ if (trace_tool) {
+ trace_instance_stop(&trace_tool->trace);
+ if (trace_tool->record)
+ trace_instance_stop(&trace_tool->record->trace);
+ }
}
/*
@@ -38,6 +46,18 @@ static void set_signals(struct common_params *params)
}
/*
+ * unset_signals - unsets the signals to stop the tool
+ */
+static void unset_signals(struct common_params *params)
+{
+ signal(SIGINT, SIG_DFL);
+ if (params->duration) {
+ alarm(0);
+ signal(SIGALRM, SIG_DFL);
+ }
+}
+
+/*
* common_apply_config - apply common configs to the initialized tool
*/
int
@@ -55,7 +75,7 @@ common_apply_config(struct osnoise_tool *tool, struct common_params *params)
}
if (!params->cpus) {
- for (i = 0; i < sysconf(_SC_NPROCESSORS_CONF); i++)
+ for (i = 0; i < nr_cpus; i++)
CPU_SET(i, &params->monitored_cpus);
}
@@ -95,6 +115,38 @@ out_err:
}
+/**
+ * common_threshold_handler - handle latency threshold overflow
+ * @tool: pointer to the osnoise_tool instance containing trace contexts
+ *
+ * Executes the configured threshold actions (e.g., saving trace, printing,
+ * sending signals). If the continue flag is set (--on-threshold continue),
+ * restarts the auxiliary trace instances to continue monitoring.
+ *
+ * Return: 0 for success, -1 for error.
+ */
+int
+common_threshold_handler(const struct osnoise_tool *tool)
+{
+ actions_perform(&tool->params->threshold_actions);
+
+ if (!should_continue_tracing(tool->params))
+ /* continue flag not set, break */
+ return 0;
+
+ /* continue action reached, re-enable tracing */
+ if (tool->record && trace_instance_start(&tool->record->trace))
+ goto err;
+ if (tool->aa && trace_instance_start(&tool->aa->trace))
+ goto err;
+
+ return 0;
+
+err:
+ err_msg("Error restarting trace\n");
+ return -1;
+}
+
int run_tool(struct tool_ops *ops, int argc, char *argv[])
{
struct common_params *params;
@@ -103,6 +155,7 @@ int run_tool(struct tool_ops *ops, int argc, char *argv[])
bool stopped;
int retval;
+ nr_cpus = get_nprocs_conf();
params = ops->parse_args(argc, argv);
if (!params)
exit(1);
@@ -116,11 +169,10 @@ int run_tool(struct tool_ops *ops, int argc, char *argv[])
tool->params = params;
/*
- * Save trace instance into global variable so that SIGINT can stop
- * the timerlat tracer.
+ * Expose the tool to signal handlers so they can stop the trace.
* Otherwise, rtla could loop indefinitely when overloaded.
*/
- trace_inst = &tool->trace;
+ trace_tool = tool;
retval = ops->apply_config(tool);
if (retval) {
@@ -128,7 +180,7 @@ int run_tool(struct tool_ops *ops, int argc, char *argv[])
goto out_free;
}
- retval = enable_tracer_by_name(trace_inst->inst, ops->tracer);
+ retval = enable_tracer_by_name(tool->trace.inst, ops->tracer);
if (retval) {
err_msg("Failed to enable %s tracer\n", ops->tracer);
goto out_free;
@@ -191,8 +243,10 @@ int run_tool(struct tool_ops *ops, int argc, char *argv[])
params->user.cgroup_name = params->cgroup_name;
retval = pthread_create(&user_thread, NULL, timerlat_u_dispatcher, &params->user);
- if (retval)
+ if (retval) {
err_msg("Error creating timerlat user-space threads\n");
+ goto out_trace;
+ }
}
retval = ops->enable(tool);
@@ -204,7 +258,7 @@ int run_tool(struct tool_ops *ops, int argc, char *argv[])
retval = ops->main(tool);
if (retval)
- goto out_trace;
+ goto out_signals;
if (params->user_workload && !params->user.stopped_running) {
params->user.should_run = 0;
@@ -226,6 +280,8 @@ int run_tool(struct tool_ops *ops, int argc, char *argv[])
if (ops->analyze)
ops->analyze(tool, stopped);
+out_signals:
+ unset_signals(params);
out_trace:
trace_events_destroy(&tool->record->trace, params->events);
params->events = NULL;
@@ -272,17 +328,14 @@ int top_main_loop(struct osnoise_tool *tool)
/* stop tracing requested, do not perform actions */
return 0;
- actions_perform(&params->threshold_actions);
+ retval = common_threshold_handler(tool);
+ if (retval)
+ return retval;
+
- if (!params->threshold_actions.continue_flag)
- /* continue flag not set, break */
+ if (!should_continue_tracing(params))
return 0;
- /* continue action reached, re-enable tracing */
- if (record)
- trace_instance_start(&record->trace);
- if (tool->aa)
- trace_instance_start(&tool->aa->trace);
trace_instance_start(trace);
}
@@ -323,18 +376,14 @@ int hist_main_loop(struct osnoise_tool *tool)
/* stop tracing requested, do not perform actions */
break;
- actions_perform(&params->threshold_actions);
+ retval = common_threshold_handler(tool);
+ if (retval)
+ return retval;
- if (!params->threshold_actions.continue_flag)
- /* continue flag not set, break */
- break;
+ if (!should_continue_tracing(params))
+ return 0;
- /* continue action reached, re-enable tracing */
- if (tool->record)
- trace_instance_start(&tool->record->trace);
- if (tool->aa)
- trace_instance_start(&tool->aa->trace);
- trace_instance_start(&tool->trace);
+ trace_instance_start(trace);
}
/* is there still any user-threads ? */
@@ -348,3 +397,61 @@ int hist_main_loop(struct osnoise_tool *tool)
return retval;
}
+
+int osn_set_stop(struct osnoise_tool *tool)
+{
+ struct common_params *params = tool->params;
+ int retval;
+
+ retval = osnoise_set_stop_us(tool->context, params->stop_us);
+ if (retval) {
+ err_msg("Failed to set stop us\n");
+ return retval;
+ }
+
+ retval = osnoise_set_stop_total_us(tool->context, params->stop_total_us);
+ if (retval) {
+ err_msg("Failed to set stop total us\n");
+ return retval;
+ }
+
+ return 0;
+}
+
+static void print_msg_array(const char * const *msgs)
+{
+ if (!msgs)
+ return;
+
+ for (int i = 0; msgs[i]; i++)
+ fprintf(stderr, "%s\n", msgs[i]);
+}
+
+/*
+ * common_usage - print complete usage information
+ */
+void common_usage(const char *tool, const char *mode,
+ const char *desc, const char * const *start_msgs, const char * const *opt_msgs)
+{
+ static const char * const common_options[] = {
+ " -h/--help: print this menu",
+ NULL
+ };
+ fprintf(stderr, "rtla %s", tool);
+ if (strcmp(mode, ""))
+ fprintf(stderr, " %s", mode);
+ fprintf(stderr, ": %s (version %s)\n\n", desc, VERSION);
+ fprintf(stderr, " usage: [rtla] %s ", tool);
+
+ if (strcmp(mode, "top") == 0)
+ fprintf(stderr, "[top] [-h] ");
+ else
+ fprintf(stderr, "%s [-h] ", mode);
+
+ print_msg_array(start_msgs);
+ fprintf(stderr, "\n");
+ print_msg_array(common_options);
+ print_msg_array(opt_msgs);
+
+ exit(EXIT_SUCCESS);
+}
diff --git a/tools/tracing/rtla/src/common.h b/tools/tracing/rtla/src/common.h
index 9ec2b7632c37..04b287a03f6d 100644
--- a/tools/tracing/rtla/src/common.h
+++ b/tools/tracing/rtla/src/common.h
@@ -51,18 +51,25 @@ struct osnoise_context {
/* -1 as init value because 0 is off */
int orig_opt_workload;
int opt_workload;
+
+ /* -1 as init value because 0 is off */
+ int orig_opt_timerlat_align;
+ int opt_timerlat_align;
+
+ /* 0 as init value */
+ unsigned long long orig_timerlat_align_us;
+ unsigned long long timerlat_align_us;
};
-extern struct trace_instance *trace_inst;
-extern int stop_tracing;
+extern volatile int stop_tracing;
struct hist_params {
- char no_irq;
- char no_thread;
- char no_header;
- char no_summary;
- char no_index;
- char with_zeros;
+ bool no_irq;
+ bool no_thread;
+ bool no_header;
+ bool no_summary;
+ bool no_index;
+ bool with_zeros;
int bucket_size;
int entries;
};
@@ -95,19 +102,21 @@ struct common_params {
/* Other parameters */
struct hist_params hist;
int output_divisor;
- int pretty_output;
- int quiet;
- int user_workload;
- int kernel_workload;
- int user_data;
- int aa_only;
+ bool pretty_output;
+ bool quiet;
+ bool user_workload;
+ bool kernel_workload;
+ bool user_data;
+ bool aa_only;
struct actions threshold_actions;
struct actions end_actions;
struct timerlat_u_params user;
};
-#define for_each_monitored_cpu(cpu, nr_cpus, common) \
+extern int nr_cpus;
+
+#define for_each_monitored_cpu(cpu, common) \
for (cpu = 0; cpu < nr_cpus; cpu++) \
if (!(common)->cpus || CPU_ISSET(cpu, &(common)->monitored_cpus))
@@ -143,6 +152,24 @@ struct tool_ops {
void (*free)(struct osnoise_tool *tool);
};
+/**
+ * should_continue_tracing - check if tracing should continue after threshold
+ * @params: pointer to the common parameters structure
+ *
+ * Returns true if the continue action was configured (--on-threshold continue),
+ * indicating that tracing should be restarted after handling the threshold event.
+ *
+ * Return: 1 if tracing should continue, 0 otherwise.
+ */
+static inline int
+should_continue_tracing(const struct common_params *params)
+{
+ return params->threshold_actions.continue_flag;
+}
+
+int
+common_threshold_handler(const struct osnoise_tool *tool);
+
int osnoise_set_cpus(struct osnoise_context *context, char *cpus);
void osnoise_restore_cpus(struct osnoise_context *context);
@@ -152,7 +179,14 @@ void osnoise_destroy_tool(struct osnoise_tool *top);
struct osnoise_tool *osnoise_init_tool(char *tool_name);
struct osnoise_tool *osnoise_init_trace_tool(const char *tracer);
bool osnoise_trace_is_off(struct osnoise_tool *tool, struct osnoise_tool *record);
+int osnoise_set_stop_us(struct osnoise_context *context, long long stop_us);
+int osnoise_set_stop_total_us(struct osnoise_context *context,
+ long long stop_total_us);
int common_apply_config(struct osnoise_tool *tool, struct common_params *params);
int top_main_loop(struct osnoise_tool *tool);
int hist_main_loop(struct osnoise_tool *tool);
+int osn_set_stop(struct osnoise_tool *tool);
+
+void common_usage(const char *tool, const char *mode,
+ const char *desc, const char * const *start_msgs, const char * const *opt_msgs);
diff --git a/tools/tracing/rtla/src/osnoise.c b/tools/tracing/rtla/src/osnoise.c
index 312c511fa004..4ff5dad013b1 100644
--- a/tools/tracing/rtla/src/osnoise.c
+++ b/tools/tracing/rtla/src/osnoise.c
@@ -15,6 +15,8 @@
#include <stdio.h>
#include <sched.h>
+#include <linux/compiler.h>
+
#include "osnoise.h"
#define DEFAULT_SAMPLE_PERIOD 1000000 /* 1s */
@@ -62,7 +64,7 @@ int osnoise_set_cpus(struct osnoise_context *context, char *cpus)
if (!context->curr_cpus)
return -1;
- snprintf(buffer, 1024, "%s\n", cpus);
+ snprintf(buffer, ARRAY_SIZE(buffer), "%s\n", cpus);
debug_msg("setting cpus to %s from %s", cpus, context->orig_cpus);
@@ -422,6 +424,86 @@ void osnoise_put_timerlat_period_us(struct osnoise_context *context)
}
/*
+ * osnoise_get_timerlat_align_us - read and save the original "timerlat_align_us"
+ */
+static long long
+osnoise_get_timerlat_align_us(struct osnoise_context *context)
+{
+ long long timerlat_align_us;
+
+ if (context->timerlat_align_us != OSNOISE_OPTION_INIT_VAL)
+ return context->timerlat_align_us;
+
+ if (context->orig_timerlat_align_us != OSNOISE_OPTION_INIT_VAL)
+ return context->orig_timerlat_align_us;
+
+ timerlat_align_us = osnoise_read_ll_config("osnoise/timerlat_align_us");
+ if (timerlat_align_us < 0)
+ goto out_err;
+
+ context->orig_timerlat_align_us = timerlat_align_us;
+ return timerlat_align_us;
+
+out_err:
+ return OSNOISE_OPTION_INIT_VAL;
+}
+
+/*
+ * osnoise_set_timerlat_align_us - set "timerlat_align_us"
+ */
+int osnoise_set_timerlat_align_us(struct osnoise_context *context, long long timerlat_align_us)
+{
+ long long curr_timerlat_align_us = osnoise_get_timerlat_align_us(context);
+ int retval;
+
+ if (curr_timerlat_align_us == OSNOISE_OPTION_INIT_VAL)
+ return -1;
+
+ retval = osnoise_write_ll_config("osnoise/timerlat_align_us", timerlat_align_us);
+ if (retval < 0)
+ return -1;
+
+ context->timerlat_align_us = timerlat_align_us;
+
+ return 0;
+}
+
+/*
+ * osnoise_restore_timerlat_align_us - restore "timerlat_align_us"
+ */
+void osnoise_restore_timerlat_align_us(struct osnoise_context *context)
+{
+ int retval;
+
+ if (context->orig_timerlat_align_us == OSNOISE_OPTION_INIT_VAL)
+ return;
+
+ if (context->orig_timerlat_align_us == context->timerlat_align_us)
+ goto out_done;
+
+ retval = osnoise_write_ll_config("osnoise/timerlat_align_us",
+ context->orig_timerlat_align_us);
+ if (retval < 0)
+ err_msg("Could not restore original osnoise timerlat_align_us\n");
+
+out_done:
+ context->timerlat_align_us = OSNOISE_OPTION_INIT_VAL;
+}
+
+/*
+ * osnoise_put_timerlat_align_us - restore original values and cleanup data
+ */
+void osnoise_put_timerlat_align_us(struct osnoise_context *context)
+{
+ osnoise_restore_timerlat_align_us(context);
+
+ if (context->orig_timerlat_align_us == OSNOISE_OPTION_INIT_VAL)
+ return;
+
+ context->orig_timerlat_align_us = OSNOISE_OPTION_INIT_VAL;
+}
+
+/*
* osnoise_get_stop_us - read and save the original "stop_tracing_us"
*/
static long long
@@ -906,6 +988,67 @@ static void osnoise_put_workload(struct osnoise_context *context)
context->orig_opt_workload = OSNOISE_OPTION_INIT_VAL;
}
+static int osnoise_get_timerlat_align(struct osnoise_context *context)
+{
+ if (context->opt_timerlat_align != OSNOISE_OPTION_INIT_VAL)
+ return context->opt_timerlat_align;
+
+ if (context->orig_opt_timerlat_align != OSNOISE_OPTION_INIT_VAL)
+ return context->orig_opt_timerlat_align;
+
+ context->orig_opt_timerlat_align = osnoise_options_get_option("TIMERLAT_ALIGN");
+
+ return context->orig_opt_timerlat_align;
+}
+
+int osnoise_set_timerlat_align(struct osnoise_context *context, bool onoff)
+{
+ int opt_timerlat_align = osnoise_get_timerlat_align(context);
+ int retval;
+
+ if (opt_timerlat_align == OSNOISE_OPTION_INIT_VAL)
+ return -1;
+
+ if (opt_timerlat_align == onoff)
+ return 0;
+
+ retval = osnoise_options_set_option("TIMERLAT_ALIGN", onoff);
+ if (retval < 0)
+ return -2;
+
+ context->opt_timerlat_align = onoff;
+
+ return 0;
+}
+
+static void osnoise_restore_timerlat_align(struct osnoise_context *context)
+{
+ int retval;
+
+ if (context->orig_opt_timerlat_align == OSNOISE_OPTION_INIT_VAL)
+ return;
+
+ if (context->orig_opt_timerlat_align == context->opt_timerlat_align)
+ goto out_done;
+
+ retval = osnoise_options_set_option("TIMERLAT_ALIGN", context->orig_opt_timerlat_align);
+ if (retval < 0)
+ err_msg("Could not restore original TIMERLAT_ALIGN option\n");
+
+out_done:
+ context->orig_opt_timerlat_align = OSNOISE_OPTION_INIT_VAL;
+}
+
+static void osnoise_put_timerlat_align(struct osnoise_context *context)
+{
+ osnoise_restore_timerlat_align(context);
+
+ if (context->orig_opt_timerlat_align == OSNOISE_OPTION_INIT_VAL)
+ return;
+
+ context->orig_opt_timerlat_align = OSNOISE_OPTION_INIT_VAL;
+}
+
enum {
FLAG_CONTEXT_NEWLY_CREATED = (1 << 0),
FLAG_CONTEXT_DELETED = (1 << 1),
@@ -938,9 +1081,7 @@ struct osnoise_context *osnoise_context_alloc(void)
{
struct osnoise_context *context;
- context = calloc(1, sizeof(*context));
- if (!context)
- return NULL;
+ context = calloc_fatal(1, sizeof(*context));
context->orig_stop_us = OSNOISE_OPTION_INIT_VAL;
context->stop_us = OSNOISE_OPTION_INIT_VAL;
@@ -960,6 +1101,12 @@ struct osnoise_context *osnoise_context_alloc(void)
context->orig_opt_workload = OSNOISE_OPTION_INIT_VAL;
context->opt_workload = OSNOISE_OPTION_INIT_VAL;
+ context->orig_opt_timerlat_align = OSNOISE_OPTION_INIT_VAL;
+ context->opt_timerlat_align = OSNOISE_OPTION_INIT_VAL;
+
+ context->orig_timerlat_align_us = OSNOISE_OPTION_INIT_VAL;
+ context->timerlat_align_us = OSNOISE_OPTION_INIT_VAL;
+
osnoise_get_context(context);
return context;
@@ -988,6 +1135,8 @@ void osnoise_put_context(struct osnoise_context *context)
osnoise_put_tracing_thresh(context);
osnoise_put_irq_disable(context);
osnoise_put_workload(context);
+ osnoise_put_timerlat_align(context);
+ osnoise_put_timerlat_align_us(context);
free(context);
}
@@ -1017,24 +1166,16 @@ void osnoise_destroy_tool(struct osnoise_tool *top)
struct osnoise_tool *osnoise_init_tool(char *tool_name)
{
struct osnoise_tool *top;
- int retval;
-
- top = calloc(1, sizeof(*top));
- if (!top)
- return NULL;
+ top = calloc_fatal(1, sizeof(*top));
top->context = osnoise_context_alloc();
- if (!top->context)
- goto out_err;
- retval = trace_instance_init(&top->trace, tool_name);
- if (retval)
- goto out_err;
+ if (trace_instance_init(&top->trace, tool_name)) {
+ osnoise_destroy_tool(top);
+ return NULL;
+ }
return top;
-out_err:
- osnoise_destroy_tool(top);
- return NULL;
}
/*
@@ -1128,18 +1269,6 @@ osnoise_apply_config(struct osnoise_tool *tool, struct osnoise_params *params)
goto out_err;
}
- retval = osnoise_set_stop_us(tool->context, params->common.stop_us);
- if (retval) {
- err_msg("Failed to set stop us\n");
- goto out_err;
- }
-
- retval = osnoise_set_stop_total_us(tool->context, params->common.stop_total_us);
- if (retval) {
- err_msg("Failed to set stop total us\n");
- goto out_err;
- }
-
retval = osnoise_set_tracing_thresh(tool->context, params->threshold);
if (retval) {
err_msg("Failed to set tracing_thresh\n");
@@ -1184,13 +1313,16 @@ int osnoise_enable(struct osnoise_tool *tool)
debug_msg("Error cleaning up the buffer");
return retval;
}
-
}
+ retval = osn_set_stop(tool);
+ if (retval)
+ return retval;
+
return 0;
}
-static void osnoise_usage(int err)
+__noreturn static void osnoise_usage(int err)
{
int i;
@@ -1228,8 +1360,8 @@ int osnoise_main(int argc, char *argv[])
}
if ((strcmp(argv[1], "-h") == 0) || (strcmp(argv[1], "--help") == 0)) {
- osnoise_usage(0);
- } else if (strncmp(argv[1], "-", 1) == 0) {
+ osnoise_usage(129);
+ } else if (str_has_prefix(argv[1], "-")) {
/* the user skipped the tool, call the default one */
run_tool(&osnoise_top_ops, argc, argv);
exit(0);
@@ -1242,8 +1374,7 @@ int osnoise_main(int argc, char *argv[])
}
usage:
- osnoise_usage(1);
- exit(1);
+ osnoise_usage(129);
}
int hwnoise_main(int argc, char *argv[])
diff --git a/tools/tracing/rtla/src/osnoise.h b/tools/tracing/rtla/src/osnoise.h
index 895687030c0b..340ff5a64e6e 100644
--- a/tools/tracing/rtla/src/osnoise.h
+++ b/tools/tracing/rtla/src/osnoise.h
@@ -34,12 +34,7 @@ int osnoise_set_runtime_period(struct osnoise_context *context,
unsigned long long period);
void osnoise_restore_runtime_period(struct osnoise_context *context);
-int osnoise_set_stop_us(struct osnoise_context *context,
- long long stop_us);
void osnoise_restore_stop_us(struct osnoise_context *context);
-
-int osnoise_set_stop_total_us(struct osnoise_context *context,
- long long stop_total_us);
void osnoise_restore_stop_total_us(struct osnoise_context *context);
int osnoise_set_timerlat_period_us(struct osnoise_context *context,
@@ -54,12 +49,16 @@ void osnoise_restore_print_stack(struct osnoise_context *context);
int osnoise_set_print_stack(struct osnoise_context *context,
long long print_stack);
+int osnoise_set_timerlat_align_us(struct osnoise_context *context,
+ long long timerlat_align_us);
+void osnoise_restore_timerlat_align_us(struct osnoise_context *context);
+
+int osnoise_set_timerlat_align(struct osnoise_context *context, bool onoff);
+
int osnoise_set_irq_disable(struct osnoise_context *context, bool onoff);
void osnoise_report_missed_events(struct osnoise_tool *tool);
int osnoise_apply_config(struct osnoise_tool *tool, struct osnoise_params *params);
-int osnoise_hist_main(int argc, char *argv[]);
-int osnoise_top_main(int argc, char **argv);
int osnoise_enable(struct osnoise_tool *tool);
int osnoise_main(int argc, char **argv);
int hwnoise_main(int argc, char **argv);
@@ -68,4 +67,3 @@ extern struct tool_ops timerlat_top_ops, timerlat_hist_ops;
extern struct tool_ops osnoise_top_ops, osnoise_hist_ops;
int run_tool(struct tool_ops *ops, int argc, char *argv[]);
-int hist_main_loop(struct osnoise_tool *tool);
diff --git a/tools/tracing/rtla/src/osnoise_hist.c b/tools/tracing/rtla/src/osnoise_hist.c
index ff8c231e47c4..dfa91d0681f8 100644
--- a/tools/tracing/rtla/src/osnoise_hist.c
+++ b/tools/tracing/rtla/src/osnoise_hist.c
@@ -4,16 +4,15 @@
*/
#define _GNU_SOURCE
-#include <getopt.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>
-#include <errno.h>
#include <stdio.h>
#include <time.h>
#include "osnoise.h"
+#include "cli.h"
struct osnoise_hist_cpu {
int *samples;
@@ -30,7 +29,6 @@ struct osnoise_hist_data {
struct osnoise_hist_cpu *hist;
int entries;
int bucket_size;
- int nr_cpus;
};
/*
@@ -42,7 +40,7 @@ osnoise_free_histogram(struct osnoise_hist_data *data)
int cpu;
/* one histogram for IRQ and one for thread, per CPU */
- for (cpu = 0; cpu < data->nr_cpus; cpu++) {
+ for (cpu = 0; cpu < nr_cpus; cpu++) {
if (data->hist[cpu].samples)
free(data->hist[cpu].samples);
}
@@ -63,7 +61,7 @@ static void osnoise_free_hist_tool(struct osnoise_tool *tool)
* osnoise_alloc_histogram - alloc runtime data
*/
static struct osnoise_hist_data
-*osnoise_alloc_histogram(int nr_cpus, int entries, int bucket_size)
+*osnoise_alloc_histogram(int entries, int bucket_size)
{
struct osnoise_hist_data *data;
int cpu;
@@ -74,7 +72,6 @@ static struct osnoise_hist_data
data->entries = entries;
data->bucket_size = bucket_size;
- data->nr_cpus = nr_cpus;
data->hist = calloc(1, sizeof(*data->hist) * nr_cpus);
if (!data->hist)
@@ -247,7 +244,7 @@ static void osnoise_hist_header(struct osnoise_tool *tool)
if (!params->common.hist.no_index)
trace_seq_printf(s, "Index");
- for_each_monitored_cpu(cpu, data->nr_cpus, &params->common) {
+ for_each_monitored_cpu(cpu, &params->common) {
if (!data->hist[cpu].count)
continue;
@@ -276,8 +273,7 @@ osnoise_print_summary(struct osnoise_params *params,
if (!params->common.hist.no_index)
trace_seq_printf(trace->seq, "count:");
- for_each_monitored_cpu(cpu, data->nr_cpus, &params->common) {
-
+ for_each_monitored_cpu(cpu, &params->common) {
if (!data->hist[cpu].count)
continue;
@@ -288,7 +284,7 @@ osnoise_print_summary(struct osnoise_params *params,
if (!params->common.hist.no_index)
trace_seq_printf(trace->seq, "min: ");
- for_each_monitored_cpu(cpu, data->nr_cpus, &params->common) {
+ for_each_monitored_cpu(cpu, &params->common) {
if (!data->hist[cpu].count)
continue;
@@ -301,7 +297,7 @@ osnoise_print_summary(struct osnoise_params *params,
if (!params->common.hist.no_index)
trace_seq_printf(trace->seq, "avg: ");
- for_each_monitored_cpu(cpu, data->nr_cpus, &params->common) {
+ for_each_monitored_cpu(cpu, &params->common) {
if (!data->hist[cpu].count)
continue;
@@ -317,7 +313,7 @@ osnoise_print_summary(struct osnoise_params *params,
if (!params->common.hist.no_index)
trace_seq_printf(trace->seq, "max: ");
- for_each_monitored_cpu(cpu, data->nr_cpus, &params->common) {
+ for_each_monitored_cpu(cpu, &params->common) {
if (!data->hist[cpu].count)
continue;
@@ -352,7 +348,7 @@ osnoise_print_stats(struct osnoise_tool *tool)
trace_seq_printf(trace->seq, "%-6d",
bucket * data->bucket_size);
- for_each_monitored_cpu(cpu, data->nr_cpus, &params->common) {
+ for_each_monitored_cpu(cpu, &params->common) {
if (!data->hist[cpu].count)
continue;
@@ -388,7 +384,7 @@ osnoise_print_stats(struct osnoise_tool *tool)
if (!params->common.hist.no_index)
trace_seq_printf(trace->seq, "over: ");
- for_each_monitored_cpu(cpu, data->nr_cpus, &params->common) {
+ for_each_monitored_cpu(cpu, &params->common) {
if (!data->hist[cpu].count)
continue;
@@ -405,285 +401,6 @@ osnoise_print_stats(struct osnoise_tool *tool)
}
/*
- * osnoise_hist_usage - prints osnoise hist usage message
- */
-static void osnoise_hist_usage(void)
-{
- int i;
-
- static const char * const msg[] = {
- "",
- " usage: rtla osnoise hist [-h] [-D] [-d s] [-a us] [-p us] [-r us] [-s us] [-S us] \\",
- " [-T us] [-t [file]] [-e sys[:event]] [--filter <filter>] [--trigger <trigger>] \\",
- " [-c cpu-list] [-H cpu-list] [-P priority] [-b N] [-E N] [--no-header] [--no-summary] \\",
- " [--no-index] [--with-zeros] [-C [cgroup_name]] [--warm-up]",
- "",
- " -h/--help: print this menu",
- " -a/--auto: set automatic trace mode, stopping the session if argument in us sample is hit",
- " -p/--period us: osnoise period in us",
- " -r/--runtime us: osnoise runtime in us",
- " -s/--stop us: stop trace if a single sample is higher than the argument in us",
- " -S/--stop-total us: stop trace if the total sample is higher than the argument in us",
- " -T/--threshold us: the minimum delta to be considered a noise",
- " -c/--cpus cpu-list: list of cpus to run osnoise threads",
- " -H/--house-keeping cpus: run rtla control threads only on the given cpus",
- " -C/--cgroup [cgroup_name]: set cgroup, if no cgroup_name is passed, the rtla's cgroup will be inherited",
- " -d/--duration time[s|m|h|d]: duration of the session",
- " -D/--debug: print debug info",
- " -t/--trace [file]: save the stopped trace to [file|osnoise_trace.txt]",
- " -e/--event <sys:event>: enable the <sys:event> in the trace instance, multiple -e are allowed",
- " --filter <filter>: enable a trace event filter to the previous -e event",
- " --trigger <trigger>: enable a trace event trigger to the previous -e event",
- " -b/--bucket-size N: set the histogram bucket size (default 1)",
- " -E/--entries N: set the number of entries of the histogram (default 256)",
- " --no-header: do not print header",
- " --no-summary: do not print summary",
- " --no-index: do not print index",
- " --with-zeros: print zero only entries",
- " -P/--priority o:prio|r:prio|f:prio|d:runtime:period: set scheduling parameters",
- " o:prio - use SCHED_OTHER with prio",
- " r:prio - use SCHED_RR with prio",
- " f:prio - use SCHED_FIFO with prio",
- " d:runtime[us|ms|s]:period[us|ms|s] - use SCHED_DEADLINE with runtime and period",
- " in nanoseconds",
- " --warm-up: let the workload run for s seconds before collecting data",
- " --trace-buffer-size kB: set the per-cpu trace buffer size in kB",
- " --on-threshold <action>: define action to be executed at stop-total threshold, multiple are allowed",
- " --on-end <action>: define action to be executed at measurement end, multiple are allowed",
- NULL,
- };
-
- fprintf(stderr, "rtla osnoise hist: a per-cpu histogram of the OS noise (version %s)\n",
- VERSION);
-
- for (i = 0; msg[i]; i++)
- fprintf(stderr, "%s\n", msg[i]);
-
- exit(EXIT_SUCCESS);
-}
-
-/*
- * osnoise_hist_parse_args - allocs, parse and fill the cmd line parameters
- */
-static struct common_params
-*osnoise_hist_parse_args(int argc, char *argv[])
-{
- struct osnoise_params *params;
- struct trace_events *tevent;
- int retval;
- int c;
- char *trace_output = NULL;
-
- params = calloc(1, sizeof(*params));
- if (!params)
- exit(1);
-
- actions_init(&params->common.threshold_actions);
- actions_init(&params->common.end_actions);
-
- /* display data in microseconds */
- params->common.output_divisor = 1000;
- params->common.hist.bucket_size = 1;
- params->common.hist.entries = 256;
-
- while (1) {
- static struct option long_options[] = {
- {"auto", required_argument, 0, 'a'},
- {"bucket-size", required_argument, 0, 'b'},
- {"entries", required_argument, 0, 'E'},
- {"cpus", required_argument, 0, 'c'},
- {"cgroup", optional_argument, 0, 'C'},
- {"debug", no_argument, 0, 'D'},
- {"duration", required_argument, 0, 'd'},
- {"house-keeping", required_argument, 0, 'H'},
- {"help", no_argument, 0, 'h'},
- {"period", required_argument, 0, 'p'},
- {"priority", required_argument, 0, 'P'},
- {"runtime", required_argument, 0, 'r'},
- {"stop", required_argument, 0, 's'},
- {"stop-total", required_argument, 0, 'S'},
- {"trace", optional_argument, 0, 't'},
- {"event", required_argument, 0, 'e'},
- {"threshold", required_argument, 0, 'T'},
- {"no-header", no_argument, 0, '0'},
- {"no-summary", no_argument, 0, '1'},
- {"no-index", no_argument, 0, '2'},
- {"with-zeros", no_argument, 0, '3'},
- {"trigger", required_argument, 0, '4'},
- {"filter", required_argument, 0, '5'},
- {"warm-up", required_argument, 0, '6'},
- {"trace-buffer-size", required_argument, 0, '7'},
- {"on-threshold", required_argument, 0, '8'},
- {"on-end", required_argument, 0, '9'},
- {0, 0, 0, 0}
- };
-
- c = getopt_long(argc, argv, "a:c:C::b:d:e:E:DhH:p:P:r:s:S:t::T:01234:5:6:7:",
- long_options, NULL);
-
- /* detect the end of the options. */
- if (c == -1)
- break;
-
- switch (c) {
- case 'a':
- /* set sample stop to auto_thresh */
- params->common.stop_us = get_llong_from_str(optarg);
-
- /* set sample threshold to 1 */
- params->threshold = 1;
-
- /* set trace */
- if (!trace_output)
- trace_output = "osnoise_trace.txt";
-
- break;
- case 'b':
- params->common.hist.bucket_size = get_llong_from_str(optarg);
- if (params->common.hist.bucket_size == 0 ||
- params->common.hist.bucket_size >= 1000000)
- fatal("Bucket size needs to be > 0 and <= 1000000");
- break;
- case 'c':
- retval = parse_cpu_set(optarg, &params->common.monitored_cpus);
- if (retval)
- fatal("Invalid -c cpu list");
- params->common.cpus = optarg;
- break;
- case 'C':
- params->common.cgroup = 1;
- params->common.cgroup_name = parse_optional_arg(argc, argv);
- break;
- case 'D':
- config_debug = 1;
- break;
- case 'd':
- params->common.duration = parse_seconds_duration(optarg);
- if (!params->common.duration)
- fatal("Invalid -D duration");
- break;
- case 'e':
- tevent = trace_event_alloc(optarg);
- if (!tevent)
- fatal("Error alloc trace event");
-
- if (params->common.events)
- tevent->next = params->common.events;
-
- params->common.events = tevent;
- break;
- case 'E':
- params->common.hist.entries = get_llong_from_str(optarg);
- if (params->common.hist.entries < 10 ||
- params->common.hist.entries > 9999999)
- fatal("Entries must be > 10 and < 9999999");
- break;
- case 'h':
- case '?':
- osnoise_hist_usage();
- break;
- case 'H':
- params->common.hk_cpus = 1;
- retval = parse_cpu_set(optarg, &params->common.hk_cpu_set);
- if (retval)
- fatal("Error parsing house keeping CPUs");
- break;
- case 'p':
- params->period = get_llong_from_str(optarg);
- if (params->period > 10000000)
- fatal("Period longer than 10 s");
- break;
- case 'P':
- retval = parse_prio(optarg, &params->common.sched_param);
- if (retval == -1)
- fatal("Invalid -P priority");
- params->common.set_sched = 1;
- break;
- case 'r':
- params->runtime = get_llong_from_str(optarg);
- if (params->runtime < 100)
- fatal("Runtime shorter than 100 us");
- break;
- case 's':
- params->common.stop_us = get_llong_from_str(optarg);
- break;
- case 'S':
- params->common.stop_total_us = get_llong_from_str(optarg);
- break;
- case 'T':
- params->threshold = get_llong_from_str(optarg);
- break;
- case 't':
- trace_output = parse_optional_arg(argc, argv);
- if (!trace_output)
- trace_output = "osnoise_trace.txt";
- break;
- case '0': /* no header */
- params->common.hist.no_header = 1;
- break;
- case '1': /* no summary */
- params->common.hist.no_summary = 1;
- break;
- case '2': /* no index */
- params->common.hist.no_index = 1;
- break;
- case '3': /* with zeros */
- params->common.hist.with_zeros = 1;
- break;
- case '4': /* trigger */
- if (params->common.events) {
- retval = trace_event_add_trigger(params->common.events, optarg);
- if (retval)
- fatal("Error adding trigger %s", optarg);
- } else {
- fatal("--trigger requires a previous -e");
- }
- break;
- case '5': /* filter */
- if (params->common.events) {
- retval = trace_event_add_filter(params->common.events, optarg);
- if (retval)
- fatal("Error adding filter %s", optarg);
- } else {
- fatal("--filter requires a previous -e");
- }
- break;
- case '6':
- params->common.warmup = get_llong_from_str(optarg);
- break;
- case '7':
- params->common.buffer_size = get_llong_from_str(optarg);
- break;
- case '8':
- retval = actions_parse(&params->common.threshold_actions, optarg,
- "osnoise_trace.txt");
- if (retval)
- fatal("Invalid action %s", optarg);
- break;
- case '9':
- retval = actions_parse(&params->common.end_actions, optarg,
- "osnoise_trace.txt");
- if (retval)
- fatal("Invalid action %s", optarg);
- break;
- default:
- fatal("Invalid option");
- }
- }
-
- if (trace_output)
- actions_add_trace_output(&params->common.threshold_actions, 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 &params->common;
-}
-
-/*
* osnoise_hist_apply_config - apply the hist configs to the initialized tool
*/
static int
@@ -699,15 +416,12 @@ static struct osnoise_tool
*osnoise_init_hist(struct common_params *params)
{
struct osnoise_tool *tool;
- int nr_cpus;
-
- nr_cpus = sysconf(_SC_NPROCESSORS_CONF);
tool = osnoise_init_tool("osnoise_hist");
if (!tool)
return NULL;
- tool->data = osnoise_alloc_histogram(nr_cpus, params->hist.entries,
+ tool->data = osnoise_alloc_histogram(params->hist.entries,
params->hist.bucket_size);
if (!tool->data)
goto out_err;
diff --git a/tools/tracing/rtla/src/osnoise_top.c b/tools/tracing/rtla/src/osnoise_top.c
index 04c699bdd736..512a6299cb01 100644
--- a/tools/tracing/rtla/src/osnoise_top.c
+++ b/tools/tracing/rtla/src/osnoise_top.c
@@ -4,7 +4,6 @@
*/
#define _GNU_SOURCE
-#include <getopt.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
@@ -13,6 +12,7 @@
#include <time.h>
#include "osnoise.h"
+#include "cli.h"
struct osnoise_top_cpu {
unsigned long long sum_runtime;
@@ -31,7 +31,6 @@ struct osnoise_top_cpu {
struct osnoise_top_data {
struct osnoise_top_cpu *cpu_data;
- int nr_cpus;
};
/*
@@ -51,7 +50,7 @@ static void osnoise_free_top_tool(struct osnoise_tool *tool)
/*
* osnoise_alloc_histogram - alloc runtime data
*/
-static struct osnoise_top_data *osnoise_alloc_top(int nr_cpus)
+static struct osnoise_top_data *osnoise_alloc_top(void)
{
struct osnoise_top_data *data;
@@ -59,8 +58,6 @@ static struct osnoise_top_data *osnoise_alloc_top(int nr_cpus)
if (!data)
return NULL;
- data->nr_cpus = nr_cpus;
-
/* one set of histograms per CPU */
data->cpu_data = calloc(1, sizeof(*data->cpu_data) * nr_cpus);
if (!data->cpu_data)
@@ -232,18 +229,14 @@ osnoise_print_stats(struct osnoise_tool *top)
{
struct osnoise_params *params = to_osnoise_params(top->params);
struct trace_instance *trace = &top->trace;
- static int nr_cpus = -1;
int i;
- if (nr_cpus == -1)
- nr_cpus = sysconf(_SC_NPROCESSORS_CONF);
-
if (!params->common.quiet)
clear_terminal(trace->seq);
osnoise_top_header(top);
- for_each_monitored_cpu(i, nr_cpus, &params->common) {
+ for_each_monitored_cpu(i, &params->common) {
osnoise_top_print(top, i);
}
@@ -253,265 +246,6 @@ osnoise_print_stats(struct osnoise_tool *top)
}
/*
- * osnoise_top_usage - prints osnoise top usage message
- */
-static void osnoise_top_usage(struct osnoise_params *params)
-{
- int i;
-
- static const char * const msg[] = {
- " [-h] [-q] [-D] [-d s] [-a us] [-p us] [-r us] [-s us] [-S us] \\",
- " [-T us] [-t [file]] [-e sys[:event]] [--filter <filter>] [--trigger <trigger>] \\",
- " [-c cpu-list] [-H cpu-list] [-P priority] [-C [cgroup_name]] [--warm-up s]",
- "",
- " -h/--help: print this menu",
- " -a/--auto: set automatic trace mode, stopping the session if argument in us sample is hit",
- " -p/--period us: osnoise period in us",
- " -r/--runtime us: osnoise runtime in us",
- " -s/--stop us: stop trace if a single sample is higher than the argument in us",
- " -S/--stop-total us: stop trace if the total sample is higher than the argument in us",
- " -T/--threshold us: the minimum delta to be considered a noise",
- " -c/--cpus cpu-list: list of cpus to run osnoise threads",
- " -H/--house-keeping cpus: run rtla control threads only on the given cpus",
- " -C/--cgroup [cgroup_name]: set cgroup, if no cgroup_name is passed, the rtla's cgroup will be inherited",
- " -d/--duration time[s|m|h|d]: duration of the session",
- " -D/--debug: print debug info",
- " -t/--trace [file]: save the stopped trace to [file|osnoise_trace.txt]",
- " -e/--event <sys:event>: enable the <sys:event> in the trace instance, multiple -e are allowed",
- " --filter <filter>: enable a trace event filter to the previous -e event",
- " --trigger <trigger>: enable a trace event trigger to the previous -e event",
- " -q/--quiet print only a summary at the end",
- " -P/--priority o:prio|r:prio|f:prio|d:runtime:period : set scheduling parameters",
- " o:prio - use SCHED_OTHER with prio",
- " r:prio - use SCHED_RR with prio",
- " f:prio - use SCHED_FIFO with prio",
- " d:runtime[us|ms|s]:period[us|ms|s] - use SCHED_DEADLINE with runtime and period",
- " in nanoseconds",
- " --warm-up s: let the workload run for s seconds before collecting data",
- " --trace-buffer-size kB: set the per-cpu trace buffer size in kB",
- " --on-threshold <action>: define action to be executed at stop-total threshold, multiple are allowed",
- " --on-end: define action to be executed at measurement end, multiple are allowed",
- NULL,
- };
-
- if (params->mode == MODE_OSNOISE) {
- fprintf(stderr,
- "rtla osnoise top: a per-cpu summary of the OS noise (version %s)\n",
- VERSION);
-
- fprintf(stderr, " usage: rtla osnoise [top]");
- }
-
- if (params->mode == MODE_HWNOISE) {
- fprintf(stderr,
- "rtla hwnoise: a summary of hardware-related noise (version %s)\n",
- VERSION);
-
- fprintf(stderr, " usage: rtla hwnoise");
- }
-
- for (i = 0; msg[i]; i++)
- fprintf(stderr, "%s\n", msg[i]);
-
- exit(EXIT_SUCCESS);
-}
-
-/*
- * 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 trace_events *tevent;
- int retval;
- int c;
- char *trace_output = NULL;
-
- params = calloc(1, sizeof(*params));
- if (!params)
- exit(1);
-
- actions_init(&params->common.threshold_actions);
- actions_init(&params->common.end_actions);
-
- 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;
- }
-
- while (1) {
- static struct option long_options[] = {
- {"auto", required_argument, 0, 'a'},
- {"cpus", required_argument, 0, 'c'},
- {"cgroup", optional_argument, 0, 'C'},
- {"debug", no_argument, 0, 'D'},
- {"duration", required_argument, 0, 'd'},
- {"event", required_argument, 0, 'e'},
- {"house-keeping", required_argument, 0, 'H'},
- {"help", no_argument, 0, 'h'},
- {"period", required_argument, 0, 'p'},
- {"priority", required_argument, 0, 'P'},
- {"quiet", no_argument, 0, 'q'},
- {"runtime", required_argument, 0, 'r'},
- {"stop", required_argument, 0, 's'},
- {"stop-total", required_argument, 0, 'S'},
- {"threshold", required_argument, 0, 'T'},
- {"trace", optional_argument, 0, 't'},
- {"trigger", required_argument, 0, '0'},
- {"filter", required_argument, 0, '1'},
- {"warm-up", required_argument, 0, '2'},
- {"trace-buffer-size", required_argument, 0, '3'},
- {"on-threshold", required_argument, 0, '4'},
- {"on-end", required_argument, 0, '5'},
- {0, 0, 0, 0}
- };
-
- c = getopt_long(argc, argv, "a:c:C::d:De:hH:p:P:qr:s:S:t::T:0:1:2:3:",
- long_options, NULL);
-
- /* Detect the end of the options. */
- if (c == -1)
- break;
-
- switch (c) {
- case 'a':
- /* set sample stop to auto_thresh */
- params->common.stop_us = get_llong_from_str(optarg);
-
- /* set sample threshold to 1 */
- params->threshold = 1;
-
- /* set trace */
- if (!trace_output)
- trace_output = "osnoise_trace.txt";
-
- break;
- case 'c':
- retval = parse_cpu_set(optarg, &params->common.monitored_cpus);
- if (retval)
- fatal("Invalid -c cpu list");
- params->common.cpus = optarg;
- break;
- case 'C':
- params->common.cgroup = 1;
- params->common.cgroup_name = parse_optional_arg(argc, argv);
- break;
- case 'D':
- config_debug = 1;
- break;
- case 'd':
- params->common.duration = parse_seconds_duration(optarg);
- if (!params->common.duration)
- fatal("Invalid -d duration");
- break;
- case 'e':
- tevent = trace_event_alloc(optarg);
- if (!tevent)
- fatal("Error alloc trace event");
-
- if (params->common.events)
- tevent->next = params->common.events;
- params->common.events = tevent;
-
- break;
- case 'h':
- case '?':
- osnoise_top_usage(params);
- break;
- case 'H':
- params->common.hk_cpus = 1;
- retval = parse_cpu_set(optarg, &params->common.hk_cpu_set);
- if (retval)
- fatal("Error parsing house keeping CPUs");
- break;
- case 'p':
- params->period = get_llong_from_str(optarg);
- if (params->period > 10000000)
- fatal("Period longer than 10 s");
- break;
- case 'P':
- retval = parse_prio(optarg, &params->common.sched_param);
- if (retval == -1)
- fatal("Invalid -P priority");
- params->common.set_sched = 1;
- break;
- case 'q':
- params->common.quiet = 1;
- break;
- case 'r':
- params->runtime = get_llong_from_str(optarg);
- if (params->runtime < 100)
- fatal("Runtime shorter than 100 us");
- break;
- case 's':
- params->common.stop_us = get_llong_from_str(optarg);
- break;
- case 'S':
- params->common.stop_total_us = get_llong_from_str(optarg);
- break;
- case 't':
- trace_output = parse_optional_arg(argc, argv);
- if (!trace_output)
- trace_output = "osnoise_trace.txt";
- break;
- case 'T':
- params->threshold = get_llong_from_str(optarg);
- break;
- case '0': /* trigger */
- if (params->common.events) {
- retval = trace_event_add_trigger(params->common.events, optarg);
- if (retval)
- fatal("Error adding trigger %s", optarg);
- } else {
- fatal("--trigger requires a previous -e");
- }
- break;
- case '1': /* filter */
- if (params->common.events) {
- retval = trace_event_add_filter(params->common.events, optarg);
- if (retval)
- fatal("Error adding filter %s", optarg);
- } else {
- fatal("--filter requires a previous -e");
- }
- break;
- case '2':
- params->common.warmup = get_llong_from_str(optarg);
- break;
- case '3':
- params->common.buffer_size = get_llong_from_str(optarg);
- break;
- case '4':
- retval = actions_parse(&params->common.threshold_actions, optarg,
- "osnoise_trace.txt");
- if (retval)
- fatal("Invalid action %s", optarg);
- break;
- case '5':
- retval = actions_parse(&params->common.end_actions, optarg,
- "osnoise_trace.txt");
- if (retval)
- fatal("Invalid action %s", optarg);
- break;
- default:
- fatal("Invalid option");
- }
- }
-
- if (trace_output)
- actions_add_trace_output(&params->common.threshold_actions, trace_output);
-
- if (geteuid())
- fatal("osnoise needs root permission");
-
- return &params->common;
-}
-
-/*
* osnoise_top_apply_config - apply the top configs to the initialized tool
*/
static int
@@ -547,15 +281,12 @@ out_err:
struct osnoise_tool *osnoise_init_top(struct common_params *params)
{
struct osnoise_tool *tool;
- int nr_cpus;
-
- nr_cpus = sysconf(_SC_NPROCESSORS_CONF);
tool = osnoise_init_tool("osnoise_top");
if (!tool)
return NULL;
- tool->data = osnoise_alloc_top(nr_cpus);
+ tool->data = osnoise_alloc_top();
if (!tool->data) {
osnoise_destroy_tool(tool);
return NULL;
diff --git a/tools/tracing/rtla/src/rtla.c b/tools/tracing/rtla/src/rtla.c
deleted file mode 100644
index 7635c70123ab..000000000000
--- a/tools/tracing/rtla/src/rtla.c
+++ /dev/null
@@ -1,89 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0
-/*
- * Copyright (C) 2021 Red Hat Inc, Daniel Bristot de Oliveira <bristot@kernel.org>
- */
-
-#include <getopt.h>
-#include <stdlib.h>
-#include <string.h>
-#include <stdio.h>
-
-#include "osnoise.h"
-#include "timerlat.h"
-
-/*
- * rtla_usage - print rtla usage
- */
-static void rtla_usage(int err)
-{
- int i;
-
- static const char *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_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_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;
-}
-
-int main(int argc, char *argv[])
-{
- int retval;
-
- /* is it an alias? */
- retval = run_command(argc, argv, 0);
- if (retval)
- exit(0);
-
- if (argc < 2)
- goto usage;
-
- if (strcmp(argv[1], "-h") == 0) {
- rtla_usage(0);
- } else if (strcmp(argv[1], "--help") == 0) {
- rtla_usage(0);
- }
-
- retval = run_command(argc, argv, 1);
- if (retval)
- exit(0);
-
-usage:
- rtla_usage(1);
- exit(1);
-}
diff --git a/tools/tracing/rtla/src/timerlat.bpf.c b/tools/tracing/rtla/src/timerlat.bpf.c
index e2265b5d6491..549d2d2191d2 100644
--- a/tools/tracing/rtla/src/timerlat.bpf.c
+++ b/tools/tracing/rtla/src/timerlat.bpf.c
@@ -40,6 +40,17 @@ struct {
__uint(max_entries, 1);
} signal_stop_tracing SEC(".maps");
+struct {
+ __uint(type, BPF_MAP_TYPE_PROG_ARRAY);
+ __uint(key_size, sizeof(unsigned int));
+ __uint(max_entries, 1);
+ __array(values, unsigned int (void *));
+} bpf_action SEC(".maps") = {
+ .values = {
+ [0] = 0
+ },
+};
+
/* Params to be set by rtla */
const volatile int bucket_size = 1;
const volatile int output_divisor = 1000;
@@ -109,7 +120,7 @@ nosubprog void update_summary(void *map,
map_set(map, SUMMARY_SUM, map_get(map, SUMMARY_SUM) + latency);
}
-nosubprog void set_stop_tracing(void)
+nosubprog void set_stop_tracing(struct trace_event_raw_timerlat_sample *tp_args)
{
int value = 0;
@@ -118,6 +129,12 @@ nosubprog void set_stop_tracing(void)
/* Signal to userspace */
bpf_ringbuf_output(&signal_stop_tracing, &value, sizeof(value), 0);
+
+ /*
+ * Call into BPF action program, if attached.
+ * Otherwise, just silently fail.
+ */
+ bpf_tail_call(tp_args, &bpf_action, 0);
}
SEC("tp/osnoise/timerlat_sample")
@@ -138,19 +155,19 @@ int handle_timerlat_sample(struct trace_event_raw_timerlat_sample *tp_args)
update_summary(&summary_irq, latency, bucket);
if (irq_threshold != 0 && latency_us >= irq_threshold)
- set_stop_tracing();
+ set_stop_tracing(tp_args);
} else if (tp_args->context == 1) {
update_main_hist(&hist_thread, bucket);
update_summary(&summary_thread, latency, bucket);
if (thread_threshold != 0 && latency_us >= thread_threshold)
- set_stop_tracing();
+ set_stop_tracing(tp_args);
} else {
update_main_hist(&hist_user, bucket);
update_summary(&summary_user, latency, bucket);
if (thread_threshold != 0 && latency_us >= thread_threshold)
- set_stop_tracing();
+ set_stop_tracing(tp_args);
}
return 0;
diff --git a/tools/tracing/rtla/src/timerlat.c b/tools/tracing/rtla/src/timerlat.c
index df4f9bfe3433..169aa9a6569d 100644
--- a/tools/tracing/rtla/src/timerlat.c
+++ b/tools/tracing/rtla/src/timerlat.c
@@ -9,11 +9,12 @@
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
-#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <sched.h>
+#include <linux/compiler.h>
+
#include "timerlat.h"
#include "timerlat_aa.h"
#include "timerlat_bpf.h"
@@ -29,12 +30,13 @@ int
timerlat_apply_config(struct osnoise_tool *tool, struct timerlat_params *params)
{
int retval;
+ const char *const rtla_no_bpf = getenv("RTLA_NO_BPF");
/*
* Try to enable BPF, unless disabled explicitly.
* If BPF enablement fails, fall back to tracefs mode.
*/
- if (getenv("RTLA_NO_BPF") && strncmp(getenv("RTLA_NO_BPF"), "1", 2) == 0) {
+ if (rtla_no_bpf && strncmp_static(rtla_no_bpf, "1") == 0) {
debug_msg("RTLA_NO_BPF set, disabling BPF\n");
params->mode = TRACING_MODE_TRACEFS;
} else if (!tep_find_event_by_name(tool->trace.tep, "osnoise", "timerlat_sample")) {
@@ -48,25 +50,17 @@ timerlat_apply_config(struct osnoise_tool *tool, struct timerlat_params *params)
}
}
- if (params->mode != TRACING_MODE_BPF) {
- /*
- * In tracefs and mixed mode, timerlat tracer handles stopping
- * on threshold
- */
- retval = osnoise_set_stop_us(tool->context, params->common.stop_us);
- if (retval) {
- err_msg("Failed to set stop us\n");
+ /* Check if BPF action program is requested but BPF is not available */
+ if (params->bpf_action_program) {
+ if (params->mode == TRACING_MODE_TRACEFS) {
+ err_msg("BPF actions are not supported in tracefs-only mode\n");
goto out_err;
}
- retval = osnoise_set_stop_total_us(tool->context, params->common.stop_total_us);
- if (retval) {
- err_msg("Failed to set stop total us\n");
+ if (timerlat_load_bpf_action_program(params->bpf_action_program))
goto out_err;
- }
}
-
retval = osnoise_set_timerlat_period_us(tool->context,
params->timerlat_period_us ?
params->timerlat_period_us :
@@ -83,6 +77,24 @@ timerlat_apply_config(struct osnoise_tool *tool, struct timerlat_params *params)
goto out_err;
}
+ retval = osnoise_set_timerlat_align(tool->context, params->timerlat_align);
+ if (retval && params->timerlat_align) {
+ /*
+ * We might be running on a kernel that does not support timerlat align.
+ * Unless user requested it explicitly, ignore the error.
+ */
+ err_msg("Failed to enable timerlat align\n");
+ goto out_err;
+ }
+
+ if (params->timerlat_align) {
+ retval = osnoise_set_timerlat_align_us(tool->context, params->timerlat_align_us);
+ if (retval) {
+ err_msg("Failed to set timerlat align us\n");
+ goto out_err;
+ }
+ }
+
/*
* If the user did not specify a type of thread, try user-threads first.
* Fall back to kernel threads otherwise.
@@ -108,7 +120,7 @@ out_err:
int timerlat_enable(struct osnoise_tool *tool)
{
struct timerlat_params *params = to_timerlat_params(tool->params);
- int retval, nr_cpus, i;
+ int retval, i;
if (params->dma_latency >= 0) {
dma_latency_fd = set_cpu_dma_latency(params->dma_latency);
@@ -124,9 +136,7 @@ int timerlat_enable(struct osnoise_tool *tool)
return -1;
}
- nr_cpus = sysconf(_SC_NPROCESSORS_CONF);
-
- for_each_monitored_cpu(i, nr_cpus, &params->common) {
+ for_each_monitored_cpu(i, &params->common) {
if (save_cpu_idle_disable_state(i) < 0) {
err_msg("Could not save cpu idle state.\n");
return -1;
@@ -143,7 +153,7 @@ int timerlat_enable(struct osnoise_tool *tool)
if (!tool->aa)
return -1;
- retval = timerlat_aa_init(tool->aa, params->dump_tasks);
+ retval = timerlat_aa_init(tool->aa, params->dump_tasks, params->stack_format);
if (retval) {
err_msg("Failed to enable the auto analysis instance\n");
return retval;
@@ -184,6 +194,16 @@ int timerlat_enable(struct osnoise_tool *tool)
}
}
+ /*
+ * In tracefs and mixed mode, timerlat tracer handles stopping
+ * on threshold
+ */
+ if (params->mode != TRACING_MODE_BPF) {
+ retval = osn_set_stop(tool);
+ if (retval)
+ return retval;
+ }
+
return 0;
}
@@ -202,7 +222,7 @@ void timerlat_analyze(struct osnoise_tool *tool, bool stopped)
* If the trace did not stop with --aa-only, at least print
* the max known latency.
*/
- max_lat = tracefs_instance_file_read(trace_inst->inst, "tracing_max_latency", NULL);
+ max_lat = tracefs_instance_file_read(tool->trace.inst, "tracing_max_latency", NULL);
if (max_lat) {
printf(" Max latency was %s\n", max_lat);
free(max_lat);
@@ -213,14 +233,13 @@ void timerlat_analyze(struct osnoise_tool *tool, bool stopped)
void timerlat_free(struct osnoise_tool *tool)
{
struct timerlat_params *params = to_timerlat_params(tool->params);
- int nr_cpus = sysconf(_SC_NPROCESSORS_CONF);
int i;
timerlat_aa_destroy();
if (dma_latency_fd >= 0)
close(dma_latency_fd);
if (params->deepest_idle_state >= -1) {
- for_each_monitored_cpu(i, nr_cpus, &params->common) {
+ for_each_monitored_cpu(i, &params->common) {
restore_cpu_idle_disable_state(i);
}
}
@@ -232,7 +251,7 @@ void timerlat_free(struct osnoise_tool *tool)
free_cpu_idle_disable_states();
}
-static void timerlat_usage(int err)
+__noreturn static void timerlat_usage(int err)
{
int i;
@@ -270,8 +289,8 @@ int timerlat_main(int argc, char *argv[])
}
if ((strcmp(argv[1], "-h") == 0) || (strcmp(argv[1], "--help") == 0)) {
- timerlat_usage(0);
- } else if (strncmp(argv[1], "-", 1) == 0) {
+ timerlat_usage(129);
+ } else if (str_has_prefix(argv[1], "-")) {
/* the user skipped the tool, call the default one */
run_tool(&timerlat_top_ops, argc, argv);
exit(0);
@@ -284,6 +303,5 @@ int timerlat_main(int argc, char *argv[])
}
usage:
- timerlat_usage(1);
- exit(1);
+ timerlat_usage(129);
}
diff --git a/tools/tracing/rtla/src/timerlat.h b/tools/tracing/rtla/src/timerlat.h
index fd6065f48bb7..84ec6d778183 100644
--- a/tools/tracing/rtla/src/timerlat.h
+++ b/tools/tracing/rtla/src/timerlat.h
@@ -1,4 +1,6 @@
// SPDX-License-Identifier: GPL-2.0
+#pragma once
+
#include "osnoise.h"
/*
@@ -23,10 +25,14 @@ struct timerlat_params {
long long timerlat_period_us;
long long print_stack;
int dma_latency;
- int no_aa;
- int dump_tasks;
+ bool no_aa;
+ bool dump_tasks;
int deepest_idle_state;
enum timerlat_tracing_mode mode;
+ const char *bpf_action_program;
+ enum stack_format stack_format;
+ bool timerlat_align;
+ unsigned long long timerlat_align_us;
};
#define to_timerlat_params(ptr) container_of(ptr, struct timerlat_params, common)
@@ -36,4 +42,3 @@ int timerlat_main(int argc, char *argv[]);
int timerlat_enable(struct osnoise_tool *tool);
void timerlat_analyze(struct osnoise_tool *tool, bool stopped);
void timerlat_free(struct osnoise_tool *tool);
-
diff --git a/tools/tracing/rtla/src/timerlat_aa.c b/tools/tracing/rtla/src/timerlat_aa.c
index 31e66ea2b144..a121ff9d2c57 100644
--- a/tools/tracing/rtla/src/timerlat_aa.c
+++ b/tools/tracing/rtla/src/timerlat_aa.c
@@ -102,8 +102,8 @@ struct timerlat_aa_data {
* The analysis context and system wide view
*/
struct timerlat_aa_context {
- int nr_cpus;
int dump_tasks;
+ enum stack_format stack_format;
/* per CPU data */
struct timerlat_aa_data *taa_data;
@@ -417,8 +417,8 @@ static int timerlat_aa_softirq_handler(struct trace_seq *s, struct tep_record *r
taa_data->thread_softirq_sum += duration;
trace_seq_printf(taa_data->softirqs_seq, " %24s:%-3llu %.*s %9.2f us\n",
- softirq_name[vector], vector,
- 24, spaces,
+ vector < ARRAY_SIZE(softirq_name) ? softirq_name[vector] : "UNKNOWN",
+ vector, 24, spaces,
ns_to_usf(duration));
return 0;
}
@@ -481,23 +481,43 @@ static int timerlat_aa_stack_handler(struct trace_seq *s, struct tep_record *rec
{
struct timerlat_aa_context *taa_ctx = timerlat_aa_get_ctx();
struct timerlat_aa_data *taa_data = timerlat_aa_get_data(taa_ctx, record->cpu);
+ enum stack_format stack_format = taa_ctx->stack_format;
unsigned long *caller;
const char *function;
- int val, i;
+ int val;
+ unsigned long long i;
trace_seq_reset(taa_data->stack_seq);
trace_seq_printf(taa_data->stack_seq, " Blocking thread stack trace\n");
caller = tep_get_field_raw(s, event, "caller", record, &val, 1);
+
if (caller) {
- for (i = 0; ; i++) {
+ unsigned long long size;
+ unsigned long long max_entries;
+
+ if (tep_get_field_val(s, event, "size", record, &size, 1) == 0)
+ max_entries = size < 64 ? size : 64;
+ else
+ max_entries = 64;
+
+ for (i = 0; i < max_entries; i++) {
function = tep_find_function(taa_ctx->tool->trace.tep, caller[i]);
- if (!function)
- break;
- trace_seq_printf(taa_data->stack_seq, " %.*s -> %s\n",
- 14, spaces, function);
+ if (!function) {
+ if (stack_format == STACK_FORMAT_TRUNCATE)
+ break;
+ else if (stack_format == STACK_FORMAT_SKIP)
+ continue;
+ else if (stack_format == STACK_FORMAT_FULL)
+ trace_seq_printf(taa_data->stack_seq, " %.*s -> 0x%lx\n",
+ 14, spaces, caller[i]);
+ } else {
+ trace_seq_printf(taa_data->stack_seq, " %.*s -> %s\n",
+ 14, spaces, function);
+ }
}
}
+
return 0;
}
@@ -738,7 +758,7 @@ void timerlat_auto_analysis(int irq_thresh, int thread_thresh)
irq_thresh = irq_thresh * 1000;
thread_thresh = thread_thresh * 1000;
- for (cpu = 0; cpu < taa_ctx->nr_cpus; cpu++) {
+ for (cpu = 0; cpu < nr_cpus; cpu++) {
taa_data = timerlat_aa_get_data(taa_ctx, cpu);
if (irq_thresh && taa_data->tlat_irq_latency >= irq_thresh) {
@@ -766,7 +786,7 @@ void timerlat_auto_analysis(int irq_thresh, int thread_thresh)
printf("\n");
printf("Printing CPU tasks:\n");
- for (cpu = 0; cpu < taa_ctx->nr_cpus; cpu++) {
+ for (cpu = 0; cpu < nr_cpus; cpu++) {
taa_data = timerlat_aa_get_data(taa_ctx, cpu);
tep = taa_ctx->tool->trace.tep;
@@ -792,7 +812,7 @@ static void timerlat_aa_destroy_seqs(struct timerlat_aa_context *taa_ctx)
if (!taa_ctx->taa_data)
return;
- for (i = 0; i < taa_ctx->nr_cpus; i++) {
+ for (i = 0; i < nr_cpus; i++) {
taa_data = timerlat_aa_get_data(taa_ctx, i);
if (taa_data->prev_irqs_seq) {
@@ -842,7 +862,7 @@ static int timerlat_aa_init_seqs(struct timerlat_aa_context *taa_ctx)
struct timerlat_aa_data *taa_data;
int i;
- for (i = 0; i < taa_ctx->nr_cpus; i++) {
+ for (i = 0; i < nr_cpus; i++) {
taa_data = timerlat_aa_get_data(taa_ctx, i);
@@ -1020,9 +1040,8 @@ out_ctx:
*
* Returns 0 on success, -1 otherwise.
*/
-int timerlat_aa_init(struct osnoise_tool *tool, int dump_tasks)
+int timerlat_aa_init(struct osnoise_tool *tool, int dump_tasks, enum stack_format stack_format)
{
- int nr_cpus = sysconf(_SC_NPROCESSORS_CONF);
struct timerlat_aa_context *taa_ctx;
int retval;
@@ -1032,9 +1051,9 @@ int timerlat_aa_init(struct osnoise_tool *tool, int dump_tasks)
__timerlat_aa_ctx = taa_ctx;
- taa_ctx->nr_cpus = nr_cpus;
taa_ctx->tool = tool;
taa_ctx->dump_tasks = dump_tasks;
+ taa_ctx->stack_format = stack_format;
taa_ctx->taa_data = calloc(nr_cpus, sizeof(*taa_ctx->taa_data));
if (!taa_ctx->taa_data)
diff --git a/tools/tracing/rtla/src/timerlat_aa.h b/tools/tracing/rtla/src/timerlat_aa.h
index cea4bb1531a8..a11b5f30cdce 100644
--- a/tools/tracing/rtla/src/timerlat_aa.h
+++ b/tools/tracing/rtla/src/timerlat_aa.h
@@ -3,7 +3,7 @@
* Copyright (C) 2023 Red Hat Inc, Daniel Bristot de Oliveira <bristot@kernel.org>
*/
-int timerlat_aa_init(struct osnoise_tool *tool, int dump_task);
+int timerlat_aa_init(struct osnoise_tool *tool, int dump_task, enum stack_format stack_format);
void timerlat_aa_destroy(void);
void timerlat_auto_analysis(int irq_thresh, int thread_thresh);
diff --git a/tools/tracing/rtla/src/timerlat_bpf.c b/tools/tracing/rtla/src/timerlat_bpf.c
index e97d16646bcd..dd3cf71d74e5 100644
--- a/tools/tracing/rtla/src/timerlat_bpf.c
+++ b/tools/tracing/rtla/src/timerlat_bpf.c
@@ -7,6 +7,10 @@
static struct timerlat_bpf *bpf;
+/* BPF object and program for action program */
+static struct bpf_object *obj;
+static struct bpf_program *prog;
+
/*
* timerlat_bpf_init - load and initialize BPF program to collect timerlat data
*/
@@ -60,6 +64,19 @@ int timerlat_bpf_init(struct timerlat_params *params)
}
/*
+ * timerlat_bpf_set_action - set action on threshold executed on BPF side
+ */
+static int timerlat_bpf_set_action(struct bpf_program *prog)
+{
+ unsigned int key = 0, value = bpf_program__fd(prog);
+
+ return bpf_map__update_elem(bpf->maps.bpf_action,
+ &key, sizeof(key),
+ &value, sizeof(value),
+ BPF_ANY);
+}
+
+/*
* timerlat_bpf_attach - attach BPF program to collect timerlat data
*/
int timerlat_bpf_attach(void)
@@ -83,6 +100,11 @@ void timerlat_bpf_detach(void)
void timerlat_bpf_destroy(void)
{
timerlat_bpf__destroy(bpf);
+ bpf = NULL;
+ if (obj)
+ bpf_object__close(obj);
+ obj = NULL;
+ prog = NULL;
}
static int handle_rb_event(void *ctx, void *data, size_t data_sz)
@@ -125,24 +147,23 @@ static int get_value(struct bpf_map *map_irq,
int key,
long long *value_irq,
long long *value_thread,
- long long *value_user,
- int cpus)
+ long long *value_user)
{
int err;
err = bpf_map__lookup_elem(map_irq, &key,
sizeof(unsigned int), value_irq,
- sizeof(long long) * cpus, 0);
+ sizeof(long long) * nr_cpus, 0);
if (err)
return err;
err = bpf_map__lookup_elem(map_thread, &key,
sizeof(unsigned int), value_thread,
- sizeof(long long) * cpus, 0);
+ sizeof(long long) * nr_cpus, 0);
if (err)
return err;
err = bpf_map__lookup_elem(map_user, &key,
sizeof(unsigned int), value_user,
- sizeof(long long) * cpus, 0);
+ sizeof(long long) * nr_cpus, 0);
if (err)
return err;
return 0;
@@ -154,13 +175,12 @@ static int get_value(struct bpf_map *map_irq,
int timerlat_bpf_get_hist_value(int key,
long long *value_irq,
long long *value_thread,
- long long *value_user,
- int cpus)
+ long long *value_user)
{
return get_value(bpf->maps.hist_irq,
bpf->maps.hist_thread,
bpf->maps.hist_user,
- key, value_irq, value_thread, value_user, cpus);
+ key, value_irq, value_thread, value_user);
}
/*
@@ -169,12 +189,55 @@ int timerlat_bpf_get_hist_value(int key,
int timerlat_bpf_get_summary_value(enum summary_field key,
long long *value_irq,
long long *value_thread,
- long long *value_user,
- int cpus)
+ long long *value_user)
{
return get_value(bpf->maps.summary_irq,
bpf->maps.summary_thread,
bpf->maps.summary_user,
- key, value_irq, value_thread, value_user, cpus);
+ key, value_irq, value_thread, value_user);
+}
+
+/*
+ * timerlat_load_bpf_action_program - load and register a BPF action program
+ */
+int timerlat_load_bpf_action_program(const char *program_path)
+{
+ int err;
+
+ obj = bpf_object__open_file(program_path, NULL);
+ if (!obj) {
+ err_msg("Failed to open BPF action program: %s\n", program_path);
+ goto out_err;
+ }
+
+ err = bpf_object__load(obj);
+ if (err) {
+ err_msg("Failed to load BPF action program: %s\n", program_path);
+ goto out_obj_err;
+ }
+
+ prog = bpf_object__find_program_by_name(obj, "action_handler");
+ if (!prog) {
+ err_msg("BPF action program must have 'action_handler' function: %s\n",
+ program_path);
+ goto out_obj_err;
+ }
+
+ err = timerlat_bpf_set_action(prog);
+ if (err) {
+ err_msg("Failed to register BPF action program: %s\n", program_path);
+ goto out_prog_err;
+ }
+
+ return 0;
+
+out_prog_err:
+ prog = NULL;
+out_obj_err:
+ bpf_object__close(obj);
+ obj = NULL;
+out_err:
+ return 1;
}
+
#endif /* HAVE_BPF_SKEL */
diff --git a/tools/tracing/rtla/src/timerlat_bpf.h b/tools/tracing/rtla/src/timerlat_bpf.h
index 118487436d30..33f0df6a9060 100644
--- a/tools/tracing/rtla/src/timerlat_bpf.h
+++ b/tools/tracing/rtla/src/timerlat_bpf.h
@@ -22,14 +22,12 @@ int timerlat_bpf_restart_tracing(void);
int timerlat_bpf_get_hist_value(int key,
long long *value_irq,
long long *value_thread,
- long long *value_user,
- int cpus);
+ long long *value_user);
int timerlat_bpf_get_summary_value(enum summary_field key,
long long *value_irq,
long long *value_thread,
- long long *value_user,
- int cpus);
-
+ long long *value_user);
+int timerlat_load_bpf_action_program(const char *program_path);
static inline int have_libbpf_support(void) { return 1; }
#else
static inline int timerlat_bpf_init(struct timerlat_params *params)
@@ -44,16 +42,18 @@ static inline int timerlat_bpf_restart_tracing(void) { return -1; };
static inline int timerlat_bpf_get_hist_value(int key,
long long *value_irq,
long long *value_thread,
- long long *value_user,
- int cpus)
+ long long *value_user)
{
return -1;
}
static inline int timerlat_bpf_get_summary_value(enum summary_field key,
long long *value_irq,
long long *value_thread,
- long long *value_user,
- int cpus)
+ long long *value_user)
+{
+ return -1;
+}
+static inline int timerlat_load_bpf_action_program(const char *program_path)
{
return -1;
}
diff --git a/tools/tracing/rtla/src/timerlat_hist.c b/tools/tracing/rtla/src/timerlat_hist.c
index 1fb471a787b7..df7b1398a966 100644
--- a/tools/tracing/rtla/src/timerlat_hist.c
+++ b/tools/tracing/rtla/src/timerlat_hist.c
@@ -4,7 +4,6 @@
*/
#define _GNU_SOURCE
-#include <getopt.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
@@ -17,6 +16,8 @@
#include "timerlat.h"
#include "timerlat_aa.h"
#include "timerlat_bpf.h"
+#include "cli.h"
+#include "common.h"
struct timerlat_hist_cpu {
int *irq;
@@ -44,7 +45,6 @@ struct timerlat_hist_data {
struct timerlat_hist_cpu *hist;
int entries;
int bucket_size;
- int nr_cpus;
};
/*
@@ -56,7 +56,7 @@ timerlat_free_histogram(struct timerlat_hist_data *data)
int cpu;
/* one histogram for IRQ and one for thread, per CPU */
- for (cpu = 0; cpu < data->nr_cpus; cpu++) {
+ for (cpu = 0; cpu < nr_cpus; cpu++) {
if (data->hist[cpu].irq)
free(data->hist[cpu].irq);
@@ -83,7 +83,7 @@ static void timerlat_free_histogram_tool(struct osnoise_tool *tool)
* timerlat_alloc_histogram - alloc runtime data
*/
static struct timerlat_hist_data
-*timerlat_alloc_histogram(int nr_cpus, int entries, int bucket_size)
+*timerlat_alloc_histogram(int entries, int bucket_size)
{
struct timerlat_hist_data *data;
int cpu;
@@ -94,7 +94,6 @@ static struct timerlat_hist_data
data->entries = entries;
data->bucket_size = bucket_size;
- data->nr_cpus = nr_cpus;
/* one set of histograms per CPU */
data->hist = calloc(1, sizeof(*data->hist) * nr_cpus);
@@ -204,17 +203,17 @@ static int timerlat_hist_bpf_pull_data(struct osnoise_tool *tool)
{
struct timerlat_hist_data *data = tool->data;
int i, j, err;
- long long value_irq[data->nr_cpus],
- value_thread[data->nr_cpus],
- value_user[data->nr_cpus];
+ long long value_irq[nr_cpus],
+ value_thread[nr_cpus],
+ value_user[nr_cpus];
/* Pull histogram */
for (i = 0; i < data->entries; i++) {
err = timerlat_bpf_get_hist_value(i, value_irq, value_thread,
- value_user, data->nr_cpus);
+ value_user);
if (err)
return err;
- for (j = 0; j < data->nr_cpus; j++) {
+ for (j = 0; j < nr_cpus; j++) {
data->hist[j].irq[i] = value_irq[j];
data->hist[j].thread[i] = value_thread[j];
data->hist[j].user[i] = value_user[j];
@@ -223,55 +222,50 @@ static int timerlat_hist_bpf_pull_data(struct osnoise_tool *tool)
/* Pull summary */
err = timerlat_bpf_get_summary_value(SUMMARY_COUNT,
- value_irq, value_thread, value_user,
- data->nr_cpus);
+ value_irq, value_thread, value_user);
if (err)
return err;
- for (i = 0; i < data->nr_cpus; i++) {
+ for (i = 0; i < nr_cpus; i++) {
data->hist[i].irq_count = value_irq[i];
data->hist[i].thread_count = value_thread[i];
data->hist[i].user_count = value_user[i];
}
err = timerlat_bpf_get_summary_value(SUMMARY_MIN,
- value_irq, value_thread, value_user,
- data->nr_cpus);
+ value_irq, value_thread, value_user);
if (err)
return err;
- for (i = 0; i < data->nr_cpus; i++) {
+ for (i = 0; i < nr_cpus; i++) {
data->hist[i].min_irq = value_irq[i];
data->hist[i].min_thread = value_thread[i];
data->hist[i].min_user = value_user[i];
}
err = timerlat_bpf_get_summary_value(SUMMARY_MAX,
- value_irq, value_thread, value_user,
- data->nr_cpus);
+ value_irq, value_thread, value_user);
if (err)
return err;
- for (i = 0; i < data->nr_cpus; i++) {
+ for (i = 0; i < nr_cpus; i++) {
data->hist[i].max_irq = value_irq[i];
data->hist[i].max_thread = value_thread[i];
data->hist[i].max_user = value_user[i];
}
err = timerlat_bpf_get_summary_value(SUMMARY_SUM,
- value_irq, value_thread, value_user,
- data->nr_cpus);
+ value_irq, value_thread, value_user);
if (err)
return err;
- for (i = 0; i < data->nr_cpus; i++) {
+ for (i = 0; i < nr_cpus; i++) {
data->hist[i].sum_irq = value_irq[i];
data->hist[i].sum_thread = value_thread[i];
data->hist[i].sum_user = value_user[i];
}
err = timerlat_bpf_get_summary_value(SUMMARY_OVERFLOW,
- value_irq, value_thread, value_user,
- data->nr_cpus);
+ value_irq, value_thread, value_user);
if (err)
return err;
- for (i = 0; i < data->nr_cpus; i++) {
+ for (i = 0; i < nr_cpus; i++) {
data->hist[i].irq[data->entries] = value_irq[i];
data->hist[i].thread[data->entries] = value_thread[i];
data->hist[i].user[data->entries] = value_user[i];
@@ -305,7 +299,7 @@ static void timerlat_hist_header(struct osnoise_tool *tool)
if (!params->common.hist.no_index)
trace_seq_printf(s, "Index");
- for_each_monitored_cpu(cpu, data->nr_cpus, &params->common) {
+ for_each_monitored_cpu(cpu, &params->common) {
if (!data->hist[cpu].irq_count && !data->hist[cpu].thread_count)
continue;
@@ -357,7 +351,7 @@ timerlat_print_summary(struct timerlat_params *params,
if (!params->common.hist.no_index)
trace_seq_printf(trace->seq, "count:");
- for_each_monitored_cpu(cpu, data->nr_cpus, &params->common) {
+ for_each_monitored_cpu(cpu, &params->common) {
if (!data->hist[cpu].irq_count && !data->hist[cpu].thread_count)
continue;
@@ -379,7 +373,7 @@ timerlat_print_summary(struct timerlat_params *params,
if (!params->common.hist.no_index)
trace_seq_printf(trace->seq, "min: ");
- for_each_monitored_cpu(cpu, data->nr_cpus, &params->common) {
+ for_each_monitored_cpu(cpu, &params->common) {
if (!data->hist[cpu].irq_count && !data->hist[cpu].thread_count)
continue;
@@ -407,7 +401,7 @@ timerlat_print_summary(struct timerlat_params *params,
if (!params->common.hist.no_index)
trace_seq_printf(trace->seq, "avg: ");
- for_each_monitored_cpu(cpu, data->nr_cpus, &params->common) {
+ for_each_monitored_cpu(cpu, &params->common) {
if (!data->hist[cpu].irq_count && !data->hist[cpu].thread_count)
continue;
@@ -435,7 +429,7 @@ timerlat_print_summary(struct timerlat_params *params,
if (!params->common.hist.no_index)
trace_seq_printf(trace->seq, "max: ");
- for_each_monitored_cpu(cpu, data->nr_cpus, &params->common) {
+ for_each_monitored_cpu(cpu, &params->common) {
if (!data->hist[cpu].irq_count && !data->hist[cpu].thread_count)
continue;
@@ -480,7 +474,7 @@ timerlat_print_stats_all(struct timerlat_params *params,
sum.min_thread = ~0;
sum.min_user = ~0;
- for_each_monitored_cpu(cpu, data->nr_cpus, &params->common) {
+ for_each_monitored_cpu(cpu, &params->common) {
if (!data->hist[cpu].irq_count && !data->hist[cpu].thread_count)
continue;
@@ -627,7 +621,7 @@ timerlat_print_stats(struct osnoise_tool *tool)
trace_seq_printf(trace->seq, "%-6d",
bucket * data->bucket_size);
- for_each_monitored_cpu(cpu, data->nr_cpus, &params->common) {
+ for_each_monitored_cpu(cpu, &params->common) {
if (!data->hist[cpu].irq_count && !data->hist[cpu].thread_count)
continue;
@@ -665,7 +659,7 @@ timerlat_print_stats(struct osnoise_tool *tool)
if (!params->common.hist.no_index)
trace_seq_printf(trace->seq, "over: ");
- for_each_monitored_cpu(cpu, data->nr_cpus, &params->common) {
+ for_each_monitored_cpu(cpu, &params->common) {
if (!data->hist[cpu].irq_count && !data->hist[cpu].thread_count)
continue;
@@ -692,366 +686,6 @@ timerlat_print_stats(struct osnoise_tool *tool)
}
/*
- * timerlat_hist_usage - prints timerlat top usage message
- */
-static void timerlat_hist_usage(void)
-{
- int i;
-
- char *msg[] = {
- "",
- " usage: [rtla] timerlat hist [-h] [-q] [-d s] [-D] [-n] [-a us] [-p us] [-i us] [-T us] [-s us] \\",
- " [-t [file]] [-e sys[:event]] [--filter <filter>] [--trigger <trigger>] [-c cpu-list] [-H cpu-list]\\",
- " [-P priority] [-E N] [-b N] [--no-irq] [--no-thread] [--no-header] [--no-summary] \\",
- " [--no-index] [--with-zeros] [--dma-latency us] [-C [cgroup_name]] [--no-aa] [--dump-task] [-u|-k]",
- " [--warm-up s] [--deepest-idle-state n]",
- "",
- " -h/--help: print this menu",
- " -a/--auto: set automatic trace mode, stopping the session if argument in us latency is hit",
- " -p/--period us: timerlat period in us",
- " -i/--irq us: stop trace if the irq latency is higher than the argument in us",
- " -T/--thread us: stop trace if the thread latency is higher than the argument in us",
- " -s/--stack us: save the stack trace at the IRQ if a thread latency is higher than the argument in us",
- " -c/--cpus cpus: run the tracer only on the given cpus",
- " -H/--house-keeping cpus: run rtla control threads only on the given cpus",
- " -C/--cgroup [cgroup_name]: set cgroup, if no cgroup_name is passed, the rtla's cgroup will be inherited",
- " -d/--duration time[m|h|d]: duration of the session in seconds",
- " --dump-tasks: prints the task running on all CPUs if stop conditions are met (depends on !--no-aa)",
- " -D/--debug: print debug info",
- " -t/--trace [file]: save the stopped trace to [file|timerlat_trace.txt]",
- " -e/--event <sys:event>: enable the <sys:event> in the trace instance, multiple -e are allowed",
- " --filter <filter>: enable a trace event filter to the previous -e event",
- " --trigger <trigger>: enable a trace event trigger to the previous -e event",
- " -n/--nano: display data in nanoseconds",
- " --no-aa: disable auto-analysis, reducing rtla timerlat cpu usage",
- " -b/--bucket-size N: set the histogram bucket size (default 1)",
- " -E/--entries N: set the number of entries of the histogram (default 256)",
- " --no-irq: ignore IRQ latencies",
- " --no-thread: ignore thread latencies",
- " --no-header: do not print header",
- " --no-summary: do not print summary",
- " --no-index: do not print index",
- " --with-zeros: print zero only entries",
- " --dma-latency us: set /dev/cpu_dma_latency latency <us> to reduce exit from idle latency",
- " -P/--priority o:prio|r:prio|f:prio|d:runtime:period : set scheduling parameters",
- " o:prio - use SCHED_OTHER with prio",
- " r:prio - use SCHED_RR with prio",
- " f:prio - use SCHED_FIFO with prio",
- " d:runtime[us|ms|s]:period[us|ms|s] - use SCHED_DEADLINE with runtime and period",
- " in nanoseconds",
- " -u/--user-threads: use rtla user-space threads instead of kernel-space timerlat threads",
- " -k/--kernel-threads: use timerlat kernel-space threads instead of rtla user-space threads",
- " -U/--user-load: enable timerlat for user-defined user-space workload",
- " --warm-up s: let the workload run for s seconds before collecting data",
- " --trace-buffer-size kB: set the per-cpu trace buffer size in kB",
- " --deepest-idle-state n: only go down to idle state n on cpus used by timerlat to reduce exit from idle latency",
- " --on-threshold <action>: define action to be executed at latency threshold, multiple are allowed",
- " --on-end <action>: define action to be executed at measurement end, multiple are allowed",
- NULL,
- };
-
- fprintf(stderr, "rtla timerlat hist: a per-cpu histogram of the timer latency (version %s)\n",
- VERSION);
-
- for (i = 0; msg[i]; i++)
- fprintf(stderr, "%s\n", msg[i]);
-
- exit(EXIT_SUCCESS);
-}
-
-/*
- * timerlat_hist_parse_args - allocs, parse and fill the cmd line parameters
- */
-static struct common_params
-*timerlat_hist_parse_args(int argc, char *argv[])
-{
- struct timerlat_params *params;
- struct trace_events *tevent;
- int auto_thresh;
- int retval;
- int c;
- char *trace_output = NULL;
-
- params = calloc(1, sizeof(*params));
- if (!params)
- exit(1);
-
- actions_init(&params->common.threshold_actions);
- actions_init(&params->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;
-
- while (1) {
- static struct option long_options[] = {
- {"auto", required_argument, 0, 'a'},
- {"cpus", required_argument, 0, 'c'},
- {"cgroup", optional_argument, 0, 'C'},
- {"bucket-size", required_argument, 0, 'b'},
- {"debug", no_argument, 0, 'D'},
- {"entries", required_argument, 0, 'E'},
- {"duration", required_argument, 0, 'd'},
- {"house-keeping", required_argument, 0, 'H'},
- {"help", no_argument, 0, 'h'},
- {"irq", required_argument, 0, 'i'},
- {"nano", no_argument, 0, 'n'},
- {"period", required_argument, 0, 'p'},
- {"priority", required_argument, 0, 'P'},
- {"stack", required_argument, 0, 's'},
- {"thread", required_argument, 0, 'T'},
- {"trace", optional_argument, 0, 't'},
- {"user-threads", no_argument, 0, 'u'},
- {"kernel-threads", no_argument, 0, 'k'},
- {"user-load", no_argument, 0, 'U'},
- {"event", required_argument, 0, 'e'},
- {"no-irq", no_argument, 0, '0'},
- {"no-thread", no_argument, 0, '1'},
- {"no-header", no_argument, 0, '2'},
- {"no-summary", no_argument, 0, '3'},
- {"no-index", no_argument, 0, '4'},
- {"with-zeros", no_argument, 0, '5'},
- {"trigger", required_argument, 0, '6'},
- {"filter", required_argument, 0, '7'},
- {"dma-latency", required_argument, 0, '8'},
- {"no-aa", no_argument, 0, '9'},
- {"dump-task", no_argument, 0, '\1'},
- {"warm-up", required_argument, 0, '\2'},
- {"trace-buffer-size", required_argument, 0, '\3'},
- {"deepest-idle-state", required_argument, 0, '\4'},
- {"on-threshold", required_argument, 0, '\5'},
- {"on-end", required_argument, 0, '\6'},
- {0, 0, 0, 0}
- };
-
- c = getopt_long(argc, argv, "a:c:C::b:d:e:E:DhH:i:knp:P:s:t::T:uU0123456:7:8:9\1\2:\3:",
- long_options, NULL);
-
- /* detect the end of the options. */
- if (c == -1)
- break;
-
- switch (c) {
- case 'a':
- auto_thresh = get_llong_from_str(optarg);
-
- /* set thread stop to auto_thresh */
- params->common.stop_total_us = auto_thresh;
- params->common.stop_us = auto_thresh;
-
- /* get stack trace */
- params->print_stack = auto_thresh;
-
- /* set trace */
- if (!trace_output)
- trace_output = "timerlat_trace.txt";
-
- break;
- case 'c':
- retval = parse_cpu_set(optarg, &params->common.monitored_cpus);
- if (retval)
- fatal("Invalid -c cpu list");
- params->common.cpus = optarg;
- break;
- case 'C':
- params->common.cgroup = 1;
- params->common.cgroup_name = parse_optional_arg(argc, argv);
- break;
- case 'b':
- params->common.hist.bucket_size = get_llong_from_str(optarg);
- if (params->common.hist.bucket_size == 0 ||
- params->common.hist.bucket_size >= 1000000)
- fatal("Bucket size needs to be > 0 and <= 1000000");
- break;
- case 'D':
- config_debug = 1;
- break;
- case 'd':
- params->common.duration = parse_seconds_duration(optarg);
- if (!params->common.duration)
- fatal("Invalid -D duration");
- break;
- case 'e':
- tevent = trace_event_alloc(optarg);
- if (!tevent)
- fatal("Error alloc trace event");
-
- if (params->common.events)
- tevent->next = params->common.events;
-
- params->common.events = tevent;
- break;
- case 'E':
- params->common.hist.entries = get_llong_from_str(optarg);
- if (params->common.hist.entries < 10 ||
- params->common.hist.entries > 9999999)
- fatal("Entries must be > 10 and < 9999999");
- break;
- case 'h':
- case '?':
- timerlat_hist_usage();
- break;
- case 'H':
- params->common.hk_cpus = 1;
- retval = parse_cpu_set(optarg, &params->common.hk_cpu_set);
- if (retval)
- fatal("Error parsing house keeping CPUs");
- break;
- case 'i':
- params->common.stop_us = get_llong_from_str(optarg);
- break;
- case 'k':
- params->common.kernel_workload = 1;
- break;
- case 'n':
- params->common.output_divisor = 1;
- break;
- case 'p':
- params->timerlat_period_us = get_llong_from_str(optarg);
- if (params->timerlat_period_us > 1000000)
- fatal("Period longer than 1 s");
- break;
- case 'P':
- retval = parse_prio(optarg, &params->common.sched_param);
- if (retval == -1)
- fatal("Invalid -P priority");
- params->common.set_sched = 1;
- break;
- case 's':
- params->print_stack = get_llong_from_str(optarg);
- break;
- case 'T':
- params->common.stop_total_us = get_llong_from_str(optarg);
- break;
- case 't':
- trace_output = parse_optional_arg(argc, argv);
- if (!trace_output)
- trace_output = "timerlat_trace.txt";
- break;
- case 'u':
- params->common.user_workload = 1;
- /* fallback: -u implies in -U */
- case 'U':
- params->common.user_data = 1;
- break;
- case '0': /* no irq */
- params->common.hist.no_irq = 1;
- break;
- case '1': /* no thread */
- params->common.hist.no_thread = 1;
- break;
- case '2': /* no header */
- params->common.hist.no_header = 1;
- break;
- case '3': /* no summary */
- params->common.hist.no_summary = 1;
- break;
- case '4': /* no index */
- params->common.hist.no_index = 1;
- break;
- case '5': /* with zeros */
- params->common.hist.with_zeros = 1;
- break;
- case '6': /* trigger */
- if (params->common.events) {
- retval = trace_event_add_trigger(params->common.events, optarg);
- if (retval)
- fatal("Error adding trigger %s", optarg);
- } else {
- fatal("--trigger requires a previous -e");
- }
- break;
- case '7': /* filter */
- if (params->common.events) {
- retval = trace_event_add_filter(params->common.events, optarg);
- if (retval)
- fatal("Error adding filter %s", optarg);
- } else {
- fatal("--filter requires a previous -e");
- }
- break;
- case '8':
- params->dma_latency = get_llong_from_str(optarg);
- if (params->dma_latency < 0 || params->dma_latency > 10000)
- fatal("--dma-latency needs to be >= 0 and < 10000");
- break;
- case '9':
- params->no_aa = 1;
- break;
- case '\1':
- params->dump_tasks = 1;
- break;
- case '\2':
- params->common.warmup = get_llong_from_str(optarg);
- break;
- case '\3':
- params->common.buffer_size = get_llong_from_str(optarg);
- break;
- case '\4':
- params->deepest_idle_state = get_llong_from_str(optarg);
- break;
- case '\5':
- retval = actions_parse(&params->common.threshold_actions, optarg,
- "timerlat_trace.txt");
- if (retval)
- fatal("Invalid action %s", optarg);
- break;
- case '\6':
- retval = actions_parse(&params->common.end_actions, optarg,
- "timerlat_trace.txt");
- if (retval)
- fatal("Invalid action %s", optarg);
- break;
- default:
- fatal("Invalid option");
- }
- }
-
- if (trace_output)
- actions_add_trace_output(&params->common.threshold_actions, 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 &params->common;
-}
-
-/*
* timerlat_hist_apply_config - apply the hist configs to the initialized tool
*/
static int
@@ -1077,15 +711,12 @@ static struct osnoise_tool
*timerlat_init_hist(struct common_params *params)
{
struct osnoise_tool *tool;
- int nr_cpus;
-
- nr_cpus = sysconf(_SC_NPROCESSORS_CONF);
tool = osnoise_init_tool("timerlat_hist");
if (!tool)
return NULL;
- tool->data = timerlat_alloc_histogram(nr_cpus, params->hist.entries,
+ tool->data = timerlat_alloc_histogram(params->hist.entries,
params->hist.bucket_size);
if (!tool->data)
goto out_err;
@@ -1102,7 +733,6 @@ out_err:
static int timerlat_hist_bpf_main_loop(struct osnoise_tool *tool)
{
- struct timerlat_params *params = to_timerlat_params(tool->params);
int retval;
while (!stop_tracing) {
@@ -1110,18 +740,17 @@ static int timerlat_hist_bpf_main_loop(struct osnoise_tool *tool)
if (!stop_tracing) {
/* Threshold overflow, perform actions on threshold */
- actions_perform(&params->common.threshold_actions);
+ retval = common_threshold_handler(tool);
+ if (retval)
+ return retval;
- if (!params->common.threshold_actions.continue_flag)
- /* continue flag not set, break */
+ if (!should_continue_tracing(tool->params))
break;
- /* continue action reached, re-enable tracing */
- if (tool->record)
- trace_instance_start(&tool->record->trace);
- if (tool->aa)
- trace_instance_start(&tool->aa->trace);
- timerlat_bpf_restart_tracing();
+ if (timerlat_bpf_restart_tracing()) {
+ err_msg("Error restarting BPF trace\n");
+ return -1;
+ }
}
}
timerlat_bpf_detach();
diff --git a/tools/tracing/rtla/src/timerlat_top.c b/tools/tracing/rtla/src/timerlat_top.c
index 29c2c1f717ed..6206a0a565ad 100644
--- a/tools/tracing/rtla/src/timerlat_top.c
+++ b/tools/tracing/rtla/src/timerlat_top.c
@@ -4,20 +4,20 @@
*/
#define _GNU_SOURCE
-#include <getopt.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>
#include <stdio.h>
#include <time.h>
-#include <errno.h>
#include <sched.h>
#include <pthread.h>
#include "timerlat.h"
#include "timerlat_aa.h"
#include "timerlat_bpf.h"
+#include "cli.h"
+#include "common.h"
struct timerlat_top_cpu {
unsigned long long irq_count;
@@ -42,7 +42,6 @@ struct timerlat_top_cpu {
struct timerlat_top_data {
struct timerlat_top_cpu *cpu_data;
- int nr_cpus;
};
/*
@@ -63,7 +62,7 @@ static void timerlat_free_top_tool(struct osnoise_tool *tool)
/*
* timerlat_alloc_histogram - alloc runtime data
*/
-static struct timerlat_top_data *timerlat_alloc_top(int nr_cpus)
+static struct timerlat_top_data *timerlat_alloc_top(void)
{
struct timerlat_top_data *data;
int cpu;
@@ -72,8 +71,6 @@ static struct timerlat_top_data *timerlat_alloc_top(int nr_cpus)
if (!data)
return NULL;
- data->nr_cpus = nr_cpus;
-
/* one set of histograms per CPU */
data->cpu_data = calloc(1, sizeof(*data->cpu_data) * nr_cpus);
if (!data->cpu_data)
@@ -191,61 +188,56 @@ static int timerlat_top_bpf_pull_data(struct osnoise_tool *tool)
{
struct timerlat_top_data *data = tool->data;
int i, err;
- long long value_irq[data->nr_cpus],
- value_thread[data->nr_cpus],
- value_user[data->nr_cpus];
+ long long value_irq[nr_cpus],
+ value_thread[nr_cpus],
+ value_user[nr_cpus];
/* Pull summary */
err = timerlat_bpf_get_summary_value(SUMMARY_CURRENT,
- value_irq, value_thread, value_user,
- data->nr_cpus);
+ value_irq, value_thread, value_user);
if (err)
return err;
- for (i = 0; i < data->nr_cpus; i++) {
+ for (i = 0; i < nr_cpus; i++) {
data->cpu_data[i].cur_irq = value_irq[i];
data->cpu_data[i].cur_thread = value_thread[i];
data->cpu_data[i].cur_user = value_user[i];
}
err = timerlat_bpf_get_summary_value(SUMMARY_COUNT,
- value_irq, value_thread, value_user,
- data->nr_cpus);
+ value_irq, value_thread, value_user);
if (err)
return err;
- for (i = 0; i < data->nr_cpus; i++) {
+ for (i = 0; i < nr_cpus; i++) {
data->cpu_data[i].irq_count = value_irq[i];
data->cpu_data[i].thread_count = value_thread[i];
data->cpu_data[i].user_count = value_user[i];
}
err = timerlat_bpf_get_summary_value(SUMMARY_MIN,
- value_irq, value_thread, value_user,
- data->nr_cpus);
+ value_irq, value_thread, value_user);
if (err)
return err;
- for (i = 0; i < data->nr_cpus; i++) {
+ for (i = 0; i < nr_cpus; i++) {
data->cpu_data[i].min_irq = value_irq[i];
data->cpu_data[i].min_thread = value_thread[i];
data->cpu_data[i].min_user = value_user[i];
}
err = timerlat_bpf_get_summary_value(SUMMARY_MAX,
- value_irq, value_thread, value_user,
- data->nr_cpus);
+ value_irq, value_thread, value_user);
if (err)
return err;
- for (i = 0; i < data->nr_cpus; i++) {
+ for (i = 0; i < nr_cpus; i++) {
data->cpu_data[i].max_irq = value_irq[i];
data->cpu_data[i].max_thread = value_thread[i];
data->cpu_data[i].max_user = value_user[i];
}
err = timerlat_bpf_get_summary_value(SUMMARY_SUM,
- value_irq, value_thread, value_user,
- data->nr_cpus);
+ value_irq, value_thread, value_user);
if (err)
return err;
- for (i = 0; i < data->nr_cpus; i++) {
+ for (i = 0; i < nr_cpus; i++) {
data->cpu_data[i].sum_irq = value_irq[i];
data->cpu_data[i].sum_thread = value_thread[i];
data->cpu_data[i].sum_user = value_user[i];
@@ -443,15 +435,11 @@ timerlat_print_stats(struct osnoise_tool *top)
struct timerlat_params *params = to_timerlat_params(top->params);
struct trace_instance *trace = &top->trace;
struct timerlat_top_cpu summary;
- static int nr_cpus = -1;
int i;
if (params->common.aa_only)
return;
- if (nr_cpus == -1)
- nr_cpus = sysconf(_SC_NPROCESSORS_CONF);
-
if (!params->common.quiet)
clear_terminal(trace->seq);
@@ -459,7 +447,7 @@ timerlat_print_stats(struct osnoise_tool *top)
timerlat_top_header(params, top);
- for_each_monitored_cpu(i, nr_cpus, &params->common) {
+ for_each_monitored_cpu(i, &params->common) {
timerlat_top_print(top, i);
timerlat_top_update_sum(top, i, &summary);
}
@@ -472,333 +460,6 @@ timerlat_print_stats(struct osnoise_tool *top)
}
/*
- * timerlat_top_usage - prints timerlat top usage message
- */
-static void timerlat_top_usage(void)
-{
- int i;
-
- static const char *const msg[] = {
- "",
- " usage: rtla timerlat [top] [-h] [-q] [-a us] [-d s] [-D] [-n] [-p us] [-i us] [-T us] [-s us] \\",
- " [[-t [file]] [-e sys[:event]] [--filter <filter>] [--trigger <trigger>] [-c cpu-list] [-H cpu-list]\\",
- " [-P priority] [--dma-latency us] [--aa-only us] [-C [cgroup_name]] [-u|-k] [--warm-up s] [--deepest-idle-state n]",
- "",
- " -h/--help: print this menu",
- " -a/--auto: set automatic trace mode, stopping the session if argument in us latency is hit",
- " --aa-only us: stop if <us> latency is hit, only printing the auto analysis (reduces CPU usage)",
- " -p/--period us: timerlat period in us",
- " -i/--irq us: stop trace if the irq latency is higher than the argument in us",
- " -T/--thread us: stop trace if the thread latency is higher than the argument in us",
- " -s/--stack us: save the stack trace at the IRQ if a thread latency is higher than the argument in us",
- " -c/--cpus cpus: run the tracer only on the given cpus",
- " -H/--house-keeping cpus: run rtla control threads only on the given cpus",
- " -C/--cgroup [cgroup_name]: set cgroup, if no cgroup_name is passed, the rtla's cgroup will be inherited",
- " -d/--duration time[s|m|h|d]: duration of the session",
- " -D/--debug: print debug info",
- " --dump-tasks: prints the task running on all CPUs if stop conditions are met (depends on !--no-aa)",
- " -t/--trace [file]: save the stopped trace to [file|timerlat_trace.txt]",
- " -e/--event <sys:event>: enable the <sys:event> in the trace instance, multiple -e are allowed",
- " --filter <command>: enable a trace event filter to the previous -e event",
- " --trigger <command>: enable a trace event trigger to the previous -e event",
- " -n/--nano: display data in nanoseconds",
- " --no-aa: disable auto-analysis, reducing rtla timerlat cpu usage",
- " -q/--quiet print only a summary at the end",
- " --dma-latency us: set /dev/cpu_dma_latency latency <us> to reduce exit from idle latency",
- " -P/--priority o:prio|r:prio|f:prio|d:runtime:period : set scheduling parameters",
- " o:prio - use SCHED_OTHER with prio",
- " r:prio - use SCHED_RR with prio",
- " f:prio - use SCHED_FIFO with prio",
- " d:runtime[us|ms|s]:period[us|ms|s] - use SCHED_DEADLINE with runtime and period",
- " in nanoseconds",
- " -u/--user-threads: use rtla user-space threads instead of kernel-space timerlat threads",
- " -k/--kernel-threads: use timerlat kernel-space threads instead of rtla user-space threads",
- " -U/--user-load: enable timerlat for user-defined user-space workload",
- " --warm-up s: let the workload run for s seconds before collecting data",
- " --trace-buffer-size kB: set the per-cpu trace buffer size in kB",
- " --deepest-idle-state n: only go down to idle state n on cpus used by timerlat to reduce exit from idle latency",
- " --on-threshold <action>: define action to be executed at latency threshold, multiple are allowed",
- " --on-end: define action to be executed at measurement end, multiple are allowed",
- NULL,
- };
-
- fprintf(stderr, "rtla timerlat top: a per-cpu summary of the timer latency (version %s)\n",
- VERSION);
-
- for (i = 0; msg[i]; i++)
- fprintf(stderr, "%s\n", msg[i]);
-
- exit(EXIT_SUCCESS);
-}
-
-/*
- * timerlat_top_parse_args - allocs, parse and fill the cmd line parameters
- */
-static struct common_params
-*timerlat_top_parse_args(int argc, char **argv)
-{
- struct timerlat_params *params;
- struct trace_events *tevent;
- long long auto_thresh;
- int retval;
- int c;
- char *trace_output = NULL;
-
- params = calloc(1, sizeof(*params));
- if (!params)
- exit(1);
-
- actions_init(&params->common.threshold_actions);
- actions_init(&params->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;
-
- /* default to BPF mode */
- params->mode = TRACING_MODE_BPF;
-
- while (1) {
- static struct option long_options[] = {
- {"auto", required_argument, 0, 'a'},
- {"cpus", required_argument, 0, 'c'},
- {"cgroup", optional_argument, 0, 'C'},
- {"debug", no_argument, 0, 'D'},
- {"duration", required_argument, 0, 'd'},
- {"event", required_argument, 0, 'e'},
- {"help", no_argument, 0, 'h'},
- {"house-keeping", required_argument, 0, 'H'},
- {"irq", required_argument, 0, 'i'},
- {"nano", no_argument, 0, 'n'},
- {"period", required_argument, 0, 'p'},
- {"priority", required_argument, 0, 'P'},
- {"quiet", no_argument, 0, 'q'},
- {"stack", required_argument, 0, 's'},
- {"thread", required_argument, 0, 'T'},
- {"trace", optional_argument, 0, 't'},
- {"user-threads", no_argument, 0, 'u'},
- {"kernel-threads", no_argument, 0, 'k'},
- {"user-load", no_argument, 0, 'U'},
- {"trigger", required_argument, 0, '0'},
- {"filter", required_argument, 0, '1'},
- {"dma-latency", required_argument, 0, '2'},
- {"no-aa", no_argument, 0, '3'},
- {"dump-tasks", no_argument, 0, '4'},
- {"aa-only", required_argument, 0, '5'},
- {"warm-up", required_argument, 0, '6'},
- {"trace-buffer-size", required_argument, 0, '7'},
- {"deepest-idle-state", required_argument, 0, '8'},
- {"on-threshold", required_argument, 0, '9'},
- {"on-end", required_argument, 0, '\1'},
- {0, 0, 0, 0}
- };
-
- c = getopt_long(argc, argv, "a:c:C::d:De:hH:i:knp:P:qs:t::T:uU0:1:2:345:6:7:",
- long_options, NULL);
-
- /* detect the end of the options. */
- if (c == -1)
- break;
-
- switch (c) {
- case 'a':
- auto_thresh = get_llong_from_str(optarg);
-
- /* set thread stop to auto_thresh */
- params->common.stop_total_us = auto_thresh;
- params->common.stop_us = auto_thresh;
-
- /* get stack trace */
- params->print_stack = auto_thresh;
-
- /* set trace */
- if (!trace_output)
- trace_output = "timerlat_trace.txt";
-
- break;
- case '5':
- /* it is here because it is similar to -a */
- auto_thresh = get_llong_from_str(optarg);
-
- /* set thread stop to auto_thresh */
- params->common.stop_total_us = auto_thresh;
- params->common.stop_us = auto_thresh;
-
- /* get stack trace */
- params->print_stack = auto_thresh;
-
- /* set aa_only to avoid parsing the trace */
- params->common.aa_only = 1;
- break;
- case 'c':
- retval = parse_cpu_set(optarg, &params->common.monitored_cpus);
- if (retval)
- fatal("Invalid -c cpu list");
- params->common.cpus = optarg;
- break;
- case 'C':
- params->common.cgroup = 1;
- params->common.cgroup_name = optarg;
- break;
- case 'D':
- config_debug = 1;
- break;
- case 'd':
- params->common.duration = parse_seconds_duration(optarg);
- if (!params->common.duration)
- fatal("Invalid -d duration");
- break;
- case 'e':
- tevent = trace_event_alloc(optarg);
- if (!tevent)
- fatal("Error alloc trace event");
-
- if (params->common.events)
- tevent->next = params->common.events;
- params->common.events = tevent;
- break;
- case 'h':
- case '?':
- timerlat_top_usage();
- break;
- case 'H':
- params->common.hk_cpus = 1;
- retval = parse_cpu_set(optarg, &params->common.hk_cpu_set);
- if (retval)
- fatal("Error parsing house keeping CPUs");
- break;
- case 'i':
- params->common.stop_us = get_llong_from_str(optarg);
- break;
- case 'k':
- params->common.kernel_workload = true;
- break;
- case 'n':
- params->common.output_divisor = 1;
- break;
- case 'p':
- params->timerlat_period_us = get_llong_from_str(optarg);
- if (params->timerlat_period_us > 1000000)
- fatal("Period longer than 1 s");
- break;
- case 'P':
- retval = parse_prio(optarg, &params->common.sched_param);
- if (retval == -1)
- fatal("Invalid -P priority");
- params->common.set_sched = 1;
- break;
- case 'q':
- params->common.quiet = 1;
- break;
- case 's':
- params->print_stack = get_llong_from_str(optarg);
- break;
- case 'T':
- params->common.stop_total_us = get_llong_from_str(optarg);
- break;
- case 't':
- trace_output = parse_optional_arg(argc, argv);
- if (!trace_output)
- trace_output = "timerlat_trace.txt";
- break;
- case 'u':
- params->common.user_workload = true;
- /* fallback: -u implies -U */
- case 'U':
- params->common.user_data = true;
- break;
- case '0': /* trigger */
- if (params->common.events) {
- retval = trace_event_add_trigger(params->common.events, optarg);
- if (retval)
- fatal("Error adding trigger %s", optarg);
- } else {
- fatal("--trigger requires a previous -e");
- }
- break;
- case '1': /* filter */
- if (params->common.events) {
- retval = trace_event_add_filter(params->common.events, optarg);
- if (retval)
- fatal("Error adding filter %s", optarg);
- } else {
- fatal("--filter requires a previous -e");
- }
- break;
- case '2': /* dma-latency */
- params->dma_latency = get_llong_from_str(optarg);
- if (params->dma_latency < 0 || params->dma_latency > 10000)
- fatal("--dma-latency needs to be >= 0 and < 10000");
- break;
- case '3': /* no-aa */
- params->no_aa = 1;
- break;
- case '4':
- params->dump_tasks = 1;
- break;
- case '6':
- params->common.warmup = get_llong_from_str(optarg);
- break;
- case '7':
- params->common.buffer_size = get_llong_from_str(optarg);
- break;
- case '8':
- params->deepest_idle_state = get_llong_from_str(optarg);
- break;
- case '9':
- retval = actions_parse(&params->common.threshold_actions, optarg,
- "timerlat_trace.txt");
- if (retval)
- fatal("Invalid action %s", optarg);
- break;
- case '\1':
- retval = actions_parse(&params->common.end_actions, optarg,
- "timerlat_trace.txt");
- if (retval)
- fatal("Invalid action %s", optarg);
- break;
- default:
- fatal("Invalid option");
- }
- }
-
- if (trace_output)
- actions_add_trace_output(&params->common.threshold_actions, 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 &params->common;
-}
-
-/*
* timerlat_top_apply_config - apply the top configs to the initialized tool
*/
static int
@@ -827,15 +488,12 @@ static struct osnoise_tool
*timerlat_init_top(struct common_params *params)
{
struct osnoise_tool *top;
- int nr_cpus;
-
- nr_cpus = sysconf(_SC_NPROCESSORS_CONF);
top = osnoise_init_tool("timerlat_top");
if (!top)
return NULL;
- top->data = timerlat_alloc_top(nr_cpus);
+ top->data = timerlat_alloc_top();
if (!top->data)
goto out_err;
@@ -855,10 +513,10 @@ out_err:
static int
timerlat_top_bpf_main_loop(struct osnoise_tool *tool)
{
- struct timerlat_params *params = to_timerlat_params(tool->params);
+ const struct common_params *params = tool->params;
int retval, wait_retval;
- if (params->common.aa_only) {
+ if (params->aa_only) {
/* Auto-analysis only, just wait for stop tracing */
timerlat_bpf_wait(-1);
return 0;
@@ -866,8 +524,8 @@ timerlat_top_bpf_main_loop(struct osnoise_tool *tool)
/* Pull and display data in a loop */
while (!stop_tracing) {
- wait_retval = timerlat_bpf_wait(params->common.quiet ? -1 :
- params->common.sleep_time);
+ wait_retval = timerlat_bpf_wait(params->quiet ? -1 :
+ params->sleep_time);
retval = timerlat_top_bpf_pull_data(tool);
if (retval) {
@@ -875,28 +533,27 @@ timerlat_top_bpf_main_loop(struct osnoise_tool *tool)
return retval;
}
- if (!params->common.quiet)
+ if (!params->quiet)
timerlat_print_stats(tool);
- if (wait_retval != 0) {
+ if (wait_retval > 0) {
/* Stopping requested by tracer */
- actions_perform(&params->common.threshold_actions);
+ retval = common_threshold_handler(tool);
+ if (retval)
+ return retval;
- if (!params->common.threshold_actions.continue_flag)
- /* continue flag not set, break */
+ if (!should_continue_tracing(tool->params))
break;
- /* continue action reached, re-enable tracing */
- if (tool->record)
- trace_instance_start(&tool->record->trace);
- if (tool->aa)
- trace_instance_start(&tool->aa->trace);
- timerlat_bpf_restart_tracing();
+ if (timerlat_bpf_restart_tracing()) {
+ err_msg("Error restarting BPF trace\n");
+ return -1;
+ }
}
/* is there still any user-threads ? */
- if (params->common.user_workload) {
- if (params->common.user.stopped_running) {
+ if (params->user_workload) {
+ if (params->user.stopped_running) {
debug_msg("timerlat user space threads stopped!\n");
break;
}
diff --git a/tools/tracing/rtla/src/timerlat_u.c b/tools/tracing/rtla/src/timerlat_u.c
index ce68e39d25fd..c80edaf07b00 100644
--- a/tools/tracing/rtla/src/timerlat_u.c
+++ b/tools/tracing/rtla/src/timerlat_u.c
@@ -16,7 +16,7 @@
#include <sys/wait.h>
#include <sys/prctl.h>
-#include "utils.h"
+#include "common.h"
#include "timerlat_u.h"
/*
@@ -32,7 +32,7 @@
static int timerlat_u_main(int cpu, struct timerlat_u_params *params)
{
struct sched_param sp = { .sched_priority = 95 };
- char buffer[1024];
+ char buffer[MAX_PATH];
int timerlat_fd;
cpu_set_t set;
int retval;
@@ -83,7 +83,7 @@ static int timerlat_u_main(int cpu, struct timerlat_u_params *params)
/* add should continue with a signal handler */
while (true) {
- retval = read(timerlat_fd, buffer, 1024);
+ retval = read(timerlat_fd, buffer, ARRAY_SIZE(buffer));
if (retval < 0)
break;
}
@@ -99,7 +99,7 @@ static int timerlat_u_main(int cpu, struct timerlat_u_params *params)
*
* Return the number of processes that received the kill.
*/
-static int timerlat_u_send_kill(pid_t *procs, int nr_cpus)
+static int timerlat_u_send_kill(pid_t *procs)
{
int killed = 0;
int i, retval;
@@ -131,7 +131,6 @@ static int timerlat_u_send_kill(pid_t *procs, int nr_cpus)
*/
void *timerlat_u_dispatcher(void *data)
{
- int nr_cpus = sysconf(_SC_NPROCESSORS_CONF);
struct timerlat_u_params *params = data;
char proc_name[128];
int procs_count = 0;
@@ -170,7 +169,7 @@ void *timerlat_u_dispatcher(void *data)
/* parent */
if (pid == -1) {
- timerlat_u_send_kill(procs, nr_cpus);
+ timerlat_u_send_kill(procs);
debug_msg("Failed to create child processes");
pthread_exit(&retval);
}
@@ -197,7 +196,7 @@ void *timerlat_u_dispatcher(void *data)
sleep(1);
}
- timerlat_u_send_kill(procs, nr_cpus);
+ timerlat_u_send_kill(procs);
while (procs_count) {
pid = waitpid(-1, &wstatus, 0);
diff --git a/tools/tracing/rtla/src/timerlat_u.h b/tools/tracing/rtla/src/timerlat_u.h
index 661511908957..a692331bd1c7 100644
--- a/tools/tracing/rtla/src/timerlat_u.h
+++ b/tools/tracing/rtla/src/timerlat_u.h
@@ -1,4 +1,5 @@
// SPDX-License-Identifier: GPL-2.0
+#pragma once
/*
* Copyright (C) 2023 Red Hat Inc, Daniel Bristot de Oliveira <bristot@kernel.org>
*/
diff --git a/tools/tracing/rtla/src/trace.c b/tools/tracing/rtla/src/trace.c
index 69cbc48d53d3..e407447773d0 100644
--- a/tools/tracing/rtla/src/trace.c
+++ b/tools/tracing/rtla/src/trace.c
@@ -2,7 +2,6 @@
#define _GNU_SOURCE
#include <sys/sendfile.h>
#include <tracefs.h>
-#include <signal.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
@@ -74,6 +73,8 @@ int save_trace_to_file(struct tracefs_instance *inst, const char *filename)
char buffer[4096];
int out_fd, in_fd;
int retval = -1;
+ ssize_t n_read;
+ ssize_t n_written;
if (!inst || !filename)
return 0;
@@ -91,15 +92,30 @@ int save_trace_to_file(struct tracefs_instance *inst, const char *filename)
goto out_close_in;
}
- do {
- retval = read(in_fd, buffer, sizeof(buffer));
- if (retval <= 0)
- goto out_close;
-
- retval = write(out_fd, buffer, retval);
- if (retval < 0)
+ for (;;) {
+ n_read = read(in_fd, buffer, sizeof(buffer));
+ if (n_read < 0) {
+ if (errno == EINTR)
+ continue;
+ err_msg("Error reading trace file: %s\n", strerror(errno));
goto out_close;
- } while (retval > 0);
+ }
+ if (n_read == 0)
+ break;
+
+ n_written = 0;
+ while (n_written < n_read) {
+ const ssize_t w = write(out_fd, buffer + n_written, n_read - n_written);
+
+ if (w < 0) {
+ if (errno == EINTR)
+ continue;
+ err_msg("Error writing trace file: %s\n", strerror(errno));
+ goto out_close;
+ }
+ n_written += w;
+ }
+ }
retval = 0;
out_close:
@@ -192,9 +208,7 @@ void trace_instance_destroy(struct trace_instance *trace)
*/
int trace_instance_init(struct trace_instance *trace, char *tool_name)
{
- trace->seq = calloc(1, sizeof(*trace->seq));
- if (!trace->seq)
- goto out_err;
+ trace->seq = calloc_fatal(1, sizeof(*trace->seq));
trace_seq_init(trace->seq);
@@ -275,15 +289,9 @@ struct trace_events *trace_event_alloc(const char *event_string)
{
struct trace_events *tevent;
- tevent = calloc(1, sizeof(*tevent));
- if (!tevent)
- return NULL;
+ tevent = calloc_fatal(1, sizeof(*tevent));
- tevent->system = strdup(event_string);
- if (!tevent->system) {
- free(tevent);
- return NULL;
- }
+ tevent->system = strdup_fatal(event_string);
tevent->event = strstr(tevent->system, ":");
if (tevent->event) {
@@ -297,31 +305,23 @@ struct trace_events *trace_event_alloc(const char *event_string)
/*
* trace_event_add_filter - record an event filter
*/
-int trace_event_add_filter(struct trace_events *event, char *filter)
+void trace_event_add_filter(struct trace_events *event, char *filter)
{
if (event->filter)
free(event->filter);
- event->filter = strdup(filter);
- if (!event->filter)
- return 1;
-
- return 0;
+ event->filter = strdup_fatal(filter);
}
/*
* trace_event_add_trigger - record an event trigger action
*/
-int trace_event_add_trigger(struct trace_events *event, char *trigger)
+void trace_event_add_trigger(struct trace_events *event, char *trigger)
{
if (event->trigger)
free(event->trigger);
- event->trigger = strdup(trigger);
- if (!event->trigger)
- return 1;
-
- return 0;
+ event->trigger = strdup_fatal(trigger);
}
/*
@@ -330,7 +330,7 @@ int trace_event_add_trigger(struct trace_events *event, char *trigger)
static void trace_event_disable_filter(struct trace_instance *instance,
struct trace_events *tevent)
{
- char filter[1024];
+ char filter[MAX_PATH];
int retval;
if (!tevent->filter)
@@ -342,7 +342,7 @@ static void trace_event_disable_filter(struct trace_instance *instance,
debug_msg("Disabling %s:%s filter %s\n", tevent->system,
tevent->event ? : "*", tevent->filter);
- snprintf(filter, 1024, "!%s\n", tevent->filter);
+ snprintf(filter, ARRAY_SIZE(filter), "!%s\n", tevent->filter);
retval = tracefs_event_file_write(instance->inst, tevent->system,
tevent->event, "filter", filter);
@@ -359,10 +359,11 @@ static void trace_event_disable_filter(struct trace_instance *instance,
static void trace_event_save_hist(struct trace_instance *instance,
struct trace_events *tevent)
{
- int retval, index, out_fd;
+ size_t index, hist_len;
mode_t mode = 0644;
- char path[1024];
+ char path[MAX_PATH];
char *hist;
+ int out_fd;
if (!tevent)
return;
@@ -372,11 +373,10 @@ static void trace_event_save_hist(struct trace_instance *instance,
return;
/* is this a hist: trigger? */
- retval = strncmp(tevent->trigger, "hist:", strlen("hist:"));
- if (retval)
+ if (!str_has_prefix(tevent->trigger, "hist:"))
return;
- snprintf(path, 1024, "%s_%s_hist.txt", tevent->system, tevent->event);
+ snprintf(path, ARRAY_SIZE(path), "%s_%s_hist.txt", tevent->system, tevent->event);
printf(" Saving event %s:%s hist to %s\n", tevent->system, tevent->event, path);
@@ -393,9 +393,18 @@ static void trace_event_save_hist(struct trace_instance *instance,
}
index = 0;
+ hist_len = strlen(hist);
do {
- index += write(out_fd, &hist[index], strlen(hist) - index);
- } while (index < strlen(hist));
+ const ssize_t written = write(out_fd, &hist[index], hist_len - index);
+
+ if (written < 0) {
+ if (errno == EINTR)
+ continue;
+ err_msg(" Error writing hist file: %s\n", strerror(errno));
+ break;
+ }
+ index += written;
+ } while (index < hist_len);
free(hist);
out_close:
@@ -408,7 +417,7 @@ out_close:
static void trace_event_disable_trigger(struct trace_instance *instance,
struct trace_events *tevent)
{
- char trigger[1024];
+ char trigger[MAX_PATH];
int retval;
if (!tevent->trigger)
@@ -422,7 +431,7 @@ static void trace_event_disable_trigger(struct trace_instance *instance,
trace_event_save_hist(instance, tevent);
- snprintf(trigger, 1024, "!%s\n", tevent->trigger);
+ snprintf(trigger, ARRAY_SIZE(trigger), "!%s\n", tevent->trigger);
retval = tracefs_event_file_write(instance->inst, tevent->system,
tevent->event, "trigger", trigger);
@@ -461,7 +470,7 @@ void trace_events_disable(struct trace_instance *instance,
static int trace_event_enable_filter(struct trace_instance *instance,
struct trace_events *tevent)
{
- char filter[1024];
+ char filter[MAX_PATH];
int retval;
if (!tevent->filter)
@@ -473,7 +482,7 @@ static int trace_event_enable_filter(struct trace_instance *instance,
return 1;
}
- snprintf(filter, 1024, "%s\n", tevent->filter);
+ snprintf(filter, ARRAY_SIZE(filter), "%s\n", tevent->filter);
debug_msg("Enabling %s:%s filter %s\n", tevent->system,
tevent->event ? : "*", tevent->filter);
@@ -496,7 +505,7 @@ static int trace_event_enable_filter(struct trace_instance *instance,
static int trace_event_enable_trigger(struct trace_instance *instance,
struct trace_events *tevent)
{
- char trigger[1024];
+ char trigger[MAX_PATH];
int retval;
if (!tevent->trigger)
@@ -508,7 +517,7 @@ static int trace_event_enable_trigger(struct trace_instance *instance,
return 1;
}
- snprintf(trigger, 1024, "%s\n", tevent->trigger);
+ snprintf(trigger, ARRAY_SIZE(trigger), "%s\n", tevent->trigger);
debug_msg("Enabling %s:%s trigger %s\n", tevent->system,
tevent->event ? : "*", tevent->trigger);
diff --git a/tools/tracing/rtla/src/trace.h b/tools/tracing/rtla/src/trace.h
index 1e5aee4b828d..95b911a2228b 100644
--- a/tools/tracing/rtla/src/trace.h
+++ b/tools/tracing/rtla/src/trace.h
@@ -45,6 +45,6 @@ void trace_events_destroy(struct trace_instance *instance,
int trace_events_enable(struct trace_instance *instance,
struct trace_events *events);
-int trace_event_add_filter(struct trace_events *event, char *filter);
-int trace_event_add_trigger(struct trace_events *event, char *trigger);
+void trace_event_add_filter(struct trace_events *event, char *filter);
+void trace_event_add_trigger(struct trace_events *event, char *trigger);
int trace_set_buffer_size(struct trace_instance *trace, int size);
diff --git a/tools/tracing/rtla/src/utils.c b/tools/tracing/rtla/src/utils.c
index 9cf5a0098e9a..cb187e7d48d1 100644
--- a/tools/tracing/rtla/src/utils.c
+++ b/tools/tracing/rtla/src/utils.c
@@ -17,11 +17,12 @@
#include <fcntl.h>
#include <sched.h>
#include <stdio.h>
+#include <limits.h>
-#include "utils.h"
+#include "common.h"
#define MAX_MSG_LENGTH 1024
-int config_debug;
+bool config_debug;
/*
* err_msg - print an error message to the stderr
@@ -112,20 +113,17 @@ void get_duration(time_t start_time, char *output, int output_size)
* Receives a cpu list, like 1-3,5 (cpus 1, 2, 3, 5), and then set
* filling cpu_set_t argument.
*
- * Returns 1 on success, 0 otherwise.
+ * Returns 0 on success, 1 otherwise.
*/
int parse_cpu_set(char *cpu_list, cpu_set_t *set)
{
const char *p;
int end_cpu;
- int nr_cpus;
int cpu;
int i;
CPU_ZERO(set);
- nr_cpus = sysconf(_SC_NPROCESSORS_CONF);
-
for (p = cpu_list; *p; ) {
cpu = atoi(p);
if (cpu < 0 || (!cpu && *p != '0') || cpu >= nr_cpus)
@@ -164,6 +162,24 @@ err:
}
/*
+ * parse_stack_format - parse the stack format
+ *
+ * Return: the stack format on success, -1 otherwise.
+ */
+int parse_stack_format(char *arg)
+{
+ if (!strcmp(arg, "truncate"))
+ return STACK_FORMAT_TRUNCATE;
+ if (!strcmp(arg, "skip"))
+ return STACK_FORMAT_SKIP;
+ if (!strcmp(arg, "full"))
+ return STACK_FORMAT_FULL;
+
+ debug_msg("Error parsing the stack format %s\n", arg);
+ return -1;
+}
+
+/*
* parse_duration - parse duration with s/m/h/d suffix converting it to seconds
*/
long parse_seconds_duration(char *val)
@@ -198,6 +214,21 @@ long parse_seconds_duration(char *val)
}
/*
+ * match_time_unit - check if str starts with unit followed by end-of-string or ':'
+ *
+ * This allows the time unit parser to work both in standalone duration strings
+ * like "100ms" and in colon-delimited SCHED_DEADLINE specifications like
+ * "d:10ms:100ms", while still rejecting malformed input like "100msx".
+ */
+static bool match_time_unit(const char *str, const char *unit)
+{
+ size_t len = strlen(unit);
+
+ return strncmp(str, unit, len) == 0 &&
+ (str[len] == '\0' || str[len] == ':');
+}
+
+/*
* parse_ns_duration - parse duration with ns/us/ms/s converting it to nanoseconds
*/
long parse_ns_duration(char *val)
@@ -208,15 +239,15 @@ long parse_ns_duration(char *val)
t = strtol(val, &end, 10);
if (end) {
- if (!strncmp(end, "ns", 2)) {
+ if (match_time_unit(end, "ns")) {
return t;
- } else if (!strncmp(end, "us", 2)) {
+ } else if (match_time_unit(end, "us")) {
t *= 1000;
return t;
- } else if (!strncmp(end, "ms", 2)) {
+ } else if (match_time_unit(end, "ms")) {
t *= 1000 * 1000;
return t;
- } else if (!strncmp(end, "s", 1)) {
+ } else if (match_time_unit(end, "s")) {
t *= 1000 * 1000 * 1000;
return t;
}
@@ -293,7 +324,7 @@ static int procfs_is_workload_pid(const char *comm_prefix, struct dirent *proc_e
return 0;
/* check if the string is a pid */
- for (t_name = proc_entry->d_name; t_name; t_name++) {
+ for (t_name = proc_entry->d_name; *t_name; t_name++) {
if (!isdigit(*t_name))
break;
}
@@ -314,8 +345,8 @@ static int procfs_is_workload_pid(const char *comm_prefix, struct dirent *proc_e
if (retval <= 0)
return 0;
- retval = strncmp(comm_prefix, buffer, strlen(comm_prefix));
- if (retval)
+ buffer[MAX_PATH-1] = '\0';
+ if (!str_has_prefix(buffer, comm_prefix))
return 0;
/* comm already have \n */
@@ -337,6 +368,7 @@ int set_comm_sched_attr(const char *comm_prefix, struct sched_attr *attr)
struct dirent *proc_entry;
DIR *procfs;
int retval;
+ int pid;
if (strlen(comm_prefix) >= MAX_PATH) {
err_msg("Command prefix is too long: %d < strlen(%s)\n",
@@ -356,20 +388,25 @@ int set_comm_sched_attr(const char *comm_prefix, struct sched_attr *attr)
if (!retval)
continue;
+ if (strtoi(proc_entry->d_name, &pid)) {
+ err_msg("'%s' is not a valid pid", proc_entry->d_name);
+ retval = 1;
+ goto out;
+ }
/* procfs_is_workload_pid confirmed it is a pid */
- retval = __set_sched_attr(atoi(proc_entry->d_name), attr);
+ retval = __set_sched_attr(pid, attr);
if (retval) {
err_msg("Error setting sched attributes for pid:%s\n", proc_entry->d_name);
- goto out_err;
+ goto out;
}
debug_msg("Set sched attributes for pid:%s\n", proc_entry->d_name);
}
- return 0;
-out_err:
+ retval = 0;
+out:
closedir(procfs);
- return 1;
+ return retval;
}
#define INVALID_VAL (~0L)
@@ -552,7 +589,6 @@ int save_cpu_idle_disable_state(unsigned int cpu)
unsigned int nr_states;
unsigned int state;
int disabled;
- int nr_cpus;
nr_states = cpuidle_state_count(cpu);
@@ -560,7 +596,6 @@ int save_cpu_idle_disable_state(unsigned int cpu)
return 0;
if (saved_cpu_idle_disable_state == NULL) {
- nr_cpus = sysconf(_SC_NPROCESSORS_CONF);
saved_cpu_idle_disable_state = calloc(nr_cpus, sizeof(unsigned int *));
if (!saved_cpu_idle_disable_state)
return -1;
@@ -637,13 +672,10 @@ int restore_cpu_idle_disable_state(unsigned int cpu)
void free_cpu_idle_disable_states(void)
{
int cpu;
- int nr_cpus;
if (!saved_cpu_idle_disable_state)
return;
- nr_cpus = sysconf(_SC_NPROCESSORS_CONF);
-
for (cpu = 0; cpu < nr_cpus; cpu++) {
free(saved_cpu_idle_disable_state[cpu]);
saved_cpu_idle_disable_state[cpu] = NULL;
@@ -742,6 +774,7 @@ static int get_self_cgroup(char *self_cg, int sizeof_self_cg)
if (fd < 0)
return 0;
+ memset(path, 0, sizeof(path));
retval = read(fd, path, MAX_PATH);
close(fd);
@@ -749,6 +782,7 @@ static int get_self_cgroup(char *self_cg, int sizeof_self_cg)
if (retval <= 0)
return 0;
+ path[MAX_PATH-1] = '\0';
start = path;
start = strstr(start, ":");
@@ -784,39 +818,42 @@ static int get_self_cgroup(char *self_cg, int sizeof_self_cg)
}
/*
- * set_comm_cgroup - Set cgroup to pid_t pid
+ * open_cgroup_procs - Open the cgroup.procs file for the given cgroup
*
- * If cgroup argument is not NULL, the threads will move to the given cgroup.
- * Otherwise, the cgroup of the calling, i.e., rtla, thread will be used.
+ * If cgroup argument is not NULL, the cgroup.procs file for that cgroup
+ * will be opened. Otherwise, the cgroup of the calling, i.e., rtla, thread
+ * will be used.
*
* Supports cgroup v2.
*
- * Returns 1 on success, 0 otherwise.
+ * Returns the file descriptor on success, -1 otherwise.
*/
-int set_pid_cgroup(pid_t pid, const char *cgroup)
+static int open_cgroup_procs(const char *cgroup)
{
char cgroup_path[MAX_PATH - strlen("/cgroup.procs")];
char cgroup_procs[MAX_PATH];
- char pid_str[24];
int retval;
int cg_fd;
+ size_t cg_path_len;
retval = find_mount("cgroup2", cgroup_path, sizeof(cgroup_path));
if (!retval) {
err_msg("Did not find cgroupv2 mount point\n");
- return 0;
+ return -1;
}
+ cg_path_len = strlen(cgroup_path);
+
if (!cgroup) {
- retval = get_self_cgroup(&cgroup_path[strlen(cgroup_path)],
- sizeof(cgroup_path) - strlen(cgroup_path));
+ retval = get_self_cgroup(&cgroup_path[cg_path_len],
+ sizeof(cgroup_path) - cg_path_len);
if (!retval) {
err_msg("Did not find self cgroup\n");
- return 0;
+ return -1;
}
} else {
- snprintf(&cgroup_path[strlen(cgroup_path)],
- sizeof(cgroup_path) - strlen(cgroup_path), "%s/", cgroup);
+ snprintf(&cgroup_path[cg_path_len],
+ sizeof(cgroup_path) - cg_path_len, "%s/", cgroup);
}
snprintf(cgroup_procs, MAX_PATH, "%s/cgroup.procs", cgroup_path);
@@ -825,6 +862,29 @@ int set_pid_cgroup(pid_t pid, const char *cgroup)
cg_fd = open(cgroup_procs, O_RDWR);
if (cg_fd < 0)
+ return -1;
+
+ return cg_fd;
+}
+
+/*
+ * set_pid_cgroup - Set cgroup to pid_t pid
+ *
+ * If cgroup argument is not NULL, the threads will move to the given cgroup.
+ * Otherwise, the cgroup of the calling, i.e., rtla, thread will be used.
+ *
+ * Supports cgroup v2.
+ *
+ * Returns 1 on success, 0 otherwise.
+ */
+int set_pid_cgroup(pid_t pid, const char *cgroup)
+{
+ char pid_str[24];
+ int retval;
+ int cg_fd;
+
+ cg_fd = open_cgroup_procs(cgroup);
+ if (cg_fd < 0)
return 0;
snprintf(pid_str, sizeof(pid_str), "%d\n", pid);
@@ -853,8 +913,6 @@ int set_pid_cgroup(pid_t pid, const char *cgroup)
*/
int set_comm_cgroup(const char *comm_prefix, const char *cgroup)
{
- char cgroup_path[MAX_PATH - strlen("/cgroup.procs")];
- char cgroup_procs[MAX_PATH];
struct dirent *proc_entry;
DIR *procfs;
int retval;
@@ -866,29 +924,7 @@ int set_comm_cgroup(const char *comm_prefix, const char *cgroup)
return 0;
}
- retval = find_mount("cgroup2", cgroup_path, sizeof(cgroup_path));
- if (!retval) {
- err_msg("Did not find cgroupv2 mount point\n");
- return 0;
- }
-
- if (!cgroup) {
- retval = get_self_cgroup(&cgroup_path[strlen(cgroup_path)],
- sizeof(cgroup_path) - strlen(cgroup_path));
- if (!retval) {
- err_msg("Did not find self cgroup\n");
- return 0;
- }
- } else {
- snprintf(&cgroup_path[strlen(cgroup_path)],
- sizeof(cgroup_path) - strlen(cgroup_path), "%s/", cgroup);
- }
-
- snprintf(cgroup_procs, MAX_PATH, "%s/cgroup.procs", cgroup_path);
-
- debug_msg("Using cgroup path at: %s\n", cgroup_procs);
-
- cg_fd = open(cgroup_procs, O_RDWR);
+ cg_fd = open_cgroup_procs(cgroup);
if (cg_fd < 0)
return 0;
@@ -975,28 +1011,59 @@ int auto_house_keeping(cpu_set_t *monitored_cpus)
return 1;
}
-/**
- * parse_optional_arg - Parse optional argument value
- *
- * Parse optional argument value, which can be in the form of:
- * -sarg, -s/--long=arg, -s/--long arg
+/*
+ * strtoi - convert string to integer with error checking
*
- * Returns arg value if found, NULL otherwise.
+ * Returns 0 on success, -1 if conversion fails or result is out of int range.
*/
-char *parse_optional_arg(int argc, char **argv)
+int strtoi(const char *s, int *res)
{
- if (optarg) {
- if (optarg[0] == '=') {
- /* skip the = */
- return &optarg[1];
- } else {
- return optarg;
- }
- /* parse argument of form -s [arg] and --long [arg]*/
- } else if (optind < argc && argv[optind][0] != '-') {
- /* consume optind */
- return argv[optind++];
- } else {
- return NULL;
- }
+ char *end_ptr;
+ long lres;
+
+ if (!*s)
+ return -1;
+
+ errno = 0;
+ lres = strtol(s, &end_ptr, 0);
+ if (errno || *end_ptr || lres > INT_MAX || lres < INT_MIN)
+ return -1;
+
+ *res = (int) lres;
+ return 0;
+}
+
+static inline void fatal_alloc(void)
+{
+ fatal("Error allocating memory\n");
+}
+
+void *calloc_fatal(size_t n, size_t size)
+{
+ void *p = calloc(n, size);
+
+ if (!p)
+ fatal_alloc();
+
+ return p;
+}
+
+void *reallocarray_fatal(void *p, size_t n, size_t size)
+{
+ p = reallocarray(p, n, size);
+
+ if (!p)
+ fatal_alloc();
+
+ return p;
+}
+
+char *strdup_fatal(const char *s)
+{
+ char *p = strdup(s);
+
+ if (!p)
+ fatal_alloc();
+
+ return p;
}
diff --git a/tools/tracing/rtla/src/utils.h b/tools/tracing/rtla/src/utils.h
index 091df4ba4587..2ba3333669bb 100644
--- a/tools/tracing/rtla/src/utils.h
+++ b/tools/tracing/rtla/src/utils.h
@@ -1,8 +1,13 @@
// SPDX-License-Identifier: GPL-2.0
#include <stdint.h>
+#include <string.h>
#include <time.h>
#include <sched.h>
+#include <stdbool.h>
+#include <stdlib.h>
+
+#include <linux/container_of.h>
/*
* '18446744073709551615\0'
@@ -12,11 +17,29 @@
#define MAX_NICE 20
#define MIN_NICE -19
-#define container_of(ptr, type, member)({ \
- const typeof(((type *)0)->member) *__mptr = (ptr); \
- (type *)((char *)__mptr - offsetof(type, member)) ; })
+#ifndef ARRAY_SIZE
+#define ARRAY_SIZE(x) (sizeof(x) / sizeof(*(x)))
+#endif
+
+/* Calculate string length at compile time (excluding null terminator) */
+#define STRING_LENGTH(s) (ARRAY_SIZE(s) - sizeof(*(s)))
+
+/* Compare string with static string, length determined at compile time */
+#define strncmp_static(s1, s2) strncmp(s1, s2, ARRAY_SIZE(s2))
-extern int config_debug;
+/**
+ * str_has_prefix - Test if a string has a given prefix
+ * @str: The string to test
+ * @prefix: The string to see if @str starts with
+ *
+ * Returns: true if @str starts with @prefix, false otherwise
+ */
+static inline bool str_has_prefix(const char *str, const char *prefix)
+{
+ return strncmp(str, prefix, strlen(prefix)) == 0;
+}
+
+extern bool config_debug;
void debug_msg(const char *fmt, ...);
void err_msg(const char *fmt, ...);
void fatal(const char *fmt, ...);
@@ -24,8 +47,6 @@ void fatal(const char *fmt, ...);
long parse_seconds_duration(char *val);
void get_duration(time_t start_time, char *output, int output_size);
-int parse_cpu_list(char *cpu_list, char **monitored_cpus);
-char *parse_optional_arg(int argc, char **argv);
long long get_llong_from_str(char *start);
static inline void
@@ -61,13 +82,23 @@ struct sched_attr {
};
#endif /* SCHED_ATTR_SIZE_VER0 */
+enum stack_format {
+ STACK_FORMAT_TRUNCATE,
+ STACK_FORMAT_SKIP,
+ STACK_FORMAT_FULL
+};
+
int parse_prio(char *arg, struct sched_attr *sched_param);
int parse_cpu_set(char *cpu_list, cpu_set_t *set);
+int parse_stack_format(char *arg);
int __set_sched_attr(int pid, struct sched_attr *attr);
int set_comm_sched_attr(const char *comm_prefix, struct sched_attr *attr);
int set_comm_cgroup(const char *comm_prefix, const char *cgroup);
int set_pid_cgroup(pid_t pid, const char *cgroup);
int set_cpu_dma_latency(int32_t latency);
+void *calloc_fatal(size_t n, size_t size);
+void *reallocarray_fatal(void *p, size_t n, size_t size);
+char *strdup_fatal(const char *s);
#ifdef HAVE_LIBCPUPOWER_SUPPORT
int save_cpu_idle_disable_state(unsigned int cpu);
int restore_cpu_idle_disable_state(unsigned int cpu);
@@ -82,12 +113,13 @@ static inline int set_deepest_cpu_idle_state(unsigned int cpu, unsigned int stat
static inline int have_libcpupower_support(void) { return 0; }
#endif /* HAVE_LIBCPUPOWER_SUPPORT */
int auto_house_keeping(cpu_set_t *monitored_cpus);
+__attribute__((__warn_unused_result__)) int strtoi(const char *s, int *res);
#define ns_to_usf(x) (((double)x/1000))
#define ns_to_per(total, part) ((part * 100) / (double)total)
enum result {
- PASSED = 0, /* same as EXIT_SUCCESS */
- ERROR = 1, /* same as EXIT_FAILURE, an error in arguments */
- FAILED = 2, /* test hit the stop tracing condition */
+ PASSED = EXIT_SUCCESS,
+ ERROR = EXIT_FAILURE,
+ FAILED, /* test hit the stop tracing condition */
};
diff --git a/tools/tracing/rtla/tests/bpf/bpf_action_map.c b/tools/tracing/rtla/tests/bpf/bpf_action_map.c
new file mode 100644
index 000000000000..1686e0b858e6
--- /dev/null
+++ b/tools/tracing/rtla/tests/bpf/bpf_action_map.c
@@ -0,0 +1,25 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/bpf.h>
+#include <bpf/bpf_tracing.h>
+
+char LICENSE[] SEC("license") = "GPL";
+
+struct {
+ __uint(type, BPF_MAP_TYPE_ARRAY);
+ __uint(max_entries, 1);
+ __type(key, unsigned int);
+ __type(value, unsigned long long);
+} rtla_test_map SEC(".maps");
+
+struct trace_event_raw_timerlat_sample;
+
+SEC("tp/timerlat_action")
+int action_handler(struct trace_event_raw_timerlat_sample *tp_args)
+{
+ unsigned int key = 0;
+ unsigned long long value = 42;
+
+ bpf_map_update_elem(&rtla_test_map, &key, &value, BPF_ANY);
+
+ return 0;
+}
diff --git a/tools/tracing/rtla/tests/engine.sh b/tools/tracing/rtla/tests/engine.sh
index c7de3d6ed6a8..4287cd64ac31 100644
--- a/tools/tracing/rtla/tests/engine.sh
+++ b/tools/tracing/rtla/tests/engine.sh
@@ -4,6 +4,9 @@ test_begin() {
# Count tests to allow the test harness to double-check if all were
# included correctly.
ctr=0
+ # Set test directory to the directory of the script
+ scriptfile=$(realpath "$0")
+ testdir=$(dirname "$scriptfile")
[ -z "$RTLA" ] && RTLA="./rtla"
[ -n "$TEST_COUNT" ] && echo "1..$TEST_COUNT"
}
@@ -51,6 +54,11 @@ check() {
then
# Reset osnoise options before running test.
[ "$NO_RESET_OSNOISE" == 1 ] || reset_osnoise
+
+ # Create a temporary directory to contain rtla output
+ tmpdir=$(mktemp -d)
+ pushd $tmpdir >/dev/null
+
# Run rtla; in case of failure, include its output as comment
# in the test results.
result=$(eval stdbuf -oL $TIMEOUT "$RTLA" $2 2>&1); exitcode=$?
@@ -82,35 +90,60 @@ check() {
echo "$result" | col -b | while read line; do echo "# $line"; done
printf "#\n# exit code %s\n" $exitcode
fi
+
+ # Remove temporary directory
+ popd >/dev/null
+ rm -r $tmpdir
fi
}
check_with_osnoise_options() {
- # Do the same as "check", but with pre-set osnoise options.
- # Note: rtla should reset the osnoise options, this is used to test
- # if it indeed does so.
- # Save original arguments
- arg1=$1
- arg2=$2
- arg3=$3
-
- # Apply osnoise options (if not dry run)
+ # Do the same as "check", but with pre-set tracefs options.
+ # Resets osnoise first, then writes the given tracefs option=value
+ # pairs before running the check with NO_RESET_OSNOISE=1.
+ # Arguments: test_name command exit_code expected_output [path=value ...]
+ # Each path is relative to /sys/kernel/tracing/
+ local arg1=$1
+ local arg2=$2
+ local arg3=$3
+ local arg4=$4
+ local opt option value
+
+ # Apply tracefs options (if not dry run)
if [ -n "$TEST_COUNT" ]
then
[ "$NO_RESET_OSNOISE" == 1 ] || reset_osnoise
- shift
- shift
- while shift
+ shift 4
+ for opt in "$@"
do
- [ "$1" == "" ] && continue
- option=$(echo $1 | cut -d '=' -f 1)
- value=$(echo $1 | cut -d '=' -f 2)
- echo "option: $option, value: $value"
- echo "$value" > "/sys/kernel/tracing/osnoise/$option" || return 1
+ [ -z "$opt" ] && continue
+ option="${opt%%=*}"
+ value="${opt#*=}"
+ # Try to apply the option, ignore errors: when pre-setting fails
+ # (e.g. kernel does not know the option), the test itself will likely
+ # also fail.
+ # Throwing an error here would cause the test to be incorrectly
+ # skipped.
+ echo "$value" > "/sys/kernel/tracing/$option"
done
fi
- NO_RESET_OSNOISE=1 check "$arg1" "$arg2" "$arg3"
+ NO_RESET_OSNOISE=1 check "$arg1" "$arg2" "$arg3" "$arg4"
+}
+
+check_top_hist() {
+ # Test one command with both "top" and "hist" tools, replacing "TOOL" in
+ # command with either "top" or "hist" respectively, and prefixing the test
+ # names with "top " and "hist ".
+ check "top $1" "$(echo "$2" | sed 's/TOOL/top/g')" "${@:3}"
+ check "hist $1" "$(echo "$2" | sed 's/TOOL/hist/g')" "${@:3}"
+}
+
+check_top_q_hist() {
+ # Same as above, but pass "-q" to top so that strings printed in main
+ # loop are on their own line for top too, not only for hist.
+ check "top $1" "$(echo "$2" | sed 's/TOOL/top -q/g')" "${@:3}"
+ check "hist $1" "$(echo "$2" | sed 's/TOOL/hist/g')" "${@:3}"
}
set_timeout() {
diff --git a/tools/tracing/rtla/tests/hwnoise.t b/tools/tracing/rtla/tests/hwnoise.t
index 23ce250a6852..b53d8d440fc5 100644
--- a/tools/tracing/rtla/tests/hwnoise.t
+++ b/tools/tracing/rtla/tests/hwnoise.t
@@ -6,7 +6,7 @@ test_begin
set_timeout 2m
check "verify help page" \
- "hwnoise --help" 0 "summary of hardware-related noise"
+ "hwnoise --help" 129 "Usage: rtla hwnoise"
check "detect noise higher than one microsecond" \
"hwnoise -c 0 -T 1 -d 5s -q" 0
check "set the automatic trace mode" \
@@ -18,5 +18,8 @@ check "stop the trace if a single sample is higher than 1 us" \
check "enable a trace event trigger" \
"hwnoise -t -e osnoise:irq_noise --trigger=\"hist:key=desc,duration:sort=desc,duration:vals=hitcount\" -d 10s" \
0 "Saving event osnoise:irq_noise hist to osnoise_irq_noise_hist.txt"
+check "verify OSNOISE_IRQ_DISABLE" \
+ "hwnoise -S 1 --on-threshold shell,command=\"$testdir/scripts/check-osnoise-option.sh OSNOISE_IRQ_DISABLE\"" \
+ 2 "^OSNOISE_IRQ_DISABLE=enabled$"
test_end
diff --git a/tools/tracing/rtla/tests/osnoise.t b/tools/tracing/rtla/tests/osnoise.t
index 396334608920..214fd40fc610 100644
--- a/tools/tracing/rtla/tests/osnoise.t
+++ b/tools/tracing/rtla/tests/osnoise.t
@@ -6,45 +6,97 @@ test_begin
set_timeout 2m
check "verify help page" \
- "osnoise --help" 0 "osnoise version"
-check "verify the --priority/-P param" \
- "osnoise top -P F:1 -c 0 -r 900000 -d 10s -q -S 1 --on-threshold shell,command=\"tests/scripts/check-priority.sh osnoise/ SCHED_FIFO 1\"" \
+ "osnoise --help" 129 "osnoise version"
+check_top_hist "verify help page" \
+ "osnoise TOOL --help" 129 "rtla osnoise"
+check_top_q_hist "verify the --stop/-s param" \
+ "osnoise TOOL -s 30 -T 1" 2 "osnoise hit stop tracing"
+check_top_q_hist "verify the --trace param" \
+ "osnoise TOOL -s 30 -T 1 -t" 2 "Saving trace to osnoise_trace.txt"
+
+# Thread tests
+check_top_q_hist "verify the --priority/-P param" \
+ "osnoise TOOL -P F:1 -c 0 -r 900000 -d 10s -S 1 --on-threshold shell,command=\"$testdir/scripts/check-priority.sh SCHED_FIFO 1\"" \
2 "Priorities are set correctly"
-check "verify the --stop/-s param" \
- "osnoise top -s 30 -T 1" 2 "osnoise hit stop tracing"
-check "verify the --trace param" \
- "osnoise hist -s 30 -T 1 -t" 2 "Saving trace to osnoise_trace.txt"
-check "verify the --entries/-E param" \
- "osnoise hist -P F:1 -c 0 -r 900000 -d 10s -b 10 -E 25"
-
-# Test setting default period by putting an absurdly high period
-# and stopping on threshold.
-# If default period is not set, this will time out.
+check_top_q_hist "verify the -C/--cgroup param" \
+ "osnoise TOOL -C -c 0 -r 900000 -d 10s -S 1 --on-threshold shell,command=\"$testdir/scripts/check-cgroup-match.sh\"" \
+ 2 "cgroup matches for all workload PIDs"
+check_top_q_hist "verify the -c/--cpus param" \
+ "osnoise TOOL -P F:1 -c 0 -r 900000 -d 10s -S 1 --on-threshold shell,command=$testdir/scripts/check-cpus.sh" 2 "^Affinity of threads: 0$"
+check_top_q_hist "verify the -H/--house-keeping param" \
+ "osnoise TOOL -P F:1 -H 0 -r 900000 -d 10s -S 1 --on-threshold shell,command=$testdir/scripts/check-housekeeping-cpus.sh" 2 "^Affinity of threads: 0$"
+
+# Histogram tests
+check "hist with -b/--bucket-size" \
+ "osnoise hist -b 1 -d 1s"
+check "hist with -E/--entries" \
+ "osnoise hist -E 10 -d 1s"
+check "hist with -E/--entries out of range" \
+ "osnoise hist -E 1 -d 1s" 129 "out of range \[10, 9999999\]"
+check "hist with --no-header" \
+ "osnoise hist --no-header -d 1s" 0 "" "RTLA osnoise histogram"
+check "hist with --with-zeros" \
+ "osnoise hist --with-zeros -b 100000 -E 21 -d 1s" 0 '^2000000\s+0\s+'
+check "hist with --no-index" \
+ "osnoise hist --no-index --with-zeros -d 1s" 0 "" "^count:"
+check "hist with --no-summary" \
+ "osnoise hist --no-summary -d 1s" 0 "" "^count:"
+
+# Tracer option tests - verify that rtla correctly sets tracefs options
+# Default tests: poison tracefs with wrong values, verify rtla resets to defaults
check_with_osnoise_options "apply default period" \
- "osnoise hist -s 1" 2 period_us=600000000
+ "osnoise top -q -S 1 --on-threshold shell,command=\"$testdir/scripts/check-tracefs-value.sh osnoise/period_us\"" \
+ 2 "^osnoise/period_us=1000000$" osnoise/period_us=600000000
+check_with_osnoise_options "apply default runtime" \
+ "osnoise top -q -S 1 --on-threshold shell,command=\"$testdir/scripts/check-tracefs-value.sh osnoise/runtime_us\"" \
+ 2 "^osnoise/runtime_us=1000000$" osnoise/runtime_us=100
+check_with_osnoise_options "apply default tracing_thresh" \
+ "osnoise top -q -S 1 --on-threshold shell,command=\"$testdir/scripts/check-tracefs-value.sh tracing_thresh\"" \
+ 2 "^tracing_thresh=0$" tracing_thresh=999999
+check_with_osnoise_options "apply default stop_tracing_us" \
+ "osnoise top -q -S 1 --on-threshold shell,command=\"$testdir/scripts/check-tracefs-value.sh osnoise/stop_tracing_us\"" \
+ 2 "^osnoise/stop_tracing_us=0$" osnoise/stop_tracing_us=999999
+check_with_osnoise_options "apply default stop_tracing_total_us" \
+ "osnoise top -q -s 1 --on-threshold shell,command=\"$testdir/scripts/check-tracefs-value.sh osnoise/stop_tracing_total_us\"" \
+ 2 "^osnoise/stop_tracing_total_us=0$" osnoise/stop_tracing_total_us=999999
+
+# Non-default tracer option tests: verify CLI options correctly set tracefs values
+check_top_q_hist "verify -p sets period_us" \
+ "osnoise TOOL -p 2000000 -S 1 --on-threshold shell,command=\"$testdir/scripts/check-tracefs-value.sh osnoise/period_us\"" \
+ 2 "^osnoise/period_us=2000000$"
+check_top_q_hist "verify -r sets runtime_us" \
+ "osnoise TOOL -r 500000 -S 1 --on-threshold shell,command=\"$testdir/scripts/check-tracefs-value.sh osnoise/runtime_us\"" \
+ 2 "^osnoise/runtime_us=500000$"
+check_top_q_hist "verify -T sets tracing_thresh" \
+ "osnoise TOOL -T 5 -S 1 --on-threshold shell,command=\"$testdir/scripts/check-tracefs-value.sh tracing_thresh\"" \
+ 2 "^tracing_thresh=5$"
+check_top_q_hist "verify -s sets stop_tracing_us" \
+ "osnoise TOOL -s 30 -S 1 --on-threshold shell,command=\"$testdir/scripts/check-tracefs-value.sh osnoise/stop_tracing_us\"" \
+ 2 "^osnoise/stop_tracing_us=30$"
+check_top_q_hist "verify -S sets stop_tracing_total_us" \
+ "osnoise TOOL -S 100 --on-threshold shell,command=\"$testdir/scripts/check-tracefs-value.sh osnoise/stop_tracing_total_us\"" \
+ 2 "^osnoise/stop_tracing_total_us=100$"
# Actions tests
-check "trace output through -t with custom filename" \
- "osnoise hist -S 2 -t custom_filename.txt" 2 "^ Saving trace to custom_filename.txt$"
-check "trace output through --on-threshold trace" \
- "osnoise hist -S 2 --on-threshold trace" 2 "^ Saving trace to osnoise_trace.txt$"
-check "trace output through --on-threshold trace with custom filename" \
- "osnoise hist -S 2 --on-threshold trace,file=custom_filename.txt" 2 "^ Saving trace to custom_filename.txt$"
-check "exec command" \
- "osnoise hist -S 2 --on-threshold shell,command='echo TestOutput'" 2 "^TestOutput$"
-check "multiple actions" \
- "osnoise hist -S 2 --on-threshold shell,command='echo -n 1' --on-threshold shell,command='echo 2'" 2 "^12$"
+check_top_q_hist "trace output through -t with custom filename" \
+ "osnoise TOOL -S 2 -t custom_filename.txt" 2 "^ Saving trace to custom_filename.txt$"
+check_top_q_hist "trace output through --on-threshold trace" \
+ "osnoise TOOL -S 2 --on-threshold trace" 2 "^ Saving trace to osnoise_trace.txt$"
+check_top_q_hist "trace output through --on-threshold trace with custom filename" \
+ "osnoise TOOL -S 2 --on-threshold trace,file=custom_filename.txt" 2 "^ Saving trace to custom_filename.txt$"
+check_top_q_hist "exec command" \
+ "osnoise TOOL -S 2 --on-threshold shell,command='echo TestOutput'" 2 "^TestOutput$"
+check_top_q_hist "multiple actions" \
+ "osnoise TOOL -S 2 --on-threshold shell,command='echo -n 1' --on-threshold shell,command='echo 2'" 2 "^12$"
check "hist stop at failed action" \
"osnoise hist -S 2 --on-threshold shell,command='echo -n 1; false' --on-threshold shell,command='echo -n 2'" 2 "^1# RTLA osnoise histogram$"
check "top stop at failed action" \
"osnoise top -S 2 --on-threshold shell,command='echo -n abc; false' --on-threshold shell,command='echo -n defgh'" 2 "^abc" "defgh"
-check "hist with continue" \
- "osnoise hist -S 2 -d 5s --on-threshold shell,command='echo TestOutput' --on-threshold continue" 0 "^TestOutput$"
-check "top with continue" \
- "osnoise top -q -S 2 -d 5s --on-threshold shell,command='echo TestOutput' --on-threshold continue" 0 "^TestOutput$"
-check "hist with trace output at end" \
- "osnoise hist -d 1s --on-end trace" 0 "^ Saving trace to osnoise_trace.txt$"
-check "top with trace output at end" \
- "osnoise top -d 1s --on-end trace" 0 "^ Saving trace to osnoise_trace.txt$"
+check_top_q_hist "with continue" \
+ "osnoise TOOL -S 2 -d 5s --on-threshold shell,command='echo TestOutput' --on-threshold continue" 0 "^TestOutput$"
+check_top_q_hist "with conditional continue" \
+ "osnoise TOOL -S 2 --on-threshold shell,command='if [ -f a ]; then echo 2; exit 1; else echo -n 1; touch a; fi' --on-threshold continue" 2 "^12$" "^2$"
+check_top_hist "with trace output at end" \
+ "osnoise TOOL -d 1s --on-end trace" 0 "^ Saving trace to osnoise_trace.txt$"
test_end
diff --git a/tools/tracing/rtla/tests/scripts/check-cgroup-match.sh b/tools/tracing/rtla/tests/scripts/check-cgroup-match.sh
new file mode 100755
index 000000000000..fdc2c68c5957
--- /dev/null
+++ b/tools/tracing/rtla/tests/scripts/check-cgroup-match.sh
@@ -0,0 +1,17 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+. "$(dirname $0)/lib/get_workload_pids.sh"
+rtla_pid=$(echo $(ps -o ppid= $$))
+rtla_cgroup=$(</proc/$rtla_pid/cgroup)
+echo "RTLA cgroup: $rtla_cgroup"
+for pid in $(get_workload_pids)
+do
+ pid_cgroup=$(</proc/$pid/cgroup)
+ echo "PID $pid cgroup: $pid_cgroup"
+ if ! [ "$pid_cgroup" = "$rtla_cgroup" ]
+ then
+ echo "Mismatch!"
+ exit 0
+ fi
+done
+echo "cgroup matches for all workload PIDs"
diff --git a/tools/tracing/rtla/tests/scripts/check-cpus.sh b/tools/tracing/rtla/tests/scripts/check-cpus.sh
new file mode 100755
index 000000000000..0b016d4a7945
--- /dev/null
+++ b/tools/tracing/rtla/tests/scripts/check-cpus.sh
@@ -0,0 +1,9 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+. "$(dirname $0)/lib/get_workload_pids.sh"
+echo -n "Affinity of threads: "
+for pid in $(get_workload_pids)
+do
+ echo -n $(taskset -c -p $pid | cut -d ':' -f 2)
+done
+echo
diff --git a/tools/tracing/rtla/tests/scripts/check-housekeeping-cpus.sh b/tools/tracing/rtla/tests/scripts/check-housekeeping-cpus.sh
new file mode 100755
index 000000000000..4742f34efb49
--- /dev/null
+++ b/tools/tracing/rtla/tests/scripts/check-housekeeping-cpus.sh
@@ -0,0 +1,4 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+pid=$(ps -o ppid= $$)
+echo "Affinity of threads:$(taskset -c -p $pid | cut -d ':' -f 2)"
diff --git a/tools/tracing/rtla/tests/scripts/check-osnoise-option.sh b/tools/tracing/rtla/tests/scripts/check-osnoise-option.sh
new file mode 100755
index 000000000000..37c62268cc15
--- /dev/null
+++ b/tools/tracing/rtla/tests/scripts/check-osnoise-option.sh
@@ -0,0 +1,16 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# Check if osnoise options are enabled or disabled.
+# Usage: check-osnoise-option.sh <OPTION1> [<OPTION2> ...]
+# Output: one line per option in the format "OPTION=enabled" or "OPTION=disabled"
+
+options=$(tr ' ' '\n' < /sys/kernel/tracing/osnoise/options)
+for name in "$@"; do
+ if echo "$options" | grep -q "^NO_${name}$"; then
+ echo "$name=disabled"
+ elif echo "$options" | grep -q "^${name}$"; then
+ echo "$name=enabled"
+ else
+ echo "$name=unsupported"
+ fi
+done
diff --git a/tools/tracing/rtla/tests/scripts/check-priority.sh b/tools/tracing/rtla/tests/scripts/check-priority.sh
index 79b702a34a96..b51d5232a868 100755
--- a/tools/tracing/rtla/tests/scripts/check-priority.sh
+++ b/tools/tracing/rtla/tests/scripts/check-priority.sh
@@ -1,8 +1,8 @@
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
-pids="$(pgrep ^$1)" || exit 1
-for pid in $pids
+. "$(dirname $0)/lib/get_workload_pids.sh"
+for pid in $(get_workload_pids)
do
- chrt -p $pid | cut -d ':' -f 2 | head -n1 | grep "^ $2\$" >/dev/null
- chrt -p $pid | cut -d ':' -f 2 | tail -n1 | grep "^ $3\$" >/dev/null
+ chrt -p $pid | cut -d ':' -f 2 | head -n1 | grep "^ $1\$" >/dev/null
+ chrt -p $pid | cut -d ':' -f 2 | tail -n1 | grep "^ $2\$" >/dev/null
done && echo "Priorities are set correctly"
diff --git a/tools/tracing/rtla/tests/scripts/check-tracefs-value.sh b/tools/tracing/rtla/tests/scripts/check-tracefs-value.sh
new file mode 100755
index 000000000000..12d0eacd6a85
--- /dev/null
+++ b/tools/tracing/rtla/tests/scripts/check-tracefs-value.sh
@@ -0,0 +1,11 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# Read tracefs values and print them.
+# Usage: check-tracefs-value.sh <relative_path1> [<relative_path2> ...]
+# Each path is relative to /sys/kernel/tracing/
+# Output: one line per file in the format "path=value"
+
+for file in "$@"; do
+ read value < "/sys/kernel/tracing/$file"
+ echo "$file=$value"
+done
diff --git a/tools/tracing/rtla/tests/scripts/check-user-kernel-threads.sh b/tools/tracing/rtla/tests/scripts/check-user-kernel-threads.sh
new file mode 100755
index 000000000000..bb7ac510a735
--- /dev/null
+++ b/tools/tracing/rtla/tests/scripts/check-user-kernel-threads.sh
@@ -0,0 +1,16 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+. "$(dirname $0)/lib/get_workload_pids.sh"
+kthreadd_pid=$(pgrep ^kthreadd$)
+cnt_kernel=0
+cnt_user=0
+for pid in $(get_workload_pids)
+do
+ if [ "$(echo $(ps -o ppid= $pid))" = "$kthreadd_pid" ]
+ then
+ ((++cnt_kernel))
+ else
+ ((++cnt_user))
+ fi
+done
+echo "$cnt_kernel kernel threads, $cnt_user user threads"
diff --git a/tools/tracing/rtla/tests/scripts/lib/get_workload_pids.sh b/tools/tracing/rtla/tests/scripts/lib/get_workload_pids.sh
new file mode 100644
index 000000000000..d10a4e3b321d
--- /dev/null
+++ b/tools/tracing/rtla/tests/scripts/lib/get_workload_pids.sh
@@ -0,0 +1,11 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+get_workload_pids() {
+ local shell_pid=$$
+ local rtla_pid=$(ps -o ppid= $shell_pid)
+
+ # kernel threads
+ pgrep -P $(pgrep ^kthreadd$) -f '^\[?(osnoise|timerlat)/[0-9]+\]?$'
+ # user threads
+ pgrep -P $rtla_pid | grep -v "^$shell_pid$"
+}
diff --git a/tools/tracing/rtla/tests/timerlat.t b/tools/tracing/rtla/tests/timerlat.t
index bbaa1897d8a8..5116857d7da4 100644
--- a/tools/tracing/rtla/tests/timerlat.t
+++ b/tools/tracing/rtla/tests/timerlat.t
@@ -21,52 +21,136 @@ export RTLA_NO_BPF=$option
# Basic tests
check "verify help page" \
- "timerlat --help" 0 "timerlat version"
-check "verify -s/--stack" \
- "timerlat top -s 3 -T 10 -t" 2 "Blocking thread stack trace"
-check "verify -P/--priority" \
- "timerlat top -P F:1 -c 0 -d 10s -q -T 1 --on-threshold shell,command=\"tests/scripts/check-priority.sh timerlatu/ SCHED_FIFO 1\"" \
+ "timerlat --help" 129 "timerlat version"
+check_top_hist "verify help page" \
+ "timerlat TOOL --help" 129 "rtla timerlat"
+check_top_hist "verify -s/--stack" \
+ "timerlat TOOL -s 3 -T 10 -t" 2 "Blocking thread stack trace"
+check_top_hist "test in nanoseconds" \
+ "timerlat TOOL -i 2 -c 0 -n -d 10s" 2 "ns"
+check_top_hist "set the automatic trace mode" \
+ "timerlat TOOL -a 5" 2 "analyzing it"
+check_top_hist "dump tasks" \
+ "timerlat TOOL -a 5 --dump-tasks" 2 "Printing CPU tasks"
+check "verify --aa-only stop on threshold" \
+ "timerlat top --aa-only 5" 2 "analyzing it" "Timer Latency"
+check "verify --aa-only max latency" \
+ "timerlat top --aa-only 2000000 -d 1s" 0 "^ Max latency was" "Timer Latency"
+check_top_hist "disable auto-analysis" \
+ "timerlat TOOL -s 3 -T 10 -t --no-aa" 2 "" "analyzing it"
+
+# Thread tests
+check_top_hist "verify -P/--priority" \
+ "timerlat TOOL -P F:1 -c 0 -d 10s -T 1 --on-threshold shell,command=\"$testdir/scripts/check-priority.sh SCHED_FIFO 1\"" \
2 "Priorities are set correctly"
-check "test in nanoseconds" \
- "timerlat top -i 2 -c 0 -n -d 10s" 2 "ns"
-check "set the automatic trace mode" \
- "timerlat top -a 5" 2 "analyzing it"
-check "dump tasks" \
- "timerlat top -a 5 --dump-tasks" 2 "Printing CPU tasks"
-check "print the auto-analysis if hits the stop tracing condition" \
- "timerlat top --aa-only 5" 2
-check "disable auto-analysis" \
- "timerlat top -s 3 -T 10 -t --no-aa" 2
-check "verify -c/--cpus" \
- "timerlat hist -c 0 -d 10s"
-check "hist test in nanoseconds" \
- "timerlat hist -i 2 -c 0 -n -d 10s" 2 "ns"
+check_top_hist "verify -C/--cgroup" \
+ "timerlat TOOL -k -C -c 0 -d 10s -T 1 --on-threshold shell,command=\"$testdir/scripts/check-cgroup-match.sh\"" \
+ 2 "cgroup matches for all workload PIDs"
+check_top_q_hist "verify -c/--cpus" \
+ "timerlat TOOL -c 0 -d 10s -T 1 --on-threshold shell,command=$testdir/scripts/check-cpus.sh" 2 "^Affinity of threads: 0$"
+check_top_q_hist "verify -H/--house-keeping" \
+ "timerlat TOOL -H 0 -d 10s -T 1 --on-threshold shell,command=$testdir/scripts/check-housekeeping-cpus.sh" 2 "^Affinity of threads: 0$"
+check_top_q_hist "verify -k/--kernel-threads" \
+ "timerlat TOOL -k -c 0 -d 10s -T 1 --on-threshold shell,command=$testdir/scripts/check-user-kernel-threads.sh" 2 "1 kernel threads, 0 user threads"
+check_top_q_hist "verify -u/--user-threads" \
+ "timerlat TOOL -u -c 0 -d 10s -T 1 --on-threshold shell,command=$testdir/scripts/check-user-kernel-threads.sh" 2 "0 kernel threads, 1 user threads"
+
+# Tracer option tests - verify that rtla correctly sets tracefs options
+# Default tests: poison tracefs with wrong values, verify rtla resets to defaults
+check_with_osnoise_options "apply default timerlat_period_us" \
+ "timerlat top -q -T 1 --on-threshold shell,command=\"$testdir/scripts/check-tracefs-value.sh osnoise/timerlat_period_us\"" \
+ 2 "^osnoise/timerlat_period_us=1000$" osnoise/timerlat_period_us=999999
+check_with_osnoise_options "apply default print_stack" \
+ "timerlat top -q -T 1 --on-threshold shell,command=\"$testdir/scripts/check-tracefs-value.sh osnoise/print_stack\"" \
+ 2 "^osnoise/print_stack=0$" osnoise/print_stack=999999
+check_with_osnoise_options "apply default stop_tracing_us" \
+ "timerlat top -q -T 1 --on-threshold shell,command=\"$testdir/scripts/check-tracefs-value.sh osnoise/stop_tracing_us\"" \
+ 2 "^osnoise/stop_tracing_us=0$" osnoise/stop_tracing_us=999999
+check_with_osnoise_options "apply default stop_tracing_total_us" \
+ "timerlat top -q -i 1 --on-threshold shell,command=\"$testdir/scripts/check-tracefs-value.sh osnoise/stop_tracing_total_us\"" \
+ 2 "^osnoise/stop_tracing_total_us=0$" osnoise/stop_tracing_total_us=999999
+
+# Non-default tracer option tests: verify CLI options correctly set tracefs values
+check_top_q_hist "verify -p sets timerlat_period_us" \
+ "timerlat TOOL -p 2000 -T 1 --on-threshold shell,command=\"$testdir/scripts/check-tracefs-value.sh osnoise/timerlat_period_us\"" \
+ 2 "^osnoise/timerlat_period_us=2000$"
+check_top_q_hist "verify -s sets print_stack" \
+ "timerlat TOOL -s 5 -T 1 --on-threshold shell,command=\"$testdir/scripts/check-tracefs-value.sh osnoise/print_stack\"" \
+ 2 "^osnoise/print_stack=5$"
+check_top_q_hist "verify -i sets stop_tracing_us" \
+ "timerlat TOOL -i 2 --on-threshold shell,command=\"$testdir/scripts/check-tracefs-value.sh osnoise/stop_tracing_us\"" \
+ 2 "^osnoise/stop_tracing_us=2$"
+check_top_q_hist "verify -T sets stop_tracing_total_us" \
+ "timerlat TOOL -T 2 --on-threshold shell,command=\"$testdir/scripts/check-tracefs-value.sh osnoise/stop_tracing_total_us\"" \
+ 2 "^osnoise/stop_tracing_total_us=2$"
+check_with_osnoise_options "apply default TIMERLAT_ALIGN" \
+ "timerlat top -q -T 1 --on-threshold shell,command=\"$testdir/scripts/check-osnoise-option.sh TIMERLAT_ALIGN\"" \
+ 2 "^TIMERLAT_ALIGN=disabled$" osnoise/options=TIMERLAT_ALIGN
+check_top_q_hist "verify -A sets TIMERLAT_ALIGN" \
+ "timerlat TOOL -A 100 -T 1 --on-threshold shell,command=\"$testdir/scripts/check-osnoise-option.sh TIMERLAT_ALIGN\"" \
+ 2 "^TIMERLAT_ALIGN=enabled$"
+check_top_q_hist "verify -A sets timerlat_align_us" \
+ "timerlat TOOL -A 100 -T 1 --on-threshold shell,command=\"$testdir/scripts/check-tracefs-value.sh osnoise/timerlat_align_us\"" \
+ 2 "^osnoise/timerlat_align_us=100$"
+
+# Histogram tests
+check "hist with -b/--bucket-size" \
+ "timerlat hist -b 1 -d 1s"
+check "hist with -E/--entries" \
+ "timerlat hist -E 10 -d 1s"
+check "hist with -E/--entries out of range" \
+ "timerlat hist -E 1 -d 1s" 129 "out of range \[10, 9999999\]"
+check "hist with --no-header" \
+ "timerlat hist --no-header -d 1s" 0 "" "RTLA timerlat histogram"
+check "hist with --with-zeros" \
+ "timerlat hist --with-zeros -b 100000 -E 21 -d 1s" 0 '^2000000\s+0\s+'
+check "hist with --no-index" \
+ "timerlat hist --no-index --with-zeros -d 1s" 0 "" "^count:"
+check "hist with --no-summary" \
+ "timerlat hist --no-summary -d 1s" 0 "" "^ALL:"
+check "hist with --no-irq" \
+ "timerlat hist --no-irq -d 1s" 0 "" "IRQ-"
+check "hist with --no-thread" \
+ "timerlat hist --no-thread -d 1s" 0 "" "Thr-"
# Actions tests
-check "trace output through -t" \
- "timerlat hist -T 2 -t" 2 "^ Saving trace to timerlat_trace.txt$"
-check "trace output through -t with custom filename" \
- "timerlat hist -T 2 -t custom_filename.txt" 2 "^ Saving trace to custom_filename.txt$"
-check "trace output through --on-threshold trace" \
- "timerlat hist -T 2 --on-threshold trace" 2 "^ Saving trace to timerlat_trace.txt$"
-check "trace output through --on-threshold trace with custom filename" \
- "timerlat hist -T 2 --on-threshold trace,file=custom_filename.txt" 2 "^ Saving trace to custom_filename.txt$"
-check "exec command" \
- "timerlat hist -T 2 --on-threshold shell,command='echo TestOutput'" 2 "^TestOutput$"
-check "multiple actions" \
- "timerlat hist -T 2 --on-threshold shell,command='echo -n 1' --on-threshold shell,command='echo 2'" 2 "^12$"
+check_top_q_hist "trace output through -t" \
+ "timerlat TOOL -T 2 -t" 2 "^ Saving trace to timerlat_trace.txt$"
+check_top_q_hist "trace output through -t with custom filename" \
+ "timerlat TOOL -T 2 -t custom_filename.txt" 2 "^ Saving trace to custom_filename.txt$"
+check_top_q_hist "trace output through --on-threshold trace" \
+ "timerlat TOOL -T 2 --on-threshold trace" 2 "^ Saving trace to timerlat_trace.txt$"
+check_top_q_hist "trace output through --on-threshold trace with custom filename" \
+ "timerlat TOOL -T 2 --on-threshold trace,file=custom_filename.txt" 2 "^ Saving trace to custom_filename.txt$"
+check_top_q_hist "exec command" \
+ "timerlat TOOL -T 2 --on-threshold shell,command='echo TestOutput'" 2 "^TestOutput$"
+check_top_q_hist "multiple actions" \
+ "timerlat TOOL -T 2 --on-threshold shell,command='echo -n 1' --on-threshold shell,command='echo 2'" 2 "^12$"
check "hist stop at failed action" \
"timerlat hist -T 2 --on-threshold shell,command='echo -n 1; false' --on-threshold shell,command='echo -n 2'" 2 "^1# RTLA timerlat histogram$"
check "top stop at failed action" \
"timerlat top -T 2 --on-threshold shell,command='echo -n abc; false' --on-threshold shell,command='echo -n defgh'" 2 "^abc" "defgh"
-check "hist with continue" \
- "timerlat hist -T 2 -d 5s --on-threshold shell,command='echo TestOutput' --on-threshold continue" 0 "^TestOutput$"
-check "top with continue" \
- "timerlat top -q -T 2 -d 5s --on-threshold shell,command='echo TestOutput' --on-threshold continue" 0 "^TestOutput$"
-check "hist with trace output at end" \
- "timerlat hist -d 1s --on-end trace" 0 "^ Saving trace to timerlat_trace.txt$"
-check "top with trace output at end" \
- "timerlat top -d 1s --on-end trace" 0 "^ Saving trace to timerlat_trace.txt$"
+check_top_q_hist "with continue" \
+ "timerlat TOOL -T 2 -d 5s --on-threshold shell,command='echo TestOutput' --on-threshold continue" 0 "^TestOutput$"
+check_top_q_hist "with conditional continue" \
+ "timerlat TOOL -T 2 --on-threshold shell,command='if [ -f a ]; then echo 2; exit 1; else echo -n 1; touch a; fi' --on-threshold continue" 2 "^12$" "^2$"
+check_top_hist "with trace output at end" \
+ "timerlat TOOL -d 1s --on-end trace" 0 "^ Saving trace to timerlat_trace.txt$"
+
+# BPF action program tests
+if [ "$option" -eq 0 ]
+then
+ # Test BPF action program properly in BPF mode
+ [ -z "$BPFTOOL" ] && BPFTOOL=bpftool
+ check_top_q_hist "with BPF action program (BPF mode)" \
+ "timerlat TOOL -T 2 --bpf-action $testdir/bpf/bpf_action_map.o --on-threshold shell,command='$BPFTOOL map dump name rtla_test_map'" \
+ 2 '"value": 42'
+else
+ # Test BPF action program failure in non-BPF mode
+ check_top_q_hist "with BPF action program (non-BPF mode)" \
+ "timerlat TOOL -T 2 --bpf-action $testdir/bpf/bpf_action_map.o" \
+ 1 "BPF actions are not supported in tracefs-only mode"
+fi
done
test_end
diff --git a/tools/tracing/rtla/tests/unit/Build b/tools/tracing/rtla/tests/unit/Build
new file mode 100644
index 000000000000..d5a0f13922be
--- /dev/null
+++ b/tools/tracing/rtla/tests/unit/Build
@@ -0,0 +1,8 @@
+unit_tests-y += utils.o
+unit_tests-y += actions.o
+unit_tests-y += unit_tests.o
+unit_tests-y += osnoise_top_cli.o
+unit_tests-y += osnoise_hist_cli.o
+unit_tests-y += timerlat_top_cli.o
+unit_tests-y += timerlat_hist_cli.o
+unit_tests-y += cli_opt_callback.o
diff --git a/tools/tracing/rtla/tests/unit/Makefile.unit b/tools/tracing/rtla/tests/unit/Makefile.unit
new file mode 100644
index 000000000000..839abda64b76
--- /dev/null
+++ b/tools/tracing/rtla/tests/unit/Makefile.unit
@@ -0,0 +1,17 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+UNIT_TESTS := $(OUTPUT)unit_tests
+UNIT_TESTS_IN := $(UNIT_TESTS)-in.o
+
+$(UNIT_TESTS): $(UNIT_TESTS_IN) $(RTLA_IN) $(LIBSUBCMD) $(LIB_STRING) $(LIB_STR_ERROR_R)
+ $(QUIET_LINK)$(CC) $(LDFLAGS) -o $@ $^ $(EXTLIBS) -lcheck
+
+$(UNIT_TESTS_IN): fixdep $(LIBSUBCMD_INCLUDES)
+ make $(build)=unit_tests
+
+unit-tests: FORCE
+ $(Q)if [ "$(feature-libcheck)" = "1" ]; then \
+ $(MAKE) $(UNIT_TESTS) && $(UNIT_TESTS); \
+ else \
+ echo "libcheck is missing, skipping unit tests. Please install check-devel/check"; \
+ fi
diff --git a/tools/tracing/rtla/tests/unit/actions.c b/tools/tracing/rtla/tests/unit/actions.c
new file mode 100644
index 000000000000..94ad5ad42774
--- /dev/null
+++ b/tools/tracing/rtla/tests/unit/actions.c
@@ -0,0 +1,393 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#define _GNU_SOURCE
+#include <check.h>
+#include <signal.h>
+
+#include "../../src/actions.h"
+
+static struct actions actions_fixture;
+
+static void actions_fixture_setup(void)
+{
+ actions_init(&actions_fixture);
+}
+
+static void actions_fixture_teardown(void)
+{
+ actions_destroy(&actions_fixture);
+}
+
+START_TEST(test_actions_init)
+{
+ struct actions actions;
+
+ actions_init(&actions);
+
+ ck_assert_int_eq(actions.len, 0);
+ ck_assert_int_eq(actions.size, action_default_size);
+ ck_assert(!actions.continue_flag);
+ ck_assert_ptr_eq(actions.trace_output_inst, NULL);
+}
+END_TEST
+
+START_TEST(test_actions_destroy)
+{
+ struct actions actions;
+
+ actions_init(&actions);
+ actions_destroy(&actions);
+}
+END_TEST
+
+START_TEST(test_actions_reallocate)
+{
+ struct actions actions;
+ int i;
+
+ actions_init(&actions);
+
+ ck_assert_int_eq(actions.len, 0);
+ ck_assert_int_eq(actions.size, action_default_size);
+
+ /* Fill size of actions array */
+ for (i = 0; i < action_default_size; i++)
+ actions_add_continue(&actions);
+
+ ck_assert_int_eq(actions.len, action_default_size);
+ ck_assert_int_eq(actions.size, action_default_size);
+
+ /* Add one more action to trigger reallocation */
+ actions_add_continue(&actions);
+
+ ck_assert_int_eq(actions.len, action_default_size + 1);
+ ck_assert_int_eq(actions.size, action_default_size * 2);
+
+ actions_destroy(&actions);
+}
+END_TEST
+
+START_TEST(test_actions_add_trace_output)
+{
+ actions_add_trace_output(&actions_fixture, "trace_output.txt");
+
+ ck_assert_int_eq(actions_fixture.len, 1);
+ ck_assert_int_eq(actions_fixture.list[0].type, ACTION_TRACE_OUTPUT);
+ ck_assert_str_eq(actions_fixture.list[0].trace_output, "trace_output.txt");
+ ck_assert(actions_fixture.present[ACTION_TRACE_OUTPUT]);
+}
+END_TEST
+
+START_TEST(test_actions_add_signal)
+{
+ actions_add_signal(&actions_fixture, SIGINT, 1234);
+
+ ck_assert_int_eq(actions_fixture.len, 1);
+ ck_assert_int_eq(actions_fixture.list[0].type, ACTION_SIGNAL);
+ ck_assert_int_eq(actions_fixture.list[0].signal, SIGINT);
+ ck_assert_int_eq(actions_fixture.list[0].pid, 1234);
+ ck_assert(actions_fixture.present[ACTION_SIGNAL]);
+}
+END_TEST
+
+START_TEST(test_actions_add_shell)
+{
+ actions_add_shell(&actions_fixture, "echo Hello");
+
+ ck_assert_int_eq(actions_fixture.len, 1);
+ ck_assert_int_eq(actions_fixture.list[0].type, ACTION_SHELL);
+ ck_assert_str_eq(actions_fixture.list[0].command, "echo Hello");
+ ck_assert(actions_fixture.present[ACTION_SHELL]);
+}
+END_TEST
+
+START_TEST(test_actions_add_continue)
+{
+ actions_add_continue(&actions_fixture);
+
+ ck_assert_int_eq(actions_fixture.len, 1);
+ ck_assert_int_eq(actions_fixture.list[0].type, ACTION_CONTINUE);
+ ck_assert(actions_fixture.present[ACTION_CONTINUE]);
+}
+END_TEST
+
+START_TEST(test_actions_add_multiple_same_action)
+{
+ actions_add_trace_output(&actions_fixture, "trace1.txt");
+ actions_add_trace_output(&actions_fixture, "trace2.txt");
+
+ ck_assert_int_eq(actions_fixture.len, 2);
+ ck_assert_int_eq(actions_fixture.list[0].type, ACTION_TRACE_OUTPUT);
+ ck_assert_str_eq(actions_fixture.list[0].trace_output, "trace1.txt");
+ ck_assert_int_eq(actions_fixture.list[1].type, ACTION_TRACE_OUTPUT);
+ ck_assert_str_eq(actions_fixture.list[1].trace_output, "trace2.txt");
+ ck_assert(actions_fixture.present[ACTION_TRACE_OUTPUT]);
+}
+END_TEST
+
+START_TEST(test_actions_add_multiple_different_action)
+{
+ actions_add_trace_output(&actions_fixture, "trace_output.txt");
+ actions_add_signal(&actions_fixture, SIGINT, 1234);
+
+ ck_assert_int_eq(actions_fixture.len, 2);
+ ck_assert_int_eq(actions_fixture.list[0].type, ACTION_TRACE_OUTPUT);
+ ck_assert_str_eq(actions_fixture.list[0].trace_output, "trace_output.txt");
+ ck_assert(actions_fixture.present[ACTION_TRACE_OUTPUT]);
+ ck_assert_int_eq(actions_fixture.list[1].type, ACTION_SIGNAL);
+ ck_assert_int_eq(actions_fixture.list[1].signal, SIGINT);
+ ck_assert_int_eq(actions_fixture.list[1].pid, 1234);
+ ck_assert(actions_fixture.present[ACTION_SIGNAL]);
+}
+END_TEST
+
+START_TEST(test_actions_parse_trace_output)
+{
+ ck_assert_int_eq(actions_parse(&actions_fixture, "trace", "trace.txt"), 0);
+
+ ck_assert_int_eq(actions_fixture.len, 1);
+ ck_assert_int_eq(actions_fixture.list[0].type, ACTION_TRACE_OUTPUT);
+ ck_assert_str_eq(actions_fixture.list[0].trace_output, "trace.txt");
+ ck_assert(actions_fixture.present[ACTION_TRACE_OUTPUT]);
+}
+END_TEST
+
+START_TEST(test_actions_parse_trace_output_arg)
+{
+ ck_assert_int_eq(actions_parse(&actions_fixture, "trace,file=trace2.txt", "trace1.txt"), 0);
+
+ ck_assert_int_eq(actions_fixture.len, 1);
+ ck_assert_int_eq(actions_fixture.list[0].type, ACTION_TRACE_OUTPUT);
+ ck_assert_str_eq(actions_fixture.list[0].trace_output, "trace2.txt");
+ ck_assert(actions_fixture.present[ACTION_TRACE_OUTPUT]);
+}
+END_TEST
+
+START_TEST(test_actions_parse_trace_output_arg_bad)
+{
+ ck_assert_int_eq(actions_parse(&actions_fixture, "trace,foo=bar", "trace_output.txt"), -1);
+
+ ck_assert_int_eq(actions_fixture.len, 0);
+ ck_assert(!actions_fixture.present[ACTION_TRACE_OUTPUT]);
+}
+END_TEST
+
+START_TEST(test_actions_parse_signal)
+{
+ ck_assert_int_eq(actions_parse(&actions_fixture, "signal,num=1,pid=1234", NULL), 0);
+
+ ck_assert_int_eq(actions_fixture.len, 1);
+ ck_assert_int_eq(actions_fixture.list[0].type, ACTION_SIGNAL);
+ ck_assert_int_eq(actions_fixture.list[0].signal, 1);
+ ck_assert_int_eq(actions_fixture.list[0].pid, 1234);
+ ck_assert(actions_fixture.present[ACTION_SIGNAL]);
+}
+END_TEST
+
+START_TEST(test_actions_parse_signal_swapped)
+{
+ ck_assert_int_eq(actions_parse(&actions_fixture, "signal,pid=1234,num=1", NULL), 0);
+
+ ck_assert_int_eq(actions_fixture.len, 1);
+ ck_assert_int_eq(actions_fixture.list[0].type, ACTION_SIGNAL);
+ ck_assert_int_eq(actions_fixture.list[0].signal, 1);
+ ck_assert_int_eq(actions_fixture.list[0].pid, 1234);
+ ck_assert(actions_fixture.present[ACTION_SIGNAL]);
+}
+END_TEST
+
+START_TEST(test_actions_parse_signal_parent)
+{
+ ck_assert_int_eq(actions_parse(&actions_fixture, "signal,pid=parent,num=1", NULL), 0);
+
+ ck_assert_int_eq(actions_fixture.len, 1);
+ ck_assert_int_eq(actions_fixture.list[0].type, ACTION_SIGNAL);
+ ck_assert_int_eq(actions_fixture.list[0].signal, 1);
+ ck_assert_int_eq(actions_fixture.list[0].pid, -1);
+ ck_assert(actions_fixture.present[ACTION_SIGNAL]);
+}
+END_TEST
+
+START_TEST(test_actions_parse_signal_no_arg)
+{
+ ck_assert_int_eq(actions_parse(&actions_fixture, "signal", NULL), -1);
+
+ ck_assert_int_eq(actions_fixture.len, 0);
+ ck_assert(!actions_fixture.present[ACTION_SIGNAL]);
+}
+END_TEST
+
+START_TEST(test_actions_parse_signal_no_pid)
+{
+ ck_assert_int_eq(actions_parse(&actions_fixture, "signal,num=1", NULL), -1);
+
+ ck_assert_int_eq(actions_fixture.len, 0);
+ ck_assert(!actions_fixture.present[ACTION_SIGNAL]);
+}
+END_TEST
+
+START_TEST(test_actions_parse_signal_no_num)
+{
+ ck_assert_int_eq(actions_parse(&actions_fixture, "signal,pid=1234", NULL), -1);
+
+ ck_assert_int_eq(actions_fixture.len, 0);
+ ck_assert(!actions_fixture.present[ACTION_SIGNAL]);
+}
+END_TEST
+
+START_TEST(test_actions_parse_signal_arg_bad)
+{
+ ck_assert_int_eq(actions_parse(&actions_fixture, "signal,foo=bar", NULL), -1);
+
+ ck_assert_int_eq(actions_fixture.len, 0);
+ ck_assert(!actions_fixture.present[ACTION_SIGNAL]);
+}
+END_TEST
+
+START_TEST(test_actions_parse_shell)
+{
+ ck_assert_int_eq(actions_parse(&actions_fixture, "shell,command=echo Hello", NULL), 0);
+
+ ck_assert_int_eq(actions_fixture.len, 1);
+ ck_assert_int_eq(actions_fixture.list[0].type, ACTION_SHELL);
+ ck_assert_str_eq(actions_fixture.list[0].command, "echo Hello");
+ ck_assert(actions_fixture.present[ACTION_SHELL]);
+}
+END_TEST
+
+START_TEST(test_actions_parse_shell_no_arg)
+{
+ ck_assert_int_eq(actions_parse(&actions_fixture, "shell", NULL), -1);
+
+ ck_assert_int_eq(actions_fixture.len, 0);
+ ck_assert(!actions_fixture.present[ACTION_SHELL]);
+}
+END_TEST
+
+START_TEST(test_actions_parse_shell_arg_bad)
+{
+ ck_assert_int_eq(actions_parse(&actions_fixture, "shell,foo=bar", NULL), -1);
+ ck_assert_int_eq(actions_fixture.len, 0);
+ ck_assert(!actions_fixture.present[ACTION_SHELL]);
+}
+END_TEST
+
+START_TEST(test_actions_parse_continue)
+{
+ ck_assert_int_eq(actions_parse(&actions_fixture, "continue", NULL), 0);
+
+ ck_assert_int_eq(actions_fixture.len, 1);
+ ck_assert_int_eq(actions_fixture.list[0].type, ACTION_CONTINUE);
+ ck_assert(actions_fixture.present[ACTION_CONTINUE]);
+}
+END_TEST
+
+START_TEST(test_actions_parse_continue_arg_bad)
+{
+ ck_assert_int_eq(actions_parse(&actions_fixture, "continue,foo=bar", NULL), -1);
+
+ ck_assert_int_eq(actions_fixture.len, 0);
+ ck_assert(!actions_fixture.present[ACTION_CONTINUE]);
+}
+END_TEST
+
+START_TEST(test_actions_parse_invalid)
+{
+ ck_assert_int_eq(actions_parse(&actions_fixture, "foobar", NULL), -1);
+
+ ck_assert_int_eq(actions_fixture.len, 0);
+}
+END_TEST
+
+START_TEST(test_actions_perform_continue)
+{
+ actions_add_continue(&actions_fixture);
+ ck_assert_int_eq(actions_perform(&actions_fixture), 0);
+
+ ck_assert(actions_fixture.continue_flag);
+}
+END_TEST
+
+START_TEST(test_actions_perform_continue_after_successful_shell_command)
+{
+ actions_add_shell(&actions_fixture, "exit 0");
+ actions_add_continue(&actions_fixture);
+ ck_assert_int_eq(actions_perform(&actions_fixture), 0 << 8);
+
+ ck_assert(actions_fixture.continue_flag);
+}
+END_TEST
+
+START_TEST(test_actions_perform_continue_after_failed_shell_command)
+{
+ actions_add_shell(&actions_fixture, "exit 1");
+ actions_add_continue(&actions_fixture);
+ ck_assert_int_eq(actions_perform(&actions_fixture), 1 << 8);
+
+ ck_assert(!actions_fixture.continue_flag);
+}
+END_TEST
+
+START_TEST(test_actions_perform_continue_unset_flag)
+{
+ actions_fixture.continue_flag = true;
+
+ actions_add_shell(&actions_fixture, "exit 1");
+ actions_add_continue(&actions_fixture);
+ ck_assert_int_eq(actions_perform(&actions_fixture), 1 << 8);
+
+ ck_assert(!actions_fixture.continue_flag);
+}
+END_TEST
+
+Suite *actions_suite(void)
+{
+ Suite *s = suite_create("actions");
+ TCase *tc;
+
+ tc = tcase_create("alloc");
+ tcase_add_test(tc, test_actions_init);
+ tcase_add_test(tc, test_actions_destroy);
+ tcase_add_test(tc, test_actions_reallocate);
+ suite_add_tcase(s, tc);
+
+ tc = tcase_create("add");
+ tcase_add_checked_fixture(tc, actions_fixture_setup, actions_fixture_teardown);
+ tcase_add_test(tc, test_actions_add_trace_output);
+ tcase_add_test(tc, test_actions_add_signal);
+ tcase_add_test(tc, test_actions_add_shell);
+ tcase_add_test(tc, test_actions_add_continue);
+ tcase_add_test(tc, test_actions_add_multiple_same_action);
+ tcase_add_test(tc, test_actions_add_multiple_different_action);
+ suite_add_tcase(s, tc);
+
+ tc = tcase_create("parse");
+ tcase_add_checked_fixture(tc, actions_fixture_setup, actions_fixture_teardown);
+ tcase_add_test(tc, test_actions_parse_trace_output);
+ tcase_add_test(tc, test_actions_parse_trace_output_arg);
+ tcase_add_test(tc, test_actions_parse_trace_output_arg_bad);
+ tcase_add_test(tc, test_actions_parse_signal);
+ tcase_add_test(tc, test_actions_parse_signal_swapped);
+ tcase_add_test(tc, test_actions_parse_signal_parent);
+ tcase_add_test(tc, test_actions_parse_signal_no_arg);
+ tcase_add_test(tc, test_actions_parse_signal_no_pid);
+ tcase_add_test(tc, test_actions_parse_signal_no_num);
+ tcase_add_test(tc, test_actions_parse_signal_arg_bad);
+ tcase_add_test(tc, test_actions_parse_shell);
+ tcase_add_test(tc, test_actions_parse_shell_no_arg);
+ tcase_add_test(tc, test_actions_parse_shell_arg_bad);
+ tcase_add_test(tc, test_actions_parse_continue);
+ tcase_add_test(tc, test_actions_parse_continue_arg_bad);
+ tcase_add_test(tc, test_actions_parse_invalid);
+ suite_add_tcase(s, tc);
+
+ tc = tcase_create("perform");
+ tcase_add_checked_fixture(tc, actions_fixture_setup, actions_fixture_teardown);
+ tcase_add_test(tc, test_actions_perform_continue);
+ tcase_add_test(tc, test_actions_perform_continue_after_successful_shell_command);
+ tcase_add_test(tc, test_actions_perform_continue_after_failed_shell_command);
+ tcase_add_test(tc, test_actions_perform_continue_unset_flag);
+ suite_add_tcase(s, tc);
+
+ return s;
+}
diff --git a/tools/tracing/rtla/tests/unit/cli_opt_callback.c b/tools/tracing/rtla/tests/unit/cli_opt_callback.c
new file mode 100644
index 000000000000..70e2576b1336
--- /dev/null
+++ b/tools/tracing/rtla/tests/unit/cli_opt_callback.c
@@ -0,0 +1,912 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <check.h>
+
+#define RTLA_ALLOW_CLI_P_H
+#include "../../src/cli_p.h"
+#include "cli_params_assert.h"
+
+#define TEST_CALLBACK(value, cb) OPT_CALLBACK('t', "test", value, "test value", "test help", cb)
+#define TEST_LLONG_RANGE(value, lo, hi) \
+ RTLA_OPT_CALLBACK_DATA('t', "test", value, "test value", "test help", \
+ opt_llong_callback, LLONG_RANGE(lo, hi))
+#define TEST_INT_RANGE(value, lo, hi) \
+ RTLA_OPT_CALLBACK_DATA('t', "test", value, "test value", "test help", \
+ opt_int_callback, INT_RANGE(lo, hi))
+
+START_TEST(test_opt_llong_callback_simple)
+{
+ long long test_value = 0;
+ const struct option opt = TEST_CALLBACK(&test_value, opt_llong_callback);
+
+ ck_assert_int_eq(opt_llong_callback(&opt, "1234567890", 0), 0);
+ ck_assert_int_eq(test_value, 1234567890);
+}
+END_TEST
+
+START_TEST(test_opt_llong_callback_max)
+{
+ long long test_value = 0;
+ const struct option opt = TEST_CALLBACK(&test_value, opt_llong_callback);
+
+ ck_assert_int_eq(opt_llong_callback(&opt, "9223372036854775807", 0), 0);
+ ck_assert_int_eq(test_value, 9223372036854775807LL);
+}
+END_TEST
+
+START_TEST(test_opt_llong_callback_min)
+{
+ long long test_value = 0;
+ const struct option opt = TEST_CALLBACK(&test_value, opt_llong_callback);
+
+ ck_assert_int_eq(opt_llong_callback(&opt, "-9223372036854775808", 0), 0);
+ ck_assert_int_eq(test_value, ~9223372036854775807LL);
+}
+END_TEST
+
+START_TEST(test_opt_llong_callback_non_numeric)
+{
+ long long test_value = 0;
+ const struct option opt = TEST_CALLBACK(&test_value, opt_llong_callback);
+
+ assert(freopen("/dev/null", "w", stderr));
+ ck_assert_int_eq(opt_llong_callback(&opt, "abc", 0), -1);
+ ck_assert_int_eq(test_value, 0);
+}
+END_TEST
+
+START_TEST(test_opt_llong_callback_non_numeric_suffix)
+{
+ long long test_value = 0;
+ const struct option opt = TEST_CALLBACK(&test_value, opt_llong_callback);
+
+ assert(freopen("/dev/null", "w", stderr));
+ ck_assert_int_eq(opt_llong_callback(&opt, "1234567890abc", 0), -1);
+ ck_assert_int_eq(test_value, 0);
+}
+END_TEST
+
+START_TEST(test_opt_llong_callback_unset)
+{
+ long long test_value = 0;
+ const struct option opt = TEST_CALLBACK(&test_value, opt_llong_callback);
+
+ ck_assert_int_eq(opt_llong_callback(&opt, "1234567890", 0), 0);
+ ck_assert_int_eq(opt_llong_callback(&opt, NULL, 1), 0);
+ ck_assert_int_eq(test_value, 0);
+}
+END_TEST
+
+START_TEST(test_opt_llong_callback_unset_defval)
+{
+ long long test_value = 0;
+ const long long default_value = 42;
+ const struct option opt = RTLA_OPT_LLONG_DEFVAL('t', "test", &test_value, "test value",
+ "test help", &default_value);
+
+ ck_assert_int_eq(opt_llong_callback(&opt, "1234567890", 0), 0);
+ ck_assert_int_eq(opt_llong_callback(&opt, NULL, 1), 0);
+ ck_assert_int_eq(test_value, default_value);
+}
+END_TEST
+
+START_TEST(test_opt_int_callback_simple)
+{
+ int test_value = 0;
+ const struct option opt = TEST_CALLBACK(&test_value, opt_int_callback);
+
+ ck_assert_int_eq(opt_int_callback(&opt, "1234567890", 0), 0);
+ ck_assert_int_eq(test_value, 1234567890);
+}
+END_TEST
+
+START_TEST(test_opt_int_callback_max)
+{
+ int test_value = 0;
+ const struct option opt = TEST_CALLBACK(&test_value, opt_int_callback);
+
+ ck_assert_int_eq(opt_int_callback(&opt, "2147483647", 0), 0);
+ ck_assert_int_eq(test_value, 2147483647);
+}
+END_TEST
+
+START_TEST(test_opt_int_callback_min)
+{
+ int test_value = 0;
+ const struct option opt = TEST_CALLBACK(&test_value, opt_int_callback);
+
+ ck_assert_int_eq(opt_int_callback(&opt, "-2147483648", 0), 0);
+ ck_assert_int_eq(test_value, -2147483648);
+}
+END_TEST
+
+START_TEST(test_opt_int_callback_non_numeric)
+{
+ int test_value = 0;
+ const struct option opt = TEST_CALLBACK(&test_value, opt_int_callback);
+
+ assert(freopen("/dev/null", "w", stderr));
+ ck_assert_int_eq(opt_int_callback(&opt, "abc", 0), -1);
+ ck_assert_int_eq(test_value, 0);
+}
+END_TEST
+
+START_TEST(test_opt_int_callback_non_numeric_suffix)
+{
+ int test_value = 0;
+ const struct option opt = TEST_CALLBACK(&test_value, opt_int_callback);
+
+ assert(freopen("/dev/null", "w", stderr));
+ ck_assert_int_eq(opt_int_callback(&opt, "1234567890abc", 0), -1);
+ ck_assert_int_eq(test_value, 0);
+}
+END_TEST
+
+START_TEST(test_opt_int_callback_unset)
+{
+ int test_value = 0;
+ const struct option opt = TEST_CALLBACK(&test_value, opt_int_callback);
+
+ ck_assert_int_eq(opt_int_callback(&opt, "1234567890", 0), 0);
+ ck_assert_int_eq(opt_int_callback(&opt, NULL, 1), 0);
+ ck_assert_int_eq(test_value, 0);
+}
+END_TEST
+
+START_TEST(test_opt_int_callback_unset_defval)
+{
+ int test_value = 0;
+ const struct option opt = RTLA_OPT_INT_DEFVAL('t', "test", &test_value, "test value",
+ "test help", 42);
+
+ ck_assert_int_eq(opt_int_callback(&opt, "1234567890", 0), 0);
+ ck_assert_int_eq(opt_int_callback(&opt, NULL, 1), 0);
+ ck_assert_int_eq(test_value, 42);
+}
+END_TEST
+
+START_TEST(test_opt_llong_callback_range_in)
+{
+ long long test_value = 0;
+ const struct option opt = TEST_LLONG_RANGE(&test_value, 10, 100);
+
+ ck_assert_int_eq(opt_llong_callback(&opt, "50", 0), 0);
+ ck_assert_int_eq(test_value, 50);
+}
+END_TEST
+
+START_TEST(test_opt_llong_callback_range_below)
+{
+ long long test_value = 0;
+ const struct option opt = TEST_LLONG_RANGE(&test_value, 10, 100);
+
+ assert(freopen("/dev/null", "w", stderr));
+ ck_assert_int_eq(opt_llong_callback(&opt, "9", 0), -1);
+}
+END_TEST
+
+START_TEST(test_opt_llong_callback_range_above)
+{
+ long long test_value = 0;
+ const struct option opt = TEST_LLONG_RANGE(&test_value, 10, 100);
+
+ assert(freopen("/dev/null", "w", stderr));
+ ck_assert_int_eq(opt_llong_callback(&opt, "101", 0), -1);
+}
+END_TEST
+
+START_TEST(test_opt_llong_callback_range_boundary)
+{
+ long long test_value = 0;
+ const struct option opt = TEST_LLONG_RANGE(&test_value, 10, 100);
+
+ ck_assert_int_eq(opt_llong_callback(&opt, "10", 0), 0);
+ ck_assert_int_eq(test_value, 10);
+ ck_assert_int_eq(opt_llong_callback(&opt, "100", 0), 0);
+ ck_assert_int_eq(test_value, 100);
+}
+END_TEST
+
+START_TEST(test_opt_int_callback_range_in)
+{
+ int test_value = 0;
+ const struct option opt = TEST_INT_RANGE(&test_value, 0, 10000);
+
+ ck_assert_int_eq(opt_int_callback(&opt, "5000", 0), 0);
+ ck_assert_int_eq(test_value, 5000);
+}
+END_TEST
+
+START_TEST(test_opt_int_callback_range_below)
+{
+ int test_value = 0;
+ const struct option opt = TEST_INT_RANGE(&test_value, 0, 10000);
+
+ assert(freopen("/dev/null", "w", stderr));
+ ck_assert_int_eq(opt_int_callback(&opt, "-1", 0), -1);
+}
+END_TEST
+
+START_TEST(test_opt_int_callback_range_above)
+{
+ int test_value = 0;
+ const struct option opt = TEST_INT_RANGE(&test_value, 0, 10000);
+
+ assert(freopen("/dev/null", "w", stderr));
+ ck_assert_int_eq(opt_int_callback(&opt, "10001", 0), -1);
+}
+END_TEST
+
+START_TEST(test_opt_int_callback_range_boundary)
+{
+ int test_value = 0;
+ const struct option opt = TEST_INT_RANGE(&test_value, 0, 10000);
+
+ ck_assert_int_eq(opt_int_callback(&opt, "0", 0), 0);
+ ck_assert_int_eq(test_value, 0);
+ ck_assert_int_eq(opt_int_callback(&opt, "10000", 0), 0);
+ ck_assert_int_eq(test_value, 10000);
+}
+END_TEST
+
+START_TEST(test_opt_cpus_cb)
+{
+ struct common_params params = {0};
+ const struct option opt = TEST_CALLBACK(&params, opt_cpus_cb);
+
+ nr_cpus = 4;
+ ck_assert_int_eq(opt_cpus_cb(&opt, "0-3", 0), 0);
+ ck_assert_str_eq(params.cpus, "0-3");
+}
+END_TEST
+
+START_TEST(test_opt_cpus_cb_invalid)
+{
+ struct common_params params = {0};
+ const struct option opt = TEST_CALLBACK(&params, opt_cpus_cb);
+
+ nr_cpus = 4;
+ assert(freopen("/dev/null", "w", stderr));
+ ck_assert_int_eq(opt_cpus_cb(&opt, "0-3,5", 0), -1);
+}
+END_TEST
+
+START_TEST(test_opt_cgroup_cb)
+{
+ struct common_params params = {0};
+ const struct option opt = TEST_CALLBACK(&params, opt_cgroup_cb);
+
+ ck_assert_int_eq(opt_cgroup_cb(&opt, "cgroup", 0), 0);
+ ck_assert_int_eq(params.cgroup, 1);
+ ck_assert_str_eq(params.cgroup_name, "cgroup");
+}
+END_TEST
+
+START_TEST(test_opt_cgroup_cb_equals)
+{
+ struct common_params params = {0};
+ const struct option opt = TEST_CALLBACK(&params, opt_cgroup_cb);
+
+ ck_assert_int_eq(opt_cgroup_cb(&opt, "=cgroup", 0), 0);
+ ck_assert_int_eq(params.cgroup, 1);
+ ck_assert_str_eq(params.cgroup_name, "cgroup");
+}
+END_TEST
+
+START_TEST(test_opt_cgroup_cb_unset)
+{
+ struct common_params params = {0};
+ const struct option opt = TEST_CALLBACK(&params, opt_cgroup_cb);
+
+ ck_assert_int_eq(opt_cgroup_cb(&opt, "cgroup", 0), 0);
+ ck_assert_int_eq(opt_cgroup_cb(&opt, NULL, 1), 0);
+ ck_assert_int_eq(params.cgroup, 0);
+ ck_assert_ptr_null(params.cgroup_name);
+}
+END_TEST
+
+START_TEST(test_opt_duration_cb)
+{
+ struct common_params params = {0};
+ const struct option opt = TEST_CALLBACK(&params, opt_duration_cb);
+
+ ck_assert_int_eq(opt_duration_cb(&opt, "1m", 0), 0);
+ ck_assert_int_eq(params.duration, 60);
+}
+END_TEST
+
+START_TEST(test_opt_duration_cb_invalid)
+{
+ struct common_params params = {0};
+ const struct option opt = TEST_CALLBACK(&params, opt_duration_cb);
+
+ assert(freopen("/dev/null", "w", stderr));
+ ck_assert_int_eq(opt_duration_cb(&opt, "abc", 0), -1);
+}
+END_TEST
+
+START_TEST(test_opt_duration_cb_unset)
+{
+ struct common_params params = {0};
+ const struct option opt = TEST_CALLBACK(&params, opt_duration_cb);
+
+ ck_assert_int_eq(opt_duration_cb(&opt, "1m", 0), 0);
+ ck_assert_int_eq(opt_duration_cb(&opt, NULL, 1), 0);
+ ck_assert_int_eq(params.duration, 0);
+}
+END_TEST
+
+START_TEST(test_opt_event_cb)
+{
+ struct trace_events *events = NULL;
+ const struct option opt = TEST_CALLBACK(&events, opt_event_cb);
+
+ ck_assert_int_eq(opt_event_cb(&opt, "sched:sched_switch", 0), 0);
+ ck_assert_str_eq(events->system, "sched");
+ ck_assert_str_eq(events->event, "sched_switch");
+ ck_assert_ptr_eq(events->next, NULL);
+}
+END_TEST
+
+START_TEST(test_opt_event_cb_multiple)
+{
+ struct trace_events *events = NULL;
+ const struct option opt = TEST_CALLBACK(&events, opt_event_cb);
+
+ ck_assert_int_eq(opt_event_cb(&opt, "sched:sched_switch", 0), 0);
+ ck_assert_int_eq(opt_event_cb(&opt, "sched:sched_wakeup", 0), 0);
+ ck_assert_str_eq(events->system, "sched");
+ ck_assert_str_eq(events->event, "sched_wakeup");
+ ck_assert_str_eq(events->next->system, "sched");
+ ck_assert_str_eq(events->next->event, "sched_switch");
+ ck_assert_ptr_eq(events->next->next, NULL);
+}
+END_TEST
+
+START_TEST(test_opt_housekeeping_cb)
+{
+ struct common_params __params = {0};
+ struct common_params *params = &__params;
+ const struct option opt = TEST_CALLBACK(params, opt_housekeeping_cb);
+
+ nr_cpus = 4;
+ ck_assert_int_eq(opt_housekeeping_cb(&opt, "0-3", 0), 0);
+ ck_assert_int_eq(params->hk_cpus, 1);
+ CLI_ASSERT_CPUSET(hk_cpu_set, 0, 1, 2, 3);
+}
+END_TEST
+
+START_TEST(test_opt_housekeeping_cb_invalid)
+{
+ struct common_params params = {0};
+ const struct option opt = TEST_CALLBACK(&params, opt_housekeeping_cb);
+
+ nr_cpus = 4;
+ assert(freopen("/dev/null", "w", stderr));
+ ck_assert_int_eq(opt_housekeeping_cb(&opt, "0-3,5", 0), -1);
+}
+END_TEST
+
+START_TEST(test_opt_housekeeping_cb_unset)
+{
+ struct common_params params = {0};
+ const struct option opt = TEST_CALLBACK(&params, opt_housekeeping_cb);
+
+ nr_cpus = 4;
+ ck_assert_int_eq(opt_housekeeping_cb(&opt, "0-3", 0), 0);
+ ck_assert_int_eq(opt_housekeeping_cb(&opt, NULL, 1), 0);
+ ck_assert_int_eq(params.hk_cpus, 0);
+ ck_assert_int_eq(CPU_COUNT(&params.hk_cpu_set), 0);
+}
+END_TEST
+
+START_TEST(test_opt_priority_cb)
+{
+ struct common_params params = {0};
+ const struct option opt = TEST_CALLBACK(&params, opt_priority_cb);
+
+ ck_assert_int_eq(opt_priority_cb(&opt, "f:95", 0), 0);
+ ck_assert_int_eq(params.sched_param.sched_policy, SCHED_FIFO);
+ ck_assert_int_eq(params.sched_param.sched_priority, 95);
+}
+END_TEST
+
+START_TEST(test_opt_priority_cb_invalid)
+{
+ struct common_params params = {0};
+ const struct option opt = TEST_CALLBACK(&params, opt_priority_cb);
+
+ assert(freopen("/dev/null", "w", stderr));
+ ck_assert_int_eq(opt_priority_cb(&opt, "abc", 0), -1);
+}
+END_TEST
+
+START_TEST(test_opt_priority_cb_unset)
+{
+ struct common_params params = {0};
+ const struct option opt = TEST_CALLBACK(&params, opt_priority_cb);
+
+ ck_assert_int_eq(opt_priority_cb(&opt, "f:95", 0), 0);
+ ck_assert_int_eq(opt_priority_cb(&opt, NULL, 1), 0);
+ ck_assert_int_eq(params.sched_param.sched_policy, 0);
+ ck_assert_int_eq(params.sched_param.sched_priority, 0);
+}
+END_TEST
+
+START_TEST(test_opt_trigger_cb)
+{
+ struct trace_events *events = trace_event_alloc("sched:sched_switch");
+ const struct option opt = TEST_CALLBACK(&events, opt_trigger_cb);
+
+ ck_assert_int_eq(opt_trigger_cb(&opt, "stacktrace", 0), 0);
+ ck_assert_str_eq(events->trigger, "stacktrace");
+}
+END_TEST
+
+START_TEST(test_opt_trigger_cb_no_event)
+{
+ struct trace_events *events = NULL;
+ const struct option opt = TEST_CALLBACK(&events, opt_trigger_cb);
+
+ assert(freopen("/dev/null", "w", stderr));
+ ck_assert_int_eq(opt_trigger_cb(&opt, "stacktrace", 0), -1);
+}
+END_TEST
+
+START_TEST(test_opt_filter_cb)
+{
+ struct trace_events *events = trace_event_alloc("sched:sched_switch");
+ const struct option opt = TEST_CALLBACK(&events, opt_filter_cb);
+
+ ck_assert_int_eq(opt_filter_cb(&opt, "comm ~ \"rtla\"", 0), 0);
+ ck_assert_str_eq(events->filter, "comm ~ \"rtla\"");
+}
+END_TEST
+
+START_TEST(test_opt_filter_cb_no_event)
+{
+ struct trace_events *events = NULL;
+ const struct option opt = TEST_CALLBACK(&events, opt_filter_cb);
+
+ assert(freopen("/dev/null", "w", stderr));
+ ck_assert_int_eq(opt_filter_cb(&opt, "comm ~ \"rtla\"", 0), -1);
+}
+END_TEST
+
+START_TEST(test_opt_osnoise_auto_cb)
+{
+ struct osnoise_params params = {0};
+ struct osnoise_cb_data cb_data = {&params};
+ const struct option opt = TEST_CALLBACK(&cb_data, opt_osnoise_auto_cb);
+
+ ck_assert_int_eq(opt_osnoise_auto_cb(&opt, "10", 0), 0);
+ ck_assert_int_eq(params.common.stop_us, 10);
+ ck_assert_int_eq(params.threshold, 1);
+ ck_assert_str_eq(cb_data.trace_output, "osnoise_trace.txt");
+}
+END_TEST
+
+START_TEST(test_opt_osnoise_auto_cb_unset)
+{
+ struct osnoise_params params = {0};
+ struct osnoise_cb_data cb_data = {&params};
+ const struct option opt = TEST_CALLBACK(&cb_data, opt_osnoise_auto_cb);
+
+ ck_assert_int_eq(opt_osnoise_auto_cb(&opt, "10", 0), 0);
+ ck_assert_int_eq(opt_osnoise_auto_cb(&opt, NULL, 1), 0);
+ ck_assert_int_eq(params.common.stop_us, 0);
+ ck_assert_int_eq(params.threshold, 0);
+ ck_assert_ptr_null(cb_data.trace_output);
+}
+END_TEST
+
+
+START_TEST(test_opt_osnoise_trace_output_cb)
+{
+ const char *trace_output = NULL;
+ const struct option opt = TEST_CALLBACK(&trace_output, opt_osnoise_trace_output_cb);
+
+ ck_assert_int_eq(opt_osnoise_trace_output_cb(&opt, "trace.txt", 0), 0);
+ ck_assert_str_eq(trace_output, "trace.txt");
+}
+END_TEST
+
+START_TEST(test_opt_osnoise_trace_output_cb_noarg)
+{
+ const char *trace_output = NULL;
+ const struct option opt = TEST_CALLBACK(&trace_output, opt_osnoise_trace_output_cb);
+
+ ck_assert_int_eq(opt_osnoise_trace_output_cb(&opt, NULL, 0), 0);
+ ck_assert_str_eq(trace_output, "osnoise_trace.txt");
+}
+END_TEST
+
+START_TEST(test_opt_osnoise_trace_output_cb_unset)
+{
+ const char *trace_output = NULL;
+ const struct option opt = TEST_CALLBACK(&trace_output, opt_osnoise_trace_output_cb);
+
+ ck_assert_int_eq(opt_osnoise_trace_output_cb(&opt, "trace.txt", 0), 0);
+ ck_assert_int_eq(opt_osnoise_trace_output_cb(&opt, NULL, 1), 0);
+ ck_assert_ptr_null(trace_output);
+}
+END_TEST
+
+START_TEST(test_opt_osnoise_on_threshold_cb)
+{
+ struct actions actions = {0};
+ const struct option opt = TEST_CALLBACK(&actions, opt_osnoise_on_threshold_cb);
+
+ ck_assert_int_eq(opt_osnoise_on_threshold_cb(&opt, "trace", 0), 0);
+ ck_assert_int_eq(actions.len, 1);
+ ck_assert_int_eq(actions.list[0].type, ACTION_TRACE_OUTPUT);
+ ck_assert_str_eq(actions.list[0].trace_output, "osnoise_trace.txt");
+}
+END_TEST
+
+START_TEST(test_opt_osnoise_on_threshold_cb_invalid)
+{
+ struct actions actions = {0};
+ const struct option opt = TEST_CALLBACK(&actions, opt_osnoise_on_threshold_cb);
+
+ assert(freopen("/dev/null", "w", stderr));
+ ck_assert_int_eq(opt_osnoise_on_threshold_cb(&opt, "abc", 0), -1);
+}
+END_TEST
+
+START_TEST(test_opt_osnoise_on_end_cb)
+{
+ struct actions actions = {0};
+ const struct option opt = TEST_CALLBACK(&actions, opt_osnoise_on_end_cb);
+
+ ck_assert_int_eq(opt_osnoise_on_end_cb(&opt, "trace", 0), 0);
+ ck_assert_int_eq(actions.len, 1);
+ ck_assert_int_eq(actions.list[0].type, ACTION_TRACE_OUTPUT);
+ ck_assert_str_eq(actions.list[0].trace_output, "osnoise_trace.txt");
+}
+END_TEST
+
+START_TEST(test_opt_osnoise_on_end_cb_invalid)
+{
+ struct actions actions = {0};
+ const struct option opt = TEST_CALLBACK(&actions, opt_osnoise_on_end_cb);
+
+ assert(freopen("/dev/null", "w", stderr));
+ ck_assert_int_eq(opt_osnoise_on_end_cb(&opt, "abc", 0), -1);
+}
+END_TEST
+
+START_TEST(test_opt_timerlat_auto_cb)
+{
+ struct timerlat_params params = {0};
+ struct timerlat_cb_data cb_data = {&params};
+ const struct option opt = TEST_CALLBACK(&cb_data, opt_timerlat_auto_cb);
+
+ ck_assert_int_eq(opt_timerlat_auto_cb(&opt, "10", 0), 0);
+ ck_assert_int_eq(params.common.stop_us, 10);
+ ck_assert_int_eq(params.common.stop_total_us, 10);
+ ck_assert_int_eq(params.print_stack, 10);
+ ck_assert_str_eq(cb_data.trace_output, "timerlat_trace.txt");
+}
+END_TEST
+
+START_TEST(test_opt_timerlat_auto_cb_unset)
+{
+ struct timerlat_params params = {0};
+ struct timerlat_cb_data cb_data = {&params};
+ const struct option opt = TEST_CALLBACK(&cb_data, opt_timerlat_auto_cb);
+
+ ck_assert_int_eq(opt_timerlat_auto_cb(&opt, "10", 0), 0);
+ ck_assert_int_eq(opt_timerlat_auto_cb(&opt, NULL, 1), 0);
+ ck_assert_int_eq(params.common.stop_us, 0);
+ ck_assert_int_eq(params.common.stop_total_us, 0);
+ ck_assert_int_eq(params.print_stack, 0);
+ ck_assert_ptr_null(cb_data.trace_output);
+}
+END_TEST
+
+
+START_TEST(test_opt_aa_only_cb)
+{
+ struct timerlat_params params = {0};
+ const struct option opt = TEST_CALLBACK(&params, opt_aa_only_cb);
+
+ ck_assert_int_eq(opt_aa_only_cb(&opt, "10", 0), 0);
+ ck_assert_int_eq(params.common.stop_us, 10);
+ ck_assert_int_eq(params.common.stop_total_us, 10);
+ ck_assert_int_eq(params.print_stack, 10);
+ ck_assert_int_eq(params.common.aa_only, 1);
+}
+END_TEST
+
+START_TEST(test_opt_aa_only_cb_unset)
+{
+ struct timerlat_params params = {0};
+ const struct option opt = TEST_CALLBACK(&params, opt_aa_only_cb);
+
+ ck_assert_int_eq(opt_aa_only_cb(&opt, "10", 0), 0);
+ ck_assert_int_eq(opt_aa_only_cb(&opt, NULL, 1), 0);
+ ck_assert_int_eq(params.common.stop_us, 0);
+ ck_assert_int_eq(params.common.stop_total_us, 0);
+ ck_assert_int_eq(params.print_stack, 0);
+ ck_assert_int_eq(params.common.aa_only, 0);
+}
+END_TEST
+
+START_TEST(test_opt_timerlat_trace_output_cb)
+{
+ const char *trace_output = NULL;
+ const struct option opt = TEST_CALLBACK(&trace_output, opt_timerlat_trace_output_cb);
+
+ ck_assert_int_eq(opt_timerlat_trace_output_cb(&opt, "trace.txt", 0), 0);
+ ck_assert_str_eq(trace_output, "trace.txt");
+}
+END_TEST
+
+START_TEST(test_opt_timerlat_trace_output_cb_noarg)
+{
+ const char *trace_output = NULL;
+ const struct option opt = TEST_CALLBACK(&trace_output, opt_timerlat_trace_output_cb);
+
+ ck_assert_int_eq(opt_timerlat_trace_output_cb(&opt, NULL, 0), 0);
+ ck_assert_str_eq(trace_output, "timerlat_trace.txt");
+}
+END_TEST
+
+START_TEST(test_opt_timerlat_trace_output_cb_unset)
+{
+ const char *trace_output = NULL;
+ const struct option opt = TEST_CALLBACK(&trace_output, opt_timerlat_trace_output_cb);
+
+ ck_assert_int_eq(opt_timerlat_trace_output_cb(&opt, "trace.txt", 0), 0);
+ ck_assert_int_eq(opt_timerlat_trace_output_cb(&opt, NULL, 1), 0);
+ ck_assert_ptr_null(trace_output);
+}
+END_TEST
+
+START_TEST(test_opt_timerlat_on_threshold_cb)
+{
+ struct actions actions = {0};
+ const struct option opt = TEST_CALLBACK(&actions, opt_timerlat_on_threshold_cb);
+
+ ck_assert_int_eq(opt_timerlat_on_threshold_cb(&opt, "trace", 0), 0);
+ ck_assert_int_eq(actions.len, 1);
+ ck_assert_int_eq(actions.list[0].type, ACTION_TRACE_OUTPUT);
+ ck_assert_str_eq(actions.list[0].trace_output, "timerlat_trace.txt");
+}
+END_TEST
+
+START_TEST(test_opt_timerlat_on_threshold_cb_invalid)
+{
+ struct actions actions = {0};
+ const struct option opt = TEST_CALLBACK(&actions, opt_timerlat_on_threshold_cb);
+
+ assert(freopen("/dev/null", "w", stderr));
+ ck_assert_int_eq(opt_timerlat_on_threshold_cb(&opt, "abc", 0), -1);
+}
+END_TEST
+
+START_TEST(test_opt_timerlat_on_end_cb)
+{
+ struct actions actions = {0};
+ const struct option opt = TEST_CALLBACK(&actions, opt_timerlat_on_end_cb);
+
+ ck_assert_int_eq(opt_timerlat_on_end_cb(&opt, "trace", 0), 0);
+ ck_assert_int_eq(actions.len, 1);
+ ck_assert_int_eq(actions.list[0].type, ACTION_TRACE_OUTPUT);
+ ck_assert_str_eq(actions.list[0].trace_output, "timerlat_trace.txt");
+}
+END_TEST
+
+START_TEST(test_opt_timerlat_on_end_cb_invalid)
+{
+ struct actions actions = {0};
+ const struct option opt = TEST_CALLBACK(&actions, opt_timerlat_on_end_cb);
+
+ assert(freopen("/dev/null", "w", stderr));
+ ck_assert_int_eq(opt_timerlat_on_end_cb(&opt, "abc", 0), -1);
+}
+END_TEST
+
+START_TEST(test_opt_user_threads_cb)
+{
+ struct timerlat_params params = {0};
+ const struct option opt = TEST_CALLBACK(&params, opt_user_threads_cb);
+
+ ck_assert_int_eq(opt_user_threads_cb(&opt, NULL, 0), 0);
+ ck_assert_int_eq(params.common.user_workload, 1);
+ ck_assert_int_eq(params.common.user_data, 1);
+}
+END_TEST
+
+START_TEST(test_opt_user_threads_cb_unset)
+{
+ struct timerlat_params params = {0};
+ const struct option opt = TEST_CALLBACK(&params, opt_user_threads_cb);
+
+ ck_assert_int_eq(opt_user_threads_cb(&opt, NULL, 0), 0);
+ ck_assert_int_eq(opt_user_threads_cb(&opt, NULL, 1), 0);
+ ck_assert_int_eq(params.common.user_workload, 0);
+ ck_assert_int_eq(params.common.user_data, 0);
+}
+END_TEST
+
+START_TEST(test_opt_nano_cb)
+{
+ struct timerlat_params params = {0};
+ const struct option opt = TEST_CALLBACK(&params, opt_nano_cb);
+
+ ck_assert_int_eq(opt_nano_cb(&opt, NULL, 0), 0);
+ ck_assert_int_eq(params.common.output_divisor, 1);
+}
+END_TEST
+
+START_TEST(test_opt_nano_cb_unset)
+{
+ struct timerlat_params params = {0};
+ const struct option opt = TEST_CALLBACK(&params, opt_nano_cb);
+
+ ck_assert_int_eq(opt_nano_cb(&opt, NULL, 0), 0);
+ ck_assert_int_eq(opt_nano_cb(&opt, NULL, 1), 0);
+ ck_assert_int_eq(params.common.output_divisor, default_output_divisor);
+}
+END_TEST
+
+START_TEST(test_opt_timerlat_align_cb)
+{
+ struct timerlat_params params = {0};
+ const struct option opt = RTLA_OPT_CALLBACK_DATA('A', "aligned", &params, "us",
+ "test", opt_timerlat_align_cb, LLONG_RANGE(0, LLONG_MAX));
+
+ ck_assert_int_eq(opt_timerlat_align_cb(&opt, "500", 0), 0);
+ ck_assert(params.timerlat_align);
+ ck_assert_int_eq(params.timerlat_align_us, 500);
+}
+END_TEST
+
+START_TEST(test_opt_timerlat_align_cb_invalid)
+{
+ struct timerlat_params params = {0};
+ const struct option opt = RTLA_OPT_CALLBACK_DATA('A', "aligned", &params, "us",
+ "test", opt_timerlat_align_cb, LLONG_RANGE(0, LLONG_MAX));
+
+ assert(freopen("/dev/null", "w", stderr));
+ ck_assert_int_eq(opt_timerlat_align_cb(&opt, "-1", 0), -1);
+}
+END_TEST
+
+START_TEST(test_opt_timerlat_align_cb_unset)
+{
+ struct timerlat_params params = {0};
+ const struct option opt = RTLA_OPT_CALLBACK_DATA('A', "aligned", &params, "us",
+ "test", opt_timerlat_align_cb, LLONG_RANGE(0, LLONG_MAX));
+
+ ck_assert_int_eq(opt_timerlat_align_cb(&opt, "500", 0), 0);
+ ck_assert_int_eq(opt_timerlat_align_cb(&opt, NULL, 1), 0);
+ ck_assert_int_eq(params.timerlat_align, 0);
+ ck_assert_int_eq(params.timerlat_align_us, 0);
+}
+END_TEST
+
+START_TEST(test_opt_stack_format_cb)
+{
+ int stack_format = 0;
+ const struct option opt = TEST_CALLBACK(&stack_format, opt_stack_format_cb);
+
+ ck_assert_int_eq(opt_stack_format_cb(&opt, "full", 0), 0);
+ ck_assert_int_eq(stack_format, STACK_FORMAT_FULL);
+}
+END_TEST
+
+START_TEST(test_opt_stack_format_cb_invalid)
+{
+ int stack_format = 0;
+ const struct option opt = TEST_CALLBACK(&stack_format, opt_stack_format_cb);
+
+ assert(freopen("/dev/null", "w", stderr));
+ ck_assert_int_eq(opt_stack_format_cb(&opt, "abc", 0), -1);
+}
+END_TEST
+
+START_TEST(test_opt_stack_format_cb_unset)
+{
+ int stack_format = 0;
+ const struct option opt = TEST_CALLBACK(&stack_format, opt_stack_format_cb);
+
+ ck_assert_int_eq(opt_stack_format_cb(&opt, "full", 0), 0);
+ ck_assert_int_eq(opt_stack_format_cb(&opt, NULL, 1), 0);
+ ck_assert_int_eq(stack_format, default_stack_format);
+}
+END_TEST
+
+
+Suite *cli_opt_callback_suite(void)
+{
+ Suite *s = suite_create("cli_opt_callback");
+ TCase *tc;
+
+ tc = tcase_create("common");
+ tcase_add_test(tc, test_opt_llong_callback_simple);
+ tcase_add_test(tc, test_opt_llong_callback_max);
+ tcase_add_test(tc, test_opt_llong_callback_min);
+ tcase_add_test(tc, test_opt_llong_callback_non_numeric);
+ tcase_add_test(tc, test_opt_llong_callback_non_numeric_suffix);
+ tcase_add_test(tc, test_opt_llong_callback_unset);
+ tcase_add_test(tc, test_opt_llong_callback_unset_defval);
+ tcase_add_test(tc, test_opt_llong_callback_range_in);
+ tcase_add_test(tc, test_opt_llong_callback_range_below);
+ tcase_add_test(tc, test_opt_llong_callback_range_above);
+ tcase_add_test(tc, test_opt_llong_callback_range_boundary);
+ tcase_add_test(tc, test_opt_int_callback_simple);
+ tcase_add_test(tc, test_opt_int_callback_max);
+ tcase_add_test(tc, test_opt_int_callback_min);
+ tcase_add_test(tc, test_opt_int_callback_non_numeric);
+ tcase_add_test(tc, test_opt_int_callback_non_numeric_suffix);
+ tcase_add_test(tc, test_opt_int_callback_unset);
+ tcase_add_test(tc, test_opt_int_callback_unset_defval);
+ tcase_add_test(tc, test_opt_int_callback_range_in);
+ tcase_add_test(tc, test_opt_int_callback_range_below);
+ tcase_add_test(tc, test_opt_int_callback_range_above);
+ tcase_add_test(tc, test_opt_int_callback_range_boundary);
+ tcase_add_test(tc, test_opt_cpus_cb);
+ tcase_add_test(tc, test_opt_cpus_cb_invalid);
+ tcase_add_test(tc, test_opt_cgroup_cb);
+ tcase_add_test(tc, test_opt_cgroup_cb_equals);
+ tcase_add_test(tc, test_opt_cgroup_cb_unset);
+ tcase_add_test(tc, test_opt_duration_cb);
+ tcase_add_test(tc, test_opt_duration_cb_unset);
+ tcase_add_test(tc, test_opt_duration_cb_invalid);
+ tcase_add_test(tc, test_opt_event_cb);
+ tcase_add_test(tc, test_opt_event_cb_multiple);
+ tcase_add_test(tc, test_opt_housekeeping_cb);
+ tcase_add_test(tc, test_opt_housekeeping_cb_invalid);
+ tcase_add_test(tc, test_opt_housekeeping_cb_unset);
+ tcase_add_test(tc, test_opt_priority_cb);
+ tcase_add_test(tc, test_opt_priority_cb_invalid);
+ tcase_add_test(tc, test_opt_priority_cb_unset);
+ tcase_add_test(tc, test_opt_trigger_cb);
+ tcase_add_test(tc, test_opt_trigger_cb_no_event);
+ tcase_add_test(tc, test_opt_filter_cb);
+ tcase_add_test(tc, test_opt_filter_cb_no_event);
+ suite_add_tcase(s, tc);
+
+ tc = tcase_create("osnoise");
+ tcase_add_test(tc, test_opt_osnoise_auto_cb);
+ tcase_add_test(tc, test_opt_osnoise_auto_cb_unset);
+ tcase_add_test(tc, test_opt_osnoise_trace_output_cb);
+ tcase_add_test(tc, test_opt_osnoise_trace_output_cb_noarg);
+ tcase_add_test(tc, test_opt_osnoise_trace_output_cb_unset);
+ tcase_add_test(tc, test_opt_osnoise_on_threshold_cb);
+ tcase_add_test(tc, test_opt_osnoise_on_threshold_cb_invalid);
+ tcase_add_test(tc, test_opt_osnoise_on_end_cb);
+ tcase_add_test(tc, test_opt_osnoise_on_end_cb_invalid);
+ suite_add_tcase(s, tc);
+
+ tc = tcase_create("timerlat");
+ tcase_add_test(tc, test_opt_timerlat_auto_cb);
+ tcase_add_test(tc, test_opt_timerlat_auto_cb_unset);
+ tcase_add_test(tc, test_opt_aa_only_cb);
+ tcase_add_test(tc, test_opt_aa_only_cb_unset);
+ tcase_add_test(tc, test_opt_timerlat_trace_output_cb);
+ tcase_add_test(tc, test_opt_timerlat_trace_output_cb_noarg);
+ tcase_add_test(tc, test_opt_timerlat_trace_output_cb_unset);
+ tcase_add_test(tc, test_opt_timerlat_on_threshold_cb);
+ tcase_add_test(tc, test_opt_timerlat_on_threshold_cb_invalid);
+ tcase_add_test(tc, test_opt_timerlat_on_end_cb);
+ tcase_add_test(tc, test_opt_timerlat_on_end_cb_invalid);
+ tcase_add_test(tc, test_opt_user_threads_cb);
+ tcase_add_test(tc, test_opt_user_threads_cb_unset);
+ tcase_add_test(tc, test_opt_nano_cb);
+ tcase_add_test(tc, test_opt_nano_cb_unset);
+ tcase_add_test(tc, test_opt_stack_format_cb);
+ tcase_add_test(tc, test_opt_stack_format_cb_invalid);
+ tcase_add_test(tc, test_opt_stack_format_cb_unset);
+ tcase_add_test(tc, test_opt_timerlat_align_cb);
+ tcase_add_test(tc, test_opt_timerlat_align_cb_invalid);
+ tcase_add_test(tc, test_opt_timerlat_align_cb_unset);
+ suite_add_tcase(s, tc);
+
+ return s;
+}
diff --git a/tools/tracing/rtla/tests/unit/cli_params_assert.h b/tools/tracing/rtla/tests/unit/cli_params_assert.h
new file mode 100644
index 000000000000..4bc7d582fcf4
--- /dev/null
+++ b/tools/tracing/rtla/tests/unit/cli_params_assert.h
@@ -0,0 +1,68 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#pragma once
+
+#include "../../src/timerlat.h"
+
+/* Tracing Options */
+
+#define CLI_ASSERT_SINGLE_EVENT(_system, _event) do {\
+ ck_assert_ptr_nonnull(params->events);\
+ ck_assert_str_eq(params->events->system, _system);\
+ ck_assert_str_eq(params->events->event, _event);\
+ ck_assert_ptr_null(params->events->next);\
+} while (0)
+
+#define CLI_ASSERT_SINGLE_FILTER(_filter) do {\
+ ck_assert_ptr_nonnull(params->events);\
+ ck_assert_str_eq(params->events->filter, _filter);\
+ ck_assert_ptr_null(params->events->next);\
+} while (0)
+
+#define CLI_ASSERT_SINGLE_TRIGGER(_trigger) do {\
+ ck_assert_ptr_nonnull(params->events);\
+ ck_assert_str_eq(params->events->trigger, _trigger);\
+ ck_assert_ptr_null(params->events->next);\
+} while (0)
+
+/* CPU Configuration */
+
+#define CLI_ASSERT_CPUSET(_field, ...) do {\
+ int n;\
+ int cpus[] = { __VA_ARGS__ };\
+ for (n = 0; n < sizeof(cpus) / sizeof(int); n++)\
+ ck_assert(CPU_ISSET(cpus[n], &params->_field));\
+ ck_assert_int_eq(CPU_COUNT(&params->_field), n);\
+} while (0)
+
+/* Auto Analysis and Actions */
+
+#define CLI_OSNOISE_ASSERT_AUTO(_stop) do {\
+ ck_assert_int_eq(params->stop_us, _stop);\
+ ck_assert_int_eq(osn_params->threshold, 1);\
+ ck_assert_int_eq(params->threshold_actions.len, 1);\
+ ck_assert_int_eq(params->threshold_actions.list[0].type, ACTION_TRACE_OUTPUT);\
+ ck_assert_str_eq(params->threshold_actions.list[0].trace_output, "osnoise_trace.txt");\
+} while (0)
+
+#define CLI_TIMERLAT_ASSERT_AUTO(_threshold) do {\
+ ck_assert_int_eq(params->stop_us, _threshold);\
+ ck_assert_int_eq(params->stop_total_us, _threshold);\
+ ck_assert_int_eq(tlat_params->print_stack, _threshold);\
+ ck_assert_int_eq(params->threshold_actions.len, 1);\
+ ck_assert_int_eq(params->threshold_actions.list[0].type, ACTION_TRACE_OUTPUT);\
+ ck_assert_str_eq(params->threshold_actions.list[0].trace_output, "timerlat_trace.txt");\
+} while (0)
+
+#define CLI_TIMERLAT_ASSERT_AA_ONLY(_threshold) do {\
+ ck_assert_int_eq(params->stop_us, _threshold);\
+ ck_assert_int_eq(params->stop_total_us, _threshold);\
+ ck_assert_int_eq(tlat_params->print_stack, _threshold);\
+ ck_assert_int_eq(params->threshold_actions.len, 0);\
+ ck_assert(params->aa_only);\
+} while (0)
+
+#define CLI_ASSERT_SINGLE_ACTION(_actions, _type, _arg, _valtype, _value) do {\
+ ck_assert_int_eq(params->_actions.len, 1);\
+ ck_assert_int_eq(params->_actions.list[0].type, _type);\
+ ck_assert_##_valtype##_eq(params->_actions.list[0]._arg, _value);\
+} while (0)
diff --git a/tools/tracing/rtla/tests/unit/osnoise_hist_cli.c b/tools/tracing/rtla/tests/unit/osnoise_hist_cli.c
new file mode 100644
index 000000000000..221985e6759f
--- /dev/null
+++ b/tools/tracing/rtla/tests/unit/osnoise_hist_cli.c
@@ -0,0 +1,575 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#define _GNU_SOURCE
+#include <check.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sched.h>
+#include <limits.h>
+#include <unistd.h>
+#include <sys/sysinfo.h>
+
+#include "cli_params_assert.h"
+#include "../../src/cli.h"
+
+#define PARSE_ARGS(...) char *argv[] = { __VA_ARGS__, NULL };\
+ int argc = sizeof(argv) / sizeof(char *) - 1;\
+ struct common_params *params =\
+ osnoise_hist_parse_args(argc, argv);\
+ struct osnoise_params *osn_params __maybe_unused =\
+ to_osnoise_params(params)
+
+/* Tracing Options */
+
+START_TEST(test_period_short)
+{
+ PARSE_ARGS("osnoise", "hist", "-p", "100000");
+
+ ck_assert_int_eq(osn_params->period, 100000);
+}
+END_TEST
+
+START_TEST(test_period_long)
+{
+ PARSE_ARGS("osnoise", "hist", "--period", "100000");
+
+ ck_assert_int_eq(osn_params->period, 100000);
+}
+END_TEST
+
+START_TEST(test_period_unset_short)
+{
+ PARSE_ARGS("osnoise", "hist", "-p", "100000", "--no-period");
+
+ ck_assert_int_eq(osn_params->period, 0);
+}
+END_TEST
+
+START_TEST(test_period_unset_long)
+{
+ PARSE_ARGS("osnoise", "hist", "--period", "100000", "--no-period");
+
+ ck_assert_int_eq(osn_params->period, 0);
+}
+END_TEST
+
+START_TEST(test_runtime_short)
+{
+ PARSE_ARGS("osnoise", "hist", "-r", "95000");
+
+ ck_assert_int_eq(osn_params->runtime, 95000);
+}
+END_TEST
+
+START_TEST(test_runtime_long)
+{
+ PARSE_ARGS("osnoise", "hist", "--runtime", "95000");
+
+ ck_assert_int_eq(osn_params->runtime, 95000);
+}
+END_TEST
+
+START_TEST(test_stop_short)
+{
+ PARSE_ARGS("osnoise", "hist", "-s", "20");
+
+ ck_assert_int_eq(params->stop_us, 20);
+}
+END_TEST
+
+START_TEST(test_stop_long)
+{
+ PARSE_ARGS("osnoise", "hist", "--stop", "20");
+
+ ck_assert_int_eq(params->stop_us, 20);
+}
+END_TEST
+
+START_TEST(test_stop_total_short)
+{
+ PARSE_ARGS("osnoise", "hist", "-S", "20");
+
+ ck_assert_int_eq(params->stop_total_us, 20);
+}
+END_TEST
+
+START_TEST(test_stop_total_long)
+{
+ PARSE_ARGS("osnoise", "hist", "--stop-total", "20");
+
+ ck_assert_int_eq(params->stop_total_us, 20);
+}
+END_TEST
+
+START_TEST(test_threshold_short)
+{
+ PARSE_ARGS("osnoise", "hist", "-T", "5");
+
+ ck_assert_int_eq(osn_params->threshold, 5);
+}
+END_TEST
+
+START_TEST(test_threshold_long)
+{
+ PARSE_ARGS("osnoise", "hist", "--threshold", "5");
+
+ ck_assert_int_eq(osn_params->threshold, 5);
+}
+END_TEST
+
+START_TEST(test_trace_short_noarg)
+{
+ PARSE_ARGS("osnoise", "hist", "-t");
+
+ CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+ "osnoise_trace.txt");
+}
+END_TEST
+
+START_TEST(test_trace_short_followarg)
+{
+ PARSE_ARGS("osnoise", "hist", "-t", "-d", "20");
+
+ CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+ "osnoise_trace.txt");
+ ck_assert_int_eq(params->duration, 20); /* check if next argument is read correctly */
+}
+END_TEST
+
+START_TEST(test_trace_short_space)
+{
+ PARSE_ARGS("osnoise", "hist", "-t", "tracefile");
+
+ CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+ "tracefile");
+}
+END_TEST
+
+START_TEST(test_trace_short_equals)
+{
+ PARSE_ARGS("osnoise", "hist", "-t=tracefile");
+
+ CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+ "tracefile");
+}
+END_TEST
+
+START_TEST(test_trace_long_noarg)
+{
+ PARSE_ARGS("osnoise", "hist", "--trace");
+
+ CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+ "osnoise_trace.txt");
+}
+END_TEST
+
+START_TEST(test_trace_long_followarg)
+{
+ PARSE_ARGS("osnoise", "hist", "--trace", "-d", "20");
+
+ CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+ "osnoise_trace.txt");
+ ck_assert_int_eq(params->duration, 20); /* check if next argument is read correctly */
+}
+END_TEST
+
+START_TEST(test_trace_long_space)
+{
+ PARSE_ARGS("osnoise", "hist", "--trace", "tracefile");
+
+ CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+ "tracefile");
+}
+END_TEST
+
+START_TEST(test_trace_long_equals)
+{
+ PARSE_ARGS("osnoise", "hist", "--trace=tracefile");
+
+ CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+ "tracefile");
+}
+END_TEST
+
+/* Event Configuration */
+
+START_TEST(test_event_short)
+{
+ PARSE_ARGS("osnoise", "hist", "-e", "system:event");
+
+ CLI_ASSERT_SINGLE_EVENT("system", "event");
+}
+END_TEST
+
+START_TEST(test_event_long)
+{
+ PARSE_ARGS("osnoise", "hist", "--event", "system:event");
+
+ CLI_ASSERT_SINGLE_EVENT("system", "event");
+}
+END_TEST
+
+START_TEST(test_filter)
+{
+ PARSE_ARGS("osnoise", "hist", "-e", "system:event", "--filter", "filter");
+
+ CLI_ASSERT_SINGLE_FILTER("filter");
+}
+END_TEST
+
+START_TEST(test_trigger)
+{
+ PARSE_ARGS("osnoise", "hist", "-e", "system:event", "--trigger", "trigger");
+
+ CLI_ASSERT_SINGLE_TRIGGER("trigger");
+}
+END_TEST
+
+/* CPU Configuration */
+
+START_TEST(test_cpus_short)
+{
+ nr_cpus = 4;
+
+ PARSE_ARGS("osnoise", "hist", "-c", "0-1,3");
+
+ ck_assert_str_eq(params->cpus, "0-1,3");
+ CLI_ASSERT_CPUSET(monitored_cpus, 0, 1, 3);
+}
+END_TEST
+
+START_TEST(test_cpus_long)
+{
+ nr_cpus = 4;
+
+ PARSE_ARGS("osnoise", "hist", "--cpus", "0-1,3");
+
+ ck_assert_str_eq(params->cpus, "0-1,3");
+ CLI_ASSERT_CPUSET(monitored_cpus, 0, 1, 3);
+}
+END_TEST
+
+START_TEST(test_housekeeping_short)
+{
+ nr_cpus = 4;
+
+ PARSE_ARGS("osnoise", "hist", "-H", "0-1,3");
+
+ CLI_ASSERT_CPUSET(hk_cpu_set, 0, 1, 3);
+}
+END_TEST
+
+START_TEST(test_housekeeping_long)
+{
+ nr_cpus = 4;
+
+ PARSE_ARGS("osnoise", "hist", "--house-keeping", "0-1,3");
+
+ CLI_ASSERT_CPUSET(hk_cpu_set, 0, 1, 3);
+}
+END_TEST
+
+/* Thread Configuration */
+
+START_TEST(test_cgroup_short_noarg)
+{
+ PARSE_ARGS("osnoise", "hist", "-C");
+
+ ck_assert(params->cgroup);
+ ck_assert_ptr_null(params->cgroup_name);
+}
+END_TEST
+
+START_TEST(test_cgroup_short_space)
+{
+ PARSE_ARGS("osnoise", "hist", "-C", "cgroup");
+
+ ck_assert(params->cgroup);
+ ck_assert_str_eq(params->cgroup_name, "cgroup");
+}
+END_TEST
+
+START_TEST(test_cgroup_short_equals)
+{
+ PARSE_ARGS("osnoise", "hist", "-C=cgroup");
+
+ ck_assert(params->cgroup);
+ ck_assert_str_eq(params->cgroup_name, "cgroup");
+}
+END_TEST
+
+START_TEST(test_cgroup_long_noarg)
+{
+ PARSE_ARGS("osnoise", "hist", "--cgroup");
+
+ ck_assert(params->cgroup);
+ ck_assert_ptr_null(params->cgroup_name);
+}
+END_TEST
+
+START_TEST(test_cgroup_long_space)
+{
+ PARSE_ARGS("osnoise", "hist", "--cgroup", "cgroup");
+
+ ck_assert(params->cgroup);
+ ck_assert_str_eq(params->cgroup_name, "cgroup");
+}
+END_TEST
+
+START_TEST(test_cgroup_long_equals)
+{
+ PARSE_ARGS("osnoise", "hist", "--cgroup=cgroup");
+
+ ck_assert(params->cgroup);
+ ck_assert_str_eq(params->cgroup_name, "cgroup");
+}
+END_TEST
+
+START_TEST(test_priority_short)
+{
+ PARSE_ARGS("osnoise", "hist", "-P", "f:95");
+
+ ck_assert_int_eq(params->sched_param.sched_policy, SCHED_FIFO);
+ ck_assert_int_eq(params->sched_param.sched_priority, 95);
+}
+END_TEST
+
+START_TEST(test_priority_long)
+{
+ PARSE_ARGS("osnoise", "hist", "--priority", "f:95");
+
+ ck_assert_int_eq(params->sched_param.sched_policy, SCHED_FIFO);
+ ck_assert_int_eq(params->sched_param.sched_priority, 95);
+}
+END_TEST
+
+/* Histogram Options */
+
+START_TEST(test_bucket_size_short)
+{
+ PARSE_ARGS("osnoise", "hist", "-b", "2");
+
+ ck_assert_int_eq(params->hist.bucket_size, 2);
+}
+END_TEST
+
+START_TEST(test_bucket_size_long)
+{
+ PARSE_ARGS("osnoise", "hist", "--bucket-size", "2");
+
+ ck_assert_int_eq(params->hist.bucket_size, 2);
+}
+END_TEST
+
+START_TEST(test_entries_short)
+{
+ PARSE_ARGS("osnoise", "hist", "-E", "512");
+
+ ck_assert_int_eq(params->hist.entries, 512);
+}
+END_TEST
+
+START_TEST(test_entries_long)
+{
+ PARSE_ARGS("osnoise", "hist", "--entries", "512");
+
+ ck_assert_int_eq(params->hist.entries, 512);
+}
+END_TEST
+
+START_TEST(test_no_header)
+{
+ PARSE_ARGS("osnoise", "hist", "--no-header");
+
+ ck_assert(params->hist.no_header);
+}
+END_TEST
+
+START_TEST(test_no_index)
+{
+ PARSE_ARGS("osnoise", "hist", "--with-zeros", "--no-index");
+
+ ck_assert(params->hist.no_index);
+}
+END_TEST
+
+START_TEST(test_no_summary)
+{
+ PARSE_ARGS("osnoise", "hist", "--no-summary");
+
+ ck_assert(params->hist.no_summary);
+}
+END_TEST
+
+START_TEST(test_with_zeros)
+{
+ PARSE_ARGS("osnoise", "hist", "--with-zeros");
+
+ ck_assert(params->hist.with_zeros);
+}
+END_TEST
+
+/* System Tuning */
+
+START_TEST(test_trace_buffer_size)
+{
+ PARSE_ARGS("osnoise", "hist", "--trace-buffer-size", "200");
+
+ ck_assert_int_eq(params->buffer_size, 200);
+}
+END_TEST
+
+START_TEST(test_warm_up)
+{
+ PARSE_ARGS("osnoise", "hist", "--warm-up", "5");
+
+ ck_assert_int_eq(params->warmup, 5);
+}
+END_TEST
+
+/* Auto Analysis and Actions */
+
+START_TEST(test_auto)
+{
+ PARSE_ARGS("osnoise", "hist", "-a", "20");
+
+ CLI_OSNOISE_ASSERT_AUTO(20);
+}
+END_TEST
+
+START_TEST(test_on_end)
+{
+ PARSE_ARGS("osnoise", "hist", "--on-end", "trace");
+
+ CLI_ASSERT_SINGLE_ACTION(end_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+ "osnoise_trace.txt");
+}
+END_TEST
+
+START_TEST(test_on_threshold)
+{
+ PARSE_ARGS("osnoise", "hist", "--on-threshold", "trace");
+
+ CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+ "osnoise_trace.txt");
+}
+END_TEST
+
+/* General */
+
+START_TEST(test_debug_short)
+{
+ PARSE_ARGS("osnoise", "hist", "-D");
+
+ ck_assert(config_debug);
+}
+END_TEST
+
+START_TEST(test_debug_long)
+{
+ PARSE_ARGS("osnoise", "hist", "--debug");
+
+ ck_assert(config_debug);
+}
+END_TEST
+
+START_TEST(test_duration_short)
+{
+ PARSE_ARGS("osnoise", "hist", "-d", "1m");
+
+ ck_assert_int_eq(params->duration, 60);
+}
+END_TEST
+
+START_TEST(test_duration_long)
+{
+ PARSE_ARGS("osnoise", "hist", "--duration", "1m");
+
+ ck_assert_int_eq(params->duration, 60);
+}
+END_TEST
+
+Suite *osnoise_hist_cli_suite(void)
+{
+ Suite *s = suite_create("osnoise_hist_cli");
+ TCase *tc;
+
+ tc = tcase_create("tracing_options");
+ tcase_add_test(tc, test_period_short);
+ tcase_add_test(tc, test_period_long);
+ tcase_add_test(tc, test_period_unset_short);
+ tcase_add_test(tc, test_period_unset_long);
+ tcase_add_test(tc, test_runtime_short);
+ tcase_add_test(tc, test_runtime_long);
+ tcase_add_test(tc, test_stop_short);
+ tcase_add_test(tc, test_stop_long);
+ tcase_add_test(tc, test_stop_total_short);
+ tcase_add_test(tc, test_stop_total_long);
+ tcase_add_test(tc, test_threshold_short);
+ tcase_add_test(tc, test_threshold_long);
+ tcase_add_test(tc, test_trace_short_noarg);
+ tcase_add_test(tc, test_trace_short_followarg);
+ tcase_add_test(tc, test_trace_short_space);
+ tcase_add_test(tc, test_trace_short_equals);
+ tcase_add_test(tc, test_trace_long_noarg);
+ tcase_add_test(tc, test_trace_long_followarg);
+ tcase_add_test(tc, test_trace_long_space);
+ tcase_add_test(tc, test_trace_long_equals);
+ suite_add_tcase(s, tc);
+
+ tc = tcase_create("event_configuration");
+ tcase_add_test(tc, test_event_short);
+ tcase_add_test(tc, test_event_long);
+ tcase_add_test(tc, test_filter);
+ tcase_add_test(tc, test_trigger);
+ suite_add_tcase(s, tc);
+
+ tc = tcase_create("cpu_configuration");
+ tcase_add_test(tc, test_cpus_short);
+ tcase_add_test(tc, test_cpus_long);
+ tcase_add_test(tc, test_housekeeping_short);
+ tcase_add_test(tc, test_housekeeping_long);
+ suite_add_tcase(s, tc);
+
+ tc = tcase_create("thread_configuration");
+ tcase_add_test(tc, test_cgroup_short_noarg);
+ tcase_add_test(tc, test_cgroup_short_space);
+ tcase_add_test(tc, test_cgroup_short_equals);
+ tcase_add_test(tc, test_cgroup_long_noarg);
+ tcase_add_test(tc, test_cgroup_long_space);
+ tcase_add_test(tc, test_cgroup_long_equals);
+ tcase_add_test(tc, test_priority_short);
+ tcase_add_test(tc, test_priority_long);
+ suite_add_tcase(s, tc);
+
+ tc = tcase_create("histogram_options");
+ tcase_add_test(tc, test_bucket_size_short);
+ tcase_add_test(tc, test_bucket_size_long);
+ tcase_add_test(tc, test_entries_short);
+ tcase_add_test(tc, test_entries_long);
+ tcase_add_test(tc, test_no_header);
+ tcase_add_test(tc, test_no_index);
+ tcase_add_test(tc, test_no_summary);
+ tcase_add_test(tc, test_with_zeros);
+ suite_add_tcase(s, tc);
+
+ tc = tcase_create("system_tuning");
+ tcase_add_test(tc, test_trace_buffer_size);
+ tcase_add_test(tc, test_warm_up);
+ suite_add_tcase(s, tc);
+
+ tc = tcase_create("aa_actions");
+ tcase_add_test(tc, test_auto);
+ tcase_add_test(tc, test_on_end);
+ tcase_add_test(tc, test_on_threshold);
+ suite_add_tcase(s, tc);
+
+ tc = tcase_create("general");
+ tcase_add_test(tc, test_debug_short);
+ tcase_add_test(tc, test_debug_long);
+ tcase_add_test(tc, test_duration_short);
+ tcase_add_test(tc, test_duration_long);
+ suite_add_tcase(s, tc);
+
+ return s;
+}
diff --git a/tools/tracing/rtla/tests/unit/osnoise_top_cli.c b/tools/tracing/rtla/tests/unit/osnoise_top_cli.c
new file mode 100644
index 000000000000..057dbe574b07
--- /dev/null
+++ b/tools/tracing/rtla/tests/unit/osnoise_top_cli.c
@@ -0,0 +1,521 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#define _GNU_SOURCE
+#include <check.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sched.h>
+#include <limits.h>
+#include <unistd.h>
+#include <sys/sysinfo.h>
+
+#include "cli_params_assert.h"
+#include "../../src/cli.h"
+
+#define PARSE_ARGS(...) char *argv[] = { __VA_ARGS__, NULL };\
+ int argc = sizeof(argv) / sizeof(char *) - 1;\
+ struct common_params *params =\
+ osnoise_top_parse_args(argc, argv);\
+ struct osnoise_params *osn_params __maybe_unused =\
+ to_osnoise_params(params)
+
+/* Tracing Options */
+
+START_TEST(test_period_short)
+{
+ PARSE_ARGS("osnoise", "top", "-p", "100000");
+
+ ck_assert_int_eq(osn_params->period, 100000);
+}
+END_TEST
+
+START_TEST(test_period_long)
+{
+ PARSE_ARGS("osnoise", "top", "--period", "100000");
+
+ ck_assert_int_eq(osn_params->period, 100000);
+}
+END_TEST
+
+START_TEST(test_period_unset_short)
+{
+ PARSE_ARGS("osnoise", "top", "-p", "100000", "--no-period");
+
+ ck_assert_int_eq(osn_params->period, 0);
+}
+END_TEST
+
+START_TEST(test_period_unset_long)
+{
+ PARSE_ARGS("osnoise", "top", "--period", "100000", "--no-period");
+
+ ck_assert_int_eq(osn_params->period, 0);
+}
+END_TEST
+
+START_TEST(test_runtime_short)
+{
+ PARSE_ARGS("osnoise", "top", "-r", "95000");
+
+ ck_assert_int_eq(osn_params->runtime, 95000);
+}
+END_TEST
+
+START_TEST(test_runtime_long)
+{
+ PARSE_ARGS("osnoise", "top", "--runtime", "95000");
+
+ ck_assert_int_eq(osn_params->runtime, 95000);
+}
+END_TEST
+
+START_TEST(test_stop_short)
+{
+ PARSE_ARGS("osnoise", "top", "-s", "20");
+
+ ck_assert_int_eq(params->stop_us, 20);
+}
+END_TEST
+
+START_TEST(test_stop_long)
+{
+ PARSE_ARGS("osnoise", "top", "--stop", "20");
+
+ ck_assert_int_eq(params->stop_us, 20);
+}
+END_TEST
+
+START_TEST(test_stop_total_short)
+{
+ PARSE_ARGS("osnoise", "top", "-S", "20");
+
+ ck_assert_int_eq(params->stop_total_us, 20);
+}
+END_TEST
+
+START_TEST(test_stop_total_long)
+{
+ PARSE_ARGS("osnoise", "top", "--stop-total", "20");
+
+ ck_assert_int_eq(params->stop_total_us, 20);
+}
+END_TEST
+
+START_TEST(test_threshold_short)
+{
+ PARSE_ARGS("osnoise", "top", "-T", "5");
+
+ ck_assert_int_eq(osn_params->threshold, 5);
+}
+END_TEST
+
+START_TEST(test_threshold_long)
+{
+ PARSE_ARGS("osnoise", "top", "--threshold", "5");
+
+ ck_assert_int_eq(osn_params->threshold, 5);
+}
+END_TEST
+
+START_TEST(test_trace_short_noarg)
+{
+ PARSE_ARGS("osnoise", "top", "-t");
+
+ CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+ "osnoise_trace.txt");
+}
+END_TEST
+
+START_TEST(test_trace_short_followarg)
+{
+ PARSE_ARGS("osnoise", "top", "-t", "-d", "20");
+
+ CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+ "osnoise_trace.txt");
+ ck_assert_int_eq(params->duration, 20); /* check if next argument is read correctly */
+}
+END_TEST
+
+START_TEST(test_trace_short_space)
+{
+ PARSE_ARGS("osnoise", "top", "-t", "tracefile");
+
+ CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+ "tracefile");
+}
+END_TEST
+
+START_TEST(test_trace_short_equals)
+{
+ PARSE_ARGS("osnoise", "top", "-t=tracefile");
+
+ CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+ "tracefile");
+}
+END_TEST
+
+START_TEST(test_trace_long_noarg)
+{
+ PARSE_ARGS("osnoise", "top", "--trace");
+
+ CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+ "osnoise_trace.txt");
+}
+END_TEST
+
+START_TEST(test_trace_long_followarg)
+{
+ PARSE_ARGS("osnoise", "top", "--trace", "-d", "20");
+
+ CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+ "osnoise_trace.txt");
+ ck_assert_int_eq(params->duration, 20); /* check if next argument is read correctly */
+}
+END_TEST
+
+START_TEST(test_trace_long_space)
+{
+ PARSE_ARGS("osnoise", "top", "--trace", "tracefile");
+
+ CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+ "tracefile");
+}
+END_TEST
+
+START_TEST(test_trace_long_equals)
+{
+ PARSE_ARGS("osnoise", "top", "--trace=tracefile");
+
+ CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+ "tracefile");
+}
+END_TEST
+
+/* Event Configuration */
+
+START_TEST(test_event_short)
+{
+ PARSE_ARGS("osnoise", "top", "-e", "system:event");
+
+ CLI_ASSERT_SINGLE_EVENT("system", "event");
+}
+END_TEST
+
+START_TEST(test_event_long)
+{
+ PARSE_ARGS("osnoise", "top", "--event", "system:event");
+
+ CLI_ASSERT_SINGLE_EVENT("system", "event");
+}
+END_TEST
+
+START_TEST(test_filter)
+{
+ PARSE_ARGS("osnoise", "top", "-e", "system:event", "--filter", "filter");
+
+ CLI_ASSERT_SINGLE_FILTER("filter");
+}
+END_TEST
+
+START_TEST(test_trigger)
+{
+ PARSE_ARGS("osnoise", "top", "-e", "system:event", "--trigger", "trigger");
+
+ CLI_ASSERT_SINGLE_TRIGGER("trigger");
+}
+END_TEST
+
+/* CPU Configuration */
+
+START_TEST(test_cpus_short)
+{
+ nr_cpus = 4;
+
+ PARSE_ARGS("osnoise", "top", "-c", "0-1,3");
+
+ ck_assert_str_eq(params->cpus, "0-1,3");
+ CLI_ASSERT_CPUSET(monitored_cpus, 0, 1, 3);
+}
+END_TEST
+
+START_TEST(test_cpus_long)
+{
+ nr_cpus = 4;
+
+ PARSE_ARGS("osnoise", "top", "--cpus", "0-1,3");
+
+ ck_assert_str_eq(params->cpus, "0-1,3");
+ CLI_ASSERT_CPUSET(monitored_cpus, 0, 1, 3);
+}
+END_TEST
+
+START_TEST(test_housekeeping_short)
+{
+ nr_cpus = 4;
+
+ PARSE_ARGS("osnoise", "top", "-H", "0-1,3");
+
+ CLI_ASSERT_CPUSET(hk_cpu_set, 0, 1, 3);
+}
+END_TEST
+
+START_TEST(test_housekeeping_long)
+{
+ nr_cpus = 4;
+
+ PARSE_ARGS("osnoise", "top", "--house-keeping", "0-1,3");
+
+ CLI_ASSERT_CPUSET(hk_cpu_set, 0, 1, 3);
+}
+END_TEST
+
+/* Thread Configuration */
+
+START_TEST(test_cgroup_short_noarg)
+{
+ PARSE_ARGS("osnoise", "top", "-C");
+
+ ck_assert(params->cgroup);
+ ck_assert_ptr_null(params->cgroup_name);
+}
+END_TEST
+
+START_TEST(test_cgroup_short_space)
+{
+ PARSE_ARGS("osnoise", "top", "-C", "cgroup");
+
+ ck_assert(params->cgroup);
+ ck_assert_str_eq(params->cgroup_name, "cgroup");
+}
+END_TEST
+
+START_TEST(test_cgroup_short_equals)
+{
+ PARSE_ARGS("osnoise", "top", "-C=cgroup");
+
+ ck_assert(params->cgroup);
+ ck_assert_str_eq(params->cgroup_name, "cgroup");
+}
+END_TEST
+
+START_TEST(test_cgroup_long_noarg)
+{
+ PARSE_ARGS("osnoise", "top", "--cgroup");
+
+ ck_assert(params->cgroup);
+ ck_assert_ptr_null(params->cgroup_name);
+}
+END_TEST
+
+START_TEST(test_cgroup_long_space)
+{
+ PARSE_ARGS("osnoise", "top", "--cgroup", "cgroup");
+
+ ck_assert(params->cgroup);
+ ck_assert_str_eq(params->cgroup_name, "cgroup");
+}
+END_TEST
+
+START_TEST(test_cgroup_long_equals)
+{
+ PARSE_ARGS("osnoise", "top", "--cgroup=cgroup");
+
+ ck_assert(params->cgroup);
+ ck_assert_str_eq(params->cgroup_name, "cgroup");
+}
+END_TEST
+
+START_TEST(test_priority_short)
+{
+ PARSE_ARGS("osnoise", "top", "-P", "f:95");
+
+ ck_assert_int_eq(params->sched_param.sched_policy, SCHED_FIFO);
+ ck_assert_int_eq(params->sched_param.sched_priority, 95);
+}
+END_TEST
+
+START_TEST(test_priority_long)
+{
+ PARSE_ARGS("osnoise", "top", "--priority", "f:95");
+
+ ck_assert_int_eq(params->sched_param.sched_policy, SCHED_FIFO);
+ ck_assert_int_eq(params->sched_param.sched_priority, 95);
+}
+END_TEST
+
+/* Output */
+
+START_TEST(test_quiet_short)
+{
+ PARSE_ARGS("osnoise", "top", "-q");
+
+ ck_assert(params->quiet);
+}
+END_TEST
+
+START_TEST(test_quiet_long)
+{
+ PARSE_ARGS("osnoise", "top", "--quiet");
+
+ ck_assert(params->quiet);
+}
+END_TEST
+
+/* Auto Analysis and Actions */
+
+START_TEST(test_auto)
+{
+ PARSE_ARGS("osnoise", "top", "-a", "20");
+
+ CLI_OSNOISE_ASSERT_AUTO(20);
+}
+END_TEST
+
+START_TEST(test_on_end)
+{
+ PARSE_ARGS("osnoise", "top", "--on-end", "trace");
+
+ CLI_ASSERT_SINGLE_ACTION(end_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+ "osnoise_trace.txt");
+}
+END_TEST
+
+START_TEST(test_on_threshold)
+{
+ PARSE_ARGS("osnoise", "top", "--on-threshold", "trace");
+
+ CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+ "osnoise_trace.txt");
+}
+END_TEST
+
+/* System Tuning */
+
+START_TEST(test_trace_buffer_size)
+{
+ PARSE_ARGS("osnoise", "top", "--trace-buffer-size", "200");
+
+ ck_assert_int_eq(params->buffer_size, 200);
+}
+END_TEST
+
+START_TEST(test_warm_up)
+{
+ PARSE_ARGS("osnoise", "top", "--warm-up", "5");
+
+ ck_assert_int_eq(params->warmup, 5);
+}
+END_TEST
+
+/* General */
+
+START_TEST(test_debug_short)
+{
+ PARSE_ARGS("osnoise", "top", "-D");
+
+ ck_assert(config_debug);
+}
+END_TEST
+
+START_TEST(test_debug_long)
+{
+ PARSE_ARGS("osnoise", "top", "--debug");
+
+ ck_assert(config_debug);
+}
+END_TEST
+
+START_TEST(test_duration_short)
+{
+ PARSE_ARGS("osnoise", "top", "-d", "1m");
+
+ ck_assert_int_eq(params->duration, 60);
+}
+END_TEST
+
+START_TEST(test_duration_long)
+{
+ PARSE_ARGS("osnoise", "top", "--duration", "1m");
+
+ ck_assert_int_eq(params->duration, 60);
+}
+END_TEST
+
+Suite *osnoise_top_cli_suite(void)
+{
+ Suite *s = suite_create("osnoise_top_cli");
+ TCase *tc;
+
+ tc = tcase_create("tracing_options");
+ tcase_add_test(tc, test_period_short);
+ tcase_add_test(tc, test_period_long);
+ tcase_add_test(tc, test_period_unset_short);
+ tcase_add_test(tc, test_period_unset_long);
+ tcase_add_test(tc, test_runtime_short);
+ tcase_add_test(tc, test_runtime_long);
+ tcase_add_test(tc, test_stop_short);
+ tcase_add_test(tc, test_stop_long);
+ tcase_add_test(tc, test_stop_total_short);
+ tcase_add_test(tc, test_stop_total_long);
+ tcase_add_test(tc, test_threshold_short);
+ tcase_add_test(tc, test_threshold_long);
+ tcase_add_test(tc, test_trace_short_noarg);
+ tcase_add_test(tc, test_trace_short_followarg);
+ tcase_add_test(tc, test_trace_short_space);
+ tcase_add_test(tc, test_trace_short_equals);
+ tcase_add_test(tc, test_trace_long_noarg);
+ tcase_add_test(tc, test_trace_long_followarg);
+ tcase_add_test(tc, test_trace_long_space);
+ tcase_add_test(tc, test_trace_long_equals);
+ suite_add_tcase(s, tc);
+
+ tc = tcase_create("event_configuration");
+ tcase_add_test(tc, test_event_short);
+ tcase_add_test(tc, test_event_long);
+ tcase_add_test(tc, test_filter);
+ tcase_add_test(tc, test_trigger);
+ suite_add_tcase(s, tc);
+
+ tc = tcase_create("cpu_configuration");
+ tcase_add_test(tc, test_cpus_short);
+ tcase_add_test(tc, test_cpus_long);
+ tcase_add_test(tc, test_housekeeping_short);
+ tcase_add_test(tc, test_housekeeping_long);
+ suite_add_tcase(s, tc);
+
+ tc = tcase_create("thread_configuration");
+ tcase_add_test(tc, test_cgroup_short_noarg);
+ tcase_add_test(tc, test_cgroup_short_space);
+ tcase_add_test(tc, test_cgroup_short_equals);
+ tcase_add_test(tc, test_cgroup_long_noarg);
+ tcase_add_test(tc, test_cgroup_long_space);
+ tcase_add_test(tc, test_cgroup_long_equals);
+ tcase_add_test(tc, test_priority_short);
+ tcase_add_test(tc, test_priority_long);
+ suite_add_tcase(s, tc);
+
+ tc = tcase_create("output");
+ tcase_add_test(tc, test_quiet_short);
+ tcase_add_test(tc, test_quiet_long);
+ suite_add_tcase(s, tc);
+
+ tc = tcase_create("system_tuning");
+ tcase_add_test(tc, test_trace_buffer_size);
+ tcase_add_test(tc, test_warm_up);
+ suite_add_tcase(s, tc);
+
+ tc = tcase_create("aa_actions");
+ tcase_add_test(tc, test_auto);
+ tcase_add_test(tc, test_on_end);
+ tcase_add_test(tc, test_on_threshold);
+ suite_add_tcase(s, tc);
+
+ tc = tcase_create("general");
+ tcase_add_test(tc, test_debug_short);
+ tcase_add_test(tc, test_debug_long);
+ tcase_add_test(tc, test_duration_short);
+ tcase_add_test(tc, test_duration_long);
+ suite_add_tcase(s, tc);
+
+ return s;
+}
diff --git a/tools/tracing/rtla/tests/unit/timerlat_hist_cli.c b/tools/tracing/rtla/tests/unit/timerlat_hist_cli.c
new file mode 100644
index 000000000000..d8dd9d752636
--- /dev/null
+++ b/tools/tracing/rtla/tests/unit/timerlat_hist_cli.c
@@ -0,0 +1,740 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#define _GNU_SOURCE
+#include <check.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sched.h>
+#include <limits.h>
+#include <unistd.h>
+#include <sys/sysinfo.h>
+
+#include <linux/container_of.h>
+
+#include "cli_params_assert.h"
+#include "../../src/cli.h"
+
+#define PARSE_ARGS(...) char *argv[] = { __VA_ARGS__, NULL };\
+ int argc = sizeof(argv) / sizeof(char *) - 1;\
+ struct common_params *params =\
+ timerlat_hist_parse_args(argc, argv);\
+ struct timerlat_params *tlat_params __maybe_unused =\
+ to_timerlat_params(params)
+
+/* Tracing Options */
+
+START_TEST(test_irq_short)
+{
+ PARSE_ARGS("timerlat", "hist", "-i", "20");
+
+ ck_assert_int_eq(params->stop_us, 20);
+}
+END_TEST
+
+START_TEST(test_irq_long)
+{
+ PARSE_ARGS("timerlat", "hist", "--irq", "20");
+
+ ck_assert_int_eq(params->stop_us, 20);
+}
+END_TEST
+
+START_TEST(test_period_short)
+{
+ PARSE_ARGS("timerlat", "hist", "-p", "200");
+
+ ck_assert_int_eq(tlat_params->timerlat_period_us, 200);
+}
+END_TEST
+
+START_TEST(test_period_long)
+{
+ PARSE_ARGS("timerlat", "hist", "--period", "200");
+
+ ck_assert_int_eq(tlat_params->timerlat_period_us, 200);
+}
+END_TEST
+
+START_TEST(test_period_unset_short)
+{
+ PARSE_ARGS("timerlat", "hist", "-p", "200", "--no-period");
+
+ ck_assert_int_eq(tlat_params->timerlat_period_us, 0);
+}
+END_TEST
+
+START_TEST(test_period_unset_long)
+{
+ PARSE_ARGS("timerlat", "hist", "--period", "200", "--no-period");
+
+ ck_assert_int_eq(tlat_params->timerlat_period_us, 0);
+}
+END_TEST
+
+START_TEST(test_stack_short)
+{
+ PARSE_ARGS("timerlat", "hist", "-s", "20");
+
+ ck_assert_int_eq(tlat_params->print_stack, 20);
+}
+END_TEST
+
+START_TEST(test_stack_long)
+{
+ PARSE_ARGS("timerlat", "hist", "--stack", "20");
+
+ ck_assert_int_eq(tlat_params->print_stack, 20);
+}
+END_TEST
+
+START_TEST(test_thread_short)
+{
+ PARSE_ARGS("timerlat", "hist", "-T", "20");
+
+ ck_assert_int_eq(params->stop_total_us, 20);
+}
+END_TEST
+
+START_TEST(test_thread_long)
+{
+ PARSE_ARGS("timerlat", "hist", "--thread", "20");
+
+ ck_assert_int_eq(params->stop_total_us, 20);
+}
+END_TEST
+
+START_TEST(test_trace_short_noarg)
+{
+ PARSE_ARGS("timerlat", "hist", "-t");
+
+ CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+ "timerlat_trace.txt");
+}
+END_TEST
+
+START_TEST(test_trace_short_followarg)
+{
+ PARSE_ARGS("timerlat", "hist", "-t", "-d", "20");
+
+ CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+ "timerlat_trace.txt");
+ ck_assert_int_eq(params->duration, 20); /* check if next argument is read correctly */
+}
+END_TEST
+
+START_TEST(test_trace_short_space)
+{
+ PARSE_ARGS("timerlat", "hist", "-t", "tracefile");
+
+ CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+ "tracefile");
+}
+END_TEST
+
+START_TEST(test_trace_short_equals)
+{
+ PARSE_ARGS("timerlat", "hist", "-t=tracefile");
+
+ CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+ "tracefile");
+}
+END_TEST
+
+START_TEST(test_trace_long_noarg)
+{
+ PARSE_ARGS("timerlat", "hist", "--trace");
+
+ CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+ "timerlat_trace.txt");
+}
+END_TEST
+
+START_TEST(test_trace_long_followarg)
+{
+ PARSE_ARGS("timerlat", "hist", "--trace", "-d", "20");
+
+ CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+ "timerlat_trace.txt");
+ ck_assert_int_eq(params->duration, 20); /* check if next argument is read correctly */
+}
+END_TEST
+
+START_TEST(test_trace_long_space)
+{
+ PARSE_ARGS("timerlat", "hist", "--trace", "tracefile");
+
+ CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+ "tracefile");
+}
+END_TEST
+
+START_TEST(test_trace_long_equals)
+{
+ PARSE_ARGS("timerlat", "hist", "--trace=tracefile");
+
+ CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+ "tracefile");
+}
+END_TEST
+
+/* Event Configuration */
+
+START_TEST(test_event_short)
+{
+ PARSE_ARGS("timerlat", "hist", "-e", "system:event");
+
+ CLI_ASSERT_SINGLE_EVENT("system", "event");
+}
+END_TEST
+
+START_TEST(test_event_long)
+{
+ PARSE_ARGS("timerlat", "hist", "--event", "system:event");
+
+ CLI_ASSERT_SINGLE_EVENT("system", "event");
+}
+END_TEST
+
+START_TEST(test_filter)
+{
+ PARSE_ARGS("timerlat", "hist", "-e", "system:event", "--filter", "filter");
+
+ CLI_ASSERT_SINGLE_FILTER("filter");
+}
+END_TEST
+
+START_TEST(test_trigger)
+{
+ PARSE_ARGS("timerlat", "hist", "-e", "system:event", "--trigger", "trigger");
+
+ CLI_ASSERT_SINGLE_TRIGGER("trigger");
+}
+END_TEST
+
+/* CPU Configuration */
+
+START_TEST(test_cpus_short)
+{
+ nr_cpus = 4;
+
+ PARSE_ARGS("timerlat", "hist", "-c", "0-1,3");
+
+ ck_assert_str_eq(params->cpus, "0-1,3");
+ CLI_ASSERT_CPUSET(monitored_cpus, 0, 1, 3);
+}
+END_TEST
+
+START_TEST(test_cpus_long)
+{
+ nr_cpus = 4;
+
+ PARSE_ARGS("timerlat", "hist", "--cpus", "0-1,3");
+
+ ck_assert_str_eq(params->cpus, "0-1,3");
+ CLI_ASSERT_CPUSET(monitored_cpus, 0, 1, 3);
+}
+END_TEST
+
+START_TEST(test_housekeeping_short)
+{
+ nr_cpus = 4;
+
+ PARSE_ARGS("timerlat", "hist", "-H", "0-1,3");
+
+ CLI_ASSERT_CPUSET(hk_cpu_set, 0, 1, 3);
+}
+END_TEST
+
+START_TEST(test_housekeeping_long)
+{
+ nr_cpus = 4;
+
+ PARSE_ARGS("timerlat", "hist", "--house-keeping", "0-1,3");
+
+ CLI_ASSERT_CPUSET(hk_cpu_set, 0, 1, 3);
+}
+END_TEST
+
+/* Thread Configuration */
+
+START_TEST(test_cgroup_short_noarg)
+{
+ PARSE_ARGS("timerlat", "hist", "-C");
+
+ ck_assert(params->cgroup);
+ ck_assert_ptr_null(params->cgroup_name);
+}
+END_TEST
+
+START_TEST(test_cgroup_short_space)
+{
+ PARSE_ARGS("timerlat", "hist", "-C", "cgroup");
+
+ ck_assert(params->cgroup);
+ ck_assert_str_eq(params->cgroup_name, "cgroup");
+}
+END_TEST
+
+START_TEST(test_cgroup_short_equals)
+{
+ PARSE_ARGS("timerlat", "hist", "-C=cgroup");
+
+ ck_assert(params->cgroup);
+ ck_assert_str_eq(params->cgroup_name, "cgroup");
+}
+END_TEST
+
+START_TEST(test_cgroup_long_noarg)
+{
+ PARSE_ARGS("timerlat", "hist", "--cgroup");
+
+ ck_assert(params->cgroup);
+ ck_assert_ptr_null(params->cgroup_name);
+}
+END_TEST
+
+START_TEST(test_cgroup_long_space)
+{
+ PARSE_ARGS("timerlat", "hist", "--cgroup", "cgroup");
+
+ ck_assert(params->cgroup);
+ ck_assert_str_eq(params->cgroup_name, "cgroup");
+}
+END_TEST
+
+START_TEST(test_cgroup_long_equals)
+{
+ PARSE_ARGS("timerlat", "hist", "--cgroup=cgroup");
+
+ ck_assert(params->cgroup);
+ ck_assert_str_eq(params->cgroup_name, "cgroup");
+}
+END_TEST
+
+START_TEST(test_kernel_threads_short)
+{
+ PARSE_ARGS("timerlat", "hist", "-k");
+
+ ck_assert(params->kernel_workload);
+ ck_assert(!params->user_workload);
+ ck_assert(!params->user_data);
+}
+END_TEST
+
+START_TEST(test_kernel_threads_long)
+{
+ PARSE_ARGS("timerlat", "hist", "--kernel-threads");
+
+ ck_assert(params->kernel_workload);
+ ck_assert(!params->user_workload);
+ ck_assert(!params->user_data);
+}
+END_TEST
+
+START_TEST(test_priority_short)
+{
+ PARSE_ARGS("timerlat", "hist", "-P", "f:95");
+
+ ck_assert_int_eq(params->sched_param.sched_policy, SCHED_FIFO);
+ ck_assert_int_eq(params->sched_param.sched_priority, 95);
+}
+END_TEST
+
+START_TEST(test_priority_long)
+{
+ PARSE_ARGS("timerlat", "hist", "--priority", "f:95");
+
+ ck_assert_int_eq(params->sched_param.sched_policy, SCHED_FIFO);
+ ck_assert_int_eq(params->sched_param.sched_priority, 95);
+}
+END_TEST
+
+START_TEST(test_user_load_short)
+{
+ PARSE_ARGS("timerlat", "hist", "-U");
+
+ ck_assert(!params->kernel_workload);
+ ck_assert(!params->user_workload);
+ ck_assert(params->user_data);
+}
+END_TEST
+
+START_TEST(test_user_load_long)
+{
+ PARSE_ARGS("timerlat", "hist", "--user-load");
+
+ ck_assert(!params->kernel_workload);
+ ck_assert(!params->user_workload);
+ ck_assert(params->user_data);
+}
+END_TEST
+
+START_TEST(test_user_threads_short)
+{
+ PARSE_ARGS("timerlat", "hist", "-u");
+
+ ck_assert(!params->kernel_workload);
+ ck_assert(params->user_workload);
+ ck_assert(params->user_data);
+}
+END_TEST
+
+START_TEST(test_user_threads_long)
+{
+ PARSE_ARGS("timerlat", "hist", "--user-threads");
+
+ ck_assert(!params->kernel_workload);
+ ck_assert(params->user_workload);
+ ck_assert(params->user_data);
+}
+END_TEST
+
+START_TEST(test_aligned_short)
+{
+ PARSE_ARGS("timerlat", "hist", "-A", "500");
+
+ ck_assert(tlat_params->timerlat_align);
+ ck_assert_int_eq(tlat_params->timerlat_align_us, 500);
+}
+END_TEST
+
+START_TEST(test_aligned_long)
+{
+ PARSE_ARGS("timerlat", "hist", "--aligned", "500");
+
+ ck_assert(tlat_params->timerlat_align);
+ ck_assert_int_eq(tlat_params->timerlat_align_us, 500);
+}
+END_TEST
+
+/* Histogram Options */
+
+START_TEST(test_bucket_size_short)
+{
+ PARSE_ARGS("timerlat", "hist", "-b", "2");
+
+ ck_assert_int_eq(params->hist.bucket_size, 2);
+}
+END_TEST
+
+START_TEST(test_bucket_size_long)
+{
+ PARSE_ARGS("timerlat", "hist", "--bucket-size", "2");
+
+ ck_assert_int_eq(params->hist.bucket_size, 2);
+}
+END_TEST
+
+START_TEST(test_entries_short)
+{
+ PARSE_ARGS("timerlat", "hist", "-E", "512");
+
+ ck_assert_int_eq(params->hist.entries, 512);
+}
+END_TEST
+
+START_TEST(test_entries_long)
+{
+ PARSE_ARGS("timerlat", "hist", "--entries", "512");
+
+ ck_assert_int_eq(params->hist.entries, 512);
+}
+END_TEST
+
+START_TEST(test_no_header)
+{
+ PARSE_ARGS("timerlat", "hist", "--no-header");
+
+ ck_assert(params->hist.no_header);
+}
+END_TEST
+
+START_TEST(test_no_index)
+{
+ PARSE_ARGS("timerlat", "hist", "--with-zeros", "--no-index");
+
+ ck_assert(params->hist.no_index);
+}
+END_TEST
+
+START_TEST(test_no_irq)
+{
+ PARSE_ARGS("timerlat", "hist", "--no-irq");
+
+ ck_assert(params->hist.no_irq);
+}
+END_TEST
+
+START_TEST(test_no_summary)
+{
+ PARSE_ARGS("timerlat", "hist", "--no-summary");
+
+ ck_assert(params->hist.no_summary);
+}
+END_TEST
+
+START_TEST(test_no_thread)
+{
+ PARSE_ARGS("timerlat", "hist", "--no-thread");
+
+ ck_assert(params->hist.no_thread);
+}
+END_TEST
+
+START_TEST(test_with_zeros)
+{
+ PARSE_ARGS("timerlat", "hist", "--with-zeros");
+
+ ck_assert(params->hist.with_zeros);
+}
+END_TEST
+
+/* Output */
+
+START_TEST(test_nano_short)
+{
+ PARSE_ARGS("timerlat", "hist", "-n");
+
+ ck_assert_int_eq(params->output_divisor, 1);
+}
+END_TEST
+
+START_TEST(test_nano_long)
+{
+ PARSE_ARGS("timerlat", "hist", "--nano");
+
+ ck_assert_int_eq(params->output_divisor, 1);
+}
+END_TEST
+
+/* System Tuning */
+
+START_TEST(test_deepest_idle_state)
+{
+ PARSE_ARGS("timerlat", "hist", "--deepest-idle-state", "1");
+
+ ck_assert_int_eq(tlat_params->deepest_idle_state, 1);
+}
+END_TEST
+
+START_TEST(test_dma_latency)
+{
+ PARSE_ARGS("timerlat", "hist", "--dma-latency", "10");
+
+ ck_assert_int_eq(tlat_params->dma_latency, 10);
+}
+END_TEST
+
+START_TEST(test_trace_buffer_size)
+{
+ PARSE_ARGS("timerlat", "hist", "--trace-buffer-size", "200");
+
+ ck_assert_int_eq(params->buffer_size, 200);
+}
+END_TEST
+
+START_TEST(test_warm_up)
+{
+ PARSE_ARGS("timerlat", "hist", "--warm-up", "5");
+
+ ck_assert_int_eq(params->warmup, 5);
+}
+END_TEST
+
+/* Auto Analysis and Actions */
+
+START_TEST(test_auto)
+{
+ PARSE_ARGS("timerlat", "hist", "-a", "20");
+
+ CLI_TIMERLAT_ASSERT_AUTO(20);
+}
+END_TEST
+
+START_TEST(test_bpf_action)
+{
+ PARSE_ARGS("timerlat", "hist", "--bpf-action", "program");
+
+ ck_assert_str_eq(tlat_params->bpf_action_program, "program");
+}
+END_TEST
+
+START_TEST(test_dump_tasks)
+{
+ PARSE_ARGS("timerlat", "hist", "--dump-tasks");
+
+ ck_assert(tlat_params->dump_tasks);
+}
+END_TEST
+
+START_TEST(test_no_aa)
+{
+ PARSE_ARGS("timerlat", "hist", "--no-aa");
+
+ ck_assert(tlat_params->no_aa);
+}
+END_TEST
+
+START_TEST(test_on_end)
+{
+ PARSE_ARGS("timerlat", "hist", "--on-end", "trace");
+
+ CLI_ASSERT_SINGLE_ACTION(end_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+ "timerlat_trace.txt");
+}
+END_TEST
+
+START_TEST(test_on_threshold)
+{
+ PARSE_ARGS("timerlat", "hist", "--on-threshold", "trace");
+
+ CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+ "timerlat_trace.txt");
+}
+END_TEST
+
+START_TEST(test_stack_format)
+{
+ PARSE_ARGS("timerlat", "hist", "--stack-format", "truncate");
+
+ ck_assert_int_eq(tlat_params->stack_format, STACK_FORMAT_TRUNCATE);
+}
+END_TEST
+
+/* General */
+
+START_TEST(test_debug_short)
+{
+ PARSE_ARGS("timerlat", "hist", "-D");
+
+ ck_assert(config_debug);
+}
+END_TEST
+
+START_TEST(test_debug_long)
+{
+ PARSE_ARGS("timerlat", "hist", "--debug");
+
+ ck_assert(config_debug);
+}
+END_TEST
+
+START_TEST(test_duration_short)
+{
+ PARSE_ARGS("timerlat", "hist", "-d", "1m");
+
+ ck_assert_int_eq(params->duration, 60);
+}
+END_TEST
+
+START_TEST(test_duration_long)
+{
+ PARSE_ARGS("timerlat", "hist", "--duration", "1m");
+
+ ck_assert_int_eq(params->duration, 60);
+}
+END_TEST
+
+Suite *timerlat_hist_cli_suite(void)
+{
+ Suite *s = suite_create("timerlat_hist_cli");
+ TCase *tc;
+
+ tc = tcase_create("tracing_options");
+ tcase_add_test(tc, test_irq_short);
+ tcase_add_test(tc, test_irq_long);
+ tcase_add_test(tc, test_period_short);
+ tcase_add_test(tc, test_period_long);
+ tcase_add_test(tc, test_period_unset_short);
+ tcase_add_test(tc, test_period_unset_long);
+ tcase_add_test(tc, test_stack_short);
+ tcase_add_test(tc, test_stack_long);
+ tcase_add_test(tc, test_thread_short);
+ tcase_add_test(tc, test_thread_long);
+ tcase_add_test(tc, test_trace_short_noarg);
+ tcase_add_test(tc, test_trace_short_followarg);
+ tcase_add_test(tc, test_trace_short_space);
+ tcase_add_test(tc, test_trace_short_equals);
+ tcase_add_test(tc, test_trace_long_noarg);
+ tcase_add_test(tc, test_trace_long_followarg);
+ tcase_add_test(tc, test_trace_long_space);
+ tcase_add_test(tc, test_trace_long_equals);
+ suite_add_tcase(s, tc);
+
+ tc = tcase_create("event_configuration");
+ tcase_add_test(tc, test_event_short);
+ tcase_add_test(tc, test_event_long);
+ tcase_add_test(tc, test_filter);
+ tcase_add_test(tc, test_trigger);
+ suite_add_tcase(s, tc);
+
+ tc = tcase_create("cpu_configuration");
+ tcase_add_test(tc, test_cpus_short);
+ tcase_add_test(tc, test_cpus_long);
+ tcase_add_test(tc, test_housekeeping_short);
+ tcase_add_test(tc, test_housekeeping_long);
+ suite_add_tcase(s, tc);
+
+ tc = tcase_create("thread_configuration");
+ tcase_add_test(tc, test_cgroup_short_noarg);
+ tcase_add_test(tc, test_cgroup_short_space);
+ tcase_add_test(tc, test_cgroup_short_equals);
+ tcase_add_test(tc, test_cgroup_long_noarg);
+ tcase_add_test(tc, test_cgroup_long_space);
+ tcase_add_test(tc, test_cgroup_long_equals);
+ tcase_add_test(tc, test_kernel_threads_short);
+ tcase_add_test(tc, test_kernel_threads_long);
+ tcase_add_test(tc, test_priority_short);
+ tcase_add_test(tc, test_priority_long);
+ tcase_add_test(tc, test_user_load_short);
+ tcase_add_test(tc, test_user_load_long);
+ tcase_add_test(tc, test_user_threads_short);
+ tcase_add_test(tc, test_user_threads_long);
+ tcase_add_test(tc, test_aligned_short);
+ tcase_add_test(tc, test_aligned_long);
+ suite_add_tcase(s, tc);
+
+ tc = tcase_create("histogram_options");
+ tcase_add_test(tc, test_bucket_size_short);
+ tcase_add_test(tc, test_bucket_size_long);
+ tcase_add_test(tc, test_entries_short);
+ tcase_add_test(tc, test_entries_long);
+ tcase_add_test(tc, test_no_header);
+ tcase_add_test(tc, test_no_index);
+ tcase_add_test(tc, test_no_irq);
+ tcase_add_test(tc, test_no_summary);
+ tcase_add_test(tc, test_no_thread);
+ tcase_add_test(tc, test_with_zeros);
+ suite_add_tcase(s, tc);
+
+ tc = tcase_create("output");
+ tcase_add_test(tc, test_nano_short);
+ tcase_add_test(tc, test_nano_long);
+ suite_add_tcase(s, tc);
+
+ tc = tcase_create("system_tuning");
+ tcase_add_test(tc, test_deepest_idle_state);
+ tcase_add_test(tc, test_dma_latency);
+ tcase_add_test(tc, test_trace_buffer_size);
+ tcase_add_test(tc, test_warm_up);
+ suite_add_tcase(s, tc);
+
+ tc = tcase_create("aa_actions");
+ tcase_add_test(tc, test_auto);
+ tcase_add_test(tc, test_bpf_action);
+ tcase_add_test(tc, test_dump_tasks);
+ tcase_add_test(tc, test_no_aa);
+ tcase_add_test(tc, test_on_end);
+ tcase_add_test(tc, test_on_threshold);
+ tcase_add_test(tc, test_stack_format);
+ suite_add_tcase(s, tc);
+
+ tc = tcase_create("general");
+ tcase_add_test(tc, test_debug_short);
+ tcase_add_test(tc, test_debug_long);
+ tcase_add_test(tc, test_duration_short);
+ tcase_add_test(tc, test_duration_long);
+ suite_add_tcase(s, tc);
+
+ return s;
+}
diff --git a/tools/tracing/rtla/tests/unit/timerlat_top_cli.c b/tools/tracing/rtla/tests/unit/timerlat_top_cli.c
new file mode 100644
index 000000000000..e9fb1a86ab8c
--- /dev/null
+++ b/tools/tracing/rtla/tests/unit/timerlat_top_cli.c
@@ -0,0 +1,672 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#define _GNU_SOURCE
+#include <check.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sched.h>
+#include <limits.h>
+#include <unistd.h>
+#include <sys/sysinfo.h>
+
+#include <linux/container_of.h>
+
+#include "cli_params_assert.h"
+#include "../../src/cli.h"
+
+#define PARSE_ARGS(...) char *argv[] = { __VA_ARGS__, NULL };\
+ int argc = sizeof(argv) / sizeof(char *) - 1;\
+ struct common_params *params =\
+ timerlat_top_parse_args(argc, argv);\
+ struct timerlat_params *tlat_params __maybe_unused =\
+ to_timerlat_params(params)
+
+/* Tracing Options */
+
+START_TEST(test_irq_short)
+{
+ PARSE_ARGS("timerlat", "top", "-i", "20");
+
+ ck_assert_int_eq(params->stop_us, 20);
+}
+END_TEST
+
+START_TEST(test_irq_long)
+{
+ PARSE_ARGS("timerlat", "top", "--irq", "20");
+
+ ck_assert_int_eq(params->stop_us, 20);
+}
+END_TEST
+
+START_TEST(test_period_short)
+{
+ PARSE_ARGS("timerlat", "top", "-p", "200");
+
+ ck_assert_int_eq(tlat_params->timerlat_period_us, 200);
+}
+END_TEST
+
+START_TEST(test_period_long)
+{
+ PARSE_ARGS("timerlat", "top", "--period", "200");
+
+ ck_assert_int_eq(tlat_params->timerlat_period_us, 200);
+}
+END_TEST
+
+START_TEST(test_period_unset_short)
+{
+ PARSE_ARGS("timerlat", "top", "-p", "200", "--no-period");
+
+ ck_assert_int_eq(tlat_params->timerlat_period_us, 0);
+}
+END_TEST
+
+START_TEST(test_period_unset_long)
+{
+ PARSE_ARGS("timerlat", "top", "--period", "200", "--no-period");
+
+ ck_assert_int_eq(tlat_params->timerlat_period_us, 0);
+}
+END_TEST
+
+START_TEST(test_stack_short)
+{
+ PARSE_ARGS("timerlat", "top", "-s", "20");
+
+ ck_assert_int_eq(tlat_params->print_stack, 20);
+}
+END_TEST
+
+START_TEST(test_stack_long)
+{
+ PARSE_ARGS("timerlat", "top", "--stack", "20");
+
+ ck_assert_int_eq(tlat_params->print_stack, 20);
+}
+END_TEST
+
+START_TEST(test_thread_short)
+{
+ PARSE_ARGS("timerlat", "top", "-T", "20");
+
+ ck_assert_int_eq(params->stop_total_us, 20);
+}
+END_TEST
+
+START_TEST(test_thread_long)
+{
+ PARSE_ARGS("timerlat", "top", "--thread", "20");
+
+ ck_assert_int_eq(params->stop_total_us, 20);
+}
+END_TEST
+
+/* Event Configuration */
+
+START_TEST(test_event_short)
+{
+ PARSE_ARGS("timerlat", "top", "-e", "system:event");
+
+ CLI_ASSERT_SINGLE_EVENT("system", "event");
+}
+END_TEST
+
+START_TEST(test_event_long)
+{
+ PARSE_ARGS("timerlat", "top", "--event", "system:event");
+
+ CLI_ASSERT_SINGLE_EVENT("system", "event");
+}
+END_TEST
+
+START_TEST(test_filter)
+{
+ PARSE_ARGS("timerlat", "top", "-e", "system:event", "--filter", "filter");
+
+ CLI_ASSERT_SINGLE_FILTER("filter");
+}
+END_TEST
+
+START_TEST(test_trigger)
+{
+ PARSE_ARGS("timerlat", "top", "-e", "system:event", "--trigger", "trigger");
+
+ CLI_ASSERT_SINGLE_TRIGGER("trigger");
+}
+END_TEST
+
+START_TEST(test_trace_short_noarg)
+{
+ PARSE_ARGS("timerlat", "top", "-t");
+
+ CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+ "timerlat_trace.txt");
+}
+END_TEST
+
+START_TEST(test_trace_short_followarg)
+{
+ PARSE_ARGS("timerlat", "top", "-t", "-d", "20");
+
+ CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+ "timerlat_trace.txt");
+ ck_assert_int_eq(params->duration, 20); /* check if next argument is read correctly */
+}
+END_TEST
+
+START_TEST(test_trace_short_space)
+{
+ PARSE_ARGS("timerlat", "top", "-t", "tracefile");
+
+ CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+ "tracefile");
+}
+END_TEST
+
+START_TEST(test_trace_short_equals)
+{
+ PARSE_ARGS("timerlat", "top", "-t=tracefile");
+
+ CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+ "tracefile");
+}
+END_TEST
+
+START_TEST(test_trace_long_noarg)
+{
+ PARSE_ARGS("timerlat", "top", "--trace");
+
+ CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+ "timerlat_trace.txt");
+}
+END_TEST
+
+START_TEST(test_trace_long_followarg)
+{
+ PARSE_ARGS("timerlat", "top", "--trace", "-d", "20");
+
+ CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+ "timerlat_trace.txt");
+ ck_assert_int_eq(params->duration, 20); /* check if next argument is read correctly */
+}
+END_TEST
+
+START_TEST(test_trace_long_space)
+{
+ PARSE_ARGS("timerlat", "top", "--trace", "tracefile");
+
+ CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+ "tracefile");
+}
+END_TEST
+
+START_TEST(test_trace_long_equals)
+{
+ PARSE_ARGS("timerlat", "top", "--trace=tracefile");
+
+ CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+ "tracefile");
+}
+END_TEST
+
+/* CPU Configuration */
+
+START_TEST(test_cpus_short)
+{
+ nr_cpus = 4;
+
+ PARSE_ARGS("timerlat", "top", "-c", "0-1,3");
+
+ ck_assert_str_eq(params->cpus, "0-1,3");
+ CLI_ASSERT_CPUSET(monitored_cpus, 0, 1, 3);
+}
+END_TEST
+
+START_TEST(test_cpus_long)
+{
+ nr_cpus = 4;
+
+ PARSE_ARGS("timerlat", "top", "--cpus", "0-1,3");
+
+ ck_assert_str_eq(params->cpus, "0-1,3");
+ CLI_ASSERT_CPUSET(monitored_cpus, 0, 1, 3);
+}
+END_TEST
+
+START_TEST(test_housekeeping_short)
+{
+ nr_cpus = 4;
+
+ PARSE_ARGS("timerlat", "top", "-H", "0-1,3");
+
+ CLI_ASSERT_CPUSET(hk_cpu_set, 0, 1, 3);
+}
+END_TEST
+
+START_TEST(test_housekeeping_long)
+{
+ nr_cpus = 4;
+
+ PARSE_ARGS("timerlat", "top", "--house-keeping", "0-1,3");
+
+ CLI_ASSERT_CPUSET(hk_cpu_set, 0, 1, 3);
+}
+END_TEST
+
+/* Thread Configuration */
+
+START_TEST(test_cgroup_short_noarg)
+{
+ PARSE_ARGS("timerlat", "top", "-C");
+
+ ck_assert(params->cgroup);
+ ck_assert_ptr_null(params->cgroup_name);
+}
+END_TEST
+
+START_TEST(test_cgroup_short_space)
+{
+ PARSE_ARGS("timerlat", "top", "-C", "cgroup");
+
+ ck_assert(params->cgroup);
+ ck_assert_str_eq(params->cgroup_name, "cgroup");
+}
+END_TEST
+
+START_TEST(test_cgroup_short_equals)
+{
+ PARSE_ARGS("timerlat", "top", "-C=cgroup");
+
+ ck_assert(params->cgroup);
+ ck_assert_str_eq(params->cgroup_name, "cgroup");
+}
+END_TEST
+
+START_TEST(test_cgroup_long_noarg)
+{
+ PARSE_ARGS("timerlat", "top", "--cgroup");
+
+ ck_assert(params->cgroup);
+ ck_assert_ptr_null(params->cgroup_name);
+}
+END_TEST
+
+START_TEST(test_cgroup_long_space)
+{
+ PARSE_ARGS("timerlat", "top", "--cgroup", "cgroup");
+
+ ck_assert(params->cgroup);
+ ck_assert_str_eq(params->cgroup_name, "cgroup");
+}
+END_TEST
+
+START_TEST(test_cgroup_long_equals)
+{
+ PARSE_ARGS("timerlat", "top", "--cgroup=cgroup");
+
+ ck_assert(params->cgroup);
+ ck_assert_str_eq(params->cgroup_name, "cgroup");
+}
+END_TEST
+
+START_TEST(test_kernel_threads_short)
+{
+ PARSE_ARGS("timerlat", "top", "-k");
+
+ ck_assert(params->kernel_workload);
+ ck_assert(!params->user_workload);
+ ck_assert(!params->user_data);
+}
+END_TEST
+
+START_TEST(test_kernel_threads_long)
+{
+ PARSE_ARGS("timerlat", "top", "--kernel-threads");
+
+ ck_assert(params->kernel_workload);
+ ck_assert(!params->user_workload);
+ ck_assert(!params->user_data);
+}
+END_TEST
+
+START_TEST(test_priority_short)
+{
+ PARSE_ARGS("timerlat", "top", "-P", "f:95");
+
+ ck_assert_int_eq(params->sched_param.sched_policy, SCHED_FIFO);
+ ck_assert_int_eq(params->sched_param.sched_priority, 95);
+}
+END_TEST
+
+START_TEST(test_priority_long)
+{
+ PARSE_ARGS("timerlat", "top", "--priority", "f:95");
+
+ ck_assert_int_eq(params->sched_param.sched_policy, SCHED_FIFO);
+ ck_assert_int_eq(params->sched_param.sched_priority, 95);
+}
+END_TEST
+
+START_TEST(test_user_load_short)
+{
+ PARSE_ARGS("timerlat", "top", "-U");
+
+ ck_assert(!params->kernel_workload);
+ ck_assert(!params->user_workload);
+ ck_assert(params->user_data);
+}
+END_TEST
+
+START_TEST(test_user_load_long)
+{
+ PARSE_ARGS("timerlat", "top", "--user-load");
+
+ ck_assert(!params->kernel_workload);
+ ck_assert(!params->user_workload);
+ ck_assert(params->user_data);
+}
+END_TEST
+
+START_TEST(test_user_threads_short)
+{
+ PARSE_ARGS("timerlat", "top", "-u");
+
+ ck_assert(!params->kernel_workload);
+ ck_assert(params->user_workload);
+ ck_assert(params->user_data);
+}
+END_TEST
+
+START_TEST(test_user_threads_long)
+{
+ PARSE_ARGS("timerlat", "top", "--user-threads");
+
+ ck_assert(!params->kernel_workload);
+ ck_assert(params->user_workload);
+ ck_assert(params->user_data);
+}
+END_TEST
+
+START_TEST(test_aligned_short)
+{
+ PARSE_ARGS("timerlat", "top", "-A", "500");
+
+ ck_assert(tlat_params->timerlat_align);
+ ck_assert_int_eq(tlat_params->timerlat_align_us, 500);
+}
+END_TEST
+
+START_TEST(test_aligned_long)
+{
+ PARSE_ARGS("timerlat", "top", "--aligned", "500");
+
+ ck_assert(tlat_params->timerlat_align);
+ ck_assert_int_eq(tlat_params->timerlat_align_us, 500);
+}
+END_TEST
+
+/* Output */
+
+START_TEST(test_nano_short)
+{
+ PARSE_ARGS("timerlat", "top", "-n");
+
+ ck_assert_int_eq(params->output_divisor, 1);
+}
+END_TEST
+
+START_TEST(test_nano_long)
+{
+ PARSE_ARGS("timerlat", "top", "--nano");
+
+ ck_assert_int_eq(params->output_divisor, 1);
+}
+END_TEST
+
+START_TEST(test_quiet_short)
+{
+ PARSE_ARGS("timerlat", "top", "-q");
+
+ ck_assert(params->quiet);
+}
+END_TEST
+
+START_TEST(test_quiet_long)
+{
+ PARSE_ARGS("timerlat", "top", "--quiet");
+
+ ck_assert(params->quiet);
+}
+END_TEST
+
+/* System Tuning */
+
+START_TEST(test_deepest_idle_state)
+{
+ PARSE_ARGS("timerlat", "top", "--deepest-idle-state", "1");
+
+ ck_assert_int_eq(tlat_params->deepest_idle_state, 1);
+}
+END_TEST
+
+START_TEST(test_dma_latency)
+{
+ PARSE_ARGS("timerlat", "top", "--dma-latency", "10");
+
+ ck_assert_int_eq(tlat_params->dma_latency, 10);
+}
+END_TEST
+
+START_TEST(test_trace_buffer_size)
+{
+ PARSE_ARGS("timerlat", "top", "--trace-buffer-size", "200");
+
+ ck_assert_int_eq(params->buffer_size, 200);
+}
+END_TEST
+
+START_TEST(test_warm_up)
+{
+ PARSE_ARGS("timerlat", "top", "--warm-up", "5");
+
+ ck_assert_int_eq(params->warmup, 5);
+}
+END_TEST
+
+/* Auto Analysis and Actions */
+
+START_TEST(test_auto)
+{
+ PARSE_ARGS("timerlat", "top", "-a", "20");
+
+ CLI_TIMERLAT_ASSERT_AUTO(20);
+}
+END_TEST
+
+START_TEST(test_aa_only)
+{
+ PARSE_ARGS("timerlat", "top", "--aa-only", "20");
+
+ CLI_TIMERLAT_ASSERT_AA_ONLY(20);
+}
+END_TEST
+
+START_TEST(test_bpf_action)
+{
+ PARSE_ARGS("timerlat", "top", "--bpf-action", "program");
+
+ ck_assert_str_eq(tlat_params->bpf_action_program, "program");
+}
+END_TEST
+
+START_TEST(test_dump_tasks)
+{
+ PARSE_ARGS("timerlat", "top", "--dump-tasks");
+
+ ck_assert(tlat_params->dump_tasks);
+}
+END_TEST
+
+START_TEST(test_no_aa)
+{
+ PARSE_ARGS("timerlat", "top", "--no-aa");
+
+ ck_assert(tlat_params->no_aa);
+}
+END_TEST
+
+START_TEST(test_on_end)
+{
+ PARSE_ARGS("timerlat", "top", "--on-end", "trace");
+
+ CLI_ASSERT_SINGLE_ACTION(end_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+ "timerlat_trace.txt");
+}
+END_TEST
+
+START_TEST(test_on_threshold)
+{
+ PARSE_ARGS("timerlat", "top", "--on-threshold", "trace");
+
+ CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+ "timerlat_trace.txt");
+}
+END_TEST
+
+START_TEST(test_stack_format)
+{
+ PARSE_ARGS("timerlat", "top", "--stack-format", "truncate");
+
+ ck_assert_int_eq(tlat_params->stack_format, STACK_FORMAT_TRUNCATE);
+}
+END_TEST
+
+/* General */
+
+START_TEST(test_debug_short)
+{
+ PARSE_ARGS("timerlat", "top", "-D");
+
+ ck_assert(config_debug);
+}
+END_TEST
+
+START_TEST(test_debug_long)
+{
+ PARSE_ARGS("timerlat", "top", "--debug");
+
+ ck_assert(config_debug);
+}
+END_TEST
+
+START_TEST(test_duration_short)
+{
+ PARSE_ARGS("timerlat", "top", "-d", "1m");
+
+ ck_assert_int_eq(params->duration, 60);
+}
+END_TEST
+
+START_TEST(test_duration_long)
+{
+ PARSE_ARGS("timerlat", "top", "--duration", "1m");
+
+ ck_assert_int_eq(params->duration, 60);
+}
+END_TEST
+
+Suite *timerlat_top_cli_suite(void)
+{
+ Suite *s = suite_create("timerlat_top_cli");
+ TCase *tc;
+
+ tc = tcase_create("tracing_options");
+ tcase_add_test(tc, test_irq_short);
+ tcase_add_test(tc, test_irq_long);
+ tcase_add_test(tc, test_period_short);
+ tcase_add_test(tc, test_period_long);
+ tcase_add_test(tc, test_period_unset_short);
+ tcase_add_test(tc, test_period_unset_long);
+ tcase_add_test(tc, test_stack_short);
+ tcase_add_test(tc, test_stack_long);
+ tcase_add_test(tc, test_thread_short);
+ tcase_add_test(tc, test_thread_long);
+ tcase_add_test(tc, test_trace_short_noarg);
+ tcase_add_test(tc, test_trace_short_followarg);
+ tcase_add_test(tc, test_trace_short_space);
+ tcase_add_test(tc, test_trace_short_equals);
+ tcase_add_test(tc, test_trace_long_noarg);
+ tcase_add_test(tc, test_trace_long_followarg);
+ tcase_add_test(tc, test_trace_long_space);
+ tcase_add_test(tc, test_trace_long_equals);
+ suite_add_tcase(s, tc);
+
+ tc = tcase_create("event_configuration");
+ tcase_add_test(tc, test_event_short);
+ tcase_add_test(tc, test_event_long);
+ tcase_add_test(tc, test_filter);
+ tcase_add_test(tc, test_trigger);
+ suite_add_tcase(s, tc);
+
+ tc = tcase_create("cpu_configuration");
+ tcase_add_test(tc, test_cpus_short);
+ tcase_add_test(tc, test_cpus_long);
+ tcase_add_test(tc, test_housekeeping_short);
+ tcase_add_test(tc, test_housekeeping_long);
+ suite_add_tcase(s, tc);
+
+ tc = tcase_create("thread_configuration");
+ tcase_add_test(tc, test_cgroup_short_noarg);
+ tcase_add_test(tc, test_cgroup_short_space);
+ tcase_add_test(tc, test_cgroup_short_equals);
+ tcase_add_test(tc, test_cgroup_long_noarg);
+ tcase_add_test(tc, test_cgroup_long_space);
+ tcase_add_test(tc, test_cgroup_long_equals);
+ tcase_add_test(tc, test_kernel_threads_short);
+ tcase_add_test(tc, test_kernel_threads_long);
+ tcase_add_test(tc, test_priority_short);
+ tcase_add_test(tc, test_priority_long);
+ tcase_add_test(tc, test_user_load_short);
+ tcase_add_test(tc, test_user_load_long);
+ tcase_add_test(tc, test_user_threads_short);
+ tcase_add_test(tc, test_user_threads_long);
+ tcase_add_test(tc, test_aligned_short);
+ tcase_add_test(tc, test_aligned_long);
+ suite_add_tcase(s, tc);
+
+ tc = tcase_create("output");
+ tcase_add_test(tc, test_nano_short);
+ tcase_add_test(tc, test_nano_long);
+ tcase_add_test(tc, test_quiet_short);
+ tcase_add_test(tc, test_quiet_long);
+ suite_add_tcase(s, tc);
+
+ tc = tcase_create("system_tuning");
+ tcase_add_test(tc, test_deepest_idle_state);
+ tcase_add_test(tc, test_dma_latency);
+ tcase_add_test(tc, test_trace_buffer_size);
+ tcase_add_test(tc, test_warm_up);
+ suite_add_tcase(s, tc);
+
+ tc = tcase_create("aa_actions");
+ tcase_add_test(tc, test_auto);
+ tcase_add_test(tc, test_aa_only);
+ tcase_add_test(tc, test_bpf_action);
+ tcase_add_test(tc, test_dump_tasks);
+ tcase_add_test(tc, test_no_aa);
+ tcase_add_test(tc, test_on_end);
+ tcase_add_test(tc, test_on_threshold);
+ tcase_add_test(tc, test_stack_format);
+ suite_add_tcase(s, tc);
+
+ tc = tcase_create("general");
+ tcase_add_test(tc, test_debug_short);
+ tcase_add_test(tc, test_debug_long);
+ tcase_add_test(tc, test_duration_short);
+ tcase_add_test(tc, test_duration_long);
+ suite_add_tcase(s, tc);
+
+ return s;
+}
diff --git a/tools/tracing/rtla/tests/unit/unit_tests.c b/tools/tracing/rtla/tests/unit/unit_tests.c
new file mode 100644
index 000000000000..75ca813f81ca
--- /dev/null
+++ b/tools/tracing/rtla/tests/unit/unit_tests.c
@@ -0,0 +1,39 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#define _GNU_SOURCE
+#include <check.h>
+#include <stdbool.h>
+
+#include "../../src/utils.h"
+#include "../../src/cli.h"
+
+Suite *utils_suite(void);
+Suite *actions_suite(void);
+Suite *osnoise_top_cli_suite(void);
+Suite *osnoise_hist_cli_suite(void);
+Suite *timerlat_top_cli_suite(void);
+Suite *timerlat_hist_cli_suite(void);
+Suite *cli_opt_callback_suite(void);
+
+int main(int argc, char *argv[])
+{
+ int num_failed;
+ SRunner *sr;
+
+ in_unit_test = true;
+
+ sr = srunner_create(utils_suite());
+ srunner_add_suite(sr, cli_opt_callback_suite());
+ srunner_add_suite(sr, actions_suite());
+ srunner_add_suite(sr, osnoise_top_cli_suite());
+ srunner_add_suite(sr, osnoise_hist_cli_suite());
+ srunner_add_suite(sr, timerlat_top_cli_suite());
+ srunner_add_suite(sr, timerlat_hist_cli_suite());
+
+ srunner_run_all(sr, CK_VERBOSE);
+ num_failed = srunner_ntests_failed(sr);
+
+ srunner_free(sr);
+
+ return (num_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
+}
diff --git a/tools/tracing/rtla/tests/unit/utils.c b/tools/tracing/rtla/tests/unit/utils.c
new file mode 100644
index 000000000000..ce53cab49457
--- /dev/null
+++ b/tools/tracing/rtla/tests/unit/utils.c
@@ -0,0 +1,106 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#define _GNU_SOURCE
+#include <check.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sched.h>
+#include <limits.h>
+#include <unistd.h>
+#include <sys/sysinfo.h>
+
+#include "../../src/utils.h"
+
+extern int nr_cpus;
+
+START_TEST(test_strtoi)
+{
+ int result;
+ char buf[64];
+
+ ck_assert_int_eq(strtoi("123", &result), 0);
+ ck_assert_int_eq(result, 123);
+ ck_assert_int_eq(strtoi(" -456", &result), 0);
+ ck_assert_int_eq(result, -456);
+
+ snprintf(buf, sizeof(buf), "%d", INT_MAX);
+ ck_assert_int_eq(strtoi(buf, &result), 0);
+ snprintf(buf, sizeof(buf), "%ld", (long)INT_MAX + 1);
+ ck_assert_int_eq(strtoi(buf, &result), -1);
+
+ ck_assert_int_eq(strtoi("", &result), -1);
+ ck_assert_int_eq(strtoi("123abc", &result), -1);
+ ck_assert_int_eq(strtoi("123 ", &result), -1);
+}
+END_TEST
+
+START_TEST(test_parse_cpu_set)
+{
+ cpu_set_t set;
+
+ nr_cpus = 8;
+ ck_assert_int_eq(parse_cpu_set("0", &set), 0);
+ ck_assert(CPU_ISSET(0, &set));
+ ck_assert(!CPU_ISSET(1, &set));
+
+ ck_assert_int_eq(parse_cpu_set("0,2", &set), 0);
+ ck_assert(CPU_ISSET(0, &set));
+ ck_assert(CPU_ISSET(2, &set));
+
+ ck_assert_int_eq(parse_cpu_set("0-3", &set), 0);
+ ck_assert(CPU_ISSET(0, &set));
+ ck_assert(CPU_ISSET(1, &set));
+ ck_assert(CPU_ISSET(2, &set));
+ ck_assert(CPU_ISSET(3, &set));
+
+ ck_assert_int_eq(parse_cpu_set("1-3,5", &set), 0);
+ ck_assert(!CPU_ISSET(0, &set));
+ ck_assert(CPU_ISSET(1, &set));
+ ck_assert(CPU_ISSET(2, &set));
+ ck_assert(CPU_ISSET(3, &set));
+ ck_assert(!CPU_ISSET(4, &set));
+ ck_assert(CPU_ISSET(5, &set));
+
+ ck_assert_int_eq(parse_cpu_set("-1", &set), 1);
+ ck_assert_int_eq(parse_cpu_set("abc", &set), 1);
+ ck_assert_int_eq(parse_cpu_set("9999", &set), 1);
+}
+END_TEST
+
+START_TEST(test_parse_prio)
+{
+ struct sched_attr attr;
+
+ ck_assert_int_eq(parse_prio("f:50", &attr), 0);
+ ck_assert_uint_eq(attr.sched_policy, SCHED_FIFO);
+ ck_assert_uint_eq(attr.sched_priority, 50U);
+
+ ck_assert_int_eq(parse_prio("r:30", &attr), 0);
+ ck_assert_uint_eq(attr.sched_policy, SCHED_RR);
+
+ ck_assert_int_eq(parse_prio("o:0", &attr), 0);
+ ck_assert_uint_eq(attr.sched_policy, SCHED_OTHER);
+ ck_assert_int_eq(attr.sched_nice, 0);
+
+ ck_assert_int_eq(parse_prio("d:10ms:100ms", &attr), 0);
+ ck_assert_uint_eq(attr.sched_policy, 6U);
+
+ ck_assert_int_eq(parse_prio("f:999", &attr), -1);
+ ck_assert_int_eq(parse_prio("o:-20", &attr), -1);
+ ck_assert_int_eq(parse_prio("d:100ms:10ms", &attr), -1);
+ ck_assert_int_eq(parse_prio("x:50", &attr), -1);
+}
+END_TEST
+
+Suite *utils_suite(void)
+{
+ Suite *s = suite_create("utils");
+ TCase *tc = tcase_create("core");
+
+ tcase_add_test(tc, test_strtoi);
+ tcase_add_test(tc, test_parse_cpu_set);
+ tcase_add_test(tc, test_parse_prio);
+
+ suite_add_tcase(s, tc);
+ return s;
+}