Merge tag 'trace-tools-v7.2' of git://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-trace

Pull RTLA tool updates from Steven Rostedt:

 - Fix discrepancy in --dump-tasks option

   Due to a mistake, rtla-timerlat-hist used the CLI syntax
   "--dump-task" instead of the documented "--dump-tasks". Change the
   option to match both documentation and the other timerlat tool,
   rtla-timerlat-top.

 - Extend coverage of runtime tests

   Cover both top and hist tools in all applicable test cases, add tests
   for a few uncovered options, and extend checks for some existing
   tests.

 - Add unit tests for actions

   rtla's actions feature is implemented in its source file and contains
   non-trivial parsing logic. Cover it with unit tests.

 - Stop record trace on interrupt

   Fix a bug where an interval exists after receiving a signal in which
   the main instance is stopped but the record instance is not, leading
   to discrepancies in reported results and sometimes rtla hanging.

 - Restore continue flag in actions_perform()

   Fix a bug where rtla always continues tracing after hitting a
   threshold even if the continue action was triggered just once, and
   add tests verifying that the flag is reset properly.

 - Migrate command line interface to libsubcmd

   Replace rtla's argument parsing using getopt_long() with libsubcmd,
   used by perf and objtool, to reuse existing code and auto-generate
   better help messages. Extensive unit tests are included to detect
   regressions.

 - Add -A/--aligned option to timerlat tools

   Add an option to align timerlat threads, based on the recently
   introduced TIMERLAT_ALIGN option of the timerlat tracer, together
   with unit tests and documentation.

 - Document tests in README

   Document how to run unit and runtime tests in rtla's README.txt,
   including the dependencies needed to run them.

* tag 'trace-tools-v7.2' of git://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-trace: (26 commits)
  rtla: Document tests in README
  Documentation/rtla: Add -A/--aligned option
  rtla/tests: Add unit tests for -A/--aligned option
  rtla/timerlat: Add -A/--aligned CLI option
  rtla/tests: Add unit tests for CLI option callbacks
  rtla/tests: Add unit tests for _parse_args() functions
  rtla: Parse cmdline using libsubcmd
  tools subcmd: allow parsing distinct --opt and --no-opt
  tools subcmd: support optarg as separate argument
  rtla: Add libsubcmd dependency
  rtla/tests: Add runtime tests for restoring continue flag
  rtla/tests: Run runtime tests in temporary directory
  rtla/tests: Add unit test for restoring continue flag
  rtla/actions: Restore continue flag in actions_perform()
  rtla: Stop the record trace on interrupt
  rtla/tests: Add unit tests for actions module
  rtla/tests: Add runtime tests for -C/--cgroup
  rtla/tests: Add runtime test for -k and -u options
  rtla/tests: Add runtime test for -H/--house-keeping
  rtla/tests: Cover all hist options in runtime tests
  ...
diff --git a/Documentation/tools/rtla/common_appendix.txt b/Documentation/tools/rtla/common_appendix.txt
index 8c90a025..68cb158 100644
--- a/Documentation/tools/rtla/common_appendix.txt
+++ b/Documentation/tools/rtla/common_appendix.txt
@@ -26,9 +26,10 @@
 
 ::
 
- 0  Passed: the test did not hit the stop tracing condition
- 1  Error: invalid argument
- 2  Failed: the test hit the stop tracing condition
+ 0    Passed: the test did not hit the stop tracing condition
+ 1    Error: invalid argument
+ 2    Failed: the test hit the stop tracing condition
+ 129  Help: either user requested help or incorrect option was specified
 
 REPORTING BUGS
 ==============
diff --git a/Documentation/tools/rtla/common_timerlat_options.txt b/Documentation/tools/rtla/common_timerlat_options.txt
index ab159b2..100840f 100644
--- a/Documentation/tools/rtla/common_timerlat_options.txt
+++ b/Documentation/tools/rtla/common_timerlat_options.txt
@@ -95,3 +95,14 @@
         * **full**        Print the entire stack trace, including unknown addresses.
 
         For unknown addresses, the raw pointer is printed.
+
+**-A**, **--aligned** *us*
+
+        Align wake-up of timerlat threads to a set offset in microseconds.
+
+        The alignment will be applied when the threads wake up at the start of tracing while
+        the timer for the first cycle is armed. Each thread sets its timer to the wake-up time
+        of the previous thread plus the alignment.
+
+        This option may be used with any non-negative argument, including zero, which will
+        align threads so that they wake up all at the same time.
diff --git a/tools/lib/subcmd/parse-options.c b/tools/lib/subcmd/parse-options.c
index 555d617..e83200e 100644
--- a/tools/lib/subcmd/parse-options.c
+++ b/tools/lib/subcmd/parse-options.c
@@ -72,6 +72,7 @@ static int get_value(struct parse_opt_ctx_t *p,
 	const char *s, *arg = NULL;
 	const int unset = flags & OPT_UNSET;
 	int err;
+	bool force_defval = false;
 
 	if (unset && p->opt)
 		return opterror(opt, "takes no value", flags);
@@ -123,6 +124,42 @@ static int get_value(struct parse_opt_ctx_t *p,
 		}
 	}
 
