From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mail-pj1-f45.google.com (mail-pj1-f45.google.com [209.85.216.45]) (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 EA1DE416872 for ; Sat, 12 Sep 2026 08:39:57 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.216.45 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789202399; cv=none; b=EXeomQBpqWuknJsmMTggy9HZjRPg3mkiq7/cmPjD2K1DFlDWQsb3dlafOUZ+UpoTBfnC7+crxOdJlErfTstLZYl7gy7juW/cREk4JyKHMPcSSRePfCzjb+yzYeEA6ASM4PKUZ31ijx7B5lDt+Mk3uzYkrABuyegWMZCdqmXrsP0= 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.216.45 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-pj1-f45.google.com with SMTP id 98e67ed59e1d1-398a5aad413so1783833a91.3 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=gT9bJuD1CVoKNh2ckz9EdnHe/FuCsXtad/CfKSBQvcOcNVxC4D4PuGGME0aVN7+Onc tFkY2TY6P2sCUkxR/DJiGLpdSrGIsvIdYma5oq3GcYWTHZ9Ve3bSG3DvrAmW8R6kLknN R2S6pC6tSsnr5FZmo/NuSufiesBrwt++FbxtfWWBos1l70vKhXIFUlDucz9OIi7hsqfg PFkk/rEG8ofd8UzfR7I/IBPkfkGasr1xtG4RCF8ax9iEfQWB1J8Vz1vXIoHOO+ASuHhi lL7laJsZyV/W7QWC6Ro9KIUZ7N3KYzEivLBfZ2WHnmb6OIqOIzIop55FbATuKk8seyHy eCLQ== X-Forwarded-Encrypted: i=1; AKwUvBxfMJQWRh+7LYOMn5Mf/U/LQ0nC0ROh9WUSDSEXWBCGKppspJjT5lXcLCTwH8p5zOvevyoc138LF33BpYFRvPgQUxg=@vger.kernel.org X-Gm-Message-State: AFuF++lvvugM6f/j6gB3dYprkrnPMemqrYTIc5i14wXYoVaVoZcjFV6F zjl4okwyMN4Big8sX8MWqGpaSefMmiwGZ26eHPxZlueiUnKGqMq6zKSP X-Gm-Gg: AYBFou3l3UzxkEkBeP4u0zYKcpONni/D3ugPuT0UznoDvUg0F2GoOJ6GvVcMbq2c5jH tw0MAcynoCbJDFQSNjMBmiz0euE8ys+PwswT/7nSh/2yZ22rYfLpYQsvTM24Ml1QWXefWF7Ynvq q9TKa/Tpx8Ufw7PsXuAkLBzJ/EdjsAsvy0Ne8ubX3XBaVpW1JwPOtm6P/ZldHKGN9zFYFML4QwH BhSwPgSFIxay3kwsUOTp9RSyr7w0vvpfpO45M77jSo0seM5eIZUKLGiztoedSF2LlpSmsg/vUQc M+ZOGq61q9aP5gR2t62hkAnYE6VfdXppZaHYhCbJA+Rpoxp3jXWPKpKfVjN5gkX6LsBVV34PZP/ IZ4iZnnJKHRIQRYhsWWcqoEI+jgWzPHUqI3PEPhkvreg0UszzkUbgvQy5kdUsCf5hBs57MYyTMD K3VkQknMhyYBu3EcMtFwsA8mrhaIH6HNKkmKUualNLBbGACYbFH0CHz/Z3Pw3ZDA//jyjpumiNP QuNv/ooZ09ZR0LOTBSro9rymm9T+7hRTwpci43/ 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-trace-kernel@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