From: Lian Wang <[email protected]>

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 <[email protected]>
Signed-off-by: Kunwu Chan <[email protected]>
Signed-off-by: Lian Wang <[email protected]>
---
 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


Reply via email to