+	if (opt->flags & PARSE_OPT_OPTARG && !p->opt) {
+		if (!(p->flags & PARSE_OPT_OPTARG_ALLOW_NEXT)) {
+			/*
+			 * If the option has an optional argument, and the argument is not
+			 * provided in the option itself, do not attempt to get it from
+			 * the next argument, unless PARSE_OPT_OPTARG_ALLOW_NEXT is set.
+			 *
+			 * This prevents a non-option argument from being interpreted as an
+			 * optional argument of a preceding option, for example:
+			 *
+			 * $ cmd --opt val
+			 * -> is "val" argument of "--opt" or a separate non-option
+			 * argument?
+			 *
+			 * With PARSE_OPT_OPTARG_ALLOW_NEXT, "val" is interpreted as
+			 * the argument of "--opt", i.e. the same as "--opt=val".
+			 * Without PARSE_OPT_OPTARG_ALLOW_NEXT, --opt is interpreted
+			 * as having the default value, and "val" as a separate non-option
+			 * argument.
+			 *
+			 * PARSE_OPT_OPTARG_ALLOW_NEXT is useful for commands that take no
+			 * non-option arguments and want to allow more flexibility in
+			 * optional argument passing.
+			 */
+			force_defval = true;
+		}
+
+		if (p->argc <= 1 || p->argv[1][0] == '-') {
+			/*
+			 * If next argument is an option or does not exist,
+			 * use the default value.
+			 */
+			force_defval = true;
+		}
+	}
+
 	if (opt->flags & PARSE_OPT_NOBUILD) {
 		char reason[128];
 		bool noarg = false;
@@ -148,7 +185,7 @@ static int get_value(struct parse_opt_ctx_t *p,
 			noarg = true;
 		if (opt->flags & PARSE_OPT_NOARG)
 			noarg = true;
-		if (opt->flags & PARSE_OPT_OPTARG && !p->opt)
+		if (force_defval)
 			noarg = true;
 
 		switch (opt->type) {
@@ -212,7 +249,7 @@ static int get_value(struct parse_opt_ctx_t *p,
 		err = 0;
 		if (unset)
 			*(const char **)opt->value = NULL;
-		else if (opt->flags & PARSE_OPT_OPTARG && !p->opt)
+		else if (force_defval)
 			*(const char **)opt->value = (const char *)opt->defval;
 		else
 			err = get_arg(p, opt, flags, (const char **)opt->value);
@@ -244,7 +281,7 @@ static int get_value(struct parse_opt_ctx_t *p,
 			return (*opt->callback)(opt, NULL, 1) ? (-1) : 0;
 		if (opt->flags & PARSE_OPT_NOARG)
 			return (*opt->callback)(opt, NULL, 0) ? (-1) : 0;
-		if (opt->flags & PARSE_OPT_OPTARG && !p->opt)
+		if (force_defval)
 			return (*opt->callback)(opt, NULL, 0) ? (-1) : 0;
 		if (get_arg(p, opt, flags, &arg))
 			return -1;
@@ -255,7 +292,7 @@ static int get_value(struct parse_opt_ctx_t *p,
 			*(int *)opt->value = 0;
 			return 0;
 		}
-		if (opt->flags & PARSE_OPT_OPTARG && !p->opt) {
+		if (force_defval) {
 			*(int *)opt->value = opt->defval;
 			return 0;
 		}
@@ -271,7 +308,7 @@ static int get_value(struct parse_opt_ctx_t *p,
 			*(unsigned int *)opt->value = 0;
 			return 0;
 		}
-		if (opt->flags & PARSE_OPT_OPTARG && !p->opt) {
+		if (force_defval) {
 			*(unsigned int *)opt->value = opt->defval;
 			return 0;
 		}
@@ -289,7 +326,7 @@ static int get_value(struct parse_opt_ctx_t *p,
 			*(long *)opt->value = 0;
 			return 0;
 		}
-		if (opt->flags & PARSE_OPT_OPTARG && !p->opt) {
+		if (force_defval) {
 			*(long *)opt->value = opt->defval;
 			return 0;
 		}
@@ -305,7 +342,7 @@ static int get_value(struct parse_opt_ctx_t *p,
 			*(unsigned long *)opt->value = 0;
 			return 0;
 		}
-		if (opt->flags & PARSE_OPT_OPTARG && !p->opt) {
+		if (force_defval) {
 			*(unsigned long *)opt->value = opt->defval;
 			return 0;
 		}
@@ -321,7 +358,7 @@ static int get_value(struct parse_opt_ctx_t *p,
 			*(u64 *)opt->value = 0;
 			return 0;
 		}
-		if (opt->flags & PARSE_OPT_OPTARG && !p->opt) {
+		if (force_defval) {
 			*(u64 *)opt->value = opt->defval;
 			return 0;
 		}
@@ -390,7 +427,8 @@ static int parse_long_opt(struct parse_opt_ctx_t *p, const char *arg,
 			return 0;
 		}
 		if (!rest) {
-			if (strstarts(options->long_name, "no-")) {
+			if (strstarts(options->long_name, "no-") &&
+			    !(options->flags & PARSE_OPT_NOAUTONEG)) {
 				/*
 				 * The long name itself starts with "no-", so
 				 * accept the option without "no-" so that users
@@ -428,12 +466,12 @@ static int parse_long_opt(struct parse_opt_ctx_t *p, const char *arg,
 				continue;
 			}
 			/* negated and abbreviated very much? */
-			if (strstarts("no-", arg)) {
+			if (strstarts("no-", arg) && !(options->flags & PARSE_OPT_NOAUTONEG)) {
 				flags |= OPT_UNSET;
 				goto is_abbreviated;
 			}
 			/* negated? */
-			if (strncmp(arg, "no-", 3))
+			if (strncmp(arg, "no-", 3) || (options->flags & PARSE_OPT_NOAUTONEG))
 				continue;
 			flags |= OPT_UNSET;
 			rest = skip_prefix(arg + 3, options->long_name);
@@ -982,7 +1020,8 @@ int parse_options_usage(const char * const *usagestr,
 		if (strstarts(opts->long_name, optstr))
 			print_option_help(opts, 0);
 		if (strstarts("no-", optstr) &&
-		    strstarts(opts->long_name, optstr + 3))
+		    strstarts(opts->long_name, optstr + 3) &&
+			!(opts->flags & PARSE_OPT_NOAUTONEG))
 			print_option_help(opts, 0);
 	}
 
diff --git a/tools/lib/subcmd/parse-options.h b/tools/lib/subcmd/parse-options.h
index 8e91473..38df5fd2 100644
--- a/tools/lib/subcmd/parse-options.h
+++ b/tools/lib/subcmd/parse-options.h
@@ -33,6 +33,7 @@ enum parse_opt_flags {
 	PARSE_OPT_KEEP_ARGV0 = 4,
 	PARSE_OPT_KEEP_UNKNOWN = 8,
 	PARSE_OPT_NO_INTERNAL_HELP = 16,
+	PARSE_OPT_OPTARG_ALLOW_NEXT = 32,
 };
 
 enum parse_opt_option_flags {
@@ -46,6 +47,7 @@ enum parse_opt_option_flags {
 	PARSE_OPT_NOEMPTY  = 128,
 	PARSE_OPT_NOBUILD  = 256,
 	PARSE_OPT_CANSKIP  = 512,
+	PARSE_OPT_NOAUTONEG = 1024,
 };
 
 struct option;
@@ -148,6 +150,8 @@ struct option {
 	{ .type = OPTION_CALLBACK, .short_name = (s), .long_name = (l), .value = (v), .argh = "time", .help = (h), .callback = parse_opt_approxidate_cb }
 #define OPT_CALLBACK(s, l, v, a, h, f) \
 	{ .type = OPTION_CALLBACK, .short_name = (s), .long_name = (l), .value = (v), .argh = (a), .help = (h), .callback = (f) }
+#define OPT_CALLBACK_FLAG(s, l, v, a, h, f, fl) \
+	{ .type = OPTION_CALLBACK, .short_name = (s), .long_name = (l), .value = (v), .argh = (a), .help = (h), .callback = (f), .flags = (fl) }
 #define OPT_CALLBACK_SET(s, l, v, os, a, h, f) \
 	{ .type = OPTION_CALLBACK, .short_name = (s), .long_name = (l), .value = (v), .argh = (a), .help = (h), .callback = (f), .set = check_vtype(os, bool *)}
 #define OPT_CALLBACK_NOOPT(s, l, v, a, h, f) \
diff --git a/tools/tracing/rtla/.gitignore b/tools/tracing/rtla/.gitignore
index 4d39d64..c7b4bf1 100644
--- a/tools/tracing/rtla/.gitignore
+++ b/tools/tracing/rtla/.gitignore
@@ -1,6 +1,7 @@
 # SPDX-License-Identifier: GPL-2.0-only
 rtla
 rtla-static
+unit_tests
 fixdep
 feature
 FEATURE-DUMP
@@ -9,3 +10,5 @@
 osnoise_irq_noise_hist.txt
 osnoise_trace.txt
 timerlat_trace.txt
+libsubcmd/
+lib/
diff --git a/tools/tracing/rtla/Makefile b/tools/tracing/rtla/Makefile
index 45690ee..60a1025 100644
--- a/tools/tracing/rtla/Makefile
+++ b/tools/tracing/rtla/Makefile
@@ -27,6 +27,30 @@
 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_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/
 
@@ -66,7 +90,7 @@
   include Makefile.config
 endif
 
-CFLAGS		+= $(INCLUDES) $(LIB_INCLUDES)
+CFLAGS		+= $(INCLUDES) $(LIB_INCLUDES) $(TOOLS_INCLUDES) -I$(LIBSUBCMD_INCLUDES)
 
 export CFLAGS OUTPUT srctree
 
@@ -93,20 +117,48 @@
 	$(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_STRING) $(LIB_STR_ERROR_R)
+	$(QUIET_LINK)$(CC) $(LDFLAGS) -o $(RTLA) $(RTLA_IN) $(LIBSUBCMD) $(LIB_STRING) $(LIB_STR_ERROR_R) $(EXTLIBS)
 
-static: $(RTLA_IN)
+static: $(RTLA_IN) $(LIBSUBCMD) $(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_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 $@ $<
+
+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-*
diff --git a/tools/tracing/rtla/README.txt b/tools/tracing/rtla/README.txt
index a9faee4d..13b4a79 100644
--- a/tools/tracing/rtla/README.txt
+++ b/tools/tracing/rtla/README.txt
@@ -42,4 +42,34 @@
   $ 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/src/Build b/tools/tracing/rtla/src/Build
index 329e24a..a1f3ab9 100644
--- a/tools/tracing/rtla/src/Build
+++ b/tools/tracing/rtla/src/Build
@@ -11,4 +11,4 @@
 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 b0d68b5..bf13d9d 100644
--- a/tools/tracing/rtla/src/actions.c
+++ b/tools/tracing/rtla/src/actions.c
@@ -247,6 +247,8 @@ actions_perform(struct actions *self)
 	int pid, retval;
 	const struct action *action;
 
+	self->continue_flag = false;
+
 	for_each_action(self, action) {
 		switch (action->type) {
 		case ACTION_TRACE_OUTPUT:
diff --git a/tools/tracing/rtla/src/cli.c b/tools/tracing/rtla/src/cli.c
new file mode 100644
index 0000000..c5279c9
--- /dev/null
+++ b/tools/tracing/rtla/src/cli.c
@@ -0,0 +1,539 @@
+// 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);
+
+	/* display data in microseconds */
+	params->common.output_divisor = 1000;
+	params->common.hist.bucket_size = 1;
+	params->common.hist.entries = 256;
+
+	argc = parse_options(argc, (const char **)argv,
+			     osnoise_hist_options, osnoise_hist_usage,
+			     common_parse_options_flags);
+	if (argc < 0)
+		return NULL;
+
+	if (cb_data.trace_output)
+		actions_add_trace_output(&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);
+
+	/* disabled by default */
+	params->dma_latency = -1;
+	params->deepest_idle_state = -2;
+
+	/* display data in microseconds */
+	params->common.output_divisor = 1000;
+
+	/* default to BPF mode */
+	params->mode = TRACING_MODE_BPF;
+
+	/* default to truncate stack format */
+	params->stack_format = STACK_FORMAT_TRUNCATE;
+
+	argc = parse_options(argc, (const char **)argv,
+			     timerlat_top_options, timerlat_top_usage,
+			     common_parse_options_flags);
+	if (argc < 0)
+		return NULL;
+
+	if (cb_data.trace_output)
+		actions_add_trace_output(&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);
+
+	/* disabled by default */
+	params->dma_latency = -1;
+
+	/* disabled by default */
+	params->deepest_idle_state = -2;
+
+	/* display data in microseconds */
+	params->common.output_divisor = 1000;
+	params->common.hist.bucket_size = 1;
+	params->common.hist.entries = 256;
+
+	/* default to BPF mode */
+	params->mode = TRACING_MODE_BPF;
+
+	/* default to truncate stack format */
+	params->stack_format = STACK_FORMAT_TRUNCATE;
+
+	argc = parse_options(argc, (const char **)argv,
+			     timerlat_hist_options, timerlat_hist_usage,
+			     common_parse_options_flags);
+	if (argc < 0)
+		return NULL;
+
+	if (cb_data.trace_output)
+		actions_add_trace_output(&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 0000000..633a232
--- /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 0000000..3c939de
--- /dev/null
+++ b/tools/tracing/rtla/src/cli_p.h
@@ -0,0 +1,687 @@
+/* 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 <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;
+};
+
+/*
+ * 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 OPT_CALLBACK(0, "trace-buffer-size", \
+	&params->common.buffer_size, "kB", \
+	"set the per-cpu trace buffer size in kB", \
+	opt_int_callback)
+
+#define RTLA_OPT_WARM_UP OPT_CALLBACK(0, "warm-up", &params->common.warmup, "s", \
+	"let the workload run for s seconds before collecting data", \
+	opt_int_callback)
+
+#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")
+
+/*
+ * 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 || !arg)
+		return -1;
+
+	*value = get_llong_from_str((char *)arg);
+	return 0;
+}
+
+static int opt_int_callback(const struct option *opt, const char *arg, int unset)
+{
+	int *value = opt->value;
+
+	if (unset || !arg)
+		return -1;
+
+	if (strtoi(arg, 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 || !arg)
+		return -1;
+
+	retval = parse_cpu_set((char *)arg, &params->monitored_cpus);
+	if (retval)
+		fatal("Invalid -c cpu list");
+	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)
+		return -1;
+
+	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 || !arg)
+		return -1;
+
+	params->duration = parse_seconds_duration((char *)arg);
+	if (!params->duration)
+		fatal("Invalid -d duration");
+
+	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 || !arg)
+		return -1;
+
+	params->hk_cpus = 1;
+	retval = parse_cpu_set((char *)arg, &params->hk_cpu_set);
+	if (retval)
+		fatal("Error parsing house keeping CPUs");
+
+	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 || !arg)
+		return -1;
+
+	retval = parse_prio((char *)arg, &params->sched_param);
+	if (retval == -1)
+		fatal("Invalid -P priority");
+	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)
+		fatal("--trigger requires a previous -e");
+
+	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)
+		fatal("--filter requires a previous -e");
+
+	trace_event_add_filter(*events, (char *)arg);
+
+	return 0;
+}
+
+/*
+ * Macros for command line options specific to osnoise
+ */
+#define OSNOISE_OPT_PERIOD OPT_CALLBACK('p', "period", &params->period, "us", \
+	"osnoise period in us", \
+	opt_osnoise_period_cb)
+
+#define OSNOISE_OPT_RUNTIME OPT_CALLBACK('r', "runtime", &params->runtime, "us", \
+	"osnoise runtime in us", \
+	opt_osnoise_runtime_cb)
+
+#define OSNOISE_OPT_THRESHOLD OPT_CALLBACK('T', "threshold", &params->threshold, "us", \
+	"the minimum delta to be considered a noise", \
+	opt_llong_callback)
+
+/*
+ * 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 || !arg)
+		return -1;
+
+	auto_thresh = get_llong_from_str((char *)arg);
+	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_period_cb(const struct option *opt, const char *arg, int unset)
+{
+	unsigned long long *period = opt->value;
+
+	if (unset || !arg)
+		return -1;
+
+	*period = get_llong_from_str((char *)arg);
+	if (*period > 10000000)
+		fatal("Period longer than 10 s");
+
+	return 0;
+}
+
+static int opt_osnoise_runtime_cb(const struct option *opt, const char *arg, int unset)
+{
+	unsigned long long *runtime = opt->value;
+
+	if (unset || !arg)
+		return -1;
+
+	*runtime = get_llong_from_str((char *)arg);
+	if (*runtime < 100)
+		fatal("Runtime shorter than 100 us");
+
+	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)
+		return -1;
+
+	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)
+		fatal("Invalid action %s", arg);
+
+	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)
+		fatal("Invalid action %s", arg);
+
+	return 0;
+}
+
+/*
+ * Macros for command line options specific to timerlat
+ */
+#define TIMERLAT_OPT_PERIOD OPT_CALLBACK('p', "period", &params->timerlat_period_us, "us", \
+	"timerlat period in us", \
+	opt_timerlat_period_cb)
+
+#define TIMERLAT_OPT_STACK OPT_CALLBACK('s', "stack", &params->print_stack, "us", \
+	"save the stack trace at the IRQ if a thread latency is higher than the argument in us", \
+	opt_llong_callback)
+
+#define TIMERLAT_OPT_NANO OPT_CALLBACK_NOOPT('n', "nano", params, NULL, \
+	"display data in nanoseconds", \
+	opt_nano_cb)
+
+#define TIMERLAT_OPT_DMA_LATENCY OPT_CALLBACK(0, "dma-latency", &params->dma_latency, "us", \
+	"set /dev/cpu_dma_latency latency <us> to reduce exit from idle latency", \
+	opt_dma_latency_cb)
+
+#define TIMERLAT_OPT_DEEPEST_IDLE_STATE OPT_CALLBACK(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", \
+	opt_int_callback)
+
+#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 OPT_CALLBACK('A', "aligned", params, "us", \
+	"align thread wakeups to a specific offset", \
+	opt_timerlat_align_cb)
+
+/*
+ * Callback functions for command line options for timerlat tools
+ */
+
+static int opt_timerlat_period_cb(const struct option *opt, const char *arg, int unset)
+{
+	long long *period = opt->value;
+
+	if (unset || !arg)
+		return -1;
+
+	*period = get_llong_from_str((char *)arg);
+	if (*period > 1000000)
+		fatal("Period longer than 1 s");
+
+	return 0;
+}
+
+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 || !arg)
+		return -1;
+
+	auto_thresh = get_llong_from_str((char *)arg);
+	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_dma_latency_cb(const struct option *opt, const char *arg, int unset)
+{
+	int *dma_latency = opt->value;
+	int retval;
+
+	if (unset || !arg)
+		return -1;
+
+	retval = strtoi((char *)arg, dma_latency);
+	if (retval)
+		fatal("Invalid -dma-latency %s", arg);
+	if (*dma_latency < 0 || *dma_latency > 10000)
+		fatal("--dma-latency needs to be >= 0 and <= 10000");
+
+	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 || !arg)
+		return -1;
+
+	auto_thresh = get_llong_from_str((char *)arg);
+	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)
+		return -1;
+
+	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)
+		fatal("Invalid action %s", arg);
+
+	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)
+		fatal("Invalid action %s", arg);
+
+	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)
+		return -1;
+
+	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)
+		return -1;
+
+	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 || !arg)
+		return -1;
+
+	*format = parse_stack_format((char *)arg);
+
+	if (*format == -1)
+		fatal("Invalid --stack-format option");
+
+	return 0;
+}
+
+static int opt_timerlat_align_cb(const struct option *opt, const char *arg, int unset)
+{
+	struct timerlat_params *params = opt->value;
+
+	if (unset || !arg)
+		return -1;
+
+	params->timerlat_align = true;
+	params->timerlat_align_us = get_llong_from_str((char *)arg);
+
+	return 0;
+}
+
+/*
+ * Macros for command line options specific to histogram-based tools
+ */
+
+#define HIST_OPT_BUCKET_SIZE OPT_CALLBACK('b', "bucket-size", \
+	&params->common.hist.bucket_size, "N", \
+	"set the histogram bucket size (default 1)", \
+	opt_bucket_size_cb)
+
+#define HIST_OPT_ENTRIES OPT_CALLBACK('E', "entries", &params->common.hist.entries, "N", \
+	"set the number of entries of the histogram (default 256)", \
+	opt_entries_cb)
+
+#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")
+
+/* Histogram-specific callbacks */
+
+static int opt_bucket_size_cb(const struct option *opt, const char *arg, int unset)
+{
+	int *bucket_size = opt->value;
+
+	if (unset || !arg)
+		return -1;
+
+	*bucket_size = get_llong_from_str((char *)arg);
+	if (*bucket_size == 0 || *bucket_size >= 1000000)
+		fatal("Bucket size needs to be > 0 and <= 1000000");
+
+	return 0;
+}
+
+static int opt_entries_cb(const struct option *opt, const char *arg, int unset)
+{
+	int *entries = opt->value;
+
+	if (unset || !arg)
+		return -1;
+
+	*entries = get_llong_from_str((char *)arg);
+	if (*entries < 10 || *entries > 9999999)
+		fatal("Entries must be > 10 and < 10000000");
+
+	return 0;
+}
diff --git a/tools/tracing/rtla/src/common.c b/tools/tracing/rtla/src/common.c
index bc9d01d..d0a8a6e 100644
--- a/tools/tracing/rtla/src/common.c
+++ b/tools/tracing/rtla/src/common.c
@@ -5,12 +5,11 @@
 #include <signal.h>
 #include <stdlib.h>
 #include <string.h>
