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,
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 v5 0/3] trace: stack trace deduplication for ftrace ring buffer
Date: Wed, 2 Sep 2026 14:42:39 +0800 [thread overview]
Message-ID: <20260902064242.28606-1-lipengfei28@xiaomi.com> (raw)
From: Pengfei Li <lipengfei28@xiaomi.com>
Hi Steven, Masami, all,
This is v5 of the ftrace stackmap series, sent as a new thread.
v4: https://lore.kernel.org/all/20260616064119.438063-1-lipengfei28@xiaomi.com/
The series adds stack trace deduplication to ftrace. When the
'stackmap' option is enabled alongside 'stacktrace', the ring buffer
stores a 4-byte stack_id instead of a full kernel stack trace, and the
full stacks are exported once via tracefs (stack_map / stack_map_bin).
Rebased onto v7.2-rc4 (4539944e5151).
v5 is the cleanups Steven asked for on v4, five issues I found myself,
and one behaviour change: reset is simplified the way Steven suggested
(details below). No functional change to the dedup algorithm itself.
Mathieu: patch 1 is the lock-free map. The memory ordering there
(entry->val release/acquire publishing the element payload, key only
under READ_ONCE, and resetting + synchronize_rcu() for teardown) is
the part I would most like a second pair of eyes on.
Motivation
==========
The target use case is long-duration, from-boot kernel tracing where
the same stacks recur enormously often and the bottleneck is ring
buffer space, not CPU.
Concretely: tracing the slab allocator from boot for hours to study
memory aging and to catch the allocation backtraces behind a usage
peak. With a stacktrace trigger on the slab tracepoints, every event
today carries a full kernel stack (~80-160 bytes). On a fixed-size
ring buffer that bounds how far back in time the trace reaches: the
buffer wraps in seconds to minutes and the early-boot history -- the
part we care about -- is overwritten before it can be consumed.
In this workload the set of distinct stacks is small and highly
repetitive, so storing a 4-byte stack_id per event and the full stack
only once dramatically increases the time span a given buffer covers.
The intended operating model is exactly the low-overhead one ftrace is
good at: let the trace run for a long time producing a comparatively
small, dense log, then resolve stack_ids offline (cat stack_map, or
parse stack_map_bin with the included tool) during analysis.
This is complementary to, not a replacement for, the existing full
stack recording: deep stacks and the early pre-init window still fall
back to full stacks (see below).
Effect on retention
====================
Same fixed per-CPU buffer, slab allocation workload with a shallow
kernel stack (kmem_cache_alloc), stackmap OFF vs ON:
retained events bytes/event time span
stackmap OFF 645,068 ~104 B 15.0 s
stackmap ON 1,397,741 ~48 B 27.7 s
2.17x 2.17x 1.85x
The buffer holds ~2.17x more events and reaches ~1.85x further back in
time for the same memory. The win grows with stack depth and with how
repetitive the stacks are; for deep, highly-repeated stacks the
per-event size approaches the 4-byte stack_id plus event header.
Changes since v4
================
From Steven's review of v4:
- ftrace_stackmap_create() uses vcalloc() for both the hash table
and the element pool instead of vzalloc(size * n).
- ftrace_stackmap_create() error handling collapsed into a single
goto-fail ladder, freeing in reverse allocation order.
- Dropped the anonymous block in ftrace_stackmap_reset(); the cpu
iterator is declared at function scope.
- struct ftrace_stackmap_bin_entry grew a u64 ips[] flexible array
member, and stackmap_bin_open() now uses struct_size(e, ips, nr)
and writes through e->ips. The on-disk byte layout is unchanged,
so the binary format is not affected.
- Undid a few 80-column line wraps in the tracefs init path that
were hurting readability.
reset semantics -- the one behaviour change in v5:
- Steven pointed out on the v4 thread [1] that reset was stricter
than it needs to be. v5 drops both parts of that strictness:
reset no longer returns -EBUSY when tracing is on, and it no
longer clears the ring buffer. It now clears the map and nothing
else, which also let the tracer_tracing_is_on() check, the
trace_types_lock section and the snapshot-buffer handling go away
(patch 1 is 56 lines shorter as a result).
I had replied that I would do this as a follow-up rather than hold
this respin. On reflection there is no reason to ship the stricter
version first, so it is folded in here instead.
The consequence is that a trace can still contain <stack_id N>
records after a reset. Such an id either has no entry in
stack_map, or -- once tracing continues and the slot is reused --
resolves to an unrelated stack. That is misleading userspace
output, not corruption: the map is still guarded by the resetting
flag plus synchronize_rcu(), and reset frees nothing, it only
memsets storage the map still owns. This is spelled out in the
ftrace_stackmap_reset() kernel-doc and in ftrace-stackmap.rst.
[1] https://lore.kernel.org/all/20260821235129.078dd489@fedora/
Issues found locally since v4:
- The IS_ERR() branch of the stackmap init path modified
global_trace.trace_flags without trace_types_lock, racing
set_tracer_flag(). It now goes through the same
stackmap_mark_init_failed() helper as the other failure path.
- TRACE_STACK_ID was not handled in print_graph_function_flags(),
so with current_tracer=function_graph a stack_id event fell
through to print_graph_comment() instead of being punted to its
own printer like TRACE_STACK. Now handled alongside TRACE_STACK.
- stack_map_stat computed the success rate as successes * 100 before
dividing, which can overflow u64 now that the counters saturate at
LONG_MAX. Uses mul_u64_u64_div_u64().
- stack_map_bin allocated a fresh snapshot on every open, so
repeatedly opening without closing could pin a lot of vmalloc
space. It is now single-open.
- stackmap_seq_start() and stackmap_seq_next() held the seq_file
position in a u32, narrowing the loff_t the iterator is handed.
The position is driven by our own next() and bounded by map_size,
so this was not reachable in practice, but the correctness of the
truncation depended on an invariant that was not visible locally.
Both now keep it in a loff_t.
Still open
==========
Eager vs lazy allocation. The element pool is allocated at fs_initcall
when CONFIG_FTRACE_STACKMAP=y, regardless of whether userspace ever
enables the option (~8 MB at the default bits=14, up to ~135 MB at
bits=18). This keeps the hot path allocation-free with no
allocation-failure path under tracing pressure. v5 keeps eager
allocation; if you would rather not pay the resident cost when the
option is never enabled, switching to lazy allocation on the first
'echo 1 > options/stackmap' is a contained change and I will do it in
v6 rather than hold this version on it.
stack_map_bin is kept as-is. Steven's suggestion to give the bin entry
a flexible array member is taken as acceptance that the binary
interface can land now; say the word if you would rather ship the text
interface first.
Test results
============
QEMU (aarch64 virt, v7.2-rc4 + this series), no BUG/WARNING/Oops in
any run:
- functional suite: 19/19 PASS. Covers stack_map_bin magic/version
(0x46534d42 / 1, confirming the flexible-array change did not move
the on-disk layout), the single-open guard returning EBUSY on a
second open and recovering after close, reset now being accepted
while tracing is on, reset clearing the map (11 -> 0 entries) while
leaving the ring buffer untouched (149 -> 149 stack_id records),
the success_rate computation, and global-vs-secondary instance
gating including the trace_options write rejection.
- function_graph + stackmap: 4/4 PASS with 173 stack_id events in the
graph output, rendering as
ksoftirqd/0-14 [000] d..2. 6.731781: <stack_id 16416>
i.e. punted to the event's own printer rather than being formatted
as a graph comment, which is what the print_graph_function_flags()
fix is for.
- boot-time activation (trace_options=stackmap,stacktrace on the
kernel cmdline): 3/3 PASS, 58 stacks and 421 hits recorded before
init runs, 0 drops.
Build: kernel/trace/{trace_stackmap,trace,trace_functions_graph}.o
compile clean (0 errors, 0 warnings) with the arm64 cross toolchain.
Device retention numbers above were collected on a Xiaomi SM8850
(ARM64) running an Android workload, comparing the same buffer with
the option off and on.
Known limitations
=================
- Per-instance stackmap support is not included; the option is gated
to the global instance (in the tracefs layout and at the
set_tracer_flag() write path). Per-instance maps are a follow-up.
- Deduplication is best-effort, not strict: under heavy concurrent
contention two CPUs racing with the same stack hash may each claim a
different slot, producing a few duplicate entries; ref_count is then
split across them. This keeps the hot path lock-free.
- reset clears the map only and leaves the ring buffer alone, so
stack_ids in an already-collected trace can stop resolving after a
reset, as described above.
- The stackmap covers kernel stacks only.
- stack_map_bin is a best-effort snapshot, serialized against reset
but not a fully atomic export.
- trace-cmd / libtraceevent integration is left for follow-up.
Usage
=====
echo 1 > /sys/kernel/debug/tracing/options/stackmap
echo 1 > /sys/kernel/debug/tracing/options/stacktrace
Pengfei Li (3):
trace: add lock-free stackmap for stack trace deduplication
trace: integrate stackmap into ftrace stack recording path
trace: add documentation, selftest and tooling for stackmap
Documentation/trace/ftrace-stackmap.rst | 175 ++++
Documentation/trace/index.rst | 1 +
kernel/trace/Kconfig | 22 +
kernel/trace/Makefile | 1 +
kernel/trace/trace.c | 228 ++++-
kernel/trace/trace.h | 17 +
kernel/trace/trace_entries.h | 15 +
kernel/trace/trace_functions_graph.c | 1 +
kernel/trace/trace_output.c | 23 +
kernel/trace/trace_selftest.c | 1 +
kernel/trace/trace_stackmap.c | 869 ++++++++++++++++++
kernel/trace/trace_stackmap.h | 57 ++
.../ftrace/test.d/ftrace/stackmap-basic.tc | 110 +++
.../test.d/ftrace/stackmap-instance-gate.tc | 54 ++
.../ftrace/test.d/ftrace/stackmap-reset.tc | 77 ++
tools/tracing/stackmap_dump.py | 164 ++++
16 files changed, 1812 insertions(+), 3 deletions(-)
create mode 100644 Documentation/trace/ftrace-stackmap.rst
create mode 100644 kernel/trace/trace_stackmap.c
create mode 100644 kernel/trace/trace_stackmap.h
create mode 100644 tools/testing/selftests/ftrace/test.d/ftrace/stackmap-basic.tc
create mode 100644 tools/testing/selftests/ftrace/test.d/ftrace/stackmap-instance-gate.tc
create mode 100644 tools/testing/selftests/ftrace/test.d/ftrace/stackmap-reset.tc
create mode 100755 tools/tracing/stackmap_dump.py
base-commit: 4539944e515183668109bdf4d0c3d7d228383d88
--
2.34.1
next reply other threads:[~2026-09-02 6:43 UTC|newest]
Thread overview: 10+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-02 6:42 Li Pengfei [this message]
2026-09-02 6:42 ` [RFC PATCH v5 1/3] trace: add lock-free stackmap for stack trace deduplication Li Pengfei
2026-09-02 6:57 ` sashiko-bot
2026-09-03 13:19 ` Pengfei Li
2026-09-02 6:42 ` [RFC PATCH v5 2/3] trace: integrate stackmap into ftrace stack recording path Li Pengfei
2026-09-02 6:54 ` sashiko-bot
2026-09-03 13:21 ` Pengfei Li
2026-09-02 6:42 ` [RFC PATCH v5 3/3] trace: add documentation, selftest and tooling for stackmap Li Pengfei
2026-09-02 6:51 ` sashiko-bot
2026-09-03 13:22 ` Pengfei Li
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=20260902064242.28606-1-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=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