Thread (8 messages) 8 messages, 1 author, 2026-08-18

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

flat view
DORMANTno replies

From: Kunwu Chan <hidden>
Date: 2026-08-18 06:11:31
Also in: damon, linux-kselftest, linux-mm, lkml
Subsystem: damon, kernel selftest framework, memory management, the rest · Maintainers: SJ Park, Shuah Khan, Shuah Khan, Andrew Morton, Linus Torvalds

From: Lian Wang <redacted>

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 <redacted>
Signed-off-by: Kunwu Chan <redacted>
Signed-off-by: Lian Wang <redacted>
---
 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
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help