-#include <getopt.h>
 #include <sys/sysinfo.h>
 
 #include "common.h"
 
-struct trace_instance *trace_inst;
+struct osnoise_tool *trace_tool;
 volatile int stop_tracing;
 int nr_cpus;
 
@@ -21,12 +20,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);
+	}
 }
 
 /*
@@ -54,96 +57,6 @@ static void unset_signals(struct common_params *params)
 }
 
 /*
- * getopt_auto - auto-generates optstring from long_options
- */
-int getopt_auto(int argc, char **argv, const struct option *long_opts)
-{
-	char opts[256];
-	int n = 0;
-
-	for (int i = 0; long_opts[i].name; i++) {
-		if (long_opts[i].val < 32 || long_opts[i].val > 127)
-			continue;
-
-		if (n + 4 >= sizeof(opts))
-			fatal("optstring buffer overflow");
-
-		opts[n++] = long_opts[i].val;
-
-		if (long_opts[i].has_arg == required_argument)
-			opts[n++] = ':';
-		else if (long_opts[i].has_arg == optional_argument) {
-			opts[n++] = ':';
-			opts[n++] = ':';
-		}
-	}
-
-	opts[n] = '\0';
-
-	return getopt_long(argc, argv, opts, long_opts, NULL);
-}
-
-/*
- * set_common_option - set common options
- *
- * @c: option character
- * @argc: argument count
- * @argv: argument vector
- * @common: common parameters structure
- *
- * Parse command line options that are common to all rtla tools.
- *
- * Returns: 1 if the option was set, 0 otherwise.
- */
-int set_common_option(int c, int argc, char **argv, struct common_params *common)
-{
-	struct trace_events *tevent;
-
-	switch (c) {
-	case 'c':
-		if (parse_cpu_set(optarg, &common->monitored_cpus))
-			fatal("Invalid -c cpu list");
-		common->cpus = optarg;
-		break;
-	case 'C':
-		common->cgroup = 1;
-		common->cgroup_name = parse_optional_arg(argc, argv);
-		break;
-	case 'D':
-		config_debug = 1;
-		break;
-	case 'd':
-		common->duration = parse_seconds_duration(optarg);
-		if (!common->duration)
-			fatal("Invalid -d duration");
-		break;
-	case 'e':
-		tevent = trace_event_alloc(optarg);
-		if (!tevent)
-			fatal("Error alloc trace event");
-
-		if (common->events)
-			tevent->next = common->events;
-		common->events = tevent;
-		break;
-	case 'H':
-		common->hk_cpus = 1;
-		if (parse_cpu_set(optarg, &common->hk_cpu_set))
-			fatal("Error parsing house keeping CPUs");
-		break;
-	case 'P':
-		if (parse_prio(optarg, &common->sched_param) == -1)
-			fatal("Invalid -P priority");
-		common->set_sched = 1;
-		break;
-	default:
-		return 0;
-	}
-
-	return 1;
-}
-
-/*
  * common_apply_config - apply common configs to the initialized tool
  */
 int
