Linux Documentation
 help / color / mirror / Atom feed
From: Li Pengfei <ljdlns1987@gmail.com>
To: rostedt@goodmis.org, mhiramat@kernel.org
Cc: mathieu.desnoyers@efficios.com, mark.rutland@arm.com,
	corbet@lwn.net, skhan@linuxfoundation.org, lkp@intel.com,
	linux-trace-kernel@vger.kernel.org, linux-kernel@vger.kernel.org,
	linux-doc@vger.kernel.org, linux-kselftest@vger.kernel.org,
	zhangbo56@xiaomi.com, lipengfei28@xiaomi.com
Subject: [RFC PATCH v7 08/10] selftests/ftrace: add a stackmap basic functionality test
Date: Sat, 12 Sep 2026 16:37:51 +0800	[thread overview]
Message-ID: <20260912083753.3426176-9-lipengfei28@xiaomi.com> (raw)
In-Reply-To: <20260912083753.3426176-1-lipengfei28@xiaomi.com>

From: Pengfei Li <lipengfei28@xiaomi.com>

Exercise stackmap end to end through sched_switch event stack capture.
Enable stackmap and stacktrace, run a directly owned worker, and limit
the event source to switches whose prev_pid or next_pid is that worker.
Require stack ids, map records and successful lookups.

Reset the map five times while tracing and the filtered writer remain
active. Before each reset, require at least eight successes. Immediately
after reset, send SIGSTOP and wait until /proc reports the worker in a
stopped state before disabling tracing and sampling counters. Require the
post-reset success count to be below the pre-reset count, proving that the
sample belongs to a new generation. Reset again while the writer is
stopped and require exactly zero entries before resuming and refilling.

After the final refill, stop the writer and take one stable statistics
sample. Since insertion increments both entries and successes while a hit
increments successes only, require successes > entries to prove record
reuse without assuming one duplicate-prone record has ref_count > 1.
Require every post-reset stack id in the trace to resolve in the current
map. A nonzero drop count remains valid because failed map lookups try the
normal full-stack fallback.

Own the worker directly so cleanup can kill and wait for the exact
process. HUP, INT and TERM exit through the EXIT cleanup path. Cleanup
disables tracing and the event, removes the filter, turns off both
options, and resets the map.

Use element-record terminology for entries because concurrent duplicate
records are permitted by the lock-free insertion algorithm.

Signed-off-by: Pengfei Li <lipengfei28@xiaomi.com>
---
 .../ftrace/test.d/ftrace/stackmap-basic.tc    | 242 ++++++++++++++++++
 1 file changed, 242 insertions(+)
 create mode 100644 tools/testing/selftests/ftrace/test.d/ftrace/stackmap-basic.tc

