From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mail-pg1-f170.google.com (mail-pg1-f170.google.com [209.85.215.170]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id EC90F41CB4C for ; Sat, 12 Sep 2026 08:39:57 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.215.170 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789202399; cv=none; b=MT1oXMU012FTJr12zWFvRbzO+iTPhXOpeHRtEMmFGXy46hGhsyNFSaQL+Ff3JZx1X3b3DizMcVdbiRr0Ez7n6kvoPMUgoC4QbLtiR5jLXTXp6UheXBSmvdtzv+UsV910Dom567xH3ClHd2XC1+g2CL/Ad5+dBd0R7Z9ffpBgv48= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789202399; c=relaxed/simple; bh=/Ir1mqEjvgIRdqB1BYKQH3vrAGumpvHNL96pvjYtC8E=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=lwyeXdlOYClba4Bku7Qgn9cR6SJFanvRfb0U4Ya4zE/kunEcciwCdeVhs3x+XE47haE/n1Qpk/SFTOWJHHBdIyilX0lb4akdFwnTur/4uoisO/2OQnGMCNkwPNI0n6mwgorPF4bUKS4687KA3NQ0NMFGIuQMZvaK1QQYxi4t8fQ= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=DVwIE85J; arc=none smtp.client-ip=209.85.215.170 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="DVwIE85J" Received: by mail-pg1-f170.google.com with SMTP id 41be03b00d2f7-cc4be0e5351so1294033a12.0 for ; Sat, 12 Sep 2026 01:39:57 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1789202397; x=1789807197; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=nqVO+NA1CSyKoLABRCdCgiCrSwOYNF3Pr1xez243cAY=; b=DVwIE85JcfqaqQ5E7Jsaij6fjGdvBQImsQjlR5jMn1Yfb3PoeJMmpDg25OvF6lbs9L iha8W1Cs/n7Y+z+H1MR/ZRNF3Q9mRkQXJb/qp0G/9u7zRH1Jvr4MlzRgXTLuJxWSgoAO sXiVV2UTgSSUx7zEn1rUGTVCPl2sugIpqXxQwXoNMI+Y4siDAcTqAdybW0W10RXu3NNx FGztFzKmCfwKCdy73hGHbYvIfL4h0fhTcPXZ/EqA1JYSqXsjyrqjm7bh4hC8u3l6aM8/ 6LMH3Jiqj2iDb1ll4JNiJhc9NOVpZjmPn1JvLRJ1WJkAAvCY9If1Kh1qFV6qZqu4ZScF D2iw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1789202397; x=1789807197; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=nqVO+NA1CSyKoLABRCdCgiCrSwOYNF3Pr1xez243cAY=; b=ga9z2DeRSVB4tnMMgNBq1xPQqb1LZiynkMM3t2ZgzOwtfSp9doz8VIw/0ast3jvQOB h0OLad50KXDfgfppSlRP73ycC+Hv+Vcgrq+U/r8tljZqhDkNgpimh6essXkZkWlMnGbX B7/GBfzUboMoXEYXEwXNSgpZ8AnmiMVKdGfKzkUNdylsBKE4W8bcmQ9XYBYHfixxr+I7 EvzfKssjIGB/73hEmV3DpiW1s0s5OQBjw2NFzfmlfJgQDFOrcIAH9BhQCbxvIw61cFzi GbI49sIAHiMg5iaghtsKEv9fRjbZ0tI/TF0Uu6rgsZz181qT0rS3b6ZHIEwwpMdk4sbq AqhQ== X-Forwarded-Encrypted: i=1; AKwUvBw6/uvH3Y7At+3KUFLX8Wi+97YOWWO9FxlIwUaHn3YVMaObuJlMvACwrmFddRIUfPqC//NFRhADZqg=@vger.kernel.org X-Gm-Message-State: AFuF++nMsv2Zqh6q3oZxBAGVCXjiIlALl7/KyggsX5fZS9+MtkM00zn1 f6gzQwwI/T+Mi0qORMVjs+KLHZhVRKVJsEp53HF89jUnPa7yiDXo5kPI X-Gm-Gg: AYBFou17bm3Fj6jdxrUsttYqhVbO7NKZkTM8dUuBmpAvneijVmveHkioS9xSN5fPpLh t3tHlvpN7U3GCO4v/me2Hh6XppOJuduKT4yWkME2i5gSDFao2qWOw+21oVEPtDddh6upbQXVn8L W6w0KYQMwZZOXFfsscwvOskRQc6WVovZ5wte964aWJI4xvY2lwLlbhUtVhXDi/dXFdmbIaTz2F8 RDUhOw26rPDd2lKyX4GVfn65drgoYHDvyVpngHf0krfbut/yDEShqC9WryuTSB5YgMJGIpNJ2VF oJqvtQ4AFZTMmpxm+msEr1+jXhqqHgIGsN9GjriZSM0C0XYORCeHFdlv4VigswSNE8h1ZAFchyG TsXRruvsLAxniUNxEPwS2LNrqGLc5oigo6KJ5hdX6Jo6B8OxJJ+6duBhUpIvWS3ab0a92wFMKqJ IJl3JO8LZrV7vaO39K1q06+H3QD+np933glbr/IeXQ6jzqV1s9HpJ5sKHa/9z5kWD0wqy6l1Lmi qCHp3uPUVXV5LkQstbAr4l6ywku9O5+glCL8u6I X-Received: by 2002:a17:90b:39a7:b0:398:9bd1:3214 with SMTP id 98e67ed59e1d1-39d9c223eb3mr13008838a91.21.1789202397068; Sat, 12 Sep 2026 01:39:57 -0700 (PDT) Received: from lipengfei28-ThinkStation-P368.mioffice.cn ([2408:8607:1b00:8:16a3:d08b:6ddb:dec6]) by smtp.gmail.com with ESMTPSA id 98e67ed59e1d1-39d95091214sm9790583a91.3.2026.09.12.01.39.47 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sat, 12 Sep 2026 01:39:56 -0700 (PDT) From: Li Pengfei X-Google-Original-From: Li Pengfei 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 Message-Id: <20260912083753.3426176-9-lipengfei28@xiaomi.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20260912083753.3426176-1-lipengfei28@xiaomi.com> References: <20260912083753.3426176-1-lipengfei28@xiaomi.com> Precedence: bulk X-Mailing-List: linux-doc@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit From: Pengfei Li 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 --- .../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 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/.*.*/\1/p' trace | sort -nu) +if [ -z "$trace_ids" ]; then + fail "trace has no 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