@@ -255,11 +168,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) {
@@ -267,7 +179,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;
diff --git a/tools/tracing/rtla/src/common.h b/tools/tracing/rtla/src/common.h
index 8921807..04b287a 100644
--- a/tools/tracing/rtla/src/common.h
+++ b/tools/tracing/rtla/src/common.h
@@ -1,7 +1,6 @@
 /* SPDX-License-Identifier: GPL-2.0 */
 #pragma once
 
-#include <getopt.h>
 #include "actions.h"
 #include "timerlat_u.h"
 #include "trace.h"
@@ -52,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 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;
 };
@@ -96,12 +102,12 @@ 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;
@@ -177,18 +183,6 @@ 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 getopt_auto(int argc, char **argv, const struct option *long_opts);
-
-#define COMMON_OPTIONS \
-	{"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'},\
-	{"priority",            required_argument,      0, 'P'}
-int set_common_option(int c, int argc, char **argv, struct common_params *common);
-
 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);
diff --git a/tools/tracing/rtla/src/osnoise.c b/tools/tracing/rtla/src/osnoise.c
index 2db3db1..4ff5dad 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 */
@@ -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),
@@ -958,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;
@@ -986,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);
 }
@@ -1171,7 +1322,7 @@ int osnoise_enable(struct osnoise_tool *tool)
 	return 0;
 }
 
