From 704fe8f3d97bba842f5f357e317116f1b88169e8 Mon Sep 17 00:00:00 2001 From: Costa Shulyupin Date: Tue, 14 Apr 2026 21:52:18 +0300 Subject: [PATCH 01/26] tools/rtla: Fix --dump-tasks usage in timerlat Fix --dump-task to --dump-tasks in timerlat_hist usage string and getopt_long table for consistency with timerlat_top. Add missing --dump-tasks to timerlat_top usage synopsis. Assisted-by: Claude:claude-opus-4-6 Signed-off-by: Costa Shulyupin Fixes: 2091336b9a8b ("rtla/timerlat_hist: Add auto-analysis support") Link: https://lore.kernel.org/r/20260414185223.65353-1-costa.shul@redhat.com Signed-off-by: Tomas Glozar --- tools/tracing/rtla/src/timerlat_hist.c | 4 ++-- tools/tracing/rtla/src/timerlat_top.c | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/tools/tracing/rtla/src/timerlat_hist.c b/tools/tracing/rtla/src/timerlat_hist.c index 79142af4f566..3a15a85b7d72 100644 --- a/tools/tracing/rtla/src/timerlat_hist.c +++ b/tools/tracing/rtla/src/timerlat_hist.c @@ -694,7 +694,7 @@ static void timerlat_hist_usage(void) "[-d s] [-D] [-n] [-a us] [-p us] [-i us] [-T us] [-s us] \\", " [-t [file]] [-e sys[:event]] [--filter ] [--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]", + " [--no-index] [--with-zeros] [--dma-latency us] [-C [cgroup_name]] [--no-aa] [--dump-tasks] [-u|-k]", " [--warm-up s] [--deepest-idle-state n]", NULL, }; @@ -808,7 +808,7 @@ static struct common_params {"filter", required_argument, 0, '7'}, {"dma-latency", required_argument, 0, '8'}, {"no-aa", no_argument, 0, '9'}, - {"dump-task", no_argument, 0, '\1'}, + {"dump-tasks", no_argument, 0, '\1'}, {"warm-up", required_argument, 0, '\2'}, {"trace-buffer-size", required_argument, 0, '\3'}, {"deepest-idle-state", required_argument, 0, '\4'}, diff --git a/tools/tracing/rtla/src/timerlat_top.c b/tools/tracing/rtla/src/timerlat_top.c index 64cbdcc878b0..e3a374005690 100644 --- a/tools/tracing/rtla/src/timerlat_top.c +++ b/tools/tracing/rtla/src/timerlat_top.c @@ -467,7 +467,8 @@ 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 ] [--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]", + " [-P priority] [--dma-latency us] [--aa-only us] [-C [cgroup_name]] [--dump-tasks] [-u|-k] [--warm-up s]\\", + " [--deepest-idle-state n]", NULL, }; From c15c55c01e48e4639c715370f41d9d4c490f5d23 Mon Sep 17 00:00:00 2001 From: Tomas Glozar Date: Thu, 23 Apr 2026 15:05:50 +0200 Subject: [PATCH 02/26] rtla/tests: Cover both top and hist tools where possible RTLA runtime tests currently do not cover both tool variants for osnoise and timerlat properly. Many tests applicable to both tools are only tested for one tool, selected randomly. Introduce two new shell functions, check_top_hist() and check_top_q_hist(). The functions use the same syntax as check() and run check() on the arguments twice: once replacing the "TOOL" string in the command with "top" (or "top -q"), once replacing it with "hist". The top -q variant is used for tests relying on messages printed after aborting the RTLA main loop with a starting new line, which only happens for top tools in quiet mode; without -q, the top output is printed on the same line and the matches would fail. Tests that are applicable to both top and hist tools were modified to the run for both; additionally, tests that were already done for both tools were migrated to the new shell functions, unless the test command or matches differ between the tools. Additional tests were added to test tool-specific help messages. Reviewed-by: Wander Lairson Costa Link: https://lore.kernel.org/r/20260423130558.882022-2-tglozar@redhat.com Signed-off-by: Tomas Glozar --- tools/tracing/rtla/tests/engine.sh | 15 ++++++ tools/tracing/rtla/tests/osnoise.t | 46 +++++++++-------- tools/tracing/rtla/tests/timerlat.t | 76 ++++++++++++++--------------- 3 files changed, 73 insertions(+), 64 deletions(-) diff --git a/tools/tracing/rtla/tests/engine.sh b/tools/tracing/rtla/tests/engine.sh index ed261e07c6d9..27d92f19a322 100644 --- a/tools/tracing/rtla/tests/engine.sh +++ b/tools/tracing/rtla/tests/engine.sh @@ -112,6 +112,21 @@ check_with_osnoise_options() { 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/osnoise.t b/tools/tracing/rtla/tests/osnoise.t index 396334608920..ce3a448b1f87 100644 --- a/tools/tracing/rtla/tests/osnoise.t +++ b/tools/tracing/rtla/tests/osnoise.t @@ -7,13 +7,15 @@ 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\"" \ +check_top_hist "verify help page" \ + "osnoise TOOL --help" 0 "rtla osnoise" +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=\"tests/scripts/check-priority.sh osnoise/ 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_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" check "verify the --entries/-E param" \ "osnoise hist -P F:1 -c 0 -r 900000 -d 10s -b 10 -E 25" @@ -24,27 +26,23 @@ check_with_osnoise_options "apply default period" \ "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_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/timerlat.t b/tools/tracing/rtla/tests/timerlat.t index fd4935fd7b49..d7944710a859 100644 --- a/tools/tracing/rtla/tests/timerlat.t +++ b/tools/tracing/rtla/tests/timerlat.t @@ -22,64 +22,60 @@ export RTLA_NO_BPF=$option # Basic tests check "verify help page" \ "timerlat --help" 0 "timerlat version" -check "verify -s/--stack" \ - "timerlat top -s 3 -T 10 -t" 2 "Blocking thread stack trace" -check "verify -P/--priority" \ - "timerlat top -P F:1 -c 0 -d 10s -q -T 1 --on-threshold shell,command=\"tests/scripts/check-priority.sh timerlatu/ SCHED_FIFO 1\"" \ +check_top_hist "verify help page" \ + "timerlat TOOL --help" 0 "rtla timerlat" +check_top_hist "verify -s/--stack" \ + "timerlat TOOL -s 3 -T 10 -t" 2 "Blocking thread stack trace" +check_top_hist "verify -P/--priority" \ + "timerlat TOOL -P F:1 -c 0 -d 10s -T 1 --on-threshold shell,command=\"tests/scripts/check-priority.sh timerlatu/ 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_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 "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 "disable auto-analysis" \ + "timerlat TOOL -s 3 -T 10 -t --no-aa" 2 +check_top_hist "verify -c/--cpus" \ + "timerlat TOOL -c 0 -d 10s" # 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_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 tests/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 tests/bpf/bpf_action_map.o" \ 1 "BPF actions are not supported in tracefs-only mode" fi done From d18fd5a34ff3a2628f38af55f276b7377ee6dc5a Mon Sep 17 00:00:00 2001 From: Tomas Glozar Date: Thu, 23 Apr 2026 15:05:51 +0200 Subject: [PATCH 03/26] rtla/tests: Add get_workload_pids() helper RTLA runtime tests that check workload processes (currently the test case "verify -P/--priority" of timerlat.t and "verify the --priority/-P param" of osnoise.t) use "pgrep timerlatu/" or "pgrep osnoise/" respectively to identify the workload. Make them more robust by adding a get_workload_pids() helper that finds the main rtla process and returns the PIDs of all siblings other than the test script itself, plus all child processes of kthreadd that have the osnoise/timerlat kthread pattern comm. This filters out any spurious processes not related to the running test that happen to have "timerlatu/" or "osnoise/" in their command, for example, a user grepping the same names at the time of the running of the test. Reviewed-by: Wander Lairson Costa Link: https://lore.kernel.org/r/20260423130558.882022-3-tglozar@redhat.com Signed-off-by: Tomas Glozar --- tools/tracing/rtla/tests/osnoise.t | 2 +- tools/tracing/rtla/tests/scripts/check-priority.sh | 8 ++++---- .../rtla/tests/scripts/lib/get_workload_pids.sh | 11 +++++++++++ tools/tracing/rtla/tests/timerlat.t | 2 +- 4 files changed, 17 insertions(+), 6 deletions(-) create mode 100644 tools/tracing/rtla/tests/scripts/lib/get_workload_pids.sh diff --git a/tools/tracing/rtla/tests/osnoise.t b/tools/tracing/rtla/tests/osnoise.t index ce3a448b1f87..ed6ff0cc3329 100644 --- a/tools/tracing/rtla/tests/osnoise.t +++ b/tools/tracing/rtla/tests/osnoise.t @@ -10,7 +10,7 @@ check "verify help page" \ check_top_hist "verify help page" \ "osnoise TOOL --help" 0 "rtla osnoise" 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=\"tests/scripts/check-priority.sh osnoise/ SCHED_FIFO 1\"" \ + "osnoise TOOL -P F:1 -c 0 -r 900000 -d 10s -S 1 --on-threshold shell,command=\"tests/scripts/check-priority.sh SCHED_FIFO 1\"" \ 2 "Priorities are set correctly" check_top_q_hist "verify the --stop/-s param" \ "osnoise TOOL -s 30 -T 1" 2 "osnoise hit stop tracing" diff --git a/tools/tracing/rtla/tests/scripts/check-priority.sh b/tools/tracing/rtla/tests/scripts/check-priority.sh index 79b702a34a96..b51d5232a868 100755 --- a/tools/tracing/rtla/tests/scripts/check-priority.sh +++ b/tools/tracing/rtla/tests/scripts/check-priority.sh @@ -1,8 +1,8 @@ #!/bin/bash # SPDX-License-Identifier: GPL-2.0 -pids="$(pgrep ^$1)" || exit 1 -for pid in $pids +. "$(dirname $0)/lib/get_workload_pids.sh" +for pid in $(get_workload_pids) do - chrt -p $pid | cut -d ':' -f 2 | head -n1 | grep "^ $2\$" >/dev/null - chrt -p $pid | cut -d ':' -f 2 | tail -n1 | grep "^ $3\$" >/dev/null + chrt -p $pid | cut -d ':' -f 2 | head -n1 | grep "^ $1\$" >/dev/null + chrt -p $pid | cut -d ':' -f 2 | tail -n1 | grep "^ $2\$" >/dev/null done && echo "Priorities are set correctly" diff --git a/tools/tracing/rtla/tests/scripts/lib/get_workload_pids.sh b/tools/tracing/rtla/tests/scripts/lib/get_workload_pids.sh new file mode 100644 index 000000000000..8aff98cd2c1f --- /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 d7944710a859..765dffd9d42a 100644 --- a/tools/tracing/rtla/tests/timerlat.t +++ b/tools/tracing/rtla/tests/timerlat.t @@ -27,7 +27,7 @@ check_top_hist "verify help page" \ check_top_hist "verify -s/--stack" \ "timerlat TOOL -s 3 -T 10 -t" 2 "Blocking thread stack trace" check_top_hist "verify -P/--priority" \ - "timerlat TOOL -P F:1 -c 0 -d 10s -T 1 --on-threshold shell,command=\"tests/scripts/check-priority.sh timerlatu/ SCHED_FIFO 1\"" \ + "timerlat TOOL -P F:1 -c 0 -d 10s -T 1 --on-threshold shell,command=\"tests/scripts/check-priority.sh SCHED_FIFO 1\"" \ 2 "Priorities are set correctly" check_top_hist "test in nanoseconds" \ "timerlat TOOL -i 2 -c 0 -n -d 10s" 2 "ns" From 7f7ad23bced9a50d0a7115b1c9757d44325be3c2 Mon Sep 17 00:00:00 2001 From: Tomas Glozar Date: Thu, 23 Apr 2026 15:05:52 +0200 Subject: [PATCH 04/26] rtla/tests: Check -c/--cpus thread affinity RTLA runtime tests verify the -c/--cpus options, but do not check whether the correct affinity is actually applied. Add a script named check-cpus.sh that retrieves the affinity of all workload threads and use it to check the -c/--cpus option for both osnoise and timerlat tools. Also add missing -c/--cpus test for osnoise. Reviewed-by: Wander Lairson Costa Link: https://lore.kernel.org/r/20260423130558.882022-4-tglozar@redhat.com Signed-off-by: Tomas Glozar --- tools/tracing/rtla/tests/osnoise.t | 2 ++ tools/tracing/rtla/tests/scripts/check-cpus.sh | 9 +++++++++ tools/tracing/rtla/tests/timerlat.t | 4 ++-- 3 files changed, 13 insertions(+), 2 deletions(-) create mode 100755 tools/tracing/rtla/tests/scripts/check-cpus.sh diff --git a/tools/tracing/rtla/tests/osnoise.t b/tools/tracing/rtla/tests/osnoise.t index ed6ff0cc3329..5edffb23981b 100644 --- a/tools/tracing/rtla/tests/osnoise.t +++ b/tools/tracing/rtla/tests/osnoise.t @@ -18,6 +18,8 @@ check_top_q_hist "verify the --trace param" \ "osnoise TOOL -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/--cpus param" \ + "osnoise TOOL -P F:1 -c 0 -r 900000 -d 10s -S 1 --on-threshold shell,command=tests/scripts/check-cpus.sh" 2 "^Affinity of threads: 0$" # Test setting default period by putting an absurdly high period # and stopping on threshold. diff --git a/tools/tracing/rtla/tests/scripts/check-cpus.sh b/tools/tracing/rtla/tests/scripts/check-cpus.sh new file mode 100755 index 000000000000..0b016d4a7945 --- /dev/null +++ b/tools/tracing/rtla/tests/scripts/check-cpus.sh @@ -0,0 +1,9 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +. "$(dirname $0)/lib/get_workload_pids.sh" +echo -n "Affinity of threads: " +for pid in $(get_workload_pids) +do + echo -n $(taskset -c -p $pid | cut -d ':' -f 2) +done +echo diff --git a/tools/tracing/rtla/tests/timerlat.t b/tools/tracing/rtla/tests/timerlat.t index 765dffd9d42a..fb60022aaa64 100644 --- a/tools/tracing/rtla/tests/timerlat.t +++ b/tools/tracing/rtla/tests/timerlat.t @@ -39,8 +39,8 @@ check "print the auto-analysis if hits the stop tracing condition" \ "timerlat top --aa-only 5" 2 check_top_hist "disable auto-analysis" \ "timerlat TOOL -s 3 -T 10 -t --no-aa" 2 -check_top_hist "verify -c/--cpus" \ - "timerlat TOOL -c 0 -d 10s" +check_top_q_hist "verify -c/--cpus" \ + "timerlat TOOL -c 0 -d 10s -T 1 --on-threshold shell,command=tests/scripts/check-cpus.sh" 2 "^Affinity of threads: 0$" # Actions tests check_top_q_hist "trace output through -t" \ From 31664637e652931480a1a85aa03695e5f08fd4ad Mon Sep 17 00:00:00 2001 From: Tomas Glozar Date: Thu, 23 Apr 2026 15:05:53 +0200 Subject: [PATCH 05/26] rtla/tests: Use negative match when testing --aa-only For testing the -a/--auto option in timerlat tool, the string "analyzing it" is matched against to make sure auto-analysis was triggered. Use the same string as a negative match for --aa-only option test. Reviewed-by: Wander Lairson Costa Link: https://lore.kernel.org/r/20260423130558.882022-5-tglozar@redhat.com Signed-off-by: Tomas Glozar --- tools/tracing/rtla/tests/timerlat.t | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/tracing/rtla/tests/timerlat.t b/tools/tracing/rtla/tests/timerlat.t index fb60022aaa64..f47a82c115c7 100644 --- a/tools/tracing/rtla/tests/timerlat.t +++ b/tools/tracing/rtla/tests/timerlat.t @@ -38,7 +38,7 @@ check_top_hist "dump tasks" \ check "print the auto-analysis if hits the stop tracing condition" \ "timerlat top --aa-only 5" 2 check_top_hist "disable auto-analysis" \ - "timerlat TOOL -s 3 -T 10 -t --no-aa" 2 + "timerlat TOOL -s 3 -T 10 -t --no-aa" 2 "" "analyzing it" check_top_q_hist "verify -c/--cpus" \ "timerlat TOOL -c 0 -d 10s -T 1 --on-threshold shell,command=tests/scripts/check-cpus.sh" 2 "^Affinity of threads: 0$" From f78ba48f112113c89e37a914ccf52890fdd71006 Mon Sep 17 00:00:00 2001 From: Tomas Glozar Date: Thu, 23 Apr 2026 15:05:54 +0200 Subject: [PATCH 06/26] rtla/tests: Extend timerlat top --aa-only coverage rtla-timerlat-top's --aa-only option is currently only tested for return value. Extend the tests to also check that only auto-analysis is being done via a negative match for the "Timer Latency" text in the top header, and further split the test case into two: - one test case for --aa-only stopping on threshold - one test case for --aa-only exiting without threshold being hit For both cases, the expected output ("analyzing it" or "Max latency was" respectively) is checked against in addition to the negative match. Reviewed-by: Wander Lairson Costa Link: https://lore.kernel.org/r/20260423130558.882022-6-tglozar@redhat.com Signed-off-by: Tomas Glozar --- tools/tracing/rtla/tests/timerlat.t | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tools/tracing/rtla/tests/timerlat.t b/tools/tracing/rtla/tests/timerlat.t index f47a82c115c7..28c01d8b299d 100644 --- a/tools/tracing/rtla/tests/timerlat.t +++ b/tools/tracing/rtla/tests/timerlat.t @@ -35,8 +35,10 @@ 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 "print the auto-analysis if hits the stop tracing condition" \ - "timerlat top --aa-only 5" 2 +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" check_top_q_hist "verify -c/--cpus" \ From 287ff55afe0d9c3186083945f46613597bdac992 Mon Sep 17 00:00:00 2001 From: Tomas Glozar Date: Thu, 23 Apr 2026 15:05:55 +0200 Subject: [PATCH 07/26] rtla/tests: Cover all hist options in runtime tests Cover all options regarding histogram formatting for both rtla-osnoise-hist and rtla-timerlat-hist tools. All options also have output checking using positive or negative match, except for -b/--bucket-size and -E/--entries, which cannot be tested in isolated due to the output depending on the actual data collected. Old -E/--entries test for rtla-osnoise was replaced with a new one equivalent to the timerlat one. Reviewed-by: Wander Lairson Costa Link: https://lore.kernel.org/r/20260423130558.882022-7-tglozar@redhat.com Signed-off-by: Tomas Glozar --- tools/tracing/rtla/tests/osnoise.t | 18 ++++++++++++++++-- tools/tracing/rtla/tests/timerlat.t | 20 ++++++++++++++++++++ 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/tools/tracing/rtla/tests/osnoise.t b/tools/tracing/rtla/tests/osnoise.t index 5edffb23981b..773a46e2dc5f 100644 --- a/tools/tracing/rtla/tests/osnoise.t +++ b/tools/tracing/rtla/tests/osnoise.t @@ -16,11 +16,25 @@ 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" -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/--cpus param" \ "osnoise TOOL -P F:1 -c 0 -r 900000 -d 10s -S 1 --on-threshold shell,command=tests/scripts/check-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 < 9999999$" +check "hist with --no-header" \ + "osnoise hist --no-header -d 1s" 0 "" "RTLA osnoise histogram" +check "hist with --with-zeros" \ + "osnoise hist --with-zeros -b 100000 -E 21 -d 1s" 0 '^2000000\s+0\s+' +check "hist with --no-index" \ + "osnoise hist --no-index --with-zeros -d 1s" 0 "" "^count:" +check "hist with --no-summary" \ + "osnoise hist --no-summary -d 1s" 0 "" "^count:" + # Test setting default period by putting an absurdly high period # and stopping on threshold. # If default period is not set, this will time out. diff --git a/tools/tracing/rtla/tests/timerlat.t b/tools/tracing/rtla/tests/timerlat.t index 28c01d8b299d..a14d9ec32ede 100644 --- a/tools/tracing/rtla/tests/timerlat.t +++ b/tools/tracing/rtla/tests/timerlat.t @@ -44,6 +44,26 @@ check_top_hist "disable auto-analysis" \ check_top_q_hist "verify -c/--cpus" \ "timerlat TOOL -c 0 -d 10s -T 1 --on-threshold shell,command=tests/scripts/check-cpus.sh" 2 "^Affinity of threads: 0$" +# 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 < 9999999$" +check "hist with --no-header" \ + "timerlat hist --no-header -d 1s" 0 "" "RTLA timerlat histogram" +check "hist with --with-zeros" \ + "timerlat hist --with-zeros -b 100000 -E 21 -d 1s" 0 '^2000000\s+0\s+' +check "hist with --no-index" \ + "timerlat hist --no-index --with-zeros -d 1s" 0 "" "^count:" +check "hist with --no-summary" \ + "timerlat hist --no-summary -d 1s" 0 "" "^ALL:" +check "hist with --no-irq" \ + "timerlat hist --no-irq -d 1s" 0 "" "IRQ-" +check "hist with --no-thread" \ + "timerlat hist --no-thread -d 1s" 0 "" "Thr-" + # Actions tests check_top_q_hist "trace output through -t" \ "timerlat TOOL -T 2 -t" 2 "^ Saving trace to timerlat_trace.txt$" From 66c1d48db61999bc3114b12a1f0becf9bd218d83 Mon Sep 17 00:00:00 2001 From: Tomas Glozar Date: Thu, 23 Apr 2026 15:05:56 +0200 Subject: [PATCH 08/26] rtla/tests: Add runtime test for -H/--house-keeping Add a runtime test for -H/--house-keeping option for both osnoise and timerlat tools, with affinity checking similar to what is done for -c/--cpus. Reviewed-by: Wander Lairson Costa Link: https://lore.kernel.org/r/20260423130558.882022-8-tglozar@redhat.com Signed-off-by: Tomas Glozar --- tools/tracing/rtla/tests/osnoise.t | 2 ++ tools/tracing/rtla/tests/scripts/check-housekeeping-cpus.sh | 4 ++++ tools/tracing/rtla/tests/timerlat.t | 2 ++ 3 files changed, 8 insertions(+) create mode 100755 tools/tracing/rtla/tests/scripts/check-housekeeping-cpus.sh diff --git a/tools/tracing/rtla/tests/osnoise.t b/tools/tracing/rtla/tests/osnoise.t index 773a46e2dc5f..cdea84914345 100644 --- a/tools/tracing/rtla/tests/osnoise.t +++ b/tools/tracing/rtla/tests/osnoise.t @@ -18,6 +18,8 @@ check_top_q_hist "verify the --trace param" \ "osnoise TOOL -s 30 -T 1 -t" 2 "Saving trace to osnoise_trace.txt" 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=tests/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=tests/scripts/check-housekeeping-cpus.sh" 2 "^Affinity of threads: 0$" # Histogram tests check "hist with -b/--bucket-size" \ diff --git a/tools/tracing/rtla/tests/scripts/check-housekeeping-cpus.sh b/tools/tracing/rtla/tests/scripts/check-housekeeping-cpus.sh new file mode 100755 index 000000000000..4742f34efb49 --- /dev/null +++ b/tools/tracing/rtla/tests/scripts/check-housekeeping-cpus.sh @@ -0,0 +1,4 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +pid=$(ps -o ppid= $$) +echo "Affinity of threads:$(taskset -c -p $pid | cut -d ':' -f 2)" diff --git a/tools/tracing/rtla/tests/timerlat.t b/tools/tracing/rtla/tests/timerlat.t index a14d9ec32ede..20f68bcbcb27 100644 --- a/tools/tracing/rtla/tests/timerlat.t +++ b/tools/tracing/rtla/tests/timerlat.t @@ -43,6 +43,8 @@ check_top_hist "disable auto-analysis" \ "timerlat TOOL -s 3 -T 10 -t --no-aa" 2 "" "analyzing it" check_top_q_hist "verify -c/--cpus" \ "timerlat TOOL -c 0 -d 10s -T 1 --on-threshold shell,command=tests/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=tests/scripts/check-housekeeping-cpus.sh" 2 "^Affinity of threads: 0$" # Histogram tests check "hist with -b/--bucket-size" \ From a98dad63cda3b2b9a6159f3d87228c6f2c6dd094 Mon Sep 17 00:00:00 2001 From: Tomas Glozar Date: Thu, 23 Apr 2026 15:05:57 +0200 Subject: [PATCH 09/26] rtla/tests: Add runtime test for -k and -u options Add runtime test for rtla-timerlat's -k/--kernel-threads and -u/--user-threads options using get_workload_pids.sh to check whether the appropriate threads are being created. The tests are implemented for both top and hist. Additionally, all tests related to timerlat threads are moved to a separate section in the test files. The latter is also done for rtla-osnoise tests. Reviewed-by: Wander Lairson Costa Link: https://lore.kernel.org/r/20260423130558.882022-9-tglozar@redhat.com Signed-off-by: Tomas Glozar --- tools/tracing/rtla/tests/osnoise.t | 8 +++++--- .../tests/scripts/check-user-kernel-threads.sh | 16 ++++++++++++++++ tools/tracing/rtla/tests/timerlat.t | 12 +++++++++--- 3 files changed, 30 insertions(+), 6 deletions(-) create mode 100755 tools/tracing/rtla/tests/scripts/check-user-kernel-threads.sh diff --git a/tools/tracing/rtla/tests/osnoise.t b/tools/tracing/rtla/tests/osnoise.t index cdea84914345..d0b623233db5 100644 --- a/tools/tracing/rtla/tests/osnoise.t +++ b/tools/tracing/rtla/tests/osnoise.t @@ -9,13 +9,15 @@ check "verify help page" \ "osnoise --help" 0 "osnoise version" check_top_hist "verify help page" \ "osnoise TOOL --help" 0 "rtla osnoise" -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=\"tests/scripts/check-priority.sh SCHED_FIFO 1\"" \ - 2 "Priorities are set correctly" 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=\"tests/scripts/check-priority.sh SCHED_FIFO 1\"" \ + 2 "Priorities are set correctly" 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=tests/scripts/check-cpus.sh" 2 "^Affinity of threads: 0$" check_top_q_hist "verify the -H/--house-keeping param" \ diff --git a/tools/tracing/rtla/tests/scripts/check-user-kernel-threads.sh b/tools/tracing/rtla/tests/scripts/check-user-kernel-threads.sh new file mode 100755 index 000000000000..bb7ac510a735 --- /dev/null +++ b/tools/tracing/rtla/tests/scripts/check-user-kernel-threads.sh @@ -0,0 +1,16 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +. "$(dirname $0)/lib/get_workload_pids.sh" +kthreadd_pid=$(pgrep ^kthreadd$) +cnt_kernel=0 +cnt_user=0 +for pid in $(get_workload_pids) +do + if [ "$(echo $(ps -o ppid= $pid))" = "$kthreadd_pid" ] + then + ((++cnt_kernel)) + else + ((++cnt_user)) + fi +done +echo "$cnt_kernel kernel threads, $cnt_user user threads" diff --git a/tools/tracing/rtla/tests/timerlat.t b/tools/tracing/rtla/tests/timerlat.t index 20f68bcbcb27..3557adbdebae 100644 --- a/tools/tracing/rtla/tests/timerlat.t +++ b/tools/tracing/rtla/tests/timerlat.t @@ -26,9 +26,6 @@ check_top_hist "verify help page" \ "timerlat TOOL --help" 0 "rtla timerlat" check_top_hist "verify -s/--stack" \ "timerlat TOOL -s 3 -T 10 -t" 2 "Blocking thread stack trace" -check_top_hist "verify -P/--priority" \ - "timerlat TOOL -P F:1 -c 0 -d 10s -T 1 --on-threshold shell,command=\"tests/scripts/check-priority.sh SCHED_FIFO 1\"" \ - 2 "Priorities are set correctly" 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" \ @@ -41,10 +38,19 @@ 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=\"tests/scripts/check-priority.sh SCHED_FIFO 1\"" \ + 2 "Priorities are set correctly" check_top_q_hist "verify -c/--cpus" \ "timerlat TOOL -c 0 -d 10s -T 1 --on-threshold shell,command=tests/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=tests/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=tests/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=tests/scripts/check-user-kernel-threads.sh" 2 "0 kernel threads, 1 user threads" # Histogram tests check "hist with -b/--bucket-size" \ From 117a366c5b000c4b4292d94dd1397e62d9a44d79 Mon Sep 17 00:00:00 2001 From: Tomas Glozar Date: Thu, 23 Apr 2026 15:05:58 +0200 Subject: [PATCH 10/26] rtla/tests: Add runtime tests for -C/--cgroup Add a new script check-cgroup-match.sh that retrieves the cgroup of the main rtla process and compares it to the cgroup of the rtla workload threads. Add a new test based on this script, for both osnoise and timerlat tools, testing the variant of -C without argument (which sets the cgroup of the workload to the cgroup of the rtla main process). Note that this has to be tested in kernel mode to be significant for timerlat tool, as user workloads inherit the parent rtla process cgroup even without the option. Reviewed-by: Wander Lairson Costa Link: https://lore.kernel.org/r/20260423130558.882022-10-tglozar@redhat.com Signed-off-by: Tomas Glozar --- tools/tracing/rtla/tests/osnoise.t | 3 +++ .../rtla/tests/scripts/check-cgroup-match.sh | 17 +++++++++++++++++ tools/tracing/rtla/tests/timerlat.t | 3 +++ 3 files changed, 23 insertions(+) create mode 100755 tools/tracing/rtla/tests/scripts/check-cgroup-match.sh diff --git a/tools/tracing/rtla/tests/osnoise.t b/tools/tracing/rtla/tests/osnoise.t index d0b623233db5..06787471d0e8 100644 --- a/tools/tracing/rtla/tests/osnoise.t +++ b/tools/tracing/rtla/tests/osnoise.t @@ -18,6 +18,9 @@ check_top_q_hist "verify the --trace param" \ 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=\"tests/scripts/check-priority.sh SCHED_FIFO 1\"" \ 2 "Priorities are set correctly" +check_top_q_hist "verify the -C/--cgroup param" \ + "osnoise TOOL -C -c 0 -r 900000 -d 10s -S 1 --on-threshold shell,command=\"tests/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=tests/scripts/check-cpus.sh" 2 "^Affinity of threads: 0$" check_top_q_hist "verify the -H/--house-keeping param" \ diff --git a/tools/tracing/rtla/tests/scripts/check-cgroup-match.sh b/tools/tracing/rtla/tests/scripts/check-cgroup-match.sh new file mode 100755 index 000000000000..fdc2c68c5957 --- /dev/null +++ b/tools/tracing/rtla/tests/scripts/check-cgroup-match.sh @@ -0,0 +1,17 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +. "$(dirname $0)/lib/get_workload_pids.sh" +rtla_pid=$(echo $(ps -o ppid= $$)) +rtla_cgroup=$( Date: Fri, 24 Apr 2026 16:02:44 +0200 Subject: [PATCH 11/26] rtla/tests: Add unit tests for actions module Add unit tests covering all functions in the actions module, including both valid and invalid inputs and all action types, except for actions_perform(), where only shell and continue actions are tested. To support testing multiple modules, the unit test build was modified so that it links the entire rtla-in.o file. For this to work, the main() function in rtla.c was declared weak, so that the unit test main is able to override it. Other included minor changes to unit tests are: - Make unit test output verbose to show which tests are being run, now that we have more than 3 tests. - Add unit_tests file to .gitignore. - Split unit test sources to one file per test suite, and keep only main() function in unit_tests.c. - Fix Makefile dependencies so that "make unit-tests" will rebuild the binary with the changes in the commit. Also with the linking the entire rtla-in.o file, it now has rtla's nr_cpus symbol, so the declaration in utils unit tests is made extern. Assisted-by: Composer:composer-2-fast Link: https://lore.kernel.org/r/20260424140244.958495-1-tglozar@redhat.com Signed-off-by: Tomas Glozar --- tools/tracing/rtla/.gitignore | 1 + tools/tracing/rtla/src/rtla.c | 3 + tools/tracing/rtla/tests/unit/Build | 3 +- tools/tracing/rtla/tests/unit/Makefile.unit | 6 +- tools/tracing/rtla/tests/unit/actions.c | 380 ++++++++++++++++++++ tools/tracing/rtla/tests/unit/unit_tests.c | 107 +----- tools/tracing/rtla/tests/unit/utils.c | 106 ++++++ 7 files changed, 502 insertions(+), 104 deletions(-) create mode 100644 tools/tracing/rtla/tests/unit/actions.c create mode 100644 tools/tracing/rtla/tests/unit/utils.c diff --git a/tools/tracing/rtla/.gitignore b/tools/tracing/rtla/.gitignore index 4d39d64ac08c..231fb8d67f97 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 diff --git a/tools/tracing/rtla/src/rtla.c b/tools/tracing/rtla/src/rtla.c index 7635c70123ab..3398250076ea 100644 --- a/tools/tracing/rtla/src/rtla.c +++ b/tools/tracing/rtla/src/rtla.c @@ -61,6 +61,9 @@ int run_command(int argc, char **argv, int start_position) 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; diff --git a/tools/tracing/rtla/tests/unit/Build b/tools/tracing/rtla/tests/unit/Build index 5f1e531ea8c9..2749f4cf202a 100644 --- a/tools/tracing/rtla/tests/unit/Build +++ b/tools/tracing/rtla/tests/unit/Build @@ -1,2 +1,3 @@ +unit_tests-y += utils.o +unit_tests-y += actions.o unit_tests-y += unit_tests.o -unit_tests-y +=../../src/utils.o diff --git a/tools/tracing/rtla/tests/unit/Makefile.unit b/tools/tracing/rtla/tests/unit/Makefile.unit index 2088c9cc3571..bacb00164e46 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) + $(QUIET_LINK)$(CC) $(LDFLAGS) -o $@ $^ $(EXTLIBS) -lcheck -$(UNIT_TESTS_IN): +$(UNIT_TESTS_IN): fixdep 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 000000000000..a5808ab71a4d --- /dev/null +++ b/tools/tracing/rtla/tests/unit/actions.c @@ -0,0 +1,380 @@ +// SPDX-License-Identifier: GPL-2.0 + +#define _GNU_SOURCE +#include +#include + +#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 + +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); + 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 f3c6d89e3300..f87d761f9b12 100644 --- a/tools/tracing/rtla/tests/unit/unit_tests.c +++ b/tools/tracing/rtla/tests/unit/unit_tests.c @@ -2,115 +2,22 @@ #define _GNU_SOURCE #include -#include -#include -#include -#include -#include -#include +#include #include "../../src/utils.h" -int nr_cpus; -START_TEST(test_strtoi) -{ - int result; - char buf[64]; +Suite *utils_suite(void); +Suite *actions_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; sr = srunner_create(utils_suite()); - srunner_run_all(sr, CK_NORMAL); + srunner_add_suite(sr, actions_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 000000000000..ce53cab49457 --- /dev/null +++ b/tools/tracing/rtla/tests/unit/utils.c @@ -0,0 +1,106 @@ +// SPDX-License-Identifier: GPL-2.0 + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include + +#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; +} From f03a59f949176ce4312cb466245d1243aaf40389 Mon Sep 17 00:00:00 2001 From: Crystal Wood Date: Tue, 12 May 2026 12:37:31 -0500 Subject: [PATCH 12/26] rtla: Stop the record trace on interrupt Before, when rtla got a signal, it stopped the main trace but not the record trace. With "--on-end trace", this can lead to save_trace_to_file() failing to keep up, especially on a debug kernel. Plus, it adds post-stoppage noise to the trace file. Signed-off-by: Crystal Wood Fixes: c73cab9dbed0 ("rtla/timerlat_hist: Stop timerlat tracer on signal") Fixes: a4dfce7559d7 ("rtla/timerlat_top: Stop timerlat tracer on signal") Fixes: 3aadb65db5d6 ("rtla/timerlat: Add action on end feature") Link: https://lore.kernel.org/r/20260512173731.2151841-1-crwood@redhat.com Signed-off-by: Tomas Glozar --- tools/tracing/rtla/src/common.c | 19 +++++++++++-------- tools/tracing/rtla/src/common.h | 1 - tools/tracing/rtla/src/timerlat.c | 2 +- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/tools/tracing/rtla/src/common.c b/tools/tracing/rtla/src/common.c index 35e3d3aa922e..effad523e8cf 100644 --- a/tools/tracing/rtla/src/common.c +++ b/tools/tracing/rtla/src/common.c @@ -10,7 +10,7 @@ #include "common.h" -struct trace_instance *trace_inst; +struct osnoise_tool *trace_tool; volatile int stop_tracing; int nr_cpus; @@ -21,12 +21,16 @@ static void stop_trace(int sig) * Stop requested twice in a row; abort event processing and * exit immediately */ - tracefs_iterate_stop(trace_inst->inst); + if (trace_tool) + tracefs_iterate_stop(trace_tool->trace.inst); return; } stop_tracing = 1; - if (trace_inst) - trace_instance_stop(trace_inst); + if (trace_tool) { + trace_instance_stop(&trace_tool->trace); + if (trace_tool->record) + trace_instance_stop(&trace_tool->record->trace); + } } /* @@ -273,11 +277,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) { @@ -285,7 +288,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 51665db4ffce..eba40b6d9504 100644 --- a/tools/tracing/rtla/src/common.h +++ b/tools/tracing/rtla/src/common.h @@ -54,7 +54,6 @@ struct osnoise_context { int opt_workload; }; -extern struct trace_instance *trace_inst; extern volatile int stop_tracing; struct hist_params { diff --git a/tools/tracing/rtla/src/timerlat.c b/tools/tracing/rtla/src/timerlat.c index f8c057518d22..637f68d684f5 100644 --- a/tools/tracing/rtla/src/timerlat.c +++ b/tools/tracing/rtla/src/timerlat.c @@ -202,7 +202,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); From dd520daffbc901f10d49a51a58313547d417b506 Mon Sep 17 00:00:00 2001 From: Tomas Glozar Date: Tue, 26 May 2026 12:25:20 +0200 Subject: [PATCH 13/26] rtla/actions: Restore continue flag in actions_perform() Currently, actions_perform() only ever sets the continue flag (when performing the continue action), but never resets it. That leads to RTLA continuing tracing even if the continue action was not performed in the current iteration. For example, the following command: $ rtla timerlat hist -T 100 --on-threshold shell,command=' echo Spike! if [ -f /tmp/a ] then exit 1 else touch /tmp/a fi' --on-threshold continue should print Spike! at most once, because after hitting the threshold for the first time, /tmp/a exists, the shell action will fail, and the continue action is not performed. However, unless /tmp/a exists before the measurement, it will print Spike! until stopped, as the continue flag stays set. Set the continue flag to false in the beginning of actions_perform() to make RTLA continue only if the action was actually performed. Fixes: 8d933d5c89e8 ("rtla/timerlat: Add continue action") Link: https://lore.kernel.org/r/20260526102523.2662391-1-tglozar@redhat.com [ correct Fixes tag to include 12 characters of hash ] Signed-off-by: Tomas Glozar --- tools/tracing/rtla/src/actions.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tools/tracing/rtla/src/actions.c b/tools/tracing/rtla/src/actions.c index b0d68b5de08d..bf13d9d68f16 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: From a92bd1a28b5dab2899b9f51dd2780b2d9e52e324 Mon Sep 17 00:00:00 2001 From: Tomas Glozar Date: Tue, 26 May 2026 12:25:21 +0200 Subject: [PATCH 14/26] rtla/tests: Add unit test for restoring continue flag In case an action preceding the continue action fails, not only the continue flag should not be set, it should be unset if it was set from a previous run of actions_perform(). Add a unit test to check if this is implemented correctly. Link: https://lore.kernel.org/r/20260526102523.2662391-2-tglozar@redhat.com Signed-off-by: Tomas Glozar --- tools/tracing/rtla/tests/unit/actions.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tools/tracing/rtla/tests/unit/actions.c b/tools/tracing/rtla/tests/unit/actions.c index a5808ab71a4d..94ad5ad42774 100644 --- a/tools/tracing/rtla/tests/unit/actions.c +++ b/tools/tracing/rtla/tests/unit/actions.c @@ -328,6 +328,18 @@ START_TEST(test_actions_perform_continue_after_failed_shell_command) } 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"); @@ -374,6 +386,7 @@ Suite *actions_suite(void) 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; From ad5b50a0959fb67100ddb93aeb0ea40201272cb2 Mon Sep 17 00:00:00 2001 From: Tomas Glozar Date: Tue, 26 May 2026 12:25:22 +0200 Subject: [PATCH 15/26] rtla/tests: Run runtime tests in temporary directory Create a temporary directory before each test case to serve as working directory during the duration of the test. This prevents littering of the original working directory as well as allows tests to use it to avoid path conflicts. In order not to break already existing tests, also add a new "testdir" variable containing the directory where the test file is located. This is then used to locate artifacts used during testing like BPF programs and scripts for checking the tracer threads. Link: https://lore.kernel.org/r/20260526102523.2662391-3-tglozar@redhat.com Signed-off-by: Tomas Glozar --- tools/tracing/rtla/tests/engine.sh | 12 ++++++++++++ tools/tracing/rtla/tests/osnoise.t | 8 ++++---- tools/tracing/rtla/tests/timerlat.t | 16 ++++++++-------- 3 files changed, 24 insertions(+), 12 deletions(-) diff --git a/tools/tracing/rtla/tests/engine.sh b/tools/tracing/rtla/tests/engine.sh index 27d92f19a322..5bf8453d354d 100644 --- a/tools/tracing/rtla/tests/engine.sh +++ b/tools/tracing/rtla/tests/engine.sh @@ -4,6 +4,9 @@ test_begin() { # Count tests to allow the test harness to double-check if all were # included correctly. ctr=0 + # Set test directory to the directory of the script + scriptfile=$(realpath "$0") + testdir=$(dirname "$scriptfile") [ -z "$RTLA" ] && RTLA="./rtla" [ -n "$TEST_COUNT" ] && echo "1..$TEST_COUNT" } @@ -51,6 +54,11 @@ check() { then # Reset osnoise options before running test. [ "$NO_RESET_OSNOISE" == 1 ] || reset_osnoise + + # Create a temporary directory to contain rtla output + tmpdir=$(mktemp -d) + pushd $tmpdir >/dev/null + # Run rtla; in case of failure, include its output as comment # in the test results. result=$(eval stdbuf -oL $TIMEOUT "$RTLA" $2 2>&1); exitcode=$? @@ -82,6 +90,10 @@ check() { echo "$result" | col -b | while read line; do echo "# $line"; done printf "#\n# exit code %s\n" $exitcode fi + + # Remove temporary directory + popd >/dev/null + rm -r $tmpdir fi } diff --git a/tools/tracing/rtla/tests/osnoise.t b/tools/tracing/rtla/tests/osnoise.t index 06787471d0e8..9c2f84a4187d 100644 --- a/tools/tracing/rtla/tests/osnoise.t +++ b/tools/tracing/rtla/tests/osnoise.t @@ -16,15 +16,15 @@ check_top_q_hist "verify the --trace param" \ # 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=\"tests/scripts/check-priority.sh SCHED_FIFO 1\"" \ + "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_top_q_hist "verify the -C/--cgroup param" \ - "osnoise TOOL -C -c 0 -r 900000 -d 10s -S 1 --on-threshold shell,command=\"tests/scripts/check-cgroup-match.sh\"" \ + "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=tests/scripts/check-cpus.sh" 2 "^Affinity of threads: 0$" + "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=tests/scripts/check-housekeeping-cpus.sh" 2 "^Affinity of threads: 0$" + "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" \ diff --git a/tools/tracing/rtla/tests/timerlat.t b/tools/tracing/rtla/tests/timerlat.t index 3ebfe316b39e..f3e5f99e862b 100644 --- a/tools/tracing/rtla/tests/timerlat.t +++ b/tools/tracing/rtla/tests/timerlat.t @@ -41,19 +41,19 @@ check_top_hist "disable auto-analysis" \ # Thread tests check_top_hist "verify -P/--priority" \ - "timerlat TOOL -P F:1 -c 0 -d 10s -T 1 --on-threshold shell,command=\"tests/scripts/check-priority.sh SCHED_FIFO 1\"" \ + "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_top_hist "verify -C/--cgroup" \ - "timerlat TOOL -k -C -c 0 -d 10s -T 1 --on-threshold shell,command=\"tests/scripts/check-cgroup-match.sh\"" \ + "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=tests/scripts/check-cpus.sh" 2 "^Affinity of threads: 0$" + "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=tests/scripts/check-housekeeping-cpus.sh" 2 "^Affinity of threads: 0$" + "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=tests/scripts/check-user-kernel-threads.sh" 2 "1 kernel threads, 0 user 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=tests/scripts/check-user-kernel-threads.sh" 2 "0 kernel threads, 1 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" \ @@ -103,12 +103,12 @@ then # Test BPF action program properly in BPF mode [ -z "$BPFTOOL" ] && BPFTOOL=bpftool check_top_q_hist "with BPF action program (BPF mode)" \ - "timerlat TOOL -T 2 --bpf-action tests/bpf/bpf_action_map.o --on-threshold shell,command='$BPFTOOL map dump name rtla_test_map'" \ + "timerlat TOOL -T 2 --bpf-action $testdir/bpf/bpf_action_map.o --on-threshold shell,command='$BPFTOOL map dump name rtla_test_map'" \ 2 '"value": 42' else # Test BPF action program failure in non-BPF mode check_top_q_hist "with BPF action program (non-BPF mode)" \ - "timerlat TOOL -T 2 --bpf-action tests/bpf/bpf_action_map.o" \ + "timerlat TOOL -T 2 --bpf-action $testdir/bpf/bpf_action_map.o" \ 1 "BPF actions are not supported in tracefs-only mode" fi done From 6a1700b2f402190d01e2a5fd9d39f6a20bd9dc01 Mon Sep 17 00:00:00 2001 From: Tomas Glozar Date: Tue, 26 May 2026 12:25:23 +0200 Subject: [PATCH 16/26] rtla/tests: Add runtime tests for restoring continue flag In case an action preceding the continue action fails, not only the continue flag should not be set, it should be unset if it was set from a previous run of actions_perform(). Add a runtime test to both osnoise and timerlat tools that checks that this works properly by creating a temporary file. Link: https://lore.kernel.org/r/20260526102523.2662391-4-tglozar@redhat.com Signed-off-by: Tomas Glozar --- tools/tracing/rtla/tests/osnoise.t | 2 ++ tools/tracing/rtla/tests/timerlat.t | 2 ++ 2 files changed, 4 insertions(+) diff --git a/tools/tracing/rtla/tests/osnoise.t b/tools/tracing/rtla/tests/osnoise.t index 9c2f84a4187d..a7956ab605cd 100644 --- a/tools/tracing/rtla/tests/osnoise.t +++ b/tools/tracing/rtla/tests/osnoise.t @@ -65,6 +65,8 @@ 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_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$" diff --git a/tools/tracing/rtla/tests/timerlat.t b/tools/tracing/rtla/tests/timerlat.t index f3e5f99e862b..19fd5af26ebb 100644 --- a/tools/tracing/rtla/tests/timerlat.t +++ b/tools/tracing/rtla/tests/timerlat.t @@ -94,6 +94,8 @@ 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_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$" From 48209d763c22354c78f6138f4a83814c2f2a3578 Mon Sep 17 00:00:00 2001 From: Tomas Glozar Date: Thu, 28 May 2026 12:32:49 +0200 Subject: [PATCH 17/26] rtla: Add libsubcmd dependency In preparation for migrating RTLA to libsubcmd, build libsubcmd from the appropriate directory next to the RTLA build proper, and link the resulting object to RTLA. libsubcmd uses str_error_r() and strlcpy() at several places. To support these, also link the respective libraries from tools/lib. For completeness, also add tools/include to include path. This will allow other userspace functions and macros shipped with the kernel to be used in RTLA; perf and bpftool, two other users of libsubcmd, already do that. To prevent a name conflict, rename RTLA's run_command() function to run_tool_command(), and replace RTLA's own container_of implementation with the one in tools/include/linux/container_of.h. Assisted-by: Composer:composer-1 Link: https://lore.kernel.org/r/20260528103254.2990068-2-tglozar@redhat.com Signed-off-by: Tomas Glozar --- tools/tracing/rtla/.gitignore | 2 ++ tools/tracing/rtla/Makefile | 66 ++++++++++++++++++++++++++++++---- tools/tracing/rtla/src/rtla.c | 8 ++--- tools/tracing/rtla/src/utils.h | 6 ++-- 4 files changed, 67 insertions(+), 15 deletions(-) diff --git a/tools/tracing/rtla/.gitignore b/tools/tracing/rtla/.gitignore index 231fb8d67f97..c7b4bf1c8ba9 100644 --- a/tools/tracing/rtla/.gitignore +++ b/tools/tracing/rtla/.gitignore @@ -10,3 +10,5 @@ custom_filename.txt 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 45690ee14544..60a102538988 100644 --- a/tools/tracing/rtla/Makefile +++ b/tools/tracing/rtla/Makefile @@ -27,6 +27,30 @@ endif RTLA := $(OUTPUT)rtla RTLA_IN := $(RTLA)-in.o +LIBSUBCMD_DIR = $(srctree)/tools/lib/subcmd/ +ifneq ($(OUTPUT),) + LIBSUBCMD_OUTPUT = $(abspath $(OUTPUT))/libsubcmd +else + LIBSUBCMD_OUTPUT = $(CURDIR)/libsubcmd +endif +LIBSUBCMD = $(LIBSUBCMD_OUTPUT)/libsubcmd.a +LIBSUBCMD_INCLUDES = $(LIBSUBCMD_OUTPUT)/include +LIBSUBCMD_MAKEFLAGS = O=$(LIBSUBCMD_OUTPUT) DESTDIR=$(LIBSUBCMD_OUTPUT) prefix= subdir= + +TOOLS_INCLUDES = -I$(srctree)/tools/include + +ifneq ($(OUTPUT),) + LIB_OUTPUT = $(abspath $(OUTPUT))/lib +else + LIB_OUTPUT = $(CURDIR)/lib +endif + +LIB_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 @@ ifeq ($(config),1) 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 @@ tests/bpf/bpf_action_map.o: tests/bpf/bpf_action_map.c $(Q)echo "BPF skeleton support is disabled, skipping tests/bpf/bpf_action_map.o" endif -$(RTLA): $(RTLA_IN) - $(QUIET_LINK)$(CC) $(LDFLAGS) -o $(RTLA) $(RTLA_IN) $(EXTLIBS) +$(RTLA): $(RTLA_IN) $(LIBSUBCMD) $(LIB_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/src/rtla.c b/tools/tracing/rtla/src/rtla.c index 3398250076ea..f4342ca684c3 100644 --- a/tools/tracing/rtla/src/rtla.c +++ b/tools/tracing/rtla/src/rtla.c @@ -38,12 +38,12 @@ static void rtla_usage(int err) } /* - * run_command - try to run a rtla tool command + * 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_command(int argc, char **argv, int start_position) +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]); @@ -69,7 +69,7 @@ int main(int argc, char *argv[]) int retval; /* is it an alias? */ - retval = run_command(argc, argv, 0); + retval = run_tool_command(argc, argv, 0); if (retval) exit(0); @@ -82,7 +82,7 @@ int main(int argc, char *argv[]) rtla_usage(0); } - retval = run_command(argc, argv, 1); + retval = run_tool_command(argc, argv, 1); if (retval) exit(0); diff --git a/tools/tracing/rtla/src/utils.h b/tools/tracing/rtla/src/utils.h index e794ede64b2c..96fd72042717 100644 --- a/tools/tracing/rtla/src/utils.h +++ b/tools/tracing/rtla/src/utils.h @@ -7,6 +7,8 @@ #include #include +#include + /* * '18446744073709551615\0' */ @@ -37,10 +39,6 @@ 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; void debug_msg(const char *fmt, ...); void err_msg(const char *fmt, ...); From 534d9a93dbff2f964a85aef7a7fbfba82d2277bb Mon Sep 17 00:00:00 2001 From: Tomas Glozar Date: Thu, 28 May 2026 12:32:50 +0200 Subject: [PATCH 18/26] tools subcmd: support optarg as separate argument In addition to "-ovalue" and "--opt=value" syntax, allow also "-o value" and "--opt value" for options with optional argument when the newly added PARSE_OPT_OPTARG_ALLOW_NEXT flag is set. This behavior is turned off by default since it does not make sense for tools using non-option command line arguments. Consider the ambiguity of "cmd -d x", where "-d x" can mean either "-d with argument of x" or "-d without argument, followed by non-option argument x". This is not an issue in the case that the tool takes no non-option arguments. To implement this, a new local variable, force_defval, is created in get_value(), along with a comment explaining the logic. Link: https://lore.kernel.org/r/20260528103254.2990068-3-tglozar@redhat.com Signed-off-by: Tomas Glozar --- tools/lib/subcmd/parse-options.c | 53 +++++++++++++++++++++++++++----- tools/lib/subcmd/parse-options.h | 1 + 2 files changed, 46 insertions(+), 8 deletions(-) diff --git a/tools/lib/subcmd/parse-options.c b/tools/lib/subcmd/parse-options.c index 555d617c1f50..664b2053bb77 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; } diff --git a/tools/lib/subcmd/parse-options.h b/tools/lib/subcmd/parse-options.h index 8e9147358a28..c573a0ca5ca6 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 { From da62fc345846211442d01feeae34b376e4242c89 Mon Sep 17 00:00:00 2001 From: Tomas Glozar Date: Thu, 28 May 2026 12:32:51 +0200 Subject: [PATCH 19/26] tools subcmd: allow parsing distinct --opt and --no-opt libsubcmd automatically generates for every option --opt an equivalent negated option, --no-opt, to unset the option. Vice versa, for every option declared as --no-opt, a shorthand --opt is declared for convenience. Add a flag, PARSE_OPT_NOAUTONEG, to disable this behavior. This new flag behaves similarly to the already existing PARSE_OPT_NONEG, only it does not reject the --no-opt variant, but leaves it undefined. That is useful when there is a conflicting distinct --no-opt option in the syntax of the tool. PARSE_OPT_NOAUTONEG is enabled per-option, allowing to unset other options that do not have this conflict. Link: https://lore.kernel.org/r/20260528103254.2990068-4-tglozar@redhat.com Signed-off-by: Tomas Glozar --- tools/lib/subcmd/parse-options.c | 10 ++++++---- tools/lib/subcmd/parse-options.h | 3 +++ 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/tools/lib/subcmd/parse-options.c b/tools/lib/subcmd/parse-options.c index 664b2053bb77..e83200e9f56a 100644 --- a/tools/lib/subcmd/parse-options.c +++ b/tools/lib/subcmd/parse-options.c @@ -427,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 @@ -465,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); @@ -1019,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 c573a0ca5ca6..38df5fd21963 100644 --- a/tools/lib/subcmd/parse-options.h +++ b/tools/lib/subcmd/parse-options.h @@ -47,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; @@ -149,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) \ From 5d9af63e80b5a202e69ce5bcf54af320e46f397a Mon Sep 17 00:00:00 2001 From: Tomas Glozar Date: Thu, 28 May 2026 12:32:52 +0200 Subject: [PATCH 20/26] rtla: Parse cmdline using libsubcmd Instead of using getopt_long() directly to parse the command line arguments given to an RTLA tool, use libsubcmd's parse_options(). Utilizing libsubcmd for parsing command line arguments has several benefits: - A help message is automatically generated by libsubcmd from the specification, removing the need of writing it by hand. - Options are sorted into groups based on which part of tracing (CPU, thread, auto-analysis, tuning, histogram) they relate to. - Common parsing patterns for numerical and boolean values now share code, with the target variable being stored in the option array. To avoid duplication of the option parsing logic, RTLA-specific macros defining struct option values are created: - RTLA_OPT_* for options common to all tools - OSNOISE_OPT_* and TIMERLAT_OPT_* for options specific to osnoise/timerlat tools - HIST_OPT_* macros for options specific to histogram-based tools. Individual *_parse_args() functions then construct an array out of these macros that is then passed to libsubcmd's parse_options(). All code specific to command line options parsing is moved out of the individual tool files into a new file, cli.c, which also contains the contents of the rtla.c file. A private header, cli_p.h, is added alongside the public header cli.h, so that unit tests are able to test statically declared option callbacks. Minor changes: - The return value of tool-level help option changes to 129, as this is the value set by libsubcmd; this is reflected in affected test cases. The implementation of help for command-level and tracer-level help is set to 129 as well for consistency, and the change is reflected in exit value documentation. - Related to the above, {rtla,osnoise,timerlat}_usage() are marked __noreturn and exit() is removed from after they are called for cleaner code. - The error messages for invalid argument for options --dma-latency and -E/--entries were corrected, fixing off-by-one in the limits. Note that unsetting options (using --no- syntax) is currently not implemented for options that use custom callbacks. For --irq and --thread, it will never be implemented, as they conflict with already existing --no-irq and --no-thread with a different meaning. Assisted-by: Composer:composer-1.5 Link: https://lore.kernel.org/r/20260528103254.2990068-5-tglozar@redhat.com Signed-off-by: Tomas Glozar --- Documentation/tools/rtla/common_appendix.txt | 7 +- tools/tracing/rtla/src/Build | 2 +- tools/tracing/rtla/src/cli.c | 537 +++++++++++++++ tools/tracing/rtla/src/cli.h | 7 + tools/tracing/rtla/src/cli_p.h | 670 +++++++++++++++++++ tools/tracing/rtla/src/common.c | 109 --- tools/tracing/rtla/src/common.h | 27 +- tools/tracing/rtla/src/osnoise.c | 9 +- tools/tracing/rtla/src/osnoise_hist.c | 221 +----- tools/tracing/rtla/src/osnoise_top.c | 200 +----- tools/tracing/rtla/src/rtla.c | 92 --- tools/tracing/rtla/src/timerlat.c | 9 +- tools/tracing/rtla/src/timerlat.h | 4 +- tools/tracing/rtla/src/timerlat_hist.c | 317 +-------- tools/tracing/rtla/src/timerlat_top.c | 286 +------- tools/tracing/rtla/src/utils.c | 28 +- tools/tracing/rtla/src/utils.h | 3 +- tools/tracing/rtla/tests/hwnoise.t | 2 +- tools/tracing/rtla/tests/osnoise.t | 6 +- tools/tracing/rtla/tests/timerlat.t | 6 +- 20 files changed, 1256 insertions(+), 1286 deletions(-) create mode 100644 tools/tracing/rtla/src/cli.c create mode 100644 tools/tracing/rtla/src/cli.h create mode 100644 tools/tracing/rtla/src/cli_p.h delete mode 100644 tools/tracing/rtla/src/rtla.c diff --git a/Documentation/tools/rtla/common_appendix.txt b/Documentation/tools/rtla/common_appendix.txt index 8c90a02588e7..68cb15840d3a 100644 --- a/Documentation/tools/rtla/common_appendix.txt +++ b/Documentation/tools/rtla/common_appendix.txt @@ -26,9 +26,10 @@ EXIT STATUS :: - 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/tools/tracing/rtla/src/Build b/tools/tracing/rtla/src/Build index 329e24a40cf7..a1f3ab927207 100644 --- a/tools/tracing/rtla/src/Build +++ b/tools/tracing/rtla/src/Build @@ -11,4 +11,4 @@ rtla-y += timerlat_hist.o rtla-y += timerlat_u.o rtla-y += timerlat_aa.o rtla-y += timerlat_bpf.o -rtla-y += rtla.o +rtla-y += cli.o diff --git a/tools/tracing/rtla/src/cli.c b/tools/tracing/rtla/src/cli.c new file mode 100644 index 000000000000..7f531519df44 --- /dev/null +++ b/tools/tracing/rtla/src/cli.c @@ -0,0 +1,537 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2021 Red Hat Inc, Daniel Bristot de Oliveira + */ + +#define _GNU_SOURCE +#include +#include +#include +#include +#include + +#include + +#define RTLA_ALLOW_CLI_P_H +#include "cli_p.h" + +static const char * const osnoise_top_usage[] = { + "rtla osnoise [top] [] [-h|--help]", + NULL, +}; + +static const char * const osnoise_hist_usage[] = { + "rtla osnoise hist [] [-h|--help]", + NULL, +}; + +static const char * const timerlat_top_usage[] = { + "rtla timerlat [top] [] [-h|--help]", + NULL, +}; + +static const char * const timerlat_hist_usage[] = { + "rtla timerlat hist [] [-h|--help]", + NULL, +}; + +static const char * const hwnoise_usage[] = { + "rtla hwnoise [] [-h|--help]", + NULL, +}; + +static const int common_parse_options_flags = PARSE_OPT_OPTARG_ALLOW_NEXT; + +bool in_unit_test; + +/* + * osnoise_top_parse_args - allocs, parse and fill the cmd line parameters + */ +struct common_params *osnoise_top_parse_args(int argc, char **argv) +{ + struct osnoise_params *params; + struct osnoise_cb_data cb_data; + const char * const *usage; + + params = calloc_fatal(1, sizeof(*params)); + + cb_data.params = params; + cb_data.trace_output = NULL; + + if (strcmp(argv[0], "hwnoise") == 0) { + params->mode = MODE_HWNOISE; + /* + * Reduce CPU usage for 75% to avoid killing the system. + */ + params->runtime = 750000; + params->period = 1000000; + usage = hwnoise_usage; + } else { + usage = osnoise_top_usage; + } + + const struct option osnoise_top_options[] = { + OPT_GROUP("Tracing Options:"), + OSNOISE_OPT_PERIOD, + OSNOISE_OPT_RUNTIME, + RTLA_OPT_STOP('s', "stop", "single sample"), + RTLA_OPT_STOP_TOTAL('S', "stop-total", "total sample"), + OSNOISE_OPT_THRESHOLD, + RTLA_OPT_TRACE_OUTPUT("osnoise", opt_osnoise_trace_output_cb), + + OPT_GROUP("Event Configuration:"), + RTLA_OPT_EVENT, + RTLA_OPT_FILTER, + RTLA_OPT_TRIGGER, + + OPT_GROUP("CPU Configuration:"), + RTLA_OPT_CPUS, + RTLA_OPT_HOUSEKEEPING, + + OPT_GROUP("Thread Configuration:"), + RTLA_OPT_PRIORITY, + RTLA_OPT_CGROUP, + + OPT_GROUP("Output:"), + RTLA_OPT_QUIET, + + OPT_GROUP("System Tuning:"), + RTLA_OPT_TRACE_BUFFER_SIZE, + RTLA_OPT_WARM_UP, + + OPT_GROUP("Auto Analysis and Actions:"), + RTLA_OPT_AUTO(opt_osnoise_auto_cb), + RTLA_OPT_ON_THRESHOLD("stop-total", opt_osnoise_on_threshold_cb), + RTLA_OPT_ON_END(opt_osnoise_on_end_cb), + + OPT_GROUP("General:"), + RTLA_OPT_DURATION, + RTLA_OPT_DEBUG, + + OPT_END(), + }; + + actions_init(¶ms->common.threshold_actions); + actions_init(¶ms->common.end_actions); + + argc = parse_options(argc, (const char **)argv, + osnoise_top_options, + usage, + common_parse_options_flags); + if (argc < 0) + return NULL; + + if (cb_data.trace_output) + actions_add_trace_output(¶ms->common.threshold_actions, cb_data.trace_output); + + if (geteuid()) + fatal("osnoise needs root permission"); + + return ¶ms->common; +} + +/* + * osnoise_hist_parse_args - allocs, parse and fill the cmd line parameters + */ +struct common_params *osnoise_hist_parse_args(int argc, char **argv) +{ + struct osnoise_params *params; + struct osnoise_cb_data cb_data; + + params = calloc_fatal(1, sizeof(*params)); + + cb_data.params = params; + cb_data.trace_output = NULL; + + const struct option osnoise_hist_options[] = { + OPT_GROUP("Tracing Options:"), + OSNOISE_OPT_PERIOD, + OSNOISE_OPT_RUNTIME, + RTLA_OPT_STOP('s', "stop", "single sample"), + RTLA_OPT_STOP_TOTAL('S', "stop-total", "total sample"), + OSNOISE_OPT_THRESHOLD, + RTLA_OPT_TRACE_OUTPUT("osnoise", opt_osnoise_trace_output_cb), + + OPT_GROUP("Event Configuration:"), + RTLA_OPT_EVENT, + RTLA_OPT_FILTER, + RTLA_OPT_TRIGGER, + + OPT_GROUP("CPU Configuration:"), + RTLA_OPT_CPUS, + RTLA_OPT_HOUSEKEEPING, + + OPT_GROUP("Thread Configuration:"), + RTLA_OPT_PRIORITY, + RTLA_OPT_CGROUP, + + OPT_GROUP("Histogram Options:"), + HIST_OPT_BUCKET_SIZE, + HIST_OPT_ENTRIES, + HIST_OPT_NO_HEADER, + HIST_OPT_NO_SUMMARY, + HIST_OPT_NO_INDEX, + HIST_OPT_WITH_ZEROS, + + OPT_GROUP("System Tuning:"), + RTLA_OPT_TRACE_BUFFER_SIZE, + RTLA_OPT_WARM_UP, + + OPT_GROUP("Auto Analysis and Actions:"), + RTLA_OPT_AUTO(opt_osnoise_auto_cb), + RTLA_OPT_ON_THRESHOLD("stop-total", opt_osnoise_on_threshold_cb), + RTLA_OPT_ON_END(opt_osnoise_on_end_cb), + + OPT_GROUP("General:"), + RTLA_OPT_DURATION, + RTLA_OPT_DEBUG, + + OPT_END(), + }; + + actions_init(¶ms->common.threshold_actions); + actions_init(¶ms->common.end_actions); + + /* display data in microseconds */ + params->common.output_divisor = 1000; + params->common.hist.bucket_size = 1; + params->common.hist.entries = 256; + + argc = parse_options(argc, (const char **)argv, + osnoise_hist_options, osnoise_hist_usage, + common_parse_options_flags); + if (argc < 0) + return NULL; + + if (cb_data.trace_output) + actions_add_trace_output(¶ms->common.threshold_actions, cb_data.trace_output); + + if (geteuid()) + fatal("rtla needs root permission"); + + if (params->common.hist.no_index && !params->common.hist.with_zeros) + fatal("no-index set and with-zeros not set - it does not make sense"); + + return ¶ms->common; +} + +struct common_params *timerlat_top_parse_args(int argc, char **argv) +{ + struct timerlat_params *params; + struct timerlat_cb_data cb_data; + + params = calloc_fatal(1, sizeof(*params)); + + cb_data.params = params; + cb_data.trace_output = NULL; + + const struct option timerlat_top_options[] = { + OPT_GROUP("Tracing Options:"), + TIMERLAT_OPT_PERIOD, + RTLA_OPT_STOP('i', "irq", "irq latency"), + RTLA_OPT_STOP_TOTAL('T', "thread", "thread latency"), + TIMERLAT_OPT_STACK, + RTLA_OPT_TRACE_OUTPUT("timerlat", opt_timerlat_trace_output_cb), + + OPT_GROUP("Event Configuration:"), + RTLA_OPT_EVENT, + RTLA_OPT_FILTER, + RTLA_OPT_TRIGGER, + + OPT_GROUP("CPU Configuration:"), + RTLA_OPT_CPUS, + RTLA_OPT_HOUSEKEEPING, + + OPT_GROUP("Thread Configuration:"), + RTLA_OPT_PRIORITY, + RTLA_OPT_CGROUP, + RTLA_OPT_USER_THREADS, + RTLA_OPT_KERNEL_THREADS, + RTLA_OPT_USER_LOAD, + + OPT_GROUP("Output:"), + TIMERLAT_OPT_NANO, + RTLA_OPT_QUIET, + + OPT_GROUP("System Tuning:"), + TIMERLAT_OPT_DMA_LATENCY, + TIMERLAT_OPT_DEEPEST_IDLE_STATE, + RTLA_OPT_TRACE_BUFFER_SIZE, + RTLA_OPT_WARM_UP, + + OPT_GROUP("Auto Analysis and Actions:"), + RTLA_OPT_AUTO(opt_timerlat_auto_cb), + TIMERLAT_OPT_AA_ONLY, + TIMERLAT_OPT_NO_AA, + TIMERLAT_OPT_DUMPS_TASKS, + RTLA_OPT_ON_THRESHOLD("latency", opt_timerlat_on_threshold_cb), + RTLA_OPT_ON_END(opt_timerlat_on_end_cb), + TIMERLAT_OPT_BPF_ACTION, + TIMERLAT_OPT_STACK_FORMAT, + + OPT_GROUP("General:"), + RTLA_OPT_DURATION, + RTLA_OPT_DEBUG, + + OPT_END(), + }; + + actions_init(¶ms->common.threshold_actions); + actions_init(¶ms->common.end_actions); + + /* disabled by default */ + params->dma_latency = -1; + params->deepest_idle_state = -2; + + /* display data in microseconds */ + params->common.output_divisor = 1000; + + /* default to BPF mode */ + params->mode = TRACING_MODE_BPF; + + /* default to truncate stack format */ + params->stack_format = STACK_FORMAT_TRUNCATE; + + argc = parse_options(argc, (const char **)argv, + timerlat_top_options, timerlat_top_usage, + common_parse_options_flags); + if (argc < 0) + return NULL; + + if (cb_data.trace_output) + actions_add_trace_output(¶ms->common.threshold_actions, cb_data.trace_output); + + if (geteuid()) + fatal("rtla needs root permission"); + + /* + * Auto analysis only happens if stop tracing, thus: + */ + if (!params->common.stop_us && !params->common.stop_total_us) + params->no_aa = 1; + + if (params->no_aa && params->common.aa_only) + fatal("--no-aa and --aa-only are mutually exclusive!"); + + if (params->common.kernel_workload && params->common.user_workload) + fatal("--kernel-threads and --user-threads are mutually exclusive!"); + + /* + * If auto-analysis or trace output is enabled, switch from BPF mode to + * mixed mode + */ + if (params->mode == TRACING_MODE_BPF && + (params->common.threshold_actions.present[ACTION_TRACE_OUTPUT] || + params->common.end_actions.present[ACTION_TRACE_OUTPUT] || + !params->no_aa)) + params->mode = TRACING_MODE_MIXED; + + return ¶ms->common; +} + +struct common_params *timerlat_hist_parse_args(int argc, char **argv) +{ + struct timerlat_params *params; + struct timerlat_cb_data cb_data; + + params = calloc_fatal(1, sizeof(*params)); + + cb_data.params = params; + cb_data.trace_output = NULL; + + const struct option timerlat_hist_options[] = { + OPT_GROUP("Tracing Options:"), + TIMERLAT_OPT_PERIOD, + RTLA_OPT_STOP('i', "irq", "irq latency"), + RTLA_OPT_STOP_TOTAL('T', "thread", "thread latency"), + TIMERLAT_OPT_STACK, + RTLA_OPT_TRACE_OUTPUT("timerlat", opt_timerlat_trace_output_cb), + + OPT_GROUP("Event Configuration:"), + RTLA_OPT_EVENT, + RTLA_OPT_FILTER, + RTLA_OPT_TRIGGER, + + OPT_GROUP("CPU Configuration:"), + RTLA_OPT_CPUS, + RTLA_OPT_HOUSEKEEPING, + + OPT_GROUP("Thread Configuration:"), + RTLA_OPT_PRIORITY, + RTLA_OPT_CGROUP, + RTLA_OPT_USER_THREADS, + RTLA_OPT_KERNEL_THREADS, + RTLA_OPT_USER_LOAD, + + OPT_GROUP("Histogram Options:"), + HIST_OPT_BUCKET_SIZE, + HIST_OPT_ENTRIES, + HIST_OPT_NO_IRQ, + HIST_OPT_NO_THREAD, + HIST_OPT_NO_HEADER, + HIST_OPT_NO_SUMMARY, + HIST_OPT_NO_INDEX, + HIST_OPT_WITH_ZEROS, + + OPT_GROUP("Output:"), + TIMERLAT_OPT_NANO, + + OPT_GROUP("System Tuning:"), + TIMERLAT_OPT_DMA_LATENCY, + TIMERLAT_OPT_DEEPEST_IDLE_STATE, + RTLA_OPT_TRACE_BUFFER_SIZE, + RTLA_OPT_WARM_UP, + + OPT_GROUP("Auto Analysis and Actions:"), + RTLA_OPT_AUTO(opt_timerlat_auto_cb), + TIMERLAT_OPT_NO_AA, + TIMERLAT_OPT_DUMPS_TASKS, + RTLA_OPT_ON_THRESHOLD("latency", opt_timerlat_on_threshold_cb), + RTLA_OPT_ON_END(opt_timerlat_on_end_cb), + TIMERLAT_OPT_BPF_ACTION, + TIMERLAT_OPT_STACK_FORMAT, + + OPT_GROUP("General:"), + RTLA_OPT_DURATION, + RTLA_OPT_DEBUG, + + OPT_END(), + }; + + actions_init(¶ms->common.threshold_actions); + actions_init(¶ms->common.end_actions); + + /* disabled by default */ + params->dma_latency = -1; + + /* disabled by default */ + params->deepest_idle_state = -2; + + /* display data in microseconds */ + params->common.output_divisor = 1000; + params->common.hist.bucket_size = 1; + params->common.hist.entries = 256; + + /* default to BPF mode */ + params->mode = TRACING_MODE_BPF; + + /* default to truncate stack format */ + params->stack_format = STACK_FORMAT_TRUNCATE; + + argc = parse_options(argc, (const char **)argv, + timerlat_hist_options, timerlat_hist_usage, + common_parse_options_flags); + if (argc < 0) + return NULL; + + if (cb_data.trace_output) + actions_add_trace_output(¶ms->common.threshold_actions, cb_data.trace_output); + + if (geteuid()) + fatal("rtla needs root permission"); + + if (params->common.hist.no_irq && params->common.hist.no_thread) + fatal("no-irq and no-thread set, there is nothing to do here"); + + if (params->common.hist.no_index && !params->common.hist.with_zeros) + fatal("no-index set with with-zeros is not set - it does not make sense"); + + /* + * Auto analysis only happens if stop tracing, thus: + */ + if (!params->common.stop_us && !params->common.stop_total_us) + params->no_aa = 1; + + if (params->common.kernel_workload && params->common.user_workload) + fatal("--kernel-threads and --user-threads are mutually exclusive!"); + + /* + * If auto-analysis or trace output is enabled, switch from BPF mode to + * mixed mode + */ + if (params->mode == TRACING_MODE_BPF && + (params->common.threshold_actions.present[ACTION_TRACE_OUTPUT] || + params->common.end_actions.present[ACTION_TRACE_OUTPUT] || + !params->no_aa)) + params->mode = TRACING_MODE_MIXED; + + return ¶ms->common; +} + +/* + * rtla_usage - print rtla usage + */ +__noreturn static void rtla_usage(int err) +{ + int i; + + static const char * const msg[] = { + "", + "rtla version " VERSION, + "", + " usage: rtla COMMAND ...", + "", + " commands:", + " osnoise - gives information about the operating system noise (osnoise)", + " hwnoise - gives information about hardware-related noise", + " timerlat - measures the timer irq and thread latency", + "", + NULL, + }; + + for (i = 0; msg[i]; i++) + fprintf(stderr, "%s\n", msg[i]); + exit(err); +} + +/* + * run_tool_command - try to run a rtla tool command + * + * It returns 0 if it fails. The tool's main will generally not + * return as they should call exit(). + */ +int run_tool_command(int argc, char **argv, int start_position) +{ + if (strcmp(argv[start_position], "osnoise") == 0) { + osnoise_main(argc-start_position, &argv[start_position]); + goto ran; + } else if (strcmp(argv[start_position], "hwnoise") == 0) { + hwnoise_main(argc-start_position, &argv[start_position]); + goto ran; + } else if (strcmp(argv[start_position], "timerlat") == 0) { + timerlat_main(argc-start_position, &argv[start_position]); + goto ran; + } + + return 0; +ran: + return 1; +} + +/* Set main as weak to allow overriding it for building unit test binary */ +#pragma weak main + +int main(int argc, char *argv[]) +{ + int retval; + + /* is it an alias? */ + retval = run_tool_command(argc, argv, 0); + if (retval) + exit(0); + + if (argc < 2) + goto usage; + + if (strcmp(argv[1], "-h") == 0) + rtla_usage(129); + else if (strcmp(argv[1], "--help") == 0) + rtla_usage(129); + + retval = run_tool_command(argc, argv, 1); + if (retval) + exit(0); + +usage: + rtla_usage(129); +} diff --git a/tools/tracing/rtla/src/cli.h b/tools/tracing/rtla/src/cli.h new file mode 100644 index 000000000000..c49ccb3e92f5 --- /dev/null +++ b/tools/tracing/rtla/src/cli.h @@ -0,0 +1,7 @@ +/* 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); diff --git a/tools/tracing/rtla/src/cli_p.h b/tools/tracing/rtla/src/cli_p.h new file mode 100644 index 000000000000..3cea4f6e976e --- /dev/null +++ b/tools/tracing/rtla/src/cli_p.h @@ -0,0 +1,670 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#pragma once + +#ifndef RTLA_ALLOW_CLI_P_H +#error "Private header file included outside of cli.c module" +#endif + +#include +#include + +#include "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, \ + ¶ms->common.stop_us, \ + "us", \ + "stop trace if " name " is higher than the argument in us", \ + opt_llong_callback, PARSE_OPT_NOAUTONEG) + +#define RTLA_OPT_STOP_TOTAL(short, long, name) OPT_CALLBACK_FLAG(short, long, \ + ¶ms->common.stop_total_us, \ + "us", \ + "stop trace if " name " is higher than the argument in us", \ + opt_llong_callback, PARSE_OPT_NOAUTONEG) + +#define RTLA_OPT_TRACE_OUTPUT(tracer, cb) OPT_CALLBACK_OPTARG('t', "trace", \ + (const char **)&cb_data.trace_output, \ + tracer "_trace.txt", \ + "[file]", \ + "save the stopped trace to [file|" tracer "_trace.txt]", \ + cb) + +#define RTLA_OPT_CPUS OPT_CALLBACK('c', "cpus", ¶ms->common, \ + "cpu-list", \ + "run the tracer only on the given cpus", \ + opt_cpus_cb) + +#define RTLA_OPT_CGROUP OPT_CALLBACK_OPTARG('C', "cgroup", ¶ms->common, \ + "[cgroup_name]", NULL, \ + "set cgroup, no argument means rtla's cgroup will be inherited", \ + opt_cgroup_cb) + +#define RTLA_OPT_USER_THREADS OPT_CALLBACK_NOOPT('u', "user-threads", params, NULL, \ + "use rtla user-space threads instead of kernel-space timerlat threads", \ + opt_user_threads_cb) + +#define RTLA_OPT_KERNEL_THREADS OPT_BOOLEAN('k', "kernel-threads", \ + ¶ms->common.kernel_workload, \ + "use timerlat kernel-space threads instead of rtla user-space threads") + +#define RTLA_OPT_USER_LOAD OPT_BOOLEAN('U', "user-load", ¶ms->common.user_data, \ + "enable timerlat for user-defined user-space workload") + +#define RTLA_OPT_DURATION OPT_CALLBACK('d', "duration", ¶ms->common, \ + "time[s|m|h|d]", \ + "set the duration of the session", \ + opt_duration_cb) + +#define RTLA_OPT_EVENT OPT_CALLBACK('e', "event", ¶ms->common.events, \ + "sys:event", \ + "enable the in the trace instance, multiple -e are allowed", \ + opt_event_cb) + +#define RTLA_OPT_HOUSEKEEPING OPT_CALLBACK('H', "house-keeping", ¶ms->common, \ + "cpu-list", \ + "run rtla control threads only on the given cpus", \ + opt_housekeeping_cb) + +#define RTLA_OPT_PRIORITY OPT_CALLBACK('P', "priority", ¶ms->common, \ + "o:prio|r:prio|f:prio|d:runtime:period", \ + "set scheduling parameters", \ + opt_priority_cb) + +#define RTLA_OPT_TRIGGER OPT_CALLBACK(0, "trigger", ¶ms->common.events, \ + "trigger", \ + "enable a trace event trigger to the previous -e event", \ + opt_trigger_cb) + +#define RTLA_OPT_FILTER OPT_CALLBACK(0, "filter", ¶ms->common.events, \ + "filter", \ + "enable a trace event filter to the previous -e event", \ + opt_filter_cb) + +#define RTLA_OPT_QUIET OPT_BOOLEAN('q', "quiet", ¶ms->common.quiet, \ + "print only a summary at the end") + +#define RTLA_OPT_TRACE_BUFFER_SIZE OPT_CALLBACK(0, "trace-buffer-size", \ + ¶ms->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", ¶ms->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", \ + ¶ms->common.threshold_actions, \ + "action", \ + "define action to be executed at " threshold " threshold, multiple are allowed", \ + cb) + +#define RTLA_OPT_ON_END(cb) OPT_CALLBACK(0, "on-end", ¶ms->common.end_actions, \ + "action", \ + "define action to be executed at measurement end, multiple are allowed", \ + cb) + +#define RTLA_OPT_DEBUG OPT_BOOLEAN('D', "debug", &config_debug, \ + "print debug info") + +/* + * 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, ¶ms->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= next to -C[ ] */ + ++params->cgroup_name; + + return 0; +} + +static int opt_duration_cb(const struct option *opt, const char *arg, int unset) +{ + struct common_params *params = opt->value; + + if (unset || !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, ¶ms->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, ¶ms->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", ¶ms->period, "us", \ + "osnoise period in us", \ + opt_osnoise_period_cb) + +#define OSNOISE_OPT_RUNTIME OPT_CALLBACK('r', "runtime", ¶ms->runtime, "us", \ + "osnoise runtime in us", \ + opt_osnoise_runtime_cb) + +#define OSNOISE_OPT_THRESHOLD OPT_CALLBACK('T', "threshold", ¶ms->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= next to -t[ ] */ + ++*trace_output; + } + + return 0; +} + +static int opt_osnoise_on_threshold_cb(const struct option *opt, const char *arg, int unset) +{ + struct actions *actions = opt->value; + int retval; + + if (unset || !arg) + return -1; + + retval = actions_parse(actions, (char *)arg, "osnoise_trace.txt"); + if (retval) + 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", ¶ms->timerlat_period_us, "us", \ + "timerlat period in us", \ + opt_timerlat_period_cb) + +#define TIMERLAT_OPT_STACK OPT_CALLBACK('s', "stack", ¶ms->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", ¶ms->dma_latency, "us", \ + "set /dev/cpu_dma_latency latency to reduce exit from idle latency", \ + opt_dma_latency_cb) + +#define TIMERLAT_OPT_DEEPEST_IDLE_STATE OPT_CALLBACK(0, "deepest-idle-state", \ + ¶ms->deepest_idle_state, "n", \ + "only go down to idle state n on cpus used by timerlat to reduce exit from idle latency", \ + opt_int_callback) + +#define TIMERLAT_OPT_AA_ONLY OPT_CALLBACK(0, "aa-only", params, "us", \ + "stop if latency is hit, only printing the auto analysis (reduces CPU usage)", \ + opt_aa_only_cb) + +#define TIMERLAT_OPT_NO_AA OPT_BOOLEAN(0, "no-aa", ¶ms->no_aa, \ + "disable auto-analysis, reducing rtla timerlat cpu usage") + +#define TIMERLAT_OPT_DUMPS_TASKS OPT_BOOLEAN(0, "dump-tasks", ¶ms->dump_tasks, \ + "prints the task running on all CPUs if stop conditions are met (depends on !--no-aa)") + +#define TIMERLAT_OPT_BPF_ACTION OPT_STRING(0, "bpf-action", ¶ms->bpf_action_program, \ + "program", \ + "load and execute BPF program when latency threshold is exceeded") + +#define TIMERLAT_OPT_STACK_FORMAT OPT_CALLBACK(0, "stack-format", ¶ms->stack_format, "format", \ + "set the stack format (truncate, skip, full)", \ + opt_stack_format_cb) + +/* + * 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= next to -t[ ] */ + ++*trace_output; + } + + return 0; +} + +static int opt_timerlat_on_threshold_cb(const struct option *opt, const char *arg, int unset) +{ + struct actions *actions = opt->value; + int retval; + + if (unset || !arg) + return -1; + + retval = actions_parse(actions, (char *)arg, "timerlat_trace.txt"); + if (retval) + 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; +} + +/* + * Macros for command line options specific to histogram-based tools + */ + +#define HIST_OPT_BUCKET_SIZE OPT_CALLBACK('b', "bucket-size", \ + ¶ms->common.hist.bucket_size, "N", \ + "set the histogram bucket size (default 1)", \ + opt_bucket_size_cb) + +#define HIST_OPT_ENTRIES OPT_CALLBACK('E', "entries", ¶ms->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", ¶ms->common.hist.no_irq, \ + "ignore IRQ latencies", PARSE_OPT_NOAUTONEG) + +#define HIST_OPT_NO_THREAD OPT_BOOLEAN_FLAG(0, "no-thread", ¶ms->common.hist.no_thread, \ + "ignore thread latencies", PARSE_OPT_NOAUTONEG) + +#define HIST_OPT_NO_HEADER OPT_BOOLEAN(0, "no-header", ¶ms->common.hist.no_header, \ + "do not print header") + +#define HIST_OPT_NO_SUMMARY OPT_BOOLEAN(0, "no-summary", ¶ms->common.hist.no_summary, \ + "do not print summary") + +#define HIST_OPT_NO_INDEX OPT_BOOLEAN(0, "no-index", ¶ms->common.hist.no_index, \ + "do not print index") + +#define HIST_OPT_WITH_ZEROS OPT_BOOLEAN(0, "with-zeros", ¶ms->common.hist.with_zeros, \ + "print zero only entries") + +/* 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 effad523e8cf..d0a8a6edbf0c 100644 --- a/tools/tracing/rtla/src/common.c +++ b/tools/tracing/rtla/src/common.c @@ -5,7 +5,6 @@ #include #include #include -#include #include #include "common.h" @@ -57,114 +56,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); -} - -/* - * common_parse_options - parse common command line options - * - * @argc: argument count - * @argv: argument vector - * @common: common parameters structure - * - * Parse command line options that are common to all rtla tools. - * - * Returns: non zero if a common option was parsed, or 0 - * if the option should be handled by tool-specific parsing. - */ -int common_parse_options(int argc, char **argv, struct common_params *common) -{ - struct trace_events *tevent; - int saved_state = optind; - int c; - - static struct option long_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'}, - {0, 0, 0, 0} - }; - - opterr = 0; - c = getopt_auto(argc, argv, long_options); - opterr = 1; - - 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: - optind = saved_state; - return 0; - } - - return c; -} - /* * common_apply_config - apply common configs to the initialized tool */ diff --git a/tools/tracing/rtla/src/common.h b/tools/tracing/rtla/src/common.h index eba40b6d9504..0dfca83bd726 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 #include "actions.h" #include "timerlat_u.h" #include "trace.h" @@ -57,12 +56,12 @@ struct osnoise_context { extern volatile int stop_tracing; struct hist_params { - char no_irq; - char no_thread; - char no_header; - char no_summary; - char no_index; - char with_zeros; + bool no_irq; + bool no_thread; + bool no_header; + bool no_summary; + bool no_index; + bool with_zeros; int bucket_size; int entries; }; @@ -95,12 +94,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; @@ -176,8 +175,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); -int common_parse_options(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 2db3db155c44..e1e32898af2d 100644 --- a/tools/tracing/rtla/src/osnoise.c +++ b/tools/tracing/rtla/src/osnoise.c @@ -15,6 +15,8 @@ #include #include +#include + #include "osnoise.h" #define DEFAULT_SAMPLE_PERIOD 1000000 /* 1s */ @@ -1171,7 +1173,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 +1211,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 +1225,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_hist.c b/tools/tracing/rtla/src/osnoise_hist.c index 8ad816b80265..dfa91d0681f8 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 #include #include #include @@ -13,6 +12,7 @@ #include #include "osnoise.h" +#include "cli.h" struct osnoise_hist_cpu { int *samples; @@ -400,225 +400,6 @@ osnoise_print_stats(struct osnoise_tool *tool) osnoise_report_missed_events(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 ] [--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 : enable the in the trace instance, multiple -e are allowed", - " --filter : enable a trace event filter to the previous -e event", - " --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 : 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, - }; - - 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(¶ms->common.threshold_actions); - actions_init(¶ms->common.end_actions); - - /* display data in microseconds */ - params->common.output_divisor = 1000; - params->common.hist.bucket_size = 1; - params->common.hist.entries = 256; - - while (1) { - static struct option long_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} - }; - - if (common_parse_options(argc, argv, ¶ms->common)) - continue; - - c = getopt_auto(argc, argv, long_options); - - /* detect the end of the options. */ - if (c == -1) - break; - - switch (c) { - case 'a': - /* set sample stop to auto_thresh */ - params->common.stop_us = get_llong_from_str(optarg); - - /* set sample threshold to 1 */ - params->threshold = 1; - - /* set trace */ - if (!trace_output) - trace_output = "osnoise_trace.txt"; - - break; - case 'b': - params->common.hist.bucket_size = get_llong_from_str(optarg); - if (params->common.hist.bucket_size == 0 || - params->common.hist.bucket_size >= 1000000) - fatal("Bucket size needs to be > 0 and <= 1000000"); - break; - case '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(¶ms->common.threshold_actions, optarg, - "osnoise_trace.txt"); - if (retval) - fatal("Invalid action %s", optarg); - break; - case '9': - retval = actions_parse(¶ms->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(¶ms->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 ¶ms->common; -} - /* * osnoise_hist_apply_config - apply the hist configs to the initialized tool */ diff --git a/tools/tracing/rtla/src/osnoise_top.c b/tools/tracing/rtla/src/osnoise_top.c index 244bdce022ad..512a6299cb01 100644 --- a/tools/tracing/rtla/src/osnoise_top.c +++ b/tools/tracing/rtla/src/osnoise_top.c @@ -4,7 +4,6 @@ */ #define _GNU_SOURCE -#include #include #include #include @@ -13,6 +12,7 @@ #include #include "osnoise.h" +#include "cli.h" struct osnoise_top_cpu { unsigned long long sum_runtime; @@ -245,204 +245,6 @@ osnoise_print_stats(struct osnoise_tool *top) osnoise_report_missed_events(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 ] [--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 : enable the in the trace instance, multiple -e are allowed", - " --filter : enable a trace event filter to the previous -e event", - " --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 : 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(¶ms->common.threshold_actions); - actions_init(¶ms->common.end_actions); - - if (strcmp(argv[0], "hwnoise") == 0) { - params->mode = MODE_HWNOISE; - /* - * Reduce CPU usage for 75% to avoid killing the system. - */ - params->runtime = 750000; - params->period = 1000000; - } - - while (1) { - static struct option long_options[] = { - {"auto", required_argument, 0, 'a'}, - {"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} - }; - - if (common_parse_options(argc, argv, ¶ms->common)) - continue; - - c = getopt_auto(argc, argv, long_options); - - /* Detect the end of the options. */ - if (c == -1) - break; - - switch (c) { - case 'a': - /* set sample stop to auto_thresh */ - params->common.stop_us = get_llong_from_str(optarg); - - /* set sample threshold to 1 */ - params->threshold = 1; - - /* set trace */ - if (!trace_output) - trace_output = "osnoise_trace.txt"; - - break; - case '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(¶ms->common.threshold_actions, optarg, - "osnoise_trace.txt"); - if (retval) - fatal("Invalid action %s", optarg); - break; - case '5': - retval = actions_parse(¶ms->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(¶ms->common.threshold_actions, trace_output); - - if (geteuid()) - fatal("osnoise needs root permission"); - - return ¶ms->common; -} - /* * osnoise_top_apply_config - apply the top configs to the initialized tool */ diff --git a/tools/tracing/rtla/src/rtla.c b/tools/tracing/rtla/src/rtla.c deleted file mode 100644 index f4342ca684c3..000000000000 --- a/tools/tracing/rtla/src/rtla.c +++ /dev/null @@ -1,92 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Copyright (C) 2021 Red Hat Inc, Daniel Bristot de Oliveira - */ - -#include -#include -#include -#include - -#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_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(0); - } else if (strcmp(argv[1], "--help") == 0) { - rtla_usage(0); - } - - retval = run_tool_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 637f68d684f5..f990c8365776 100644 --- a/tools/tracing/rtla/src/timerlat.c +++ b/tools/tracing/rtla/src/timerlat.c @@ -13,6 +13,8 @@ #include #include +#include + #include "timerlat.h" #include "timerlat_aa.h" #include "timerlat_bpf.h" @@ -231,7 +233,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 +271,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 +285,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 364203a29abd..37a808f1611e 100644 --- a/tools/tracing/rtla/src/timerlat.h +++ b/tools/tracing/rtla/src/timerlat.h @@ -23,8 +23,8 @@ 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; diff --git a/tools/tracing/rtla/src/timerlat_hist.c b/tools/tracing/rtla/src/timerlat_hist.c index 3a15a85b7d72..df7b1398a966 100644 --- a/tools/tracing/rtla/src/timerlat_hist.c +++ b/tools/tracing/rtla/src/timerlat_hist.c @@ -4,7 +4,6 @@ */ #define _GNU_SOURCE -#include #include #include #include @@ -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 { @@ -685,321 +685,6 @@ timerlat_print_stats(struct osnoise_tool *tool) osnoise_report_missed_events(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 ] [--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-tasks] [-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 : enable the in the trace instance, multiple -e are allowed", - " --filter : enable a trace event filter to the previous -e event", - " --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 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 : 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 : load and execute BPF program when latency threshold is exceeded", - " --stack-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(¶ms->common.threshold_actions); - actions_init(¶ms->common.end_actions); - - /* disabled by default */ - params->dma_latency = -1; - - /* disabled by default */ - params->deepest_idle_state = -2; - - /* display data in microseconds */ - params->common.output_divisor = 1000; - params->common.hist.bucket_size = 1; - params->common.hist.entries = 256; - - /* default to BPF mode */ - params->mode = TRACING_MODE_BPF; - - /* default to truncate stack format */ - params->stack_format = STACK_FORMAT_TRUNCATE; - - while (1) { - static struct option long_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-tasks", 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} - }; - - if (common_parse_options(argc, argv, ¶ms->common)) - continue; - - c = getopt_auto(argc, argv, long_options); - - /* 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(¶ms->common.threshold_actions, optarg, - "timerlat_trace.txt"); - if (retval) - fatal("Invalid action %s", optarg); - break; - case '\6': - retval = actions_parse(¶ms->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(¶ms->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 ¶ms->common; -} - /* * timerlat_hist_apply_config - apply the hist configs to the initialized tool */ diff --git a/tools/tracing/rtla/src/timerlat_top.c b/tools/tracing/rtla/src/timerlat_top.c index e3a374005690..18e1071a2e24 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 #include #include #include @@ -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 { @@ -459,290 +459,6 @@ timerlat_print_stats(struct osnoise_tool *top) osnoise_report_missed_events(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 ] [--trigger ] [-c cpu-list] [-H cpu-list]\\", - " [-P priority] [--dma-latency us] [--aa-only us] [-C [cgroup_name]] [--dump-tasks] [-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 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 : enable the in the trace instance, multiple -e are allowed", - " --filter : enable a trace event filter to the previous -e event", - " --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", - " -q/--quiet print only a summary at the end", - " --dma-latency us: set /dev/cpu_dma_latency latency 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 : 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 : load and execute BPF program when latency threshold is exceeded", - " --stack-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(¶ms->common.threshold_actions); - actions_init(¶ms->common.end_actions); - - /* disabled by default */ - params->dma_latency = -1; - - /* disabled by default */ - params->deepest_idle_state = -2; - - /* display data in microseconds */ - params->common.output_divisor = 1000; - - /* 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[] = { - {"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} - }; - - if (common_parse_options(argc, argv, ¶ms->common)) - continue; - - c = getopt_auto(argc, argv, long_options); - - /* 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(¶ms->common.threshold_actions, optarg, - "timerlat_trace.txt"); - if (retval) - fatal("Invalid action %s", optarg); - break; - case '\1': - retval = actions_parse(¶ms->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(¶ms->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 ¶ms->common; -} - /* * timerlat_top_apply_config - apply the top configs to the initialized tool */ diff --git a/tools/tracing/rtla/src/utils.c b/tools/tracing/rtla/src/utils.c index 9cec5b3e02c8..cb187e7d48d1 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 96fd72042717..2ba3333669bb 100644 --- a/tools/tracing/rtla/src/utils.h +++ b/tools/tracing/rtla/src/utils.h @@ -39,7 +39,7 @@ static inline bool str_has_prefix(const char *str, const char *prefix) return strncmp(str, prefix, strlen(prefix)) == 0; } -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, ...); @@ -47,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/hwnoise.t b/tools/tracing/rtla/tests/hwnoise.t index 23ce250a6852..cfe687ff5ee1 100644 --- a/tools/tracing/rtla/tests/hwnoise.t +++ b/tools/tracing/rtla/tests/hwnoise.t @@ -6,7 +6,7 @@ test_begin set_timeout 2m check "verify help page" \ - "hwnoise --help" 0 "summary of hardware-related noise" + "hwnoise --help" 129 "Usage: rtla hwnoise" check "detect noise higher than one microsecond" \ "hwnoise -c 0 -T 1 -d 5s -q" 0 check "set the automatic trace mode" \ diff --git a/tools/tracing/rtla/tests/osnoise.t b/tools/tracing/rtla/tests/osnoise.t index a7956ab605cd..346a14a860c8 100644 --- a/tools/tracing/rtla/tests/osnoise.t +++ b/tools/tracing/rtla/tests/osnoise.t @@ -6,9 +6,9 @@ test_begin set_timeout 2m check "verify help page" \ - "osnoise --help" 0 "osnoise version" + "osnoise --help" 129 "osnoise version" check_top_hist "verify help page" \ - "osnoise TOOL --help" 0 "rtla osnoise" + "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" \ @@ -32,7 +32,7 @@ check "hist with -b/--bucket-size" \ 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 < 9999999$" + "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" \ diff --git a/tools/tracing/rtla/tests/timerlat.t b/tools/tracing/rtla/tests/timerlat.t index 19fd5af26ebb..8193048e8c8c 100644 --- a/tools/tracing/rtla/tests/timerlat.t +++ b/tools/tracing/rtla/tests/timerlat.t @@ -21,9 +21,9 @@ export RTLA_NO_BPF=$option # Basic tests check "verify help page" \ - "timerlat --help" 0 "timerlat version" + "timerlat --help" 129 "timerlat version" check_top_hist "verify help page" \ - "timerlat TOOL --help" 0 "rtla timerlat" + "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" \ @@ -61,7 +61,7 @@ check "hist with -b/--bucket-size" \ 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 < 9999999$" + "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" \ From 244d0cbff2efa13931115784e5dc4d1270a04ec7 Mon Sep 17 00:00:00 2001 From: Tomas Glozar Date: Thu, 28 May 2026 12:32:53 +0200 Subject: [PATCH 21/26] rtla/tests: Add unit tests for _parse_args() functions Add a test suite for the _parse_args() function of each tool that checks the params structures (struct common_params, struct osnoise_params, struct timerlat_params) returned by them for correctness. One test case is added per option, as well as a few special cases for tricky combinations of options. Test cases are ordered the same as the option arrays and help message to allow easy checking of whether all options are covered. This should help clarify what the proper command line behavior of RTLA is in case there are holes in the documentation and verify that the intended behavior is implemented correctly. A few necessary changes to the unit tests were done as part of this commit: - Unit tests now also link to libsubcmd and its dependencies. - A new global variable in_unit_test is added to RTLA's CLI interface, causing it to skip check for root if running in unit tests. This allows the CLI unit tests to run as non-root, like existing unit tests. There is quite a lot of duplication, some of it is mitigated with macros, but partially it is intentional so that future changes in behavior are tracked across tools. Link: https://lore.kernel.org/r/20260528103254.2990068-6-tglozar@redhat.com Signed-off-by: Tomas Glozar --- tools/tracing/rtla/src/cli.c | 8 +- tools/tracing/rtla/src/cli.h | 2 + tools/tracing/rtla/tests/unit/Build | 4 + tools/tracing/rtla/tests/unit/Makefile.unit | 2 +- .../rtla/tests/unit/cli_params_assert.h | 68 ++ .../rtla/tests/unit/osnoise_hist_cli.c | 557 ++++++++++++++ .../tracing/rtla/tests/unit/osnoise_top_cli.c | 503 +++++++++++++ .../rtla/tests/unit/timerlat_hist_cli.c | 702 ++++++++++++++++++ .../rtla/tests/unit/timerlat_top_cli.c | 634 ++++++++++++++++ tools/tracing/rtla/tests/unit/unit_tests.c | 11 + 10 files changed, 2486 insertions(+), 5 deletions(-) create mode 100644 tools/tracing/rtla/tests/unit/cli_params_assert.h create mode 100644 tools/tracing/rtla/tests/unit/osnoise_hist_cli.c create mode 100644 tools/tracing/rtla/tests/unit/osnoise_top_cli.c create mode 100644 tools/tracing/rtla/tests/unit/timerlat_hist_cli.c create mode 100644 tools/tracing/rtla/tests/unit/timerlat_top_cli.c diff --git a/tools/tracing/rtla/src/cli.c b/tools/tracing/rtla/src/cli.c index 7f531519df44..709219341a56 100644 --- a/tools/tracing/rtla/src/cli.c +++ b/tools/tracing/rtla/src/cli.c @@ -124,7 +124,7 @@ struct common_params *osnoise_top_parse_args(int argc, char **argv) if (cb_data.trace_output) actions_add_trace_output(¶ms->common.threshold_actions, cb_data.trace_output); - if (geteuid()) + if (geteuid() && !in_unit_test) fatal("osnoise needs root permission"); return ¶ms->common; @@ -206,7 +206,7 @@ struct common_params *osnoise_hist_parse_args(int argc, char **argv) if (cb_data.trace_output) actions_add_trace_output(¶ms->common.threshold_actions, cb_data.trace_output); - if (geteuid()) + if (geteuid() && !in_unit_test) fatal("rtla needs root permission"); if (params->common.hist.no_index && !params->common.hist.with_zeros) @@ -301,7 +301,7 @@ struct common_params *timerlat_top_parse_args(int argc, char **argv) if (cb_data.trace_output) actions_add_trace_output(¶ms->common.threshold_actions, cb_data.trace_output); - if (geteuid()) + if (geteuid() && !in_unit_test) fatal("rtla needs root permission"); /* @@ -427,7 +427,7 @@ struct common_params *timerlat_hist_parse_args(int argc, char **argv) if (cb_data.trace_output) actions_add_trace_output(¶ms->common.threshold_actions, cb_data.trace_output); - if (geteuid()) + if (geteuid() && !in_unit_test) fatal("rtla needs root permission"); if (params->common.hist.no_irq && params->common.hist.no_thread) diff --git a/tools/tracing/rtla/src/cli.h b/tools/tracing/rtla/src/cli.h index c49ccb3e92f5..633a2322cf89 100644 --- a/tools/tracing/rtla/src/cli.h +++ b/tools/tracing/rtla/src/cli.h @@ -5,3 +5,5 @@ 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/tests/unit/Build b/tools/tracing/rtla/tests/unit/Build index 2749f4cf202a..16139f17ea1f 100644 --- a/tools/tracing/rtla/tests/unit/Build +++ b/tools/tracing/rtla/tests/unit/Build @@ -1,3 +1,7 @@ unit_tests-y += utils.o unit_tests-y += actions.o unit_tests-y += unit_tests.o +unit_tests-y += osnoise_top_cli.o +unit_tests-y += osnoise_hist_cli.o +unit_tests-y += timerlat_top_cli.o +unit_tests-y += timerlat_hist_cli.o diff --git a/tools/tracing/rtla/tests/unit/Makefile.unit b/tools/tracing/rtla/tests/unit/Makefile.unit index bacb00164e46..8c33a9583c30 100644 --- a/tools/tracing/rtla/tests/unit/Makefile.unit +++ b/tools/tracing/rtla/tests/unit/Makefile.unit @@ -3,7 +3,7 @@ UNIT_TESTS := $(OUTPUT)unit_tests UNIT_TESTS_IN := $(UNIT_TESTS)-in.o -$(UNIT_TESTS): $(UNIT_TESTS_IN) $(RTLA_IN) +$(UNIT_TESTS): $(UNIT_TESTS_IN) $(RTLA_IN) $(LIBSUBCMD) $(LIB_STRING) $(LIB_STR_ERROR_R) $(QUIET_LINK)$(CC) $(LDFLAGS) -o $@ $^ $(EXTLIBS) -lcheck $(UNIT_TESTS_IN): fixdep diff --git a/tools/tracing/rtla/tests/unit/cli_params_assert.h b/tools/tracing/rtla/tests/unit/cli_params_assert.h new file mode 100644 index 000000000000..4bc7d582fcf4 --- /dev/null +++ b/tools/tracing/rtla/tests/unit/cli_params_assert.h @@ -0,0 +1,68 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#pragma once + +#include "../../src/timerlat.h" + +/* Tracing Options */ + +#define CLI_ASSERT_SINGLE_EVENT(_system, _event) do {\ + ck_assert_ptr_nonnull(params->events);\ + ck_assert_str_eq(params->events->system, _system);\ + ck_assert_str_eq(params->events->event, _event);\ + ck_assert_ptr_null(params->events->next);\ +} while (0) + +#define CLI_ASSERT_SINGLE_FILTER(_filter) do {\ + ck_assert_ptr_nonnull(params->events);\ + ck_assert_str_eq(params->events->filter, _filter);\ + ck_assert_ptr_null(params->events->next);\ +} while (0) + +#define CLI_ASSERT_SINGLE_TRIGGER(_trigger) do {\ + ck_assert_ptr_nonnull(params->events);\ + ck_assert_str_eq(params->events->trigger, _trigger);\ + ck_assert_ptr_null(params->events->next);\ +} while (0) + +/* CPU Configuration */ + +#define CLI_ASSERT_CPUSET(_field, ...) do {\ + int n;\ + int cpus[] = { __VA_ARGS__ };\ + for (n = 0; n < sizeof(cpus) / sizeof(int); n++)\ + ck_assert(CPU_ISSET(cpus[n], ¶ms->_field));\ + ck_assert_int_eq(CPU_COUNT(¶ms->_field), n);\ +} while (0) + +/* Auto Analysis and Actions */ + +#define CLI_OSNOISE_ASSERT_AUTO(_stop) do {\ + ck_assert_int_eq(params->stop_us, _stop);\ + ck_assert_int_eq(osn_params->threshold, 1);\ + ck_assert_int_eq(params->threshold_actions.len, 1);\ + ck_assert_int_eq(params->threshold_actions.list[0].type, ACTION_TRACE_OUTPUT);\ + ck_assert_str_eq(params->threshold_actions.list[0].trace_output, "osnoise_trace.txt");\ +} while (0) + +#define CLI_TIMERLAT_ASSERT_AUTO(_threshold) do {\ + ck_assert_int_eq(params->stop_us, _threshold);\ + ck_assert_int_eq(params->stop_total_us, _threshold);\ + ck_assert_int_eq(tlat_params->print_stack, _threshold);\ + ck_assert_int_eq(params->threshold_actions.len, 1);\ + ck_assert_int_eq(params->threshold_actions.list[0].type, ACTION_TRACE_OUTPUT);\ + ck_assert_str_eq(params->threshold_actions.list[0].trace_output, "timerlat_trace.txt");\ +} while (0) + +#define CLI_TIMERLAT_ASSERT_AA_ONLY(_threshold) do {\ + ck_assert_int_eq(params->stop_us, _threshold);\ + ck_assert_int_eq(params->stop_total_us, _threshold);\ + ck_assert_int_eq(tlat_params->print_stack, _threshold);\ + ck_assert_int_eq(params->threshold_actions.len, 0);\ + ck_assert(params->aa_only);\ +} while (0) + +#define CLI_ASSERT_SINGLE_ACTION(_actions, _type, _arg, _valtype, _value) do {\ + ck_assert_int_eq(params->_actions.len, 1);\ + ck_assert_int_eq(params->_actions.list[0].type, _type);\ + ck_assert_##_valtype##_eq(params->_actions.list[0]._arg, _value);\ +} while (0) diff --git a/tools/tracing/rtla/tests/unit/osnoise_hist_cli.c b/tools/tracing/rtla/tests/unit/osnoise_hist_cli.c new file mode 100644 index 000000000000..3661529f93dc --- /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 +#include +#include +#include +#include +#include +#include + +#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 000000000000..f3a8633cc84e --- /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 +#include +#include +#include +#include +#include +#include + +#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 000000000000..81dc04596cd1 --- /dev/null +++ b/tools/tracing/rtla/tests/unit/timerlat_hist_cli.c @@ -0,0 +1,702 @@ +// SPDX-License-Identifier: GPL-2.0 + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include + +#include + +#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 + +/* 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); + suite_add_tcase(s, tc); + + tc = tcase_create("histogram_options"); + tcase_add_test(tc, test_bucket_size_short); + tcase_add_test(tc, test_bucket_size_long); + tcase_add_test(tc, test_entries_short); + tcase_add_test(tc, test_entries_long); + tcase_add_test(tc, test_no_header); + tcase_add_test(tc, test_no_index); + tcase_add_test(tc, test_no_irq); + tcase_add_test(tc, test_no_summary); + tcase_add_test(tc, test_no_thread); + tcase_add_test(tc, test_with_zeros); + suite_add_tcase(s, tc); + + tc = tcase_create("output"); + tcase_add_test(tc, test_nano_short); + tcase_add_test(tc, test_nano_long); + suite_add_tcase(s, tc); + + tc = tcase_create("system_tuning"); + tcase_add_test(tc, test_deepest_idle_state); + tcase_add_test(tc, test_dma_latency); + tcase_add_test(tc, test_trace_buffer_size); + tcase_add_test(tc, test_warm_up); + suite_add_tcase(s, tc); + + tc = tcase_create("aa_actions"); + tcase_add_test(tc, test_auto); + tcase_add_test(tc, test_bpf_action); + tcase_add_test(tc, test_dump_tasks); + tcase_add_test(tc, test_no_aa); + tcase_add_test(tc, test_on_end); + tcase_add_test(tc, test_on_threshold); + tcase_add_test(tc, test_stack_format); + suite_add_tcase(s, tc); + + tc = tcase_create("general"); + tcase_add_test(tc, test_debug_short); + tcase_add_test(tc, test_debug_long); + tcase_add_test(tc, test_duration_short); + tcase_add_test(tc, test_duration_long); + suite_add_tcase(s, tc); + + return s; +} diff --git a/tools/tracing/rtla/tests/unit/timerlat_top_cli.c b/tools/tracing/rtla/tests/unit/timerlat_top_cli.c new file mode 100644 index 000000000000..1c39008564c5 --- /dev/null +++ b/tools/tracing/rtla/tests/unit/timerlat_top_cli.c @@ -0,0 +1,634 @@ +// SPDX-License-Identifier: GPL-2.0 + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include + +#include + +#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 + +/* 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); + 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 f87d761f9b12..64884f6cbdeb 100644 --- a/tools/tracing/rtla/tests/unit/unit_tests.c +++ b/tools/tracing/rtla/tests/unit/unit_tests.c @@ -5,17 +5,28 @@ #include #include "../../src/utils.h" +#include "../../src/cli.h" Suite *utils_suite(void); Suite *actions_suite(void); +Suite *osnoise_top_cli_suite(void); +Suite *osnoise_hist_cli_suite(void); +Suite *timerlat_top_cli_suite(void); +Suite *timerlat_hist_cli_suite(void); int main(int argc, char *argv[]) { int num_failed; SRunner *sr; + in_unit_test = true; + sr = srunner_create(utils_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); From 596a9bed0aec60490dffd85bd1865430ce298eba Mon Sep 17 00:00:00 2001 From: Tomas Glozar Date: Thu, 28 May 2026 12:32:54 +0200 Subject: [PATCH 22/26] rtla/tests: Add unit tests for CLI option callbacks In addition to testing all tool_parse_args() functions, test also all callbacks used for parsing custom option formats. The callbacks represent a middle layer between the parsing functions and utility functions dedicated to checking specific argument formats, for example, scheduling class and duration. Callback tests are run before parsing functions to make sure any issue in the former is reported before it is encountered through the latter. Tests verify both successful parsing and proper rejection of invalid inputs (via exit tests). To enable testing static callbacks, a pragma once guard is added to timerlat.h for safe inclusion by cli_p.h. Add dependency of UNIT_TESTS_IN on LIBSUBCMD_INCLUDES, as the new test file tests/unit/cli_opt_callback.c includes cli_p.h which includes subcmd/parse-options.h. Link: https://lore.kernel.org/r/20260528103254.2990068-7-tglozar@redhat.com Signed-off-by: Tomas Glozar --- tools/tracing/rtla/src/timerlat.h | 2 + tools/tracing/rtla/tests/unit/Build | 1 + tools/tracing/rtla/tests/unit/Makefile.unit | 2 +- .../rtla/tests/unit/cli_opt_callback.c | 704 ++++++++++++++++++ tools/tracing/rtla/tests/unit/unit_tests.c | 2 + 5 files changed, 710 insertions(+), 1 deletion(-) create mode 100644 tools/tracing/rtla/tests/unit/cli_opt_callback.c diff --git a/tools/tracing/rtla/src/timerlat.h b/tools/tracing/rtla/src/timerlat.h index 37a808f1611e..38ab6b41a15e 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" /* diff --git a/tools/tracing/rtla/tests/unit/Build b/tools/tracing/rtla/tests/unit/Build index 16139f17ea1f..d5a0f13922be 100644 --- a/tools/tracing/rtla/tests/unit/Build +++ b/tools/tracing/rtla/tests/unit/Build @@ -5,3 +5,4 @@ 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 8c33a9583c30..839abda64b76 100644 --- a/tools/tracing/rtla/tests/unit/Makefile.unit +++ b/tools/tracing/rtla/tests/unit/Makefile.unit @@ -6,7 +6,7 @@ UNIT_TESTS_IN := $(UNIT_TESTS)-in.o $(UNIT_TESTS): $(UNIT_TESTS_IN) $(RTLA_IN) $(LIBSUBCMD) $(LIB_STRING) $(LIB_STR_ERROR_R) $(QUIET_LINK)$(CC) $(LDFLAGS) -o $@ $^ $(EXTLIBS) -lcheck -$(UNIT_TESTS_IN): fixdep +$(UNIT_TESTS_IN): fixdep $(LIBSUBCMD_INCLUDES) make $(build)=unit_tests unit-tests: FORCE diff --git a/tools/tracing/rtla/tests/unit/cli_opt_callback.c b/tools/tracing/rtla/tests/unit/cli_opt_callback.c new file mode 100644 index 000000000000..01647f4227d1 --- /dev/null +++ b/tools/tracing/rtla/tests/unit/cli_opt_callback.c @@ -0,0 +1,704 @@ +// SPDX-License-Identifier: GPL-2.0 + +#define _GNU_SOURCE +#include +#include + +#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(¶ms, 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(¶ms, 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(¶ms, 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(¶ms, 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(¶ms, 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(¶ms, 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(¶ms, 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(¶ms, 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(¶ms, 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 = {¶ms}; + 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 = {¶ms}; + 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(¶ms, 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(¶ms, 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(¶ms, 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_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); + 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/unit_tests.c b/tools/tracing/rtla/tests/unit/unit_tests.c index 64884f6cbdeb..75ca813f81ca 100644 --- a/tools/tracing/rtla/tests/unit/unit_tests.c +++ b/tools/tracing/rtla/tests/unit/unit_tests.c @@ -13,6 +13,7 @@ Suite *osnoise_top_cli_suite(void); Suite *osnoise_hist_cli_suite(void); Suite *timerlat_top_cli_suite(void); Suite *timerlat_hist_cli_suite(void); +Suite *cli_opt_callback_suite(void); int main(int argc, char *argv[]) { @@ -22,6 +23,7 @@ int main(int argc, char *argv[]) in_unit_test = true; sr = srunner_create(utils_suite()); + srunner_add_suite(sr, cli_opt_callback_suite()); srunner_add_suite(sr, actions_suite()); srunner_add_suite(sr, osnoise_top_cli_suite()); srunner_add_suite(sr, osnoise_hist_cli_suite()); From 03d745b9843560ab89a796d0d9311bed5c6df6d6 Mon Sep 17 00:00:00 2001 From: Tomas Glozar Date: Wed, 27 May 2026 16:49:26 +0200 Subject: [PATCH 23/26] rtla/timerlat: Add -A/--aligned CLI option Add a new option, -A/--aligned, that enables timerlat thread alignment implemented on the kernel-side in commit 4245bf4dc58f ("tracing/osnoise: Add option to align tlat threads"). The option takes an argument, representing alignment between timerlat threads in microseconds. The feature is modeled after the option of the same name in the cyclictest tool. Link: https://lore.kernel.org/r/20260527144928.2944472-1-tglozar@redhat.com Signed-off-by: Tomas Glozar --- tools/tracing/rtla/src/cli.c | 2 + tools/tracing/rtla/src/cli_p.h | 17 ++++ tools/tracing/rtla/src/common.h | 8 ++ tools/tracing/rtla/src/osnoise.c | 149 ++++++++++++++++++++++++++++++ tools/tracing/rtla/src/osnoise.h | 6 ++ tools/tracing/rtla/src/timerlat.c | 18 ++++ tools/tracing/rtla/src/timerlat.h | 2 + 7 files changed, 202 insertions(+) diff --git a/tools/tracing/rtla/src/cli.c b/tools/tracing/rtla/src/cli.c index 709219341a56..c5279c987531 100644 --- a/tools/tracing/rtla/src/cli.c +++ b/tools/tracing/rtla/src/cli.c @@ -248,6 +248,7 @@ struct common_params *timerlat_top_parse_args(int argc, char **argv) RTLA_OPT_USER_THREADS, RTLA_OPT_KERNEL_THREADS, RTLA_OPT_USER_LOAD, + TIMERLAT_OPT_ALIGNED, OPT_GROUP("Output:"), TIMERLAT_OPT_NANO, @@ -362,6 +363,7 @@ struct common_params *timerlat_hist_parse_args(int argc, char **argv) RTLA_OPT_USER_THREADS, RTLA_OPT_KERNEL_THREADS, RTLA_OPT_USER_LOAD, + TIMERLAT_OPT_ALIGNED, OPT_GROUP("Histogram Options:"), HIST_OPT_BUCKET_SIZE, diff --git a/tools/tracing/rtla/src/cli_p.h b/tools/tracing/rtla/src/cli_p.h index 3cea4f6e976e..3c939de9abf0 100644 --- a/tools/tracing/rtla/src/cli_p.h +++ b/tools/tracing/rtla/src/cli_p.h @@ -447,6 +447,10 @@ static int opt_osnoise_on_end_cb(const struct option *opt, const char *arg, int "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 */ @@ -608,6 +612,19 @@ static int opt_stack_format_cb(const struct option *opt, const char *arg, int un 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 */ diff --git a/tools/tracing/rtla/src/common.h b/tools/tracing/rtla/src/common.h index 0dfca83bd726..04b287a03f6d 100644 --- a/tools/tracing/rtla/src/common.h +++ b/tools/tracing/rtla/src/common.h @@ -51,6 +51,14 @@ 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 volatile int stop_tracing; diff --git a/tools/tracing/rtla/src/osnoise.c b/tools/tracing/rtla/src/osnoise.c index e1e32898af2d..4ff5dad013b1 100644 --- a/tools/tracing/rtla/src/osnoise.c +++ b/tools/tracing/rtla/src/osnoise.c @@ -423,6 +423,86 @@ void osnoise_put_timerlat_period_us(struct osnoise_context *context) context->orig_timerlat_period_us = OSNOISE_TIME_INIT_VAL; } +/* + * 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" */ @@ -908,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), @@ -960,6 +1101,12 @@ struct osnoise_context *osnoise_context_alloc(void) context->orig_opt_workload = OSNOISE_OPTION_INIT_VAL; context->opt_workload = OSNOISE_OPTION_INIT_VAL; + context->orig_opt_timerlat_align = OSNOISE_OPTION_INIT_VAL; + context->opt_timerlat_align = OSNOISE_OPTION_INIT_VAL; + + context->orig_timerlat_align_us = OSNOISE_OPTION_INIT_VAL; + context->timerlat_align_us = OSNOISE_OPTION_INIT_VAL; + osnoise_get_context(context); return context; @@ -988,6 +1135,8 @@ void osnoise_put_context(struct osnoise_context *context) osnoise_put_tracing_thresh(context); osnoise_put_irq_disable(context); osnoise_put_workload(context); + osnoise_put_timerlat_align(context); + osnoise_put_timerlat_align_us(context); free(context); } diff --git a/tools/tracing/rtla/src/osnoise.h b/tools/tracing/rtla/src/osnoise.h index 168669aa7e0d..340ff5a64e6e 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/timerlat.c b/tools/tracing/rtla/src/timerlat.c index f990c8365776..169aa9a6569d 100644 --- a/tools/tracing/rtla/src/timerlat.c +++ b/tools/tracing/rtla/src/timerlat.c @@ -77,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. diff --git a/tools/tracing/rtla/src/timerlat.h b/tools/tracing/rtla/src/timerlat.h index 38ab6b41a15e..84ec6d778183 100644 --- a/tools/tracing/rtla/src/timerlat.h +++ b/tools/tracing/rtla/src/timerlat.h @@ -31,6 +31,8 @@ struct timerlat_params { 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) From 5daa3c4fa49474fc8935722b3bbc7181aefe7169 Mon Sep 17 00:00:00 2001 From: Tomas Glozar Date: Wed, 27 May 2026 16:49:27 +0200 Subject: [PATCH 24/26] rtla/tests: Add unit tests for -A/--aligned option Add both parse_args() and opt_* tests for the newly added -A/--aligned option. Assisted-by: Claude:claude-4.5-opus-high-thinking Link: https://lore.kernel.org/r/20260527144928.2944472-2-tglozar@redhat.com Signed-off-by: Tomas Glozar --- .../rtla/tests/unit/cli_opt_callback.c | 12 +++++++++++ .../rtla/tests/unit/timerlat_hist_cli.c | 20 +++++++++++++++++++ .../rtla/tests/unit/timerlat_top_cli.c | 20 +++++++++++++++++++ 3 files changed, 52 insertions(+) diff --git a/tools/tracing/rtla/tests/unit/cli_opt_callback.c b/tools/tracing/rtla/tests/unit/cli_opt_callback.c index 01647f4227d1..4a406af42821 100644 --- a/tools/tracing/rtla/tests/unit/cli_opt_callback.c +++ b/tools/tracing/rtla/tests/unit/cli_opt_callback.c @@ -545,6 +545,17 @@ START_TEST(test_opt_nano_cb) } END_TEST +START_TEST(test_opt_timerlat_align_cb) +{ + struct timerlat_params params = {0}; + const struct option opt = TEST_CALLBACK(¶ms, 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; @@ -689,6 +700,7 @@ Suite *cli_opt_callback_suite(void) 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"); diff --git a/tools/tracing/rtla/tests/unit/timerlat_hist_cli.c b/tools/tracing/rtla/tests/unit/timerlat_hist_cli.c index 81dc04596cd1..968bf962f53f 100644 --- a/tools/tracing/rtla/tests/unit/timerlat_hist_cli.c +++ b/tools/tracing/rtla/tests/unit/timerlat_hist_cli.c @@ -373,6 +373,24 @@ START_TEST(test_user_threads_long) } 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) @@ -654,6 +672,8 @@ Suite *timerlat_hist_cli_suite(void) 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"); diff --git a/tools/tracing/rtla/tests/unit/timerlat_top_cli.c b/tools/tracing/rtla/tests/unit/timerlat_top_cli.c index 1c39008564c5..33aa6588d503 100644 --- a/tools/tracing/rtla/tests/unit/timerlat_top_cli.c +++ b/tools/tracing/rtla/tests/unit/timerlat_top_cli.c @@ -373,6 +373,24 @@ START_TEST(test_user_threads_long) } 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) @@ -596,6 +614,8 @@ Suite *timerlat_top_cli_suite(void) 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"); From 2240050a93ac6328754f38d2545b414ba5654dc8 Mon Sep 17 00:00:00 2001 From: Tomas Glozar Date: Wed, 27 May 2026 16:49:28 +0200 Subject: [PATCH 25/26] Documentation/rtla: Add -A/--aligned option Cover the newly added -A/--aligned option that aligns timerlat threads using the corresponding feature of the timerlat tracer. A note is added to clarify what alignment means, similar to the note in the tracer implementation in commit 4245bf4dc58f ("tracing/osnoise: Add option to align tlat threads"). Link: https://lore.kernel.org/r/20260527144928.2944472-3-tglozar@redhat.com [ remove spurious newline ] Signed-off-by: Tomas Glozar --- Documentation/tools/rtla/common_timerlat_options.txt | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Documentation/tools/rtla/common_timerlat_options.txt b/Documentation/tools/rtla/common_timerlat_options.txt index ab159b2cbfe7..100840f4c0ed 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. From db956bcf8d681b5a01ebe04c79f6a7b29b9934f9 Mon Sep 17 00:00:00 2001 From: Tomas Glozar Date: Thu, 14 May 2026 09:30:38 +0200 Subject: [PATCH 26/26] rtla: Document tests in README RTLA tests are not documented anywhere. Mention both runtime and unit tests in the README, with instructions on how to run them and a list of dependencies and required system configuration. Link: https://lore.kernel.org/r/20260514073038.204428-1-tglozar@redhat.com Signed-off-by: Tomas Glozar --- tools/tracing/rtla/README.txt | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/tools/tracing/rtla/README.txt b/tools/tracing/rtla/README.txt index a9faee4dbb3a..13b4a798b487 100644 --- a/tools/tracing/rtla/README.txt +++ b/tools/tracing/rtla/README.txt @@ -42,4 +42,34 @@ For development, we suggest the following steps for compiling rtla: $ make $ sudo make install +Running tests + +RTLA has two test suites: a runtime test suite and a unit test suite. + +The runtime test suite is available as "make check" (root required) and has +the following dependencies, in addition to RTLA build dependencies: + +- Perl +- Test::Harness (libtest-harness-perl on Debian/Ubuntu, perl-Test-Harness on Fedora/RHEL) +- bash +- coreutils +- ldd +- util-linux +- procps(-ng) +- bpftool (if rtla is built against libbpf) + +as well as the following required system configuration: + +- CONFIG_OSNOISE_TRACER=y +- CONFIG_TIMERLAT_TRACER=y +- tracefs mounted and readable at /sys/kernel/tracing + +The unit test suite is available as "make unit-tests" and has the following +dependencies: + +- libcheck + +Unlike the runtime test suite, root is not required to run unit tests, nor is +a tracefs/osnoise/timerlat-capable kernel required. + For further information, please refer to the rtla man page.