Linux Kernel Selftest development
 help / color / mirror / Atom feed
From: Kunwu Chan <kunwu.chan@gmail.com>
To: sj@kernel.org, akpm@linux-foundation.org
Cc: damon@lists.linux.dev, linux-mm@kvack.org,
	linux-kernel@vger.kernel.org, linux-trace-kernel@vger.kernel.org,
	linux-kselftest@vger.kernel.org, rostedt@goodmis.org,
	mhiramat@kernel.org, mathieu.desnoyers@efficios.com,
	shuah@kernel.org, lianux.mm@gmail.com,
	Kunwu Chan <kunwu.chan@gmail.com>
Subject: [RFC PATCH 1/7] mm/damon/perf: add observability framework with tracepoints and CONFIG switch
Date: Tue, 18 Aug 2026 14:10:25 +0800	[thread overview]
Message-ID: <20260818061031.827057-2-kunwu.chan@linux.dev> (raw)
In-Reply-To: <20260818061031.827057-1-kunwu.chan@linux.dev>

From: Kunwu Chan <kunwu.chan@gmail.com>

Add the observe framework headers together with the compile-time switch
that gates the whole feature: the three DAMON perf tracepoints this
series adds (guarded with CONFIG_DAMON_PERF_OBSERVE so they are not
registered when the switch is off; damon_perf_ring_overflow is provided
by the base series), the observe API declarations with static-inline
no-ops for disabled builds, the access-report contract (miss-reason
enum, report-source enum, report source field, per-event cpu_state
member), and the Kconfig and Makefile wiring.

The sample tracepoint carries what the PMU actually populated
(data->sample_flags), what was requested (perf_event->attr.
sample_type), and the execution context (process/softirq/hardirq/NMI)
in a single line, so PMU support gaps and context expectations (e.g.
IBS overflow in NMI, SPE AUX drain in process context) are verifiable
at a glance.

Every following commit in this series builds with CONFIG_DAMON_PERF_OBSERVE
both enabled and disabled.

Co-developed-by: Lian Wang <lianux.mm@gmail.com>
Signed-off-by: Lian Wang <lianux.mm@gmail.com>
Signed-off-by: Kunwu Chan <kunwu.chan@gmail.com>
---
 include/linux/damon.h        |  44 +++++++
 include/trace/events/damon.h | 120 ++++++++++++++++++
 mm/damon/Kconfig             |  17 +++
 mm/damon/Makefile            |   1 +
 mm/damon/perf/Makefile       |   3 +
 mm/damon/perf/perf.h         | 228 +++++++++++++++++++++++++++++++++++
 6 files changed, 413 insertions(+)
 create mode 100644 mm/damon/perf/Makefile
 create mode 100644 mm/damon/perf/perf.h

diff --git a/include/linux/damon.h b/include/linux/damon.h
index 11f1c1071b9b..c191c065b0e4 100644
--- a/include/linux/damon.h
+++ b/include/linux/damon.h
@@ -116,6 +116,19 @@ struct damon_target {
 	bool obsolete;
 };
 
+/**
+ * enum damon_report_source - Tells which subsystem produced an access report.
+ *
+ * Ring and matching counters aggregate all sources; this enum lets callers
+ * tag reports so that tracepoints and future per-source breakdowns can
+ * distinguish NMI overflow-handler samples from
+ * page-fault hints.
+ */
+enum damon_report_source {
+	DAMON_REPORT_SRC_PERF_OVERFLOW = 0,  /* overflow_handler (IBS, PEBS) */
+	DAMON_REPORT_SRC_PAGE_FAULT,         /* damon_report_page_fault() */
+};
+
 /**
  * struct damon_access_report - Represent single access report information.
  * @paddr:		Start physical address of the accessed address range.
@@ -125,6 +138,8 @@ struct damon_target {
  * @tid:		The task id of the task that made the access.
  * @tgid:		Thread group id of the task that made the access.
  * @is_write:		Whether the access is write.
+ * @source:		Which subsystem produced this report
+ *			(enum damon_report_source).
  *
  * Any DAMON API callers that notified access events can report the information
  * to DAMON using damon_report_access().  This struct contains the reporting
@@ -138,10 +153,28 @@ struct damon_access_report {
 	pid_t tid;
 	pid_t tgid;
 	bool is_write;
+#ifdef CONFIG_DAMON_PERF_OBSERVE
+	int source;
+#endif /* CONFIG_DAMON_PERF_OBSERVE */
 /* private: */
 	unsigned long report_jiffies;	/* when this report is made */
 };
 