-static void osnoise_usage(int err)
+__noreturn static void osnoise_usage(int err)
 {
 	int i;
 
@@ -1209,7 +1360,7 @@ int osnoise_main(int argc, char *argv[])
 	}
 
 	if ((strcmp(argv[1], "-h") == 0) || (strcmp(argv[1], "--help") == 0)) {
-		osnoise_usage(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);
@@ -1223,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 168669a..340ff5a 100644
--- a/tools/tracing/rtla/src/osnoise.h
+++ b/tools/tracing/rtla/src/osnoise.h
@@ -49,6 +49,12 @@ 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);
diff --git a/tools/tracing/rtla/src/osnoise_hist.c b/tools/tracing/rtla/src/osnoise_hist.c
index cb4ce58..dfa91d0 100644
--- a/tools/tracing/rtla/src/osnoise_hist.c
+++ b/tools/tracing/rtla/src/osnoise_hist.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_hist_cpu {
 	int			*samples;
@@ -401,226 +401,6 @@ osnoise_print_stats(struct osnoise_tool *tool)
 }
 
 /*
- * osnoise_hist_usage - prints osnoise hist usage message
- */
-static void osnoise_hist_usage(void)
-{
-	static const char * const msg_start[] = {
-		"[-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]",
-		NULL,
-	};
-
-	static const char * const msg_opts[] = {
-		"	  -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,
-	};
-
-	common_usage("osnoise", "hist", "a per-cpu histogram of the OS noise",
-		     msg_start, msg_opts);
-}
-
-/*
- * 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;
-	int retval;
-	int c;
-	char *trace_output = NULL;
-
-	params = calloc_fatal(1, sizeof(*params));
-
-	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[] = {
-			COMMON_OPTIONS,
-			{"auto",		required_argument,	0, 'a'},
-			{"bucket-size",		required_argument,	0, 'b'},
-			{"entries",		required_argument,	0, 'E'},
-			{"help",		no_argument,		0, 'h'},
-			{"period",		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'},
-			{"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_auto(argc, argv, long_options);
-
-		/* detect the end of the options. */
-		if (c == -1)
-			break;
-
-		if (set_common_option(c, argc, argv, &params->common))
-			continue;
-
-		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 '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 'p':
-			params->period = get_llong_from_str(optarg);
-			if (params->period > 10000000)
-				fatal("Period longer than 10 s");
-			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)
-				trace_event_add_trigger(params->common.events, optarg);
-			else
-				fatal("--trigger requires a previous -e");
-			break;
-		case '5': /* filter */
-			if (params->common.events)
-				trace_event_add_filter(params->common.events, 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
diff --git a/tools/tracing/rtla/src/osnoise_top.c b/tools/tracing/rtla/src/osnoise_top.c
index e65312e..512a629 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;
@@ -246,205 +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)
-{
-	const char *tool, *mode, *desc;
-
-	static const char * const msg_start[] = {
-		"[-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]",
-		NULL,
-	};
-
-	static const char * const msg_opts[] = {
-		"	  -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) {
-		tool = "osnoise";
-		mode = "top";
-		desc = "a per-cpu summary of the OS noise";
-	} else {
-		tool = "hwnoise";
-		mode = "";
-		desc = "a summary of hardware-related noise";
-	}
-
-	common_usage(tool, mode, desc, msg_start, msg_opts);
-}
-
-/*
- * 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;
-	int retval;
-	int c;
-	char *trace_output = NULL;
-
-	params = calloc_fatal(1, sizeof(*params));
-
-	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[] = {
-			COMMON_OPTIONS,
-			{"auto",		required_argument,	0, 'a'},
-			{"help",		no_argument,		0, 'h'},
-			{"period",		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_auto(argc, argv, long_options);
-
-		/* Detect the end of the options. */
-		if (c == -1)
-			break;
-
-		if (set_common_option(c, argc, argv, &params->common))
-			continue;
-
-		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 'h':
-		case '?':
-			osnoise_top_usage(params);
-			break;
-		case 'p':
-			params->period = get_llong_from_str(optarg);
-			if (params->period > 10000000)
-				fatal("Period longer than 10 s");
-			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)
-				trace_event_add_trigger(params->common.events, optarg);
-			else
-				fatal("--trigger requires a previous -e");
-			break;
-		case '1': /* filter */
-			if (params->common.events)
-				trace_event_add_filter(params->common.events, 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
diff --git a/tools/tracing/rtla/src/rtla.c b/tools/tracing/rtla/src/rtla.c
deleted file mode 100644
index 7635c70..0000000
--- 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.c b/tools/tracing/rtla/src/timerlat.c
index f8c0575..169aa9a 100644
--- a/tools/tracing/rtla/src/timerlat.c
+++ b/tools/tracing/rtla/src/timerlat.c
@@ -13,6 +13,8 @@
 #include <stdio.h>
 #include <sched.h>
 
+#include <linux/compiler.h>
+
 #include "timerlat.h"
 #include "timerlat_aa.h"
 #include "timerlat_bpf.h"
@@ -75,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.
@@ -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);
@@ -231,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;
 
@@ -269,7 +289,7 @@ int timerlat_main(int argc, char *argv[])
 	}
 
 	if ((strcmp(argv[1], "-h") == 0) || (strcmp(argv[1], "--help") == 0)) {
-		timerlat_usage(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);
@@ -283,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 364203a..84ec6d7 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,12 +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)
diff --git a/tools/tracing/rtla/src/timerlat_hist.c b/tools/tracing/rtla/src/timerlat_hist.c
index 4b6708e..df7b139 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,7 @@
 #include "timerlat.h"
 #include "timerlat_aa.h"
 #include "timerlat_bpf.h"