diff --git a/tools/testing/selftests/ftrace/test.d/ftrace/stackmap-basic.tc b/tools/testing/selftests/ftrace/test.d/ftrace/stackmap-basic.tc
new file mode 100644
index 000000000000..ade29b648a48
--- /dev/null
+++ b/tools/testing/selftests/ftrace/test.d/ftrace/stackmap-basic.tc
@@ -0,0 +1,242 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0
+# description: ftrace - stackmap basic functionality
+# requires: stack_map stack_map_stat options/stackmap options/stacktrace events/sched/sched_switch/enable
+
+# Test that ftrace stackmap deduplication works:
+# 1. Enable stackmap and stack capture for sched_switch events
+# 2. Run a scheduling workload briefly
+# 3. Verify trace contains <stack_id> events
+# 4. Verify stack_map has entries and at least some successes. Drops are
+#    a legitimate by-design fallback counter and may be nonzero.
+# 5. Verify reset succeeds while tracing is active (it clears the map
+#    only and leaves the ring buffer alone)
+# 6. Verify reset also clears the map when tracing is stopped
+
+fail() {
+    echo "FAIL: $1"
+    exit_fail
+}
+
+wait_for_worker_stopped() {
+    tries=0
+    while [ "$tries" -lt 100 ]; do
+        state=
+        if state=$(awk '/^State:/ { print $2 }' "/proc/$worker/status" \
+                2>/dev/null); then
+            case "$state" in
+            T|t)
+                return 0
+                ;;
+            esac
+        else
+            return 1
+        fi
+        sleep 0.01
+        tries=$((tries + 1))
+    done
+    return 1
+}
+
+worker=
+
+cleanup() {
+    disable_tracing 2>/dev/null || :
+    if [ -n "$worker" ]; then
+        kill -CONT "$worker" 2>/dev/null || :
+        kill "$worker" 2>/dev/null || :
+        wait "$worker" 2>/dev/null || :
+    fi
+    echo 0 > events/sched/sched_switch/filter 2>/dev/null || :
+    echo 0 > events/sched/sched_switch/enable 2>/dev/null || :
+    echo 0 > options/stackmap 2>/dev/null || :
+    echo 0 > options/stacktrace 2>/dev/null || :
+    echo 0 > stack_map 2>/dev/null || :
+}
+trap cleanup EXIT
+trap 'exit 1' HUP INT TERM
+
+disable_tracing
+clear_trace
+echo 0 > stack_map || fail "initial stackmap reset failed"
+
+# sched_switch gives a bounded, inexpensive writer path through
+# __ftrace_trace_stack(), unlike full function tracing under QEMU.
+echo 1 > options/stackmap
+echo 1 > options/stacktrace
+echo 1 > events/sched/sched_switch/enable
+
+# Keep a directly owned workload runnable while sched_switch records stacks.
+(while :; do :; done) &
+worker=$!
+echo "prev_pid == $worker || next_pid == $worker" > \
+    events/sched/sched_switch/filter || fail "could not filter sched_switch workload"
+
+enable_tracing
+sleep 1
+
+# Lock in the precondition that makes this an active-writer reset test.
+[ "$(cat events/sched/sched_switch/enable)" = "1" ] ||
+    fail "sched_switch event is not enabled before reset"
+[ "$(cat options/stackmap)" = "1" ] ||
+    fail "stackmap option is not enabled before reset"
+[ "$(cat options/stacktrace)" = "1" ] ||
+    fail "stacktrace option is not enabled before reset"
+[ "$(cat tracing_on)" = "1" ] ||
+    fail "tracing is not on before reset"
+kill -0 "$worker" 2>/dev/null || fail "reset workload exited early"
+
+# Reset repeatedly while the owned writer is active, then again once it is
+# quiesced. Only deterministic properties are asserted:
+#
+#   - While the writer runs, the reset must be accepted. No counter is
+#     compared here: the writer resumes claiming records as soon as reset()
+#     returns, so any snapshot taken afterwards is a moving target and would
+#     make this test flaky rather than prove anything.
+#   - With the writer stopped, sched_switch filtered to it, and tracing off,
+#     no stackmap writer remains, so the following reset must leave exactly
+#     zero entries and zero successes. That is checked every iteration.
+#
+# That an active reset really starts a new generation is proven separately and
+# deterministically in stackmap-reset.tc, where a binary fd held across the
+# reset must observe its own generation and then ESTALE.
+i=0
+while [ "$i" -lt 5 ]; do
+    tries=0
+    pre_successes=0
+    while [ "$tries" -lt 100 ]; do
+        pre_successes=$(awk '/^successes:/ { print $2 }' stack_map_stat)
+        : "${pre_successes:=0}"
+        [ "$pre_successes" -ge 8 ] && break
+        sleep 0.01
+        tries=$((tries + 1))
+    done
+    [ "$pre_successes" -ge 8 ] ||
+        fail "active writer did not reach 8 successes before reset"
+
+    echo 0 > stack_map || fail "stackmap reset failed with active writer"
+
+    kill -STOP "$worker" || fail "could not stop reset workload"
+    wait_for_worker_stopped ||
+        fail "reset workload did not enter the stopped state"
+    disable_tracing
+
+    echo 0 > stack_map || fail "stopped-writer verification reset failed"
+    reset_entries=$(awk '/^entries:/ { print $2 }' stack_map_stat)
+    : "${reset_entries:=-1}"
+    [ "$reset_entries" -eq 0 ] ||
+        fail "stopped-writer reset left $reset_entries entries"
+    reset_successes=$(awk '/^successes:/ { print $2 }' stack_map_stat)
+    : "${reset_successes:=-1}"
+    [ "$reset_successes" -eq 0 ] ||
+        fail "stopped-writer reset left successes=$reset_successes"
+
+    kill -CONT "$worker" || fail "could not resume reset workload"
+    enable_tracing
+    i=$((i + 1))
+done
+
+# Reset intentionally leaves the ring buffer untouched. Drop those old ids
+# here so every id inspected below belongs to the final map generation.
+clear_trace
+
+# Wait until aggregate counters prove that at least one operation reused an
+# existing element record: every insertion increments both entries and
+# successes, while a hit increments successes only. Stop the worker before
+# taking the decisive sample so both the counters and ring buffer are stable.
+entries_after_active_reset=0
+successes_after_active_reset=0
+i=0
+while [ "$i" -lt 50 ]; do
+    stats=$(cat stack_map_stat)
+    entries_after_active_reset=$(printf '%s\n' "$stats" |
+        awk '/^entries:/ { print $2 }')
+    successes_after_active_reset=$(printf '%s\n' "$stats" |
+        awk '/^successes:/ { print $2 }')
+    : "${entries_after_active_reset:=0}"
+    : "${successes_after_active_reset:=0}"
+    if [ "$entries_after_active_reset" -gt 0 ] &&
+       [ "$successes_after_active_reset" -gt \
+         "$entries_after_active_reset" ]; then
+        break
+    fi
+    sleep 0.1
+    i=$((i + 1))
+done
+
+kill -STOP "$worker" || fail "could not stop final reset workload"
+wait_for_worker_stopped ||
+    fail "final reset workload did not enter the stopped state"
+disable_tracing
+
+stats=$(cat stack_map_stat)
+entries_after_active_reset=$(printf '%s\n' "$stats" |
+    awk '/^entries:/ { print $2 }')
+successes_after_active_reset=$(printf '%s\n' "$stats" |
+    awk '/^successes:/ { print $2 }')
+: "${entries_after_active_reset:=0}"
+: "${successes_after_active_reset:=0}"
+if [ "$entries_after_active_reset" -eq 0 ]; then
+    fail "stackmap did not refill after active-writer reset"
+fi
+if [ "$successes_after_active_reset" -le "$entries_after_active_reset" ]; then
+    fail "aggregate counters show no element-record reuse after reset"
+fi
+
+kill -CONT "$worker" 2>/dev/null || :
+kill "$worker" 2>/dev/null || :
+wait "$worker" 2>/dev/null || :
+worker=
+
+# Stop the event before inspecting the fixed buffer and map contents.
+echo 0 > events/sched/sched_switch/enable
+trace_ids=$(sed -n 's/.*<stack_id \([0-9][0-9]*\)>.*/\1/p' trace | sort -nu)
+if [ -z "$trace_ids" ]; then
+    fail "trace has no <stack_id> events after active-writer reset"
+fi
+
+echo 0 > options/stackmap
+
+# Check stack_map_stat
+entries=$(grep "^entries:" stack_map_stat | awk '{print $2}')
+: "${entries:=0}"
+if [ "$entries" -eq 0 ]; then
+    fail "stackmap has zero entries after active-writer reset"
+fi
+
+successes=$(grep "^successes:" stack_map_stat | awk '{print $2}')
+: "${successes:=0}"
+if [ "$successes" -eq 0 ]; then
+    fail "stackmap has zero successes"
+fi
+
+drops=$(grep "^drops:" stack_map_stat | awk '{print $2}')
+: "${drops:=0}"
+# drops is a legitimate by-design fallback counter: when the map is full
+# or under heavy probe pressure, stackmap falls back to recording a full
+# stack instead of a stack_id. A nonzero drops count is therefore allowed
+# as long as deduplication also produced successful stack_id events.
+
+# Every observed id was emitted after the final reset and must resolve in
+# the current map generation.
+for id in $trace_ids; do
+    grep -q "^stack_id $id " stack_map ||
+        fail "trace stack id $id has no current stack_map record"
+done
+
+# Check stack_map text output is parseable
+first_id=$(grep "^stack_id" stack_map | head -1 | awk '{print $2}')
+if [ -z "$first_id" ]; then
+    fail "stack_map output has no stack_id entries"
+fi
+
+# Test reset works when tracing is stopped as well
+echo 0 > stack_map
+entries_after=$(cat stack_map_stat | grep "^entries:" | awk '{print $2}')
+: "${entries_after:=-1}"
+if [ "$entries_after" -ne 0 ]; then
+    fail "stackmap reset did not clear entries (got $entries_after)"
+fi
+
+echo "stackmap basic test passed: $entries element records, $successes successes, $drops drops"
+exit 0
-- 
2.34.1


  parent reply	other threads:[~2026-09-12  8:39 UTC|newest]

