[RFC PATCH 3/7] mm/damon/perf: add debugfs statistics interface
Kunwu Chan <[email protected]>
| Newsgroups | gmane.linux.kernel,gmane.linux.kernel.mm |
|---|---|
| Message-ID | <[email protected]> |
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