[RFC PATCH 3/7] mm/damon/perf: add debugfs statistics interface
From: Kunwu Chan
Date: Tue Aug 18 2026 - 02:11:21 EST
From: Lian Wang <lianux.mm@xxxxxxxxx>
Expose the aggregated per-CPU counters and the global event-stage in a
debug-only debugfs perf_stats file. The format is explicitly unstable;
tracepoints are the stable diagnostic interface.
Co-developed-by: Kunwu Chan <kunwu.chan@xxxxxxxxx>
Signed-off-by: Kunwu Chan <kunwu.chan@xxxxxxxxx>
Signed-off-by: Lian Wang <lianux.mm@xxxxxxxxx>
---
mm/damon/perf/Makefile | 2 +-
mm/damon/perf/debugfs.c | 142 ++++++++++++++++++++++++++++++++++++++++
mm/damon/perf/stats.c | 2 +-
3 files changed, 144 insertions(+), 2 deletions(-)
create mode 100644 mm/damon/perf/debugfs.c
diff --git a/mm/damon/perf/Makefile b/mm/damon/perf/Makefile
index 5c46d3da7ef8..150cbaa875fa 100644
--- a/mm/damon/perf/Makefile
+++ b/mm/damon/perf/Makefile
@@ -2,4 +2,4 @@
# Observability: per-CPU counters, tracepoints, debugfs perf_stats
obj-$(CONFIG_DAMON_PERF_OBSERVE) += damon-perf.o
-damon-perf-objs := stats.o
+damon-perf-objs := stats.o debugfs.o
diff --git a/mm/damon/perf/debugfs.c b/mm/damon/perf/debugfs.c
new file mode 100644
index 000000000000..c54dd7644ac3
--- /dev/null
+++ b/mm/damon/perf/debugfs.c
@@ -0,0 +1,142 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * DAMON Perf Observability — debugfs Interface
+ *
+ * Exposes one file under /sys/kernel/debug/damon/:
+ *
+ * perf_stats — per-CPU pipeline statistics in tabular form
+ *
+ * DEBUG ONLY — format may change without notice; do not parse in
+ * scripts. For stable diagnostics, use the tracepoints under
+ * /sys/kernel/debug/tracing/events/damon/
+ */
+
+#include <linux/cpu.h>
+#include <linux/cpumask.h>
+#include <linux/debugfs.h>
+#include <linux/seq_file.h>
+
+#include "perf.h"
+
+static struct dentry *damon_debugfs_dir;
+
+/*
+ * perf_stats
+ */
+
+static const char *state_name(int s)
+{
+ switch (s) {
+ case DAMON_PERF_STATE_UNINIT: return "UNINIT";
+ case DAMON_PERF_STATE_CREATED: return "CREATED";
+ case DAMON_PERF_STATE_BOUND: return "BOUND";
+ case DAMON_PERF_STATE_ENABLED: return "ENABLED";
+ case DAMON_PERF_STATE_RUNNING: return "RUNNING";
+ case DAMON_PERF_STATE_ERROR: return "ERROR";
+ default: return "?";
+ }
+}
+
+static int perf_stats_show(struct seq_file *m, void *v)
+{
+ struct damon_perf_stats agg, st;
+ int cpu;
+ bool first = true;
+
+ damon_perf_stats_aggregate(&agg);
+
+ seq_puts(m, " -------------- ----------\n");
+ seq_puts(m, " Counter Value\n");
+ seq_puts(m, " -------------- ----------\n");
+
+#define STAT_ROW(label, field) \
+ seq_printf(m, " %-12s %8llu\n", label, agg.field)
+
+ STAT_ROW("callback", callback);
+ STAT_ROW("valid", sample_valid);
+ STAT_ROW("null", sample_null);
+ STAT_ROW("addr_zero", sample_addr_zero);
+ STAT_ROW("kernel", sample_kernel);
+ STAT_ROW("inv_phys", sample_invalid_phys);
+ STAT_ROW("enqueue", enqueue);
+ STAT_ROW("dequeue", dequeue);
+ STAT_ROW("overflow", overflow);
+ STAT_ROW("ring_peak", ring_peak);
+ STAT_ROW("match", match);
+ STAT_ROW("miss_tgid", miss_tgid);
+ STAT_ROW("miss_region", miss_region);
+ STAT_ROW("miss_bound", miss_boundary);
+ STAT_ROW("update", update);
+
+#undef STAT_ROW
+
+ seq_puts(m, " -------------- ----------\n\n");
+
+ /* Per-CPU breakdown */
+ cpus_read_lock();
+ for_each_online_cpu(cpu) {
+ damon_perf_stats_snapshot(cpu, &st);
+
+ /* Skip truly idle CPUs */
+ if (st.cpu_state == DAMON_PERF_STATE_UNINIT &&
+ !st.callback && !st.enqueue && !st.dequeue)
+ continue;
+
+ if (first) {
+ seq_puts(m, " Per-CPU (non-zero / non-UNINIT):\n");
+ first = false;
+ }
+
+ seq_printf(m, " CPU%02d: st=%-7s cb=%llu enq=%llu deq=%llu ovf=%llu match=%llu tgid=%llu noreg=%llu bound=%llu upd=%llu\n",
+ cpu, state_name(st.cpu_state),
+ st.callback, st.enqueue, st.dequeue,
+ st.overflow, st.match,
+ st.miss_tgid, st.miss_region, st.miss_boundary,
+ st.update);
+ }
+ cpus_read_unlock();
+
+ return 0;
+}
+
+static int perf_stats_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, perf_stats_show, NULL);
+}
+
+static const struct file_operations perf_stats_fops = {
+ .open = perf_stats_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+/*
+ * Init / teardown
+ */
+
+int damon_perf_debugfs_init(void)
+{
+ if (!debugfs_initialized())
+ return -ENODEV;
+
+ /*
+ * Create the "damon" directory first. debugfs_create_dir() mounts
+ * debugfs via simple_pin_fs() before touching debugfs_mount, so it
+ * is safe to call during initcall time -- unlike debugfs_lookup(),
+ * which dereferences debugfs_mount unconditionally and crashes with
+ * a NULL mount. If the directory already exists (created by another
+ * DAMON interface) debugfs_create_dir() returns -EEXIST; fall back
+ * to debugfs_lookup(), which is now safe because the mount exists.
+ */
+ damon_debugfs_dir = debugfs_create_dir("damon", NULL);
+ if (damon_debugfs_dir == ERR_PTR(-EEXIST))
+ damon_debugfs_dir = debugfs_lookup("damon", NULL);
+ if (IS_ERR(damon_debugfs_dir))
+ return PTR_ERR(damon_debugfs_dir);
+
+ debugfs_create_file("perf_stats", 0400, damon_debugfs_dir,
+ NULL, &perf_stats_fops);
+
+ return 0;
+}
diff --git a/mm/damon/perf/stats.c b/mm/damon/perf/stats.c
index a869f115bb26..ae5b0037a31d 100644
--- a/mm/damon/perf/stats.c
+++ b/mm/damon/perf/stats.c
@@ -291,5 +291,5 @@ void damon_perf_stats_aggregate(struct damon_perf_stats *dst)
int damon_perf_framework_init(void)
{
- return 0;
+ return damon_perf_debugfs_init();
}
--
2.43.0