+#include "cli.h"
 #include "common.h"
 
 struct timerlat_hist_cpu {
@@ -686,322 +686,6 @@ timerlat_print_stats(struct osnoise_tool *tool)
 }
 
 /*
- * timerlat_hist_usage - prints timerlat top usage message
- */
-static void timerlat_hist_usage(void)
-{
-	static const char * const msg_start[] = {
-		"[-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]",
-		NULL,
-	};
-
-	static const char * const msg_opts[] = {
-		"	  -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",
-		"	     --bpf-action <program>: load and execute BPF program when latency threshold is exceeded",
-		"	     --stack-format <format>: set the stack format (truncate, skip, full)",
-		NULL,
-	};
-
-	common_usage("timerlat", "hist", "a per-cpu histogram of the timer latency",
-		     msg_start, msg_opts);
-}
-
-/*
- * 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;
-	int auto_thresh;
-	int retval;
-	int c;
-	char *trace_output = NULL;
-
-	params = calloc_fatal(1, sizeof(*params));
-
-	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;
-
-	/* default to truncate stack format */
-	params->stack_format = STACK_FORMAT_TRUNCATE;
-
-	while (1) {
-		static struct option long_options[] = {
-			COMMON_OPTIONS,
-			{"auto",		required_argument,	0, 'a'},
-			{"bucket-size",		required_argument,	0, 'b'},
-			{"entries",		required_argument,	0, 'E'},
-			{"help",		no_argument,		0, 'h'},
-			{"irq",			required_argument,	0, 'i'},
-			{"nano",		no_argument,		0, 'n'},
-			{"period",		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'},
-			{"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'},
-			{"bpf-action",		required_argument,	0, '\7'},
-			{"stack-format",	required_argument,	0, '\10'},
-			{0, 0, 0, 0}
-		};
-
-		c = getopt_auto(argc, argv, long_options);
-
-		if (set_common_option(c, argc, argv, &params->common))
-			continue;
-
-		/* 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 '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 '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 '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 '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)
-				trace_event_add_trigger(params->common.events, optarg);
-			else
-				fatal("--trigger requires a previous -e");
-			break;
-		case '7': /* filter */
-			if (params->common.events)
-				trace_event_add_filter(params->common.events, 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;
-		case '\7':
-			params->bpf_action_program = optarg;
-			break;
-		case '\10':
-			params->stack_format = parse_stack_format(optarg);
-			if (params->stack_format == -1)
-				fatal("Invalid --stack-format option");
-			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
diff --git a/tools/tracing/rtla/src/timerlat_top.c b/tools/tracing/rtla/src/timerlat_top.c
index 91f88bb..18e1071 100644
--- a/tools/tracing/rtla/src/timerlat_top.c
+++ b/tools/tracing/rtla/src/timerlat_top.c
@@ -4,7 +4,6 @@
  */
 
 #define _GNU_SOURCE
-#include <getopt.h>
 #include <stdlib.h>
 #include <string.h>
 #include <signal.h>
@@ -17,6 +16,7 @@
 #include "timerlat.h"
 #include "timerlat_aa.h"
 #include "timerlat_bpf.h"
+#include "cli.h"
 #include "common.h"
 
 struct timerlat_top_cpu {
@@ -460,290 +460,6 @@ timerlat_print_stats(struct osnoise_tool *top)
 }
 
 /*
- * timerlat_top_usage - prints timerlat top usage message
- */
-static void timerlat_top_usage(void)
-{
-	static const char *const msg_start[] = {
-		"[-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]",
-		NULL,
-	};
-
-	static const char *const msg_opts[] = {
-		"	  -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",
-		"	     --bpf-action <program>: load and execute BPF program when latency threshold is exceeded",
-		"	     --stack-format <format>: set the stack format (truncate, skip, full)",
-		NULL,
-	};
-
-	common_usage("timerlat", "top", "a per-cpu summary of the timer latency",
-		     msg_start, msg_opts);
-}
-
-/*
- * 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;
-	long long auto_thresh;
-	int retval;
-	int c;
-	char *trace_output = NULL;
-
-	params = calloc_fatal(1, sizeof(*params));
-
-	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;
-
-	/* default to truncate stack format */
-	params->stack_format = STACK_FORMAT_TRUNCATE;
-
-	while (1) {
-		static struct option long_options[] = {
-			COMMON_OPTIONS,
-			{"auto",		required_argument,	0, 'a'},
-			{"help",		no_argument,		0, 'h'},
-			{"irq",			required_argument,	0, 'i'},
-			{"nano",		no_argument,		0, 'n'},
-			{"period",		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'},
-			{"bpf-action",		required_argument,	0, '\2'},
-			{"stack-format",	required_argument,	0, '\3'},
-			{0, 0, 0, 0}
-		};
-
-		c = getopt_auto(argc, argv, long_options);
-
-		if (set_common_option(c, argc, argv, &params->common))
-			continue;
-
-		/* 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 'h':
-		case '?':
-			timerlat_top_usage();
-			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 '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)
-				trace_event_add_trigger(params->common.events, optarg);
-			else
-				fatal("--trigger requires a previous -e");
-			break;
-		case '1': /* filter */
-			if (params->common.events)
-				trace_event_add_filter(params->common.events, 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;
-		case '\2':
-			params->bpf_action_program = optarg;
-			break;
-		case '\3':
-			params->stack_format = parse_stack_format(optarg);
-			if (params->stack_format == -1)
-				fatal("Invalid --stack-format option");
-			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
diff --git a/tools/tracing/rtla/src/utils.c b/tools/tracing/rtla/src/utils.c
index 9cec5b3..cb187e7 100644
--- a/tools/tracing/rtla/src/utils.c
+++ b/tools/tracing/rtla/src/utils.c
@@ -22,7 +22,7 @@
 #include "common.h"
 
 #define MAX_MSG_LENGTH	1024
-int config_debug;
+bool config_debug;
 
 /*
  * err_msg - print an error message to the stderr
@@ -1011,32 +1011,6 @@ 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
- *
- * Returns arg value if found, NULL otherwise.
- */
-char *parse_optional_arg(int argc, char **argv)
-{
-	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;
-	}
-}
-
 /*
  * strtoi - convert string to integer with error checking
  *
diff --git a/tools/tracing/rtla/src/utils.h b/tools/tracing/rtla/src/utils.h
index e794ede..2ba3333 100644
--- a/tools/tracing/rtla/src/utils.h
+++ b/tools/tracing/rtla/src/utils.h
@@ -7,6 +7,8 @@
 #include <stdbool.h>
 #include <stdlib.h>
 
+#include <linux/container_of.h>
+
 /*
  * '18446744073709551615\0'
  */
@@ -37,11 +39,7 @@ static inline bool str_has_prefix(const char *str, const char *prefix)
 	return strncmp(str, prefix, strlen(prefix)) == 0;
 }
 
-#define container_of(ptr, type, member)({			\
-	const typeof(((type *)0)->member) *__mptr = (ptr);	\
-	(type *)((char *)__mptr - offsetof(type, member)) ; })
-
-extern int config_debug;
+extern bool config_debug;
 void debug_msg(const char *fmt, ...);
 void err_msg(const char *fmt, ...);
 void fatal(const char *fmt, ...);
@@ -49,7 +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);
 
-char *parse_optional_arg(int argc, char **argv);
 long long get_llong_from_str(char *start);
 
 static inline void
diff --git a/tools/tracing/rtla/tests/engine.sh b/tools/tracing/rtla/tests/engine.sh
index ed261e0..5bf8453 100644
--- a/tools/tracing/rtla/tests/engine.sh
+++ b/tools/tracing/rtla/tests/engine.sh
@@ -4,6 +4,9 @@
 	# 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 @@
 	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,6 +90,10 @@
 			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
 }
 
@@ -112,6 +124,21 @@
 	NO_RESET_OSNOISE=1 check "$arg1" "$arg2" "$arg3"
 }
 
+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() {
 	TIMEOUT="timeout -v -k 15s $1"
 }
diff --git a/tools/tracing/rtla/tests/hwnoise.t b/tools/tracing/rtla/tests/hwnoise.t
index 23ce250..cfe687f 100644
--- a/tools/tracing/rtla/tests/hwnoise.t
+++ b/tools/tracing/rtla/tests/hwnoise.t
@@ -6,7 +6,7 @@
 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" \
diff --git a/tools/tracing/rtla/tests/osnoise.t b/tools/tracing/rtla/tests/osnoise.t
index 3963346..346a14a 100644
--- a/tools/tracing/rtla/tests/osnoise.t
+++ b/tools/tracing/rtla/tests/osnoise.t
@@ -6,16 +6,41 @@
 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"
+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" 1 "^Entries must be > 10 and < 10000000$"
+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:"
 
 # Test setting default period by putting an absurdly high period
 # and stopping on threshold.
