From: Lian Wang <[email protected]>

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


Reply via email to