mirror of https://lore.kernel.org/lkml/
 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,
	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, lkp@intel.com
Subject: [PATCH v8 1/5] tracing: Add lock-free stackmap for stack trace deduplication
Date: Sat,  7 Nov 2026 13:51:35 +0800	[thread overview]
Message-ID: <20261107055139.3138633-2-lipengfei28@xiaomi.com> (raw)
In-Reply-To: <20261107055139.3138633-1-lipengfei28@xiaomi.com>

From: Pengfei Li <lipengfei28@xiaomi.com>

A kernel stack costs 80-160 bytes per ftrace event, and the same stacks
recur. Add a preallocated hash map that stores each distinct stack once
and returns a 64-bit cookie, made of the map generation and the table
slot, that the stack recording path can record instead of the stack.

Lookup and insert are lock-free and may run in NMI context, so the map
depends on ARCH_HAVE_NMI_SAFE_CMPXCHG like tracing_map. Reset clears the
map in place and bumps the generation so that old cookies stop matching.
The stack_map file that resolves cookies is created by the next patch.

Assisted-by: LLM
Signed-off-by: Pengfei Li <lipengfei28@xiaomi.com>
---
 kernel/trace/Kconfig          |  13 ++
 kernel/trace/Makefile         |   1 +
 kernel/trace/trace.h          |   3 +
 kernel/trace/trace_stackmap.c | 382 ++++++++++++++++++++++++++++++++++
 kernel/trace/trace_stackmap.h |  22 ++
 5 files changed, 421 insertions(+)
 create mode 100644 kernel/trace/trace_stackmap.c
 create mode 100644 kernel/trace/trace_stackmap.h

diff --git a/kernel/trace/Kconfig b/kernel/trace/Kconfig
index 084f34dc6c9f..a83c53c02eea 100644
--- a/kernel/trace/Kconfig
+++ b/kernel/trace/Kconfig
@@ -412,6 +412,19 @@ config STACK_TRACER
 
 	  Say N if unsure.
 
+config FTRACE_STACKMAP
+	bool "Ftrace stack map deduplication"
+	depends on TRACING
+	depends on STACKTRACE
+	depends on ARCH_HAVE_NMI_SAFE_CMPXCHG
+	help
+	  Store each distinct kernel stack recorded by ftrace once, in a
+	  table allocated when options/stackmap is enabled, and record a
+	  64-bit cookie in the ring buffer instead of the full stack.
+	  See Documentation/trace/ftrace-stackmap.rst.
+
+	  If unsure, say N.
+
 config TRACE_PREEMPT_TOGGLE
 	bool
 	help
diff --git a/kernel/trace/Makefile b/kernel/trace/Makefile
index f934ff586bd4..7710ec2659e9 100644
--- a/kernel/trace/Makefile
+++ b/kernel/trace/Makefile
@@ -86,6 +86,7 @@ obj-$(CONFIG_HWLAT_TRACER) += trace_hwlat.o
 obj-$(CONFIG_OSNOISE_TRACER) += trace_osnoise.o
 obj-$(CONFIG_NOP_TRACER) += trace_nop.o
 obj-$(CONFIG_STACK_TRACER) += trace_stack.o
+obj-$(CONFIG_FTRACE_STACKMAP) += trace_stackmap.o
 obj-$(CONFIG_MMIOTRACE) += trace_mmiotrace.o
 obj-$(CONFIG_FUNCTION_GRAPH_TRACER) += trace_functions_graph.o
 obj-$(CONFIG_TRACE_BRANCH_PROFILING) += trace_branch.o
diff --git a/kernel/trace/trace.h b/kernel/trace/trace.h
index 80fe152af1dd..7b2cdc0dc253 100644
--- a/kernel/trace/trace.h
+++ b/kernel/trace/trace.h
@@ -453,6 +453,9 @@ struct trace_array {
 	struct cond_snapshot	*cond_snapshot;
 #endif
 	struct trace_func_repeats	__percpu *last_func_repeats;
+#ifdef CONFIG_FTRACE_STACKMAP
+	struct ftrace_stackmap __rcu	*stackmap;
+#endif
 	/*
 	 * On boot up, the ring buffer is set to the minimum size, so that
 	 * we do not waste memory on systems that are not using tracing.
diff --git a/kernel/trace/trace_stackmap.c b/kernel/trace/trace_stackmap.c
new file mode 100644
index 000000000000..e5ffd1850ebf
--- /dev/null
+++ b/kernel/trace/trace_stackmap.c
@@ -0,0 +1,382 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Stack trace deduplication for ftrace: each distinct stack is stored once,
+ * and the ring buffer records a cookie (map generation << 32 | slot).
+ */
+
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/jhash.h>
+#include <linux/seq_file.h>
+#include <linux/vmalloc.h>
+#include <linux/atomic.h>
+#include <linux/random.h>
+#include <linux/rcupdate.h>
+#include <linux/refcount.h>
+
+#include "trace.h"
+#include "trace_stackmap.h"
+
+#define FTRACE_STACKMAP_BITS		14
+#define FTRACE_STACKMAP_MAX_PROBE	64
+
+struct stackmap_elt {
+	u32		nr;
+	atomic_t	ref_count;
+	unsigned long	ips[FTRACE_STACKMAP_MAX_DEPTH];
+};
+
+struct stackmap_entry {
+	u32			key;
+	struct stackmap_elt	*val;
+};
+
+struct ftrace_stackmap {
+	unsigned int		map_bits;
+	unsigned int		map_size;
+	unsigned int		max_elts;
+	u32			hash_seed;
+	atomic_t		next_elt;
+	struct stackmap_entry	*entries;
+	struct stackmap_elt	*elts;
+	atomic_t		resetting;
+	u32			generation;
+	refcount_t		ref;
+};
+
+/* Serializes tracefs readers against reset. */
+static DEFINE_MUTEX(stackmap_lock);
+
+static u32 stackmap_next_gen = 1;
+
+static struct ftrace_stackmap *stackmap_locked(struct trace_array *tr)
+{
+	return rcu_dereference_protected(tr->stackmap,
+					 lockdep_is_held(&trace_types_lock));
+}
+
+static struct stackmap_elt *stackmap_load_elt(struct stackmap_entry *entry)
+{
+	/* Pairs with smp_store_release() in ftrace_stackmap_get_cookie(). */
+	return smp_load_acquire(&entry->val);
+}
+
+static struct stackmap_elt *stackmap_get_elt(struct ftrace_stackmap *smap)
+{
+	int idx;
+
+	idx = atomic_fetch_add_unless(&smap->next_elt, 1, smap->max_elts);
+	return idx < smap->max_elts ? &smap->elts[idx] : NULL;
+}
+
+static void ftrace_stackmap_destroy(struct ftrace_stackmap *smap)
+{
+	vfree(smap->elts);
+	vfree(smap->entries);
+	kfree(smap);
+}
+
+static struct ftrace_stackmap *ftrace_stackmap_create(void)
+{
+	struct ftrace_stackmap *smap;
+
+	smap = kzalloc_obj(*smap, GFP_KERNEL);
+	if (!smap)
+		return ERR_PTR(-ENOMEM);
+
+	smap->map_bits = FTRACE_STACKMAP_BITS;
+	smap->max_elts = 1U << FTRACE_STACKMAP_BITS;
+	smap->map_size = 1U << (FTRACE_STACKMAP_BITS + 1);
+
+	smap->entries = __vcalloc(smap->map_size, sizeof(*smap->entries),
+				  GFP_KERNEL | __GFP_RETRY_MAYFAIL | __GFP_NOWARN);
+	if (!smap->entries)
+		goto fail;
+
+	smap->elts = __vcalloc(smap->max_elts, sizeof(*smap->elts),
+			       GFP_KERNEL | __GFP_RETRY_MAYFAIL | __GFP_NOWARN);
+	if (!smap->elts)
+		goto fail;
+
+	smap->hash_seed = get_random_u32();
+	smap->generation = stackmap_next_gen;
+	refcount_set(&smap->ref, 1);
+	return smap;
+
+fail:
+	ftrace_stackmap_destroy(smap);
+	return ERR_PTR(-ENOMEM);
+}
+
+int ftrace_stackmap_alloc(struct trace_array *tr)
+{
+	struct ftrace_stackmap *smap;
+
+	if (stackmap_locked(tr))
+		return 0;
+
+	smap = ftrace_stackmap_create();
+	if (IS_ERR(smap))
+		return PTR_ERR(smap);
+
+	rcu_assign_pointer(tr->stackmap, smap);
+	return 0;
+}
+
+static struct ftrace_stackmap *stackmap_get(struct trace_array *tr)
+{
+	struct ftrace_stackmap *smap;
+
+	guard(mutex)(&trace_types_lock);
+	smap = stackmap_locked(tr);
+	if (smap)
+		refcount_inc(&smap->ref);
+	return smap;
+}
+
+static void stackmap_put(struct ftrace_stackmap *smap)
+{
+	if (!smap)
+		return;
+	if (refcount_dec_and_test(&smap->ref))
+		ftrace_stackmap_destroy(smap);
+}
+
+static void stackmap_reset(struct trace_array *tr)
+{
+	struct ftrace_stackmap *smap = stackmap_locked(tr);
+
+	if (!smap)
+		return;
+
+	/* Keep new callers out, then wait for the ones already inside. */
+	atomic_set(&smap->resetting, 1);
+	synchronize_rcu();
+
+	scoped_guard(mutex, &stackmap_lock) {
+		memset(smap->entries, 0, sizeof(*smap->entries) * smap->map_size);
+		memset(smap->elts, 0, sizeof(*smap->elts) * smap->max_elts);
+		atomic_set(&smap->next_elt, 0);
+		smap->generation++;
+	}
+
+	/* Pairs with atomic_read_acquire() in ftrace_stackmap_get_cookie(). */
+	atomic_set_release(&smap->resetting, 0);
+}
+
+bool ftrace_stackmap_get_cookie(struct ftrace_stackmap *smap,
+				unsigned long *ips, unsigned int nr_entries,
+				u64 *cookie)
+{
+	u32 key_hash, idx, test_key, trace_len;
+	struct stackmap_entry *entry;
+	struct stackmap_elt *val;
+	int probes = 0;
+
+	/* Pairs with atomic_set_release() in stackmap_reset(). */
+	if (atomic_read_acquire(&smap->resetting))
+		return false;
+
+	trace_len = nr_entries * sizeof(unsigned long);
+	key_hash = jhash2((const u32 *)ips, trace_len / sizeof(u32),
+			  smap->hash_seed);
+	/* Zero marks a free slot. */
+	if (key_hash == 0)
+		key_hash = 1;
+
+	idx = key_hash >> (32 - (smap->map_bits + 1));
+
+	while (probes < FTRACE_STACKMAP_MAX_PROBE) {
+		idx &= smap->map_size - 1;
+		entry = &smap->entries[idx];
+		test_key = READ_ONCE(entry->key);
+
+		if (test_key == key_hash) {
+			val = stackmap_load_elt(entry);
+			/* Insert in progress; from NMI it may be the interrupted one. */
+			if (!val)
+				return false;
+			if (READ_ONCE(val->nr) == nr_entries &&
+			    memcmp(val->ips, ips, trace_len) == 0) {
+				atomic_add_unless(&val->ref_count, 1, INT_MAX);
+				goto found;
+			}
+		} else if (!test_key) {
+			if (atomic_read(&smap->next_elt) >= smap->max_elts)
+				break;
+			/* Lost the race; the slot may now hold this stack. */
+			if (cmpxchg(&entry->key, 0, key_hash) != 0)
+				continue;
+
+			val = stackmap_get_elt(smap);
+			if (!val)
+				break;
+
+			val->nr = nr_entries;
+			atomic_set(&val->ref_count, 1);
+			memcpy(val->ips, ips, trace_len);
+			/* Pairs with stackmap_load_elt(). */
+			smp_store_release(&entry->val, val);
+			goto found;
+		}
+
+		idx++;
+		probes++;
+	}
+
+	return false;
+found:
+	*cookie = ((u64)smap->generation << FTRACE_STACKMAP_COOKIE_GEN_SHIFT) | idx;
+	return true;
+}
+
+struct stackmap_seq_private {
+	struct trace_array	*tr;
+	struct ftrace_stackmap	*smap;
+};
+
+static struct stackmap_entry *stackmap_seq_seek(struct ftrace_stackmap *smap,
+						loff_t *pos)
+{
+	loff_t i;
+
+	for (i = *pos; i < smap->map_size; i++) {
+		if (READ_ONCE(smap->entries[i].key) &&
+		    stackmap_load_elt(&smap->entries[i])) {
+			*pos = i;
+			return &smap->entries[i];
+		}
+	}
+	/* Advance past the end or seq_read() repeats the last entry. */
+	*pos = smap->map_size;
+	return NULL;
+}
+
+static void *stackmap_seq_start(struct seq_file *m, loff_t *pos)
+{
+	struct stackmap_seq_private *priv = m->private;
+
+	mutex_lock(&stackmap_lock);
+	if (!priv->smap)
+		return NULL;
+	return stackmap_seq_seek(priv->smap, pos);
+}
+
+static void *stackmap_seq_next(struct seq_file *m, void *v, loff_t *pos)
+{
+	struct stackmap_seq_private *priv = m->private;
+
+	(*pos)++;
+	return stackmap_seq_seek(priv->smap, pos);
+}
+
+static void stackmap_seq_stop(struct seq_file *m, void *v)
+{
+	/* seq_file calls stop() even when start() returned NULL. */
+	mutex_unlock(&stackmap_lock);
+}
+
+static int stackmap_seq_show(struct seq_file *m, void *v)
+{
+	struct stackmap_entry *entry = v;
+	struct stackmap_seq_private *priv = m->private;
+	struct stackmap_elt *elt;
+	u32 idx = entry - priv->smap->entries;
+	u32 i, nr;
+
+	elt = stackmap_load_elt(entry);
+	if (!elt)
+		return 0;
+
+	nr = READ_ONCE(elt->nr);
+	if (nr > FTRACE_STACKMAP_MAX_DEPTH)
+		nr = FTRACE_STACKMAP_MAX_DEPTH;
+
+	seq_printf(m, "stack_id %u gen %u ref %u depth %u\n",
+		   idx, priv->smap->generation, atomic_read(&elt->ref_count), nr);
+	for (i = 0; i < nr; i++) {
+		unsigned long ip = elt->ips[i];
+
+		if (ip == FTRACE_TRAMPOLINE_MARKER)
+			seq_puts(m, " => [FTRACE TRAMPOLINE]\n");
+		else
+			seq_printf(m, " => %pS\n", (void *)ip);
+	}
+	return 0;
+}
+
+static const struct seq_operations stackmap_seq_ops = {
+	.start	= stackmap_seq_start,
+	.next	= stackmap_seq_next,
+	.stop	= stackmap_seq_stop,
+	.show	= stackmap_seq_show,
+};
+
+static int stackmap_open(struct inode *inode, struct file *file)
+{
+	struct trace_array *tr = inode->i_private;
+	struct stackmap_seq_private *priv;
+	struct seq_file *m;
+	int ret;
+
+	ret = tracing_check_open_get_tr(tr);
+	if (ret)
+		return ret;
+
+	ret = seq_open_private(file, &stackmap_seq_ops,
+			       sizeof(struct stackmap_seq_private));
+	if (ret) {
+		trace_array_put(tr);
+		return ret;
+	}
+
+	m = file->private_data;
+	priv = m->private;
+	priv->tr = tr;
+	if (file->f_mode & FMODE_READ)
+		priv->smap = stackmap_get(tr);
+	return 0;
+}
+
+static int stackmap_release(struct inode *inode, struct file *file)
+{
+	struct seq_file *m = file->private_data;
+	struct stackmap_seq_private *priv = m->private;
+	struct ftrace_stackmap *smap = priv->smap;
+	struct trace_array *tr = priv->tr;
+	int ret;
+
+	ret = seq_release_private(inode, file);
+	stackmap_put(smap);
+	trace_array_put(tr);
+	return ret;
+}
+
+static ssize_t stackmap_write(struct file *file, const char __user *ubuf,
+			      size_t count, loff_t *ppos)
+{
+	struct seq_file *m = file->private_data;
+	struct stackmap_seq_private *priv = m->private;
+	char buf[8];
+
+	if (count >= sizeof(buf))
+		return -EINVAL;
+	if (copy_from_user(buf, ubuf, count))
+		return -EFAULT;
+	buf[count] = '\0';
+
+	if (sysfs_streq(buf, "0")) {
+		guard(mutex)(&trace_types_lock);
+		stackmap_reset(priv->tr);
+		return count;
+	}
+	return -EINVAL;
+}
+
+const struct file_operations ftrace_stackmap_fops = {
+	.open		= stackmap_open,
+	.read		= seq_read,
+	.write		= stackmap_write,
+	.llseek		= seq_lseek,
+	.release	= stackmap_release,
+};
diff --git a/kernel/trace/trace_stackmap.h b/kernel/trace/trace_stackmap.h
new file mode 100644
index 000000000000..6699988da390
--- /dev/null
+++ b/kernel/trace/trace_stackmap.h
@@ -0,0 +1,22 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _TRACE_STACKMAP_H
+#define _TRACE_STACKMAP_H
+
+#include <linux/types.h>
+
+#define FTRACE_STACKMAP_MAX_DEPTH	64
+
+/* A cookie holds the generation in its high half and the slot below. */
+#define FTRACE_STACKMAP_COOKIE_GEN_SHIFT	32
+
+struct trace_array;
+struct ftrace_stackmap;
+
+int ftrace_stackmap_alloc(struct trace_array *tr);
+bool ftrace_stackmap_get_cookie(struct ftrace_stackmap *smap,
+				unsigned long *ips, unsigned int nr_entries,
+				u64 *cookie);
+
+extern const struct file_operations ftrace_stackmap_fops;
+
+#endif /* _TRACE_STACKMAP_H */
-- 
2.34.1


  reply	other threads:[~2026-11-07  5:52 UTC|newest]

Thread overview: 9+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-07  5:51 [PATCH v8 0/5] tracing: Add stack trace deduplication for the ring buffer Li Pengfei
2026-11-07  5:51 ` Li Pengfei [this message]
2026-11-07  5:51 ` [PATCH v8 2/5] tracing: Use the stackmap from the ftrace stack recording path Li Pengfei
2026-11-07 22:35   ` kernel test robot
2026-11-07  5:51 ` [PATCH v8 3/5] tracing: Add stackmap statistics interface Li Pengfei
     [not found]   ` <sashiko-outbox-202646@kernel.org>
2026-11-07  6:28     ` Li Pengfei
2026-11-07  5:51 ` [PATCH v8 4/5] Documentation: tracing: Document the ftrace stackmap Li Pengfei
2026-11-07  5:51 ` [PATCH v8 5/5] selftests/ftrace: Add a stackmap basic functionality test Li Pengfei
     [not found]   ` <sashiko-outbox-202648@kernel.org>
2026-11-07  6:28     ` 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=20261107055139.3138633-2-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

all inboxes | Powered by JetHome®