Thread overview: 11+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-12  8:37 [RFC PATCH v7 00/10] trace: stack trace deduplication for ftrace ring buffer Li Pengfei
2026-09-12  8:37 ` [RFC PATCH v7 01/10] trace: add lock-free stackmap for stack trace deduplication Li Pengfei
2026-09-12  8:37 ` [RFC PATCH v7 02/10] trace: use the stackmap from the ftrace stack recording path Li Pengfei
2026-09-12  8:37 ` [RFC PATCH v7 03/10] trace: add stackmap statistics interface Li Pengfei
2026-09-12  8:37 ` [RFC PATCH v7 04/10] trace: add stackmap binary export Li Pengfei
2026-09-12  8:37 ` [RFC PATCH v7 05/10] trace: make the stackmap capacity settable on the kernel command line Li Pengfei
2026-09-12  8:37 ` [RFC PATCH v7 06/10] Documentation: tracing: document the ftrace stackmap Li Pengfei
2026-09-12  8:37 ` [RFC PATCH v7 07/10] tools/tracing: add a parser for the stackmap binary export Li Pengfei
2026-09-12  8:37 ` Li Pengfei [this message]
2026-09-12  8:37 ` [RFC PATCH v7 09/10] selftests/ftrace: add a stackmap reset and binary ABI test Li Pengfei
2026-09-12  8:37 ` [RFC PATCH v7 10/10] selftests/ftrace: add a stackmap instance gating test Li Pengfei

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260912083753.3426176-9-lipengfei28@xiaomi.com \
    --to=ljdlns1987@gmail.com \
    --cc=corbet@lwn.net \
    --cc=linux-doc@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-kselftest@vger.kernel.org \
    --cc=linux-trace-kernel@vger.kernel.org \
    --cc=lipengfei28@xiaomi.com \
    --cc=lkp@intel.com \
    --cc=mark.rutland@arm.com \
    --cc=mathieu.desnoyers@efficios.com \
    --cc=mhiramat@kernel.org \
    --cc=rostedt@goodmis.org \
    --cc=skhan@linuxfoundation.org \
    --cc=zhangbo56@xiaomi.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox