[RFC PATCH 7/7] mm/damon/perf: add CONFIG_DAMON_PERF_DEBUG and pipeline health check

From: Kunwu Chan

Date: Tue Aug 18 2026 - 02:11:36 EST


From: Lian Wang <lianux.mm@xxxxxxxxx>

Add CONFIG_DAMON_PERF_DEBUG as an optional Kconfig option that
enables pr_debug() output for the observability pipeline via the
damon_perf_dbg() macro. Default off, zero overhead when disabled.

Add a pipeline health check to the observability selftest that
diagnoses which stage is broken when callbacks are zero, using the
existing per-CPU state and counter deltas.

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/Kconfig | 16 ++++++++++++++++
mm/damon/perf/debugfs.c | 5 ++++-
mm/damon/perf/perf.h | 7 +++++++
mm/damon/perf/stats.c | 15 ++++++++++++++-
.../selftests/damon/damon_perf_obs_test.sh | 18 +++++++++++++++++-
5 files changed, 58 insertions(+), 3 deletions(-)

diff --git a/mm/damon/Kconfig b/mm/damon/Kconfig
index 9f811510760f..35ec4d12c5b8 100644
--- a/mm/damon/Kconfig
+++ b/mm/damon/Kconfig
@@ -148,4 +148,20 @@ config DAMON_PERF_OBSERVE
static-inline no-ops with zero runtime overhead.

If unsure, say N.
+
+config DAMON_PERF_DEBUG
+ bool "DAMON Perf verbose debugging output"
+ depends on DAMON_PERF_OBSERVE
+ help
+ Enable verbose per-event and per-drain pr_debug() output
+ for the DAMON perf observability pipeline. When enabled,
+ key lifecycle transitions and sampling events are logged
+ at KERN_DEBUG level, visible via dynamic_debug or when
+ DEBUG is defined at compile time.
+
+ This adds dmesg noise and should only be enabled for
+ development or troubleshooting.
+
+ If unsure, say N.
+
endmenu
diff --git a/mm/damon/perf/debugfs.c b/mm/damon/perf/debugfs.c
index c54dd7644ac3..48f3d23c2dbc 100644
--- a/mm/damon/perf/debugfs.c
+++ b/mm/damon/perf/debugfs.c
@@ -132,11 +132,14 @@ int damon_perf_debugfs_init(void)
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))
+ if (IS_ERR(damon_debugfs_dir)) {
+ damon_perf_dbg("debugfs init failed: %ld\n", PTR_ERR(damon_debugfs_dir));
return PTR_ERR(damon_debugfs_dir);
+ }

debugfs_create_file("perf_stats", 0400, damon_debugfs_dir,
NULL, &perf_stats_fops);

+ damon_perf_dbg("debugfs init ok\n");
return 0;
}
diff --git a/mm/damon/perf/perf.h b/mm/damon/perf/perf.h
index 78e23d436336..908c06f2e3db 100644
--- a/mm/damon/perf/perf.h
+++ b/mm/damon/perf/perf.h
@@ -18,6 +18,13 @@
struct perf_event;
#include <linux/types.h>

+#ifdef CONFIG_DAMON_PERF_DEBUG
+#define damon_perf_dbg(fmt, ...) \
+ pr_debug("damon-perf: " fmt, ##__VA_ARGS__)
+#else
+#define damon_perf_dbg(fmt, ...) no_printk(fmt, ##__VA_ARGS__)
+#endif
+
struct damon_perf_event;

/*
diff --git a/mm/damon/perf/stats.c b/mm/damon/perf/stats.c
index ae5b0037a31d..e2c1e5764d41 100644
--- a/mm/damon/perf/stats.c
+++ b/mm/damon/perf/stats.c
@@ -67,6 +67,7 @@ void damon_perf_observe_event_created(struct damon_perf_event *event, int cpu)
return;
}
*per_cpu_ptr(event->cpu_state, cpu) = DAMON_PERF_STATE_CREATED;
+ damon_perf_dbg("cpu %d: event created\n", cpu);
damon_perf_cpu_state_advance(cpu, DAMON_PERF_STATE_CREATED);
}

@@ -76,6 +77,7 @@ void damon_perf_observe_event_bound(struct damon_perf_event *event,
if (event->cpu_state)
*per_cpu_ptr(event->cpu_state, cpu) = DAMON_PERF_STATE_BOUND;
damon_perf_cpu_state_advance(cpu, DAMON_PERF_STATE_BOUND);
+ damon_perf_dbg("cpu %d: event bound\n", cpu);
}

void damon_perf_observe_event_enabled(struct damon_perf_event *event,
@@ -84,12 +86,14 @@ void damon_perf_observe_event_enabled(struct damon_perf_event *event,
if (event->cpu_state)
*per_cpu_ptr(event->cpu_state, cpu) = DAMON_PERF_STATE_ENABLED;
damon_perf_cpu_state_advance(cpu, DAMON_PERF_STATE_ENABLED);
+ damon_perf_dbg("cpu %d: event enabled\n", cpu);
}

void damon_perf_observe_event_disabled(struct damon_perf_event *event,
int cpu, int state)
{
/* State unchanged: the event may be re-enabled later. */
+ damon_perf_dbg("cpu %d: event disabled\n", cpu);
}

