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
next prev parent 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