+/*
+ * Reason codes for trace_damon_perf_report_missed.
+ *
+ * DAMON_REPORT_MISS_TGID:       tgid mismatch (pid-based monitoring,
+ *                               missed at drain-loop level before the
+ *                               per-target iteration).
+ * DAMON_REPORT_MISS_NOREGION:   binary search found no containing region.
+ * DAMON_REPORT_MISS_BOUNDARY:   address + size straddles region boundary.
+ */
+enum damon_report_miss_reason {
+	DAMON_REPORT_MISS_TGID = 1,
+	DAMON_REPORT_MISS_NOREGION = 2,
+	DAMON_REPORT_MISS_BOUNDARY = 3,
+};
+
 /**
  * enum damos_action - Represents an action of a Data Access Monitoring-based
  * Operation Scheme.
@@ -1027,6 +1060,17 @@ struct damon_perf_event {
 	struct hlist_node hlist_node;
 	bool init_complete;
 	bool any_cpu_failed;
+#ifdef CONFIG_DAMON_PERF_OBSERVE
+	/*
+	 * Per-CPU lifecycle state (enum damon_perf_event_state).
+	 * Allocated lazily on the first observe_event_created(),
+	 * freed on observe_event_destroyed().  Each event tracks
+	 * its own progression through CREATED->BOUND->ENABLED,
+	 * so destroying one event does not overwrite another"s
+	 * state on the same CPU.
+	 */
+	int __percpu *cpu_state;
+#endif /* CONFIG_DAMON_PERF_OBSERVE */
 	struct damon_ctx *ctx;
 };
 
diff --git a/include/trace/events/damon.h b/include/trace/events/damon.h
index 877627c9a1a1..c87fbeefb85a 100644
--- a/include/trace/events/damon.h
+++ b/include/trace/events/damon.h
@@ -91,6 +91,126 @@ TRACE_EVENT(damon_perf_ring_overflow,
 	TP_printk("cpu=%d", __entry->cpu)
 );
 