void damon_perf_observe_event_destroyed(struct damon_perf_event *event, int cpu)
@@ -104,6 +108,7 @@ void damon_perf_observe_event_destroyed(struct damon_perf_event *event, int cpu)
*/
if (event->cpu_state)
*per_cpu_ptr(event->cpu_state, cpu) = DAMON_PERF_STATE_UNINIT;
+ damon_perf_dbg("cpu %d: event destroyed\n", cpu);
}

void damon_perf_observe_event_free(struct damon_perf_event *event)
@@ -111,6 +116,7 @@ void damon_perf_observe_event_free(struct damon_perf_event *event)
if (event->cpu_state) {
free_percpu(event->cpu_state);
event->cpu_state = NULL;
+ damon_perf_dbg("event freed\n");
}
}

@@ -231,6 +237,7 @@ void damon_perf_observe_drain(unsigned int total, unsigned int matched)
{
if (trace_damon_perf_drain_enabled())
trace_damon_perf_drain(total, matched);
+ damon_perf_dbg("drain: total=%u matched=%u\n", total, matched);
}

/*
@@ -291,5 +298,11 @@ void damon_perf_stats_aggregate(struct damon_perf_stats *dst)

int damon_perf_framework_init(void)
{
- return damon_perf_debugfs_init();
+ int ret = damon_perf_debugfs_init();
+
+ if (ret)
+ damon_perf_dbg("framework init failed: %d\n", ret);
+ else
+ damon_perf_dbg("framework init ok\n");
+ return ret;
}
diff --git a/tools/testing/selftests/damon/damon_perf_obs_test.sh b/tools/testing/selftests/damon/damon_perf_obs_test.sh
index 4c4074cdd191..cd567c151ae6 100755
--- a/tools/testing/selftests/damon/damon_perf_obs_test.sh
+++ b/tools/testing/selftests/damon/damon_perf_obs_test.sh
@@ -344,7 +344,7 @@ max_cpu_state() {
} END { print max+0 }' "$1" 2>/dev/null
}

-CPU_ST_BASE=$(max_cpu_state "$STATS_BASE")
+CPU_ST_BASE=$(max_cpu_state /sys/kernel/debug/damon/perf_stats)

if [[ "$CPU_ST_BASE" -ge 1 ]]; then
pass "Event Created (max per-CPU state >= CREATED)"
@@ -431,6 +431,22 @@ VALID=$(delta valid)
ADDR_ZERO=$(delta addr_zero)
KERNEL=$(delta kernel)
ENQUEUE=$(delta enqueue)
+# Pipeline health check: diagnose which stage is broken when
+# callbacks are zero, using the existing per-CPU state and
+# counter deltas. This is a best-effort diagnostic, not a
+# substitute for detailed per-backend debugging.
+if [[ "$CALLBACK" -eq 0 ]]; then
+ CPU_ST_BASE_VAL=$(max_cpu_state /sys/kernel/debug/damon/perf_stats)
+ if [[ "$CPU_ST_BASE_VAL" -le 1 ]]; then
+ echo " Pipeline diagnosis: event not created or bound (state=$CPU_ST_BASE_VAL)"
+ elif [[ "$CPU_ST_BASE_VAL" -eq 2 ]]; then
+ echo " Pipeline diagnosis: event bound but not enabled (state=BOUND)"
+ elif [[ "$ENQUEUE" -eq 0 ]]; then
+ echo " Pipeline diagnosis: PMU not producing data or AUX pipeline broken"
+ else
+ echo " Pipeline diagnosis: samples enqueued but none valid"
+ fi
+fi
DEQUEUE=$(delta dequeue)
OVERFLOW=$(delta overflow)
MATCH=$(delta match)
--
2.43.0