All of lore.kernel.org
 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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.