@@ -24,27 +49,25 @@
 	"osnoise hist -s 1" 2 period_us=600000000
 
 # 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 0000000..fdc2c68
--- /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 0000000..0b016d4
--- /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 0000000..4742f34
--- /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-priority.sh b/tools/tracing/rtla/tests/scripts/check-priority.sh
index 79b702a..b51d523 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-user-kernel-threads.sh b/tools/tracing/rtla/tests/scripts/check-user-kernel-threads.sh
new file mode 100755
index 0000000..bb7ac51
--- /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 0000000..8aff98c
--- /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 fd4935f..8193048 100644
--- a/tools/tracing/rtla/tests/timerlat.t
+++ b/tools/tracing/rtla/tests/timerlat.t
@@ -21,65 +21,96 @@
 
 # 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"
+
+# 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" 1 "^Entries must be > 10 and < 10000000$"
+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 "hist with BPF action program (BPF mode)" \
-		"timerlat hist -T 2 --bpf-action tests/bpf/bpf_action_map.o --on-threshold shell,command='$BPFTOOL map dump name rtla_test_map'" \
+	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 "hist with BPF action program (non-BPF mode)" \
-		"timerlat hist -T 2 --bpf-action tests/bpf/bpf_action_map.o" \
+	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
diff --git a/tools/tracing/rtla/tests/unit/Build b/tools/tracing/rtla/tests/unit/Build
index 5f1e531..d5a0f13 100644
--- a/tools/tracing/rtla/tests/unit/Build
+++ b/tools/tracing/rtla/tests/unit/Build
@@ -1,2 +1,8 @@
+unit_tests-y += utils.o
+unit_tests-y += actions.o
 unit_tests-y += unit_tests.o
-unit_tests-y +=../../src/utils.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
index 2088c9c..839abda 100644
--- a/tools/tracing/rtla/tests/unit/Makefile.unit
+++ b/tools/tracing/rtla/tests/unit/Makefile.unit
@@ -3,10 +3,10 @@
 UNIT_TESTS := $(OUTPUT)unit_tests
 UNIT_TESTS_IN := $(UNIT_TESTS)-in.o
 
-$(UNIT_TESTS): $(UNIT_TESTS_IN)
-	$(QUIET_LINK)$(CC) $(LDFLAGS) -o $@ $^ -lcheck
+$(UNIT_TESTS): $(UNIT_TESTS_IN) $(RTLA_IN) $(LIBSUBCMD) $(LIB_STRING) $(LIB_STR_ERROR_R)
+	$(QUIET_LINK)$(CC) $(LDFLAGS) -o $@ $^ $(EXTLIBS) -lcheck
 
-$(UNIT_TESTS_IN):
+$(UNIT_TESTS_IN): fixdep $(LIBSUBCMD_INCLUDES)
 	make $(build)=unit_tests
 
 unit-tests: FORCE
diff --git a/tools/tracing/rtla/tests/unit/actions.c b/tools/tracing/rtla/tests/unit/actions.c
new file mode 100644
index 0000000..94ad5ad
--- /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 0000000..4a406af
--- /dev/null
+++ b/tools/tracing/rtla/tests/unit/cli_opt_callback.c
@@ -0,0 +1,716 @@
+// 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)
+
+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_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);
+
+	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);
+
+	ck_assert_int_eq(opt_int_callback(&opt, "1234567890abc", 0), -1);
+	ck_assert_int_eq(test_value, 0);
+}
+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));
+	opt_cpus_cb(&opt, "0-3,5", 0);
+}
+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_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));
+	opt_duration_cb(&opt, "abc", 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));
+	opt_housekeeping_cb(&opt, "0-3,5", 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));
+	opt_priority_cb(&opt, "abc", 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));
+	opt_trigger_cb(&opt, "stacktrace", 0);
+}
+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));
+	opt_filter_cb(&opt, "comm ~ \"rtla\"", 0);
+}
+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_period_cb)
+{
+	unsigned long long period = 0;
+	const struct option opt = TEST_CALLBACK(&period, opt_osnoise_period_cb);
+
+	ck_assert_int_eq(opt_osnoise_period_cb(&opt, "1000000", 0), 0);
+	ck_assert_int_eq(period, 1000000);
+}
+END_TEST
+
+START_TEST(test_opt_osnoise_period_cb_invalid)
+{
+	unsigned long long period = 0;
+	const struct option opt = TEST_CALLBACK(&period, opt_osnoise_period_cb);
+
+	assert(freopen("/dev/null", "w", stderr));
+	opt_osnoise_period_cb(&opt, "10000001", 0);
+}
+END_TEST
+
+START_TEST(test_opt_osnoise_runtime_cb)
+{
+	unsigned long long runtime = 0;
+	const struct option opt = TEST_CALLBACK(&runtime, opt_osnoise_runtime_cb);
+
+	ck_assert_int_eq(opt_osnoise_runtime_cb(&opt, "900000", 0), 0);
+	ck_assert_int_eq(runtime, 900000);
+}
+END_TEST
+
+START_TEST(test_opt_osnoise_runtime_cb_invalid)
+{
+	unsigned long long runtime = 0;
+	const struct option opt = TEST_CALLBACK(&runtime, opt_osnoise_runtime_cb);
+
+	assert(freopen("/dev/null", "w", stderr));
+	opt_osnoise_runtime_cb(&opt, "99", 0);
+}
+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_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));
+	opt_osnoise_on_threshold_cb(&opt, "abc", 0);
+}
+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));
+	opt_osnoise_on_end_cb(&opt, "abc", 0);
+}
+END_TEST
+
+START_TEST(test_opt_timerlat_period_cb)
+{
+	long long period = 0;
+	const struct option opt = TEST_CALLBACK(&period, opt_timerlat_period_cb);
+
+	ck_assert_int_eq(opt_timerlat_period_cb(&opt, "1000", 0), 0);
+	ck_assert_int_eq(period, 1000);
+}
+END_TEST
+
+START_TEST(test_opt_timerlat_period_cb_invalid)
+{
+	long long period = 0;
+	const struct option opt = TEST_CALLBACK(&period, opt_timerlat_period_cb);
+
+	assert(freopen("/dev/null", "w", stderr));
+	opt_timerlat_period_cb(&opt, "1000001", 0);
+}
+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_dma_latency_cb)
+{
+	int dma_latency = 0;
+	const struct option opt = TEST_CALLBACK(&dma_latency, opt_dma_latency_cb);
+
+	ck_assert_int_eq(opt_dma_latency_cb(&opt, "1000", 0), 0);
+	ck_assert_int_eq(dma_latency, 1000);
+}
+END_TEST
+
+START_TEST(test_opt_dma_latency_cb_min)
+{
+	int dma_latency = 0;
+	const struct option opt = TEST_CALLBACK(&dma_latency, opt_dma_latency_cb);
+
+	assert(freopen("/dev/null", "w", stderr));
+	opt_dma_latency_cb(&opt, "-1", 0);
+}
+END_TEST
+
+START_TEST(test_opt_dma_latency_cb_max)
+{
+	int dma_latency = 0;
+	const struct option opt = TEST_CALLBACK(&dma_latency, opt_dma_latency_cb);
+
+	assert(freopen("/dev/null", "w", stderr));
+	opt_dma_latency_cb(&opt, "10001", 0);
+}
+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_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_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));
+	opt_timerlat_on_threshold_cb(&opt, "abc", 0);
+}
+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));
+	opt_timerlat_on_end_cb(&opt, "abc", 0);
+}
+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_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_timerlat_align_cb)
+{
+	struct timerlat_params params = {0};
+	const struct option opt = TEST_CALLBACK(&params, opt_timerlat_align_cb);
+
+	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_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));
+	opt_stack_format_cb(&opt, "abc", 0);
+}
+END_TEST
+
+START_TEST(test_opt_bucket_size_cb)
+{
+	int bucket_size = 0;
+	const struct option opt = TEST_CALLBACK(&bucket_size, opt_bucket_size_cb);
+
+	ck_assert_int_eq(opt_bucket_size_cb(&opt, "100", 0), 0);
+	ck_assert_int_eq(bucket_size, 100);
+}
+END_TEST
+
+START_TEST(test_opt_bucket_size_min)
+{
+	int bucket_size = 0;
+	const struct option opt = TEST_CALLBACK(&bucket_size, opt_bucket_size_cb);
+
+	assert(freopen("/dev/null", "w", stderr));
+	opt_bucket_size_cb(&opt, "0", 0);
+}
+END_TEST
+
+START_TEST(test_opt_bucket_size_max)
+{
+	int bucket_size = 0;
+	const struct option opt = TEST_CALLBACK(&bucket_size, opt_bucket_size_cb);
+
+	assert(freopen("/dev/null", "w", stderr));
+	opt_bucket_size_cb(&opt, "1000001", 0);
+}
+END_TEST
+
+START_TEST(test_opt_entries_cb)
+{
+	int entries = 0;
+	const struct option opt = TEST_CALLBACK(&entries, opt_entries_cb);
+
+	ck_assert_int_eq(opt_entries_cb(&opt, "100", 0), 0);
+	ck_assert_int_eq(entries, 100);
+}
+END_TEST
+
+START_TEST(test_opt_entries_min)
+{
+	int entries = 0;
+	const struct option opt = TEST_CALLBACK(&entries, opt_entries_cb);
+
+	assert(freopen("/dev/null", "w", stderr));
+	opt_entries_cb(&opt, "9", 0);
+}
+END_TEST
+
+START_TEST(test_opt_entries_max)
+{
+	int entries = 0;
+	const struct option opt = TEST_CALLBACK(&entries, opt_entries_cb);
+
+	assert(freopen("/dev/null", "w", stderr));
+	opt_entries_cb(&opt, "10000000", 0);
+}
+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_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_cpus_cb);
+	tcase_add_exit_test(tc, test_opt_cpus_cb_invalid, EXIT_FAILURE);
+	tcase_add_test(tc, test_opt_cgroup_cb);
+	tcase_add_test(tc, test_opt_cgroup_cb_equals);
+	tcase_add_test(tc, test_opt_duration_cb);
+	tcase_add_exit_test(tc, test_opt_duration_cb_invalid, EXIT_FAILURE);
+	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_exit_test(tc, test_opt_housekeeping_cb_invalid, EXIT_FAILURE);
+	tcase_add_test(tc, test_opt_priority_cb);
+	tcase_add_exit_test(tc, test_opt_priority_cb_invalid, EXIT_FAILURE);
+	tcase_add_test(tc, test_opt_trigger_cb);
+	tcase_add_exit_test(tc, test_opt_trigger_cb_no_event, EXIT_FAILURE);
+	tcase_add_test(tc, test_opt_filter_cb);
+	tcase_add_exit_test(tc, test_opt_filter_cb_no_event, EXIT_FAILURE);
+	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_period_cb);
+	tcase_add_exit_test(tc, test_opt_osnoise_period_cb_invalid, EXIT_FAILURE);
+	tcase_add_test(tc, test_opt_osnoise_runtime_cb);
+	tcase_add_exit_test(tc, test_opt_osnoise_runtime_cb_invalid, EXIT_FAILURE);
+	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_on_threshold_cb);
+	tcase_add_exit_test(tc, test_opt_osnoise_on_threshold_cb_invalid, EXIT_FAILURE);
+	tcase_add_test(tc, test_opt_osnoise_on_end_cb);
+	tcase_add_exit_test(tc, test_opt_osnoise_on_end_cb_invalid, EXIT_FAILURE);
+	suite_add_tcase(s, tc);
+
+	tc = tcase_create("timerlat");
+	tcase_add_test(tc, test_opt_timerlat_period_cb);
+	tcase_add_exit_test(tc, test_opt_timerlat_period_cb_invalid, EXIT_FAILURE);
+	tcase_add_test(tc, test_opt_timerlat_auto_cb);
+	tcase_add_test(tc, test_opt_dma_latency_cb);
+	tcase_add_exit_test(tc, test_opt_dma_latency_cb_min, EXIT_FAILURE);
+	tcase_add_exit_test(tc, test_opt_dma_latency_cb_max, EXIT_FAILURE);
+	tcase_add_test(tc, test_opt_aa_only_cb);
+	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_on_threshold_cb);
+	tcase_add_exit_test(tc, test_opt_timerlat_on_threshold_cb_invalid, EXIT_FAILURE);
+	tcase_add_test(tc, test_opt_timerlat_on_end_cb);
+	tcase_add_exit_test(tc, test_opt_timerlat_on_end_cb_invalid, EXIT_FAILURE);
+	tcase_add_test(tc, test_opt_user_threads_cb);
+	tcase_add_test(tc, test_opt_nano_cb);
+	tcase_add_test(tc, test_opt_stack_format_cb);
+	tcase_add_exit_test(tc, test_opt_stack_format_cb_invalid, EXIT_FAILURE);
+	tcase_add_test(tc, test_opt_timerlat_align_cb);
+	suite_add_tcase(s, tc);
+
+	tc = tcase_create("histogram");
+	tcase_add_test(tc, test_opt_bucket_size_cb);
+	tcase_add_exit_test(tc, test_opt_bucket_size_min, EXIT_FAILURE);
+	tcase_add_exit_test(tc, test_opt_bucket_size_max, EXIT_FAILURE);
+	tcase_add_test(tc, test_opt_entries_cb);
+	tcase_add_exit_test(tc, test_opt_entries_min, EXIT_FAILURE);
+	tcase_add_exit_test(tc, test_opt_entries_max, EXIT_FAILURE);
+	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 0000000..4bc7d58
--- /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 0000000..3661529
--- /dev/null
+++ b/tools/tracing/rtla/tests/unit/osnoise_hist_cli.c
@@ -0,0 +1,557 @@
+// 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_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_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 0000000..f3a8633
--- /dev/null
+++ b/tools/tracing/rtla/tests/unit/osnoise_top_cli.c
@@ -0,0 +1,503 @@
+// 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_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_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 0000000..968bf962
--- /dev/null
+++ b/tools/tracing/rtla/tests/unit/timerlat_hist_cli.c
@@ -0,0 +1,722 @@
+// 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_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_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 0000000..33aa658
--- /dev/null
+++ b/tools/tracing/rtla/tests/unit/timerlat_top_cli.c
@@ -0,0 +1,654 @@
+// 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_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_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
index f3c6d89..75ca813 100644
--- a/tools/tracing/rtla/tests/unit/unit_tests.c
+++ b/tools/tracing/rtla/tests/unit/unit_tests.c
@@ -2,115 +2,35 @@
 
 #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 <stdbool.h>
 
 #include "../../src/utils.h"
-int nr_cpus;
+#include "../../src/cli.h"
 
-START_TEST(test_strtoi)
-{
-	int result;
-	char buf[64];
+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);
 
-	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;
-}
-
-int main(void)
+int main(int argc, char *argv[])
 {
 	int num_failed;
 	SRunner *sr;
 
+	in_unit_test = true;
+
 	sr = srunner_create(utils_suite());
-	srunner_run_all(sr, CK_NORMAL);
+	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);
diff --git a/tools/tracing/rtla/tests/unit/utils.c b/tools/tracing/rtla/tests/unit/utils.c
new file mode 100644
index 0000000..ce53cab
--- /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;
+}