+#ifdef CONFIG_DAMON_PERF_OBSERVE
+/*
+ * Fires from NMI overflow handlers on every hardware sample received,
+ * before any DAMON-side filtering.  Records the raw address, full
+ * data_src (mem_op, mem_lvl, mem_snoop, mem_remote), period, and a
+ * reason code so userspace can distinguish:
+ *
+ *   0 = valid sample, queued to per-CPU ring
+ *   1 = data == NULL
+ *   2 = addr == 0 (PMU did not populate data->addr)
+ *   3 = kernel address (vaddr handler: addr >= TASK_SIZE)
+ *   4 = phys_addr not valid (paddr handler: !PERF_SAMPLE_PHYS_ADDR)
+ *
+ * data_src carries the raw union perf_mem_data_src value; use
+ * perf_mem__xxx macros to decode.
+ *
+ * sample_flags is what the PMU *actually* populated (from
+ * data->sample_flags); sample_type is what was *requested* (from
+ * perf_event->attr.sample_type).  Comparing them immediately
+ * reveals whether the PMU is providing the fields DAMON asked for
+ * — e.g. sample_type has PERF_SAMPLE_PHYS_ADDR but sample_flags
+ * does not → the PMU does not support physical-address sampling.
+ */
+TRACE_EVENT(damon_perf_sample,
+
+	TP_PROTO(unsigned long addr, u64 data_src, u64 period, int cpu,
+		u8 reason, u64 sample_flags, u64 sample_type,
+		u8 context),
+
+	TP_ARGS(addr, data_src, period, cpu, reason, sample_flags,
+		sample_type, context),
+
+	TP_STRUCT__entry(
+		__field(unsigned long, addr)
+		__field(u64, data_src)
+		__field(u64, period)
+		__field(int, cpu)
+		__field(u8, reason)
+		__field(u64, sample_flags)
+		__field(u64, sample_type)
+		__field(u8, context)
+	),
+
+	TP_fast_assign(
+		__entry->addr = addr;
+		__entry->data_src = data_src;
+		__entry->period = period;
+		__entry->cpu = cpu;
+		__entry->reason = reason;
+		__entry->sample_flags = sample_flags;
+		__entry->sample_type = sample_type;
+		__entry->context = context;
+	),
+
+	TP_printk("addr=0x%lx data_src=0x%llx period=%llu cpu=%d reason=%u context=%u sample_flags=0x%llx sample_type=0x%llx",
+		__entry->addr, __entry->data_src, __entry->period,
+		__entry->cpu, __entry->reason, __entry->context,
+		__entry->sample_flags, __entry->sample_type)
+);
+
+/*
+ * Fires when a report survived all ring/drain checks but could not be
+ * applied to any DAMON region.  Reasons correspond to
+ * enum damon_report_miss_reason:
+ *
+ *   DAMON_REPORT_MISS_TGID     (1): no target matched the report's tgid
+ *   DAMON_REPORT_MISS_NOREGION (2): binary search found no containing region
+ *   DAMON_REPORT_MISS_BOUNDARY (3): address + size straddles region boundary
+ *
+ * Note: tgid mismatches are now resolved in the drain loop *before*
+ * iterating targets, so there is at most one trace hit per report
+ * (rather than one per non-matching target as in earlier revisions).
+ */
+TRACE_EVENT(damon_perf_report_missed,
+
+	TP_PROTO(unsigned long addr, int cpu, int reason),
+
+	TP_ARGS(addr, cpu, reason),
+
+	TP_STRUCT__entry(
+		__field(unsigned long, addr)
+		__field(int, cpu)
+		__field(int, reason)
+	),
+
+	TP_fast_assign(
+		__entry->addr = addr;
+		__entry->cpu = cpu;
+		__entry->reason = reason;
+	),
+
+	TP_printk("addr=0x%lx cpu=%d reason=%d", __entry->addr,
+		__entry->cpu, __entry->reason)
+);
+
+/*
+ * Per-tick drain summary.  Fires from kdamond after draining the per-CPU
+ * SPSC ring, so users can observe total vs matched without polling dmesg
+ * or correlating individual miss tracepoints.
+ */
+TRACE_EVENT(damon_perf_drain,
+
+	TP_PROTO(unsigned int total, unsigned int matched),
+
+	TP_ARGS(total, matched),
+
+	TP_STRUCT__entry(
+		__field(unsigned int, total)
+		__field(unsigned int, matched)
+	),
+
+	TP_fast_assign(
+		__entry->total = total;
+		__entry->matched = matched;
+	),
+
+	TP_printk("total=%u matched=%u", __entry->total, __entry->matched)
+);
+#endif /* CONFIG_DAMON_PERF_OBSERVE */
+
 /* Per-tick DAMOS_QUOTA_NODE_ELIGIBLE_MEM_BP goal evaluation. */
 TRACE_EVENT(damos_node_eligible_mem_bp,
 
diff --git a/mm/damon/Kconfig b/mm/damon/Kconfig
index ad629f0f31d8..9f811510760f 100644
--- a/mm/damon/Kconfig
+++ b/mm/damon/Kconfig
@@ -131,4 +131,21 @@ config DAMON_ACMA
 	  min/max memory for the system and maximum memory pressure stall time
 	  ratio.
 
+config DAMON_PERF_OBSERVE
+	bool "DAMON perf event observability framework"
+	depends on DAMON
+	depends on PERF_EVENTS
+	depends on DEBUG_FS
+	default n
+	help
+	  Enable per-CPU pipeline counters, tracepoints, and a
+	  debug-only debugfs perf_stats file for DAMON
+	  hardware-sampled access reports.  The debugfs format is
+	  unstable and must not be used by scripts; counters and
+	  tracepoints are the diagnostic interface.
+
+	  When disabled, all observe functions are compiled to
+	  static-inline no-ops with zero runtime overhead.
+
+	  If unsure, say N.
 endmenu
diff --git a/mm/damon/Makefile b/mm/damon/Makefile
index 22494754f41e..04da39a9f56c 100644
--- a/mm/damon/Makefile
+++ b/mm/damon/Makefile
@@ -9,3 +9,4 @@ obj-$(CONFIG_DAMON_RECLAIM)	+= modules-common.o reclaim.o
 obj-$(CONFIG_DAMON_LRU_SORT)	+= modules-common.o lru_sort.o
 obj-$(CONFIG_DAMON_STAT)	+= modules-common.o stat.o
 obj-$(CONFIG_DAMON_ACMA)	+= modules-common.o acma.o
+obj-$(CONFIG_DAMON)		+= perf/
diff --git a/mm/damon/perf/Makefile b/mm/damon/perf/Makefile
new file mode 100644
index 000000000000..cc0d4f1d1d28
--- /dev/null
+++ b/mm/damon/perf/Makefile
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: GPL-2.0
+
+# Observability: per-CPU counters, tracepoints, debugfs perf_stats
diff --git a/mm/damon/perf/perf.h b/mm/damon/perf/perf.h
new file mode 100644
index 000000000000..78e23d436336
--- /dev/null
+++ b/mm/damon/perf/perf.h
@@ -0,0 +1,228 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * DAMON Hardware-sampled Access Report Observability Framework
+ *
+ * Single entry-point for all hardware sampling backends (ARM SPE,
+ * AMD IBS, Intel PEBS, …).  Every event, sample, ring operation,
+ * match decision, and region update flows through the
+ * damon_perf_observe_*() API, which fans out to per-CPU counters
+ * and tracepoints.  When CONFIG_DAMON_PERF_OBSERVE=n, everything
+ * compiles to static-inline no-ops.
+ *
+ * Author: Kunwu Chan <kunwu.chan@gmail.com>
+ */
+
+#ifndef _DAMON_PERF_H
+#define _DAMON_PERF_H
+
+struct perf_event;
+#include <linux/types.h>
+
+struct damon_perf_event;
+
+/*
+ * Per-event state machine
+ *
+ * Each per-CPU damon_perf_event transitions through these states.
+ * State is tracked in a per-CPU integer (damon_perf_cpu_state).
+ */
+enum damon_perf_event_state {
+	DAMON_PERF_STATE_UNINIT = 0,
+	DAMON_PERF_STATE_CREATED,	/* struct allocated, cpuhp registered */
+	DAMON_PERF_STATE_BOUND,		/* perf_event_create_kernel_counter() ok */
+	DAMON_PERF_STATE_ENABLED,	/* perf_event_enable() called */
+	DAMON_PERF_STATE_RUNNING,	/* first overflow callback received */
+	DAMON_PERF_STATE_ERROR,		/* unrecoverable failure */
+};
+
+/*
+ * Per-CPU statistics
+ *
+ * All counters are monotonic, best-effort reads.  Userspace computes
+ * deltas between snapshots.  Stored per-CPU so the NMI fast path uses
+ * this_cpu_inc() with no locking.  Counter values are raw facts:
+ * interpretation (thresholds, verdicts) belongs in userspace.
+ *
+ * The kernel provides tracepoints under events/damon/ for structured,
+ * stable diagnostics.  The debugfs perf_stats file is DEBUG ONLY and
+ * its format may change without notice.
+ */
+struct damon_perf_stats {
+	/* Per-CPU event state (enum damon_perf_event_state) */
+	int cpu_state;
+
+	/* Sampling pipeline */
+	u64 callback;
+	u64 sample_valid;
+	u64 sample_null;
+	u64 sample_addr_zero;
+	u64 sample_kernel;
+	u64 sample_invalid_phys;
+
+	/* Ring */
+	u64 enqueue;
+	u64 dequeue;
+	u64 overflow;
+	u64 ring_peak;
+
+	/* Matching */
+	u64 match;
+	u64 miss_tgid;
+	u64 miss_region;
+	u64 miss_boundary;
+	u64 update;
+};
+
+#ifdef CONFIG_DAMON_PERF_OBSERVE
+
+/*
+ * damon_perf_observe_*() — Unified Observability API
+ *
+ * These are the ONLY hooks that hardware-sampling backends should
+ * call.  They are split into NMI-safe (sampling, ring-enqueue) and
+ * process-context (event lifecycle, drain, matching, update) groups.
+ *
+ * Counters always increment when CONFIG_DAMON_PERF_OBSERVE=y.
+ * Tracepoints are guarded by trace_*_enabled() and incur zero
+ * overhead when ftrace is not attached.
+ */
+
+/* Event lifecycle — process context (kdamond / cpuhp callbacks) */
+void damon_perf_observe_event_created(struct damon_perf_event *event,
+		int cpu);
+void damon_perf_observe_event_bound(struct damon_perf_event *event,
+		int cpu, struct perf_event *perf_event);
+void damon_perf_observe_event_enabled(struct damon_perf_event *event,
+		int cpu, int state, int oncpu);
+void damon_perf_observe_event_disabled(struct damon_perf_event *event,
+		int cpu, int state);
+void damon_perf_observe_event_destroyed(struct damon_perf_event *event,
+		int cpu);
+void damon_perf_observe_event_free(struct damon_perf_event *event);
+
+/*
+ * Sample observed — NMI-safe.
+ *
+ * @reason:  0 = valid (queued to ring)
+ *           1 = data NULL
+ *           2 = addr == 0 (PMU did not populate)
+ *           3 = kernel address (vaddr only)
+ *           4 = phys_addr not valid (paddr only)
+ */
+void damon_perf_observe_sample(unsigned long addr, u64 data_src,
+		u64 period, int cpu, u8 reason,
+		u64 sample_flags, u64 sample_type);
+
+/* Ring operations — enqueue/overflow are NMI-safe */
+void damon_perf_observe_ring_enqueue(void);
+void damon_perf_observe_ring_overflow(int cpu);
+void damon_perf_observe_ring_dequeue(int cpu);
+void damon_perf_observe_ring_peak(unsigned int occupancy);
+
+/* Matching — process context (kdamond drain loop) */
+void damon_perf_observe_match(unsigned long addr, int cpu);
+void damon_perf_observe_miss(unsigned long addr, int cpu, int reason);
+void damon_perf_observe_update(int cpu);
+void damon_perf_observe_drain(unsigned int total, unsigned int matched);
+
+/* Debugfs (debug-only, format unstable) */
+int damon_perf_debugfs_init(void);
+
+/* Per-CPU stats accessors (for debugfs) */
+void damon_perf_stats_snapshot(int cpu, struct damon_perf_stats *dst);
+void damon_perf_stats_aggregate(struct damon_perf_stats *dst);
+
+/* Subsystem init */
+int damon_perf_framework_init(void);
+
+#else /* !CONFIG_DAMON_PERF_OBSERVE */
+
+static inline void damon_perf_observe_event_created(struct damon_perf_event *e,
+						    int c)
+{
+}
+
+static inline void damon_perf_observe_event_bound(struct damon_perf_event *e,
+						  int c, struct perf_event *p)
+{
+}
+
+static inline void damon_perf_observe_event_enabled(struct damon_perf_event *e,
+						    int c, int s, int o)
+{
+}
+
+static inline void damon_perf_observe_event_disabled(struct damon_perf_event *e,
+						     int c, int s)
+{
+}
+
+static inline void damon_perf_observe_event_destroyed(struct damon_perf_event *e,
+						      int c)
+{
+}
+
+static inline void damon_perf_observe_event_free(struct damon_perf_event *e)
+{
+}
+
+static inline void damon_perf_observe_sample(unsigned long a, u64 d, u64 p,
+					     int c, u8 r, u64 f, u64 t)
+{
+}
+
+static inline void damon_perf_observe_ring_enqueue(void)
+{
+}
+
+static inline void damon_perf_observe_ring_overflow(int c)
+{
+}
+
+static inline void damon_perf_observe_ring_dequeue(int c)
+{
+}
+
+static inline void damon_perf_observe_ring_peak(unsigned int o)
+{
+}
+
+static inline void damon_perf_observe_match(unsigned long a, int c)
+{
+}
+
+static inline void damon_perf_observe_miss(unsigned long a, int c, int r)
+{
+}
+
+static inline void damon_perf_observe_update(int c)
+{
+}
+
+static inline void damon_perf_observe_drain(unsigned int t, unsigned int m)
+{
+}
+
+static inline int damon_perf_debugfs_init(void)
+{
+	return 0;
+}
+
+static inline void damon_perf_stats_snapshot(int c, struct damon_perf_stats *d)
+{
+	memset(d, 0, sizeof(*d));
+}
+
+static inline void damon_perf_stats_aggregate(struct damon_perf_stats *d)
+{
+	memset(d, 0, sizeof(*d));
+}
+
+static inline int damon_perf_framework_init(void)
+{
+	return 0;
+}
+
+#endif /* CONFIG_DAMON_PERF_OBSERVE */
+
+#endif /* _DAMON_PERF_H */
-- 
2.43.0


  reply	other threads:[~2026-08-18  6:10 UTC|newest]

Thread overview: 8+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-08-18  6:10 [RFC PATCH 0/7] mm/damon/perf: observability framework for hardware-sampled access reports Kunwu Chan
2026-08-18  6:10 ` Kunwu Chan [this message]
2026-08-18  6:10 ` [RFC PATCH 2/7] mm/damon/perf: implement observe API and per-CPU statistics engine Kunwu Chan
2026-08-18  6:10 ` [RFC PATCH 3/7] mm/damon/perf: add debugfs statistics interface Kunwu Chan
2026-08-18  6:10 ` [RFC PATCH 4/7] mm/damon: integrate observe API into vaddr overflow handlers and core drain Kunwu Chan
2026-08-18  6:10 ` [RFC PATCH 5/7] selftests/damon: add automated layer-by-layer observability test Kunwu Chan
2026-08-18  6:10 ` [RFC PATCH 6/7] Docs/mm/damon: document the perf observability framework Kunwu Chan
2026-08-18  6:10 ` [RFC PATCH 7/7] mm/damon/perf: add CONFIG_DAMON_PERF_DEBUG and pipeline health check Kunwu Chan

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260818061031.827057-2-kunwu.chan@linux.dev \
    --to=kunwu.chan@gmail.com \
    --cc=akpm@linux-foundation.org \
    --cc=damon@lists.linux.dev \
    --cc=lianux.mm@gmail.com \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-kselftest@vger.kernel.org \
    --cc=linux-mm@kvack.org \
    --cc=linux-trace-kernel@vger.kernel.org \
    --cc=mathieu.desnoyers@efficios.com \
    --cc=mhiramat@kernel.org \
    --cc=rostedt@goodmis.org \
    --cc=shuah@kernel.org \
    --cc=sj@kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox