mm/damon: hardware-sampled access reports
Subsystems (4)
v2 cover mm/damon: hardware-sampled access reports
This series lets DAMON take its access information from a hardware sampler instead of from a page-table scan, and lets a scheme's score be weighted by what that sampler reported.
The change from v1 [1] is that it is now built on the data attribute probe infrastructure that has since landed in mm-new: a PMU is expressed as one more probe on a context, with its own weight and probe hits, rather than as a mechanism of its own beside the ops set. The rest of this posting is the same substrate reworked onto that, plus two consumers.
This is not a merge request. The series is based on damon/next at the base-commit below, which moves, so the tree it was built and tested from is also on
https://github.com/ravis-opensrc/linux/tree/damon/perf-rfc-v2-09-06-26
It is posted for design feedback, on the substrate and on where it belongs in the roadmap for extending DAMON beyond the pte-accessed bit [2]. That roadmap's second milestone, now open, is a first data attribute monitored through damon_report_access(), and that is what a sampling PMU is here. So this series keeps that function and its callers and replaces its body: the reporting path a hardware sampler needs cannot take a mutex, and the drain has to reach a virtual-address context as well as a physical one. The shape of the ring, the drain and the sysfs surface are what is most useful to review.
## Why a unified perf-event substrate
DAMON derives its access information from the PTE Accessed bit. A sampling PMU carries what that bit cannot: which addresses the hardware went to, and how often it went there. Many machines already have such a unit, and more than one kind of it, so what this series is after is letting DAMON's regions be tuned from whichever perf-based hardware source a machine offers rather than from the Accessed bit alone. A sampler does not arrive on a kdamond's terms, though: it delivers an address when the hardware decides to, in NMI context, with no relation to the sampling interval and no mm to walk.
The alternative is a backend per PMU vendor, each owning its own configuration, sysfs knobs and lifecycle. The perf-event direction [3] avoids that: let DAMON register kernel-counter perf events and consume samples from any sampling PMU the perf core already knows about. This series follows it, adding one substrate below the ops sets rather than an ops set per PMU -- a report ring that any in-kernel access source can push into, and a drain that folds those reports into region probe hits on the aggregation boundary the kdamond already has. What running it across vendors needed on top of that direction is:
- per-CPU lockless rings between the NMI sample handler and the kdamond drain,
- a ring partitioned by probe class, so the page-fault primitive and a PMU can populate one context without either seeing the other's reports,
- per-CPU events that follow CPU hotplug, armed when the kdamond starts and disarmed and drained when it stops,
- a per-PMU owner, so two contexts cannot claim the same PMU type,
- whichever address a PMU does report carried on the report and matched against the context's own address space, so one source serves a paddr or a vaddr context without a backend per address space.
This is tested with PEBS on Intel and IBS on AMD, both configured as perf_event attributes on a probe and using the perf core's event plumbing rather than per-vendor MSR code. A third source has already been written against the same
ring: Kunwu Chan's ARM SPE backend [5], which reaches it through an AUX buffer drained in process context instead of an overflow callback, and which the roadmap [2] places in its third milestone.
The partitioning is what lets promotion and demotion run in one context. A sampler says which regions are hot; it says nothing about which are cold, because a sampler that reports nothing about a page cannot distinguish untouched from unsampled. Region age is what a demotion scheme matches on, and age comes from the page-fault primitive. With the ring partitioned by class both are live at
once: the probe supplies hotness, the primitive supplies age, and two schemes over the same regions can move memory in both directions under one kdamond.
The two classes are not symmetric, and the asymmetry is in where a report comes from rather than in what it carries. A page fault has no monitoring context at report time, so a per-context ring is not expressible for it: those reports go to one global set of per-CPU rings, drained by the single context whose page-fault primitive is enabled. A PMU overflow handler does carry the context that armed the event, so those reports go to that context's own per-CPU rings and each context drains only what it owns. Both classes share the ring layout and the producer side, so the barrier pairing cannot drift between them.
```
page-fault report PMU sample, NMI context
no ctx at report time handler carries the owning ctx
| |
v v
+----------------------------+ +----------------------------+
| one global per-CPU ring | | per-CPU rings per context |
| set, shared by every ctx | | that armed a perf event |
+----------------------------+ +----------------------------+
| drained by the one ctx | | each ctx drains only the |
| with the pf primitive on | | rings it owns |
+----------------------------+ +----------------------------+
| |
+------------------+------------------+
|
drain on the aggregation boundary,
each report matched to a region
|
+---------------+---------------+
| |
region age region probe hits
demotion scheme promotion scheme
```That is the property this series exists to enable, and three things follow from it:
- One measurement drives both directions, at whichever scope the mode gives. Bandwidth comes from resctrl MBM, which is where the discussion on a platform-independent bandwidth signal for tiering has pointed [6]: a virtual-address context reads a monitoring group created for the processes it monitors, and a physical-address context reads the root monitoring group, so the same controller is scoped to a set of processes or to the whole machine without changing what it does. Reading it there also means a bandwidth allocation reserved for an application bounds what the controller optimises within. It spreads the hot set across nodes in the ratio that maximises achieved bandwidth when bandwidth is what the workload is short of, and it demotes cold pages off the near node in the same context at the same time -- either proactively under a fixed quota or only once DRAM is under memory pressure. Neither direction is configured as a target ratio; the proportion or weight the controller writes bounds how much memory moves while the score decides which pages move, and a hardware sample makes that score proportional to the traffic a region generates rather than one bit per page per scan. Earlier work on bandwidth-driven interleaving [4] measured the same effect from userspace.
- Bandwidth and capacity are expanded at once rather than one being chosen. Static weighted interleaving expands bandwidth, but it is a placement rule rather than a decision about any particular page, so cold pages keep their share of DRAM. NUMA balancing expands usable DRAM by promoting whatever is hot, but it does so without reference to what the far node can deliver, so it keeps promoting after the near node has stopped being the better place to read from. Here the hot set's distribution comes from a bandwidth measurement and the cold set is demoted on region age, in one context, so the far node's bandwidth is used and DRAM is not held by memory nothing is touching. The decision itself is a quota goal the kernel already carries:
node_eligible_mem_bp [8] bounds how much of a node a scheme may fill, so what the controller supplies is the bandwidth measurement, not the placement.
- When bandwidth is not the constraint it reverts to latency-first tiering on its own. The search starts with everything on the near node and moves memory outward only when the measurement improves, so on a workload that does not saturate that node no step outward improves anything: the hot set stays in DRAM for the lower access latency and cold pages keep being demoted. The same configuration covers both cases, with nothing to switch on and no ratio to revise.
## What the series adds
1. mm/damon/vaddr: support page fault access check primitive -- implements the page-fault primitive for a virtual address space context; it was available for physical addresses only. The marker whose fault reports the access is installed through the target mm and the vma of the sampling address directly, and prepare_access_checks() dispatches on the enabled primitive as the physical address space one does.
2. mm/damon/core: read the CPU number with preemption disabled -- the report path runs in fault context, which is preemptible. Read the CPU number with preemption disabled and keep it disabled across the report, so the recorded CPU stays consistent with the per-CPU ring the report is queued to.
3. mm/damon/paddr: lock the folio for the page fault primitive rmap walk -- take the folio lock unconditionally around the walk, as the other reverse mapping walks in DAMON do, so every mapping type reaches it locked, and release the reference the lookup took on each exit path.
4. mm/damon: add damos_node_eligible_mem_bp tracepoint -- a per-tick tracepoint over the node-eligible-memory quota goal evaluation, exposing the goal's target and current values, so the loop a bandwidth-driven controller steers is visible to a tracer.
5. mm/damon/core: add per-probe-class report rings and unified drain -- the substrate. Per-CPU SPSC rings an NMI-context source can publish into, partitioned by probe index into a global page-fault ring and a per-context perf ring, plus the kdamond-side drain that matches each report to a region by binary search over a per-target snapshot and credits it to that region's probe hits on the aggregation boundary. The address space of the target selects which address of a report is matched, and pid targets are filtered by thread group id.
6. mm/damon: add perf-event overflow handler feeding the report ring -- an ops-agnostic perf-event source whose overflow handler turns a PMU sample into a report, setting each address field the PMU reported as valid. A PMU that reports a virtual address only can therefore drive a virtual-address context, one that reports a physical address a physical-address context, and the same source serves either without a backend per address space. Per-CPU events are armed and released through cpuhp callbacks, and a per-PMU owner keeps two contexts from claiming the same PMU type.
7. mm/damon/ops-common: use probe-weighted score when probe weights are set -- lets a scheme's frequency subscore come from the probe hits, weighted per probe class, so what the sampler reported reaches the tiering decision. With no weights set the subscore comes from the access rate as before.
8. mm/damon: add perf_event prep for PMU-driven hotness probes -- the sysfs surface and the event lifecycle: a perf_event prep action carrying the PMU type, the event config and the sample attributes per probe, with per-CPU or single-instance arming depending on how many counters the PMU needs. Arming is deferred on a context built for a commit, so a weight-only commit leaves the running event untouched.
9. mm/damon/tests/drain-kunit: kunit for report rings and unified drain -- unit tests for the rings and the drain: inject and drain, overflow on wrap, producer routing by probe index, ring partitioning, pf-ring ownership, thread-group filtering, the address space match, and both primitives live on one context.
Patches 1-4 apply standalone on damon/next and are useful without the rest; 5-8 are the substrate and its first two consumers, and 9 covers them. If the first four read right, they need not wait on the rest of this series -- happy to repost them on their own for damon/next if that is easier to take. Two of them, 2 and 3, correct code that is already in mm-new rather than adding anything new, so they could instead go as a small fixes series carrying
Fixes: tags; guidance on which of the two routes is preferred is welcome.
## Userspace setup model
The runs were driven by an auto_tier subcommand added to damo on the branch below, which reads achieved bandwidth from resctrl MBM and searches for the hot set's distribution that maximises it, while the demotion scheme runs alongside it in the same context. That tooling is not part of this posting; it is on
https://github.com/ravis-opensrc/damo/tree/damo/auto-tier-bw-2026-09-08
- --bw_source resctrl is the option this adds: the controller reads achieved bandwidth from an MBM monitoring group and searches, rather than being given a distribution to install.
- Everything else -- intervals, schemes, filters, the probe's perf_event attributes -- is what the generator writes from the command line shown: 5 ms sampling, 100 ms aggregation, a 1 s ops update.
- Both configurations select proactive demotion, so they demote continuously rather than waiting for pressure, and both were left closed loop throughout.
- The search is the algorithm described in [4].
### Configuration A: AMD IBS Op, paddr ops, system-wide
```
$ sudo mount -t resctrl resctrl /sys/fs/resctrl
$ sudo python3 tools/damon_tier_gen.py --hotness ibs \
--near_node 0 --far_node 4 \
--cold_demote --cold_demote_mode proactive \
-o tier.yaml
$ sudo damo auto_tier tier.yaml --bw_source resctrl --verbose
``` - Scope is the machine, not a process set. The distribution is steered through node_eligible_mem_bp quota goals over each node's own physical ranges, which the generator reads from /proc/iomem.
- AMD Turin, DRAM on node 0 and a CXL node. IBS Op at a sample_period of 262144 with sample_phys_addr set, which is what a physical-address context needs from the sample.
- Workload is a hot-and-cold mix so both schemes have something to act on:
multiload drives the bandwidth-hungry hot set, and a second process allocates on the near node, touches it once and goes idle, leaving pages that age out for the demotion scheme to find.
### Configuration B: Intel PEBS L3-miss, vaddr ops, per-PID
```
$ sudo mount -t resctrl resctrl /sys/fs/resctrl
$ sudo python3 tools/damon_tier_gen.py --hotness pebs \
--pid $HOT_PID --pid $COLD_PID \
--near_node 0 --far_node 1 \
--cold_demote --cold_demote_mode proactive \
--min_nr_regions 1000 --max_nr_regions 20000 \
-o tier.yaml
$ sudo damo auto_tier tier.yaml --bw_source resctrl --verbose
``` - Scope is the processes the monitoring group names and nothing else. The distribution is steered through the hot scheme's DamosDest weights [9].
- Intel Granite Rapids, DRAM on node 0 and CXL on node 1. PEBS L3-miss at
`sample_freq` 5003 with `precise_ip` 2.
- Same workload shape.
## What the runs show
Per-node reference, each figure measured by binding the same workload to one node:
```
Granite Rapids, 64 threads x 8 GiB
node 0 1.0 TB DRAM DRAM only 269,885 MB/s
node 1 2.0 TB CXL CXL only 249,903 MB/s
Turin, 32 threads x 4 GiB
node 0 386 GB DRAM DRAM only 113,596 MB/s
node 4 1.0 TB CXL CXL only 33,253 MB/s
``` - The two machines differ in the way that matters: on the Turin the far node is 3.4x slower than the near one, on the Granite Rapids the two are within 8%.
- Each configuration gets one graph, the climb: the run itself, one mark per decision taken from the bandwidth just measured, with the share it moved to under each mark. The settled line below it is the median over the samples the run went on to hold at that share.
### Configuration B, virtual-address mode
```
MB/s climb
460k | *
440k | *
420k | *
400k |
380k | *
360k | *
340k | * *
320k | *
300k | * *
280k | *
260k | * *
--------+------------+--------------+---------------+------------+--
1 4 7 10 13
92 80 68 56 44
decision index, near-node share (%)
settled share 44, 430,547 MB/s held
``` - Thirteen decisions to settle at 44%, no target given: about 60% more than the better of the two nodes on its own, and the share does not change again over the 81 samples that follow.
- Cold demotion runs over the same interval, in the same context, actuated by the same kdamond: 34 regions totalling 15.4 GiB applied, and the idle process ends with 4,099,980 pages -- 15.6 GiB -- on the CXL node having started on DRAM.
- The promotion scheme's applied byte count is flat across the demotion ramp, 291.1 GiB before against 291.3 GiB after, so the recovered capacity is the demotion scheme's.
### Configuration A, physical-address mode
```
MB/s climb
136k |
134k | * *
132k | * *
130k | *
128k |
126k | *
124k |
122k | *
120k |
118k | *
+----+-----+-----+-----+-----+-----+-----+-----+--
1 2 3 4 5 6 7 8
92 88 84 80 76 78 80 78
decision index, near-node share (%)
settled share 78, 134,107 MB/s held
``` - Eight decisions to settle at 78, from a start with almost everything on the near node: the same algorithm reaches the optimum share on its own from the bandwidth reading alone, and cold pages are demoted to the far node while it does so.
- A distribution beats either end because both nodes supply at once: at the settled share the near node is supplying 104,618 MB/s against the 113,596 it reaches with the whole working set bound to it, so the further 37,669 MB/s arriving over CXL is bandwidth it could not have supplied at any share.
- The idle process ends with 2,863,156 pages -- 10.9 GiB -- on the CXL node, out of the 32 GiB it allocated on DRAM and stopped touching. Residency is the attributable figure in this mode rather than a per-scheme applied count, because the schemes share physical ranges.
### What the runs are meant to establish
Not the settled share and not the absolute bandwidth; those are properties of these machines and these workloads. What reproduces is:
- A controller reading achieved bandwidth converges unattended, from a configuration naming no target, to a distribution that beats either node on its own, and holds it once found.
- A demotion scheme recovers near-node capacity in the same context while that happens.
- One code path does both, steering node_eligible_mem_bp over physical ranges system-wide on one machine and DamosDest weights over a named process group on the other.
## Beyond a CPU PMU
Nothing above is specific to PEBS or IBS. A source qualifies if it can report an accessed address to the ring, which is the only thing the substrate asks of it. A CXL device's Hotness Monitoring Unit, whose r3.2 perf driver has been posted [7] exposing its hotlist through an AUX buffer, or a custom monitoring unit on an accelerator or a memory expander, reports exactly that, and a backend delivering those reports through a perf event reaches the same drain, the same probe hits and the same schemes already in the tree. Such a unit sees the traffic that reaches its own device rather than a system-wide view, so what it contributes is the hot set resident on that device: the tiering logic can promote those pages to DRAM without the host having to detect that hotness itself, and without depending on what produced the reports. That posting names driving tiering from such a unit as the intent and the in-kernel step as future work, and consuming it as a DAMON access-check primitive was raised there as one way to get it; a backend on this substrate is that path.
[1] v1 of this series
https://lore.kernel.org/damon/[email protected]/ [2] Roadmap for extending DAMON beyond pte-accessed bit https://lore.kernel.org/damon/[email protected]/ [3] mm/damon: introduce perf event based access check https://lore.kernel.org/damon/[email protected]/
[4] B. Tabatabai, R. Jonnalagadda et al., "Bandwidth Speaks, We Listen:
Dynamic Interleaving for Tiered Memory", ISMM 2026.
https://dl.acm.org/doi/10.1145/3814942.3816137
[5] mm/damon/perf: add ARM SPE AUX backend
https://lore.kernel.org/damon/[email protected]/
[6] A platform-independent subsystem for bandwidth information, and resctrl as that source
https://lore.kernel.org/linux-mm/[email protected]/
[7] CXL Hotness Monitoring Unit perf driver
https://lore.kernel.org/linux-mm/[email protected]/ [8] mm/damon: add node_eligible_mem_bp goal metric, merged for v7.2 https://lore.kernel.org/linux-mm/[email protected]/ [9] mm/damon/vaddr: allow interleaving in migrate_{hot,cold} actions, merged for v6.17 https://lore.kernel.org/linux-mm/[email protected]/
Ravi Jonnalagadda (9):
mm/damon/vaddr: support page fault access check primitive mm/damon/core: read the CPU number with preemption disabled mm/damon/paddr: lock the folio for the page fault primitive rmap walk mm/damon: add damos_node_eligible_mem_bp tracepoint mm/damon/core: add per-probe-class report rings and unified drain mm/damon: add perf-event overflow handler feeding the report ring mm/damon/ops-common: use probe-weighted score when probe weights are
set
mm/damon: add perf_event prep for PMU-driven hotness probes mm/damon/tests/drain-kunit: kunit for report rings and unified drain
include/linux/damon.h | 136 ++++- include/trace/events/damon.h | 32 + mm/damon/Kconfig | 18 + mm/damon/Makefile | 1 + mm/damon/core.c | 995 +++++++++++++++++++++++++++---- mm/damon/ops-common.c | 21 +- mm/damon/paddr.c | 37 +- mm/damon/perf_source.c | 489 +++++++++++++++ mm/damon/perf_source.h | 54 ++ mm/damon/sysfs.c | 266 ++++++++- mm/damon/tests/.kunitconfig | 4 + mm/damon/tests/core-kunit.h | 2 +- mm/damon/tests/drain-kunit.h | 1091 ++++++++++++++++++++++++++++++++++ mm/damon/tests/perf-kunit.h | 133 +++++ mm/damon/vaddr.c | 83 ++- 15 files changed, 3220 insertions(+), 142 deletions(-) create mode 100644 mm/damon/perf_source.c create mode 100644 mm/damon/perf_source.h create mode 100644 mm/damon/tests/drain-kunit.h create mode 100644 mm/damon/tests/perf-kunit.h
base-commit: e1f34dce183a96fc93bf9a42dcdc0ec0bf82a3d2
Signature · 1 line
2.43.0
RFC PATCH v2 1/9 mm/damon/vaddr: support page fault access check primitive
The page-fault sampling primitive was implemented for the physical address space only, so a virtual address space context had no way to enable it.
Implement it for the virtual address space. The prep installs the marker whose fault reports the access on the sampling address of each region, mirroring the physical address space primitive, and prepare_access_checks() now dispatches on the enabled primitive as the physical address space one does.
The target mm and the vma of the sampling address are both available here, so the marker is installed on the mapping directly. The physical address space primitive has only a physical address and reaches its mappings through a reverse mapping walk.
--- mm/damon/vaddr.c | 65 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 64 insertions(+), 1 deletion(-)
mm/damon/vaddr.c +64 -1
@@ -13,6 +13,7 @@#include <linux/pagemap.h> #include <linux/pagewalk.h> #include <linux/sched/mm.h> +#include <asm/tlb.h> #include "../internal.h" #include "ops-common.h"
@@ -329,7 +330,8 @@ static void __damon_va_prepare_access_check(struct mm_struct *mm,damon_va_mkold (mm, r->sampling_addr); } -static void damon_va_prepare_access_checks (struct damon_ctx *ctx) +/* Use page table accessed bits */ +static void damon_va_prepare_access_checks_abit (struct damon_ctx *ctx) {struct damon_target *t;struct mm_struct *mm;
@@ -345,6 +347,67 @@ static void damon_va_prepare_access_checks(struct damon_ctx *ctx) } } +/* +* Install the page table marker whose fault reports the access. The target +* mm and the vma of the address are available here, so the marker is set on +* the mapping directly, without a reverse mapping walk. +*/ +static void damon_va_change_protection (struct mm_struct *mm, +unsigned long addr) +{ +struct vm_area_struct *vma; +struct mmu_gather tlb; + +/* +* The sampling address is a random offset within the region, so round +* it down to the page it falls in. change_protection() walks whole +* page table entries and expects a page aligned range. +*/ + addr =ALIGN_DOWN (addr,PAGE_SIZE ); + +mmap_read_lock (mm); + + vma =vma_lookup (mm, addr); +if (!vma || !vma_is_accessible (vma) || (vma->vm_flags &VM_PFNMAP )) + goto unlock; + +tlb_gather_mmu (&tlb, mm); +/* todo: batch or remove tlb flushing */ +change_protection (&tlb, vma, addr, addr +PAGE_SIZE ,MM_CP_DAMON ); +tlb_finish_mmu (&tlb); + +unlock: +mmap_read_unlock (mm); +} + +/* Use page faults */ +static void damon_va_prepare_access_checks_faults (struct damon_ctx *ctx) +{ +struct damon_target *t; +struct mm_struct *mm; +struct damon_region *r; + +damon_for_each_target (t, ctx) { + mm =damon_get_mm (t); +if (!mm) +continue ; +damon_for_each_region (r, t) { + r->sampling_addr =damon_rand (ctx, r->ar.start, + r->ar.end); +damon_va_change_protection (mm, r->sampling_addr); + } +mmput (mm); + } +} + +static void damon_va_prepare_access_checks (struct damon_ctx *ctx) +{ +if (ctx->sample_control.primitives_enabled.page_table) +damon_va_prepare_access_checks_abit (ctx); +if (ctx->sample_control.primitives_enabled.page_fault) +damon_va_prepare_access_checks_faults (ctx); +} +struct damon_young_walk_private {bool young; };
Signature · 1 line
2.43.0
RFC PATCH v2 2/9 mm/damon/core: read the CPU number with preemption disabled
In reply to Ravi Jonnalagaddadamon_report_page_fault() runs in fault context, which is preemptible, so reading the CPU number there triggers a "using smp_processor_id() in preemptible" report.
Read it with preemption disabled, and keep it disabled across the report so the recorded CPU stays consistent with the per-CPU ring the report is queued to, which is selected the same way.
--- mm/damon/core.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-)
mm/damon/core.c +8 -1
@@ -2514,7 +2514,6 @@ void damon_report_page_fault(struct vm_fault *vmf, bool huge_pmd)struct damon_access_report access_report = { .vaddr = vmf->address, .size =1 ,/* todo: set appripriately */ - .cpu =smp_processor_id (), .tid =task_pid_vnr (current), .is_write = vmf->flags &FAULT_FLAG_WRITE , };
@@ -2524,7 +2523,15 @@ void damon_report_page_fault(struct vm_fault *vmf, bool huge_pmd) else access_report.paddr = PFN_PHYS(pte_pfn(vmf->orig_pte)); +/* +* This runs in fault context, which is preemptible, so the CPU number +* is read with preemption disabled. Holding it across the report also +* keeps the recorded CPU consistent with the per-CPU ring the report is +* queued to, which damon_report_access() selects the same way. +*/ + access_report.cpu =get_cpu ();damon_report_access (&access_report); + put_cpu(); }#endif
Signature · 1 line
2.43.0
RFC PATCH v2 3/9 mm/damon/paddr: lock the folio for the page fault primitive rmap walk
In reply to Ravi Jonnalagaddarmap_walk() requires the folio to be locked for every mapping type it walks, and the page fault access check primitive takes the lock only for file-backed and KSM folios, so a plain anonymous folio reaches the walk unlocked and the walk does not install the marker.
Take the lock unconditionally, as the other reverse mapping walks in DAMON do, and release the folio reference the lookup took on every exit path.
--- mm/damon/paddr.c | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-)
mm/damon/paddr.c +11 -7
@@ -91,21 +91,25 @@ static void damon_pa_change_protection(unsigned long paddr) .rmap_one = damon_pa_change_protection_one, .anon_lock = folio_lock_anon_vma_read, }; -bool need_lock;if (!folio)return ;if (!folio_mapped (folio) || !folio_raw_mapping (folio)) -return ; + goto put; - need_lock = !folio_test_anon (folio) ||folio_test_ksm (folio); -if (need_lock && !folio_trylock (folio)) -return ; +/* +* rmap_walk() requires a locked folio for every mapping type, so the +* lock is taken unconditionally, as the other reverse mapping walks in +* DAMON do. +*/ +if (!folio_trylock (folio)) + goto put;rmap_walk (folio, &rwc); -if (need_lock) -folio_unlock (folio); +folio_unlock (folio); +put: +folio_put (folio); }static void damon_pa_prepare_access_checks_faults (struct damon_ctx *ctx)
Signature · 1 line
2.43.0
RFC PATCH v2 4/9 mm/damon: add damos_node_eligible_mem_bp tracepoint
In reply to Ravi JonnalagaddaAdd a per-tick tracepoint for DAMOS_QUOTA_NODE_ELIGIBLE_MEM_BP goal evaluation, exposing the goal target/current values for observability of the node-eligible-memory bandwidth quota loop. Bandwidth-feedback tiering loops read this goal metric to detect convergence.
--- include/trace/events/damon.h | 32 ++++++++++++++++++++++++++++++++ mm/damon/core.c | 28 ++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+)
include/trace/events/damon.h +32 -0
@@ -74,6 +74,38 @@ TRACE_EVENT(damos_esz, __entry->esz) ); +/* Per-tick DAMOS_QUOTA_NODE_ELIGIBLE_MEM_BP goal evaluation. */ +TRACE_EVENT (damos_node_eligible_mem_bp, + + TP_PROTO(unsigned int context_idx, unsigned int scheme_idx, + int nid, + unsigned long target_value, unsigned long current_value), + +TP_ARGS (context_idx, scheme_idx, nid, target_value, current_value), + +TP_STRUCT__entry ( + __field(unsigned int, context_idx) + __field(unsigned int, scheme_idx) + __field(int, nid) + __field(unsigned long, target_value) + __field(unsigned long, current_value) + ), + +TP_fast_assign ( + __entry->context_idx = context_idx; + __entry->scheme_idx = scheme_idx; + __entry->nid = nid; + __entry->target_value = target_value; + __entry->current_value = current_value; + ), + +TP_printk ("ctx_idx=%u scheme_idx=%u nid=%d " +"target_value=%lu current_value=%lu" , + __entry->context_idx, __entry->scheme_idx, + __entry->nid, + __entry->target_value, __entry->current_value) +); + TRACE_EVENT_CONDITION(damos_before_apply, TP_PROTO(unsigned int context_idx, unsigned int scheme_idx,
mm/damon/core.c +28 -0
@@ -3461,6 +3461,34 @@ static unsigned long damos_quota_score(struct damon_ctx *c, struct damos *s) highest_score =max (highest_score,mult_frac (goal->current_value,10000 , goal->target_value)); + +/* +* Per-tick visibility of NODE_ELIGIBLE_MEM_BP goal evaluation +* for userspace convergence-detection. +*/ +if (goal->metric ==DAMOS_QUOTA_NODE_ELIGIBLE_MEM_BP && +trace_damos_node_eligible_mem_bp_enabled ()) { +/* +* cidx is hardcoded 0 because DAMON sysfs currently +* supports a single context per kdamond. Other DAMOS +* tracepoints (damos_before_apply, damos_esz, +* damos_stat_after_apply_interval) follow the same +* convention; once multi-ctx-per-kdamond lands the +* field becomes meaningful. +*/ +unsigned int cidx =0 , sidx =0 ; +struct damos *siter; + +damon_for_each_scheme (siter, c) { +if (siter == s) +break ; + sidx++; + } +trace_damos_node_eligible_mem_bp (cidx, sidx, + goal->nid, + goal->target_value, + goal->current_value); + } }return highest_score;
Signature · 1 line
2.43.0
RFC PATCH v2 5/9 mm/damon/core: add per-probe-class report rings and unified drain
In reply to Ravi Jonnalagaddadamon_report_access() takes a mutex, so it cannot be called from NMI context, which is where a perf-event overflow handler runs. Provide per-CPU SPSC rings instead, partitioned by the globally-meaningful probe_idx boundary into two classes:
- Page-fault reports (probe_idx == DAMON_PROBE_IDX_NONE) use a global per-CPU ring. The page_fault primitive has no damon_ctx at report time, so a per-context ring is not expressible; the global ring is drained by the single context whose page_fault primitive is enabled.
- Perf-event reports (probe_idx >= 1) use a per-context per-CPU ring. The overflow handler carries a pointer to its owning ctx, so each perf-driven context enqueues into and drains only its own ring, and no cross-context ring owner is needed.
The per-ctx perf ring is allocated when the first perf probe is armed and freed in damon_destroy_ctx() after all perf events are released, so no in-flight NMI can reach freed storage.
damon_report_access() routes each report to the global pf ring or to the report's ctx perf ring by probe_idx, sharing one copy of the NMI-safe publish sequence so the barrier pairing cannot drift between the two. Each ring has a per-CPU busy counter to detect and drop re-entrant NMI nesting on the same CPU. The producer writes the entry, issues smp_wmb(), then advances head with WRITE_ONCE(); the consumer reads head and pairs with smp_rmb() before reading the entry.
Add the kdamond consumer, kdamond_check_reported_accesses(): a dispatcher that drains the ring(s) the context's enabled primitives feed, with the per-ring drain body parameterised only by ring and pending mask. Each entry carries its own probe_idx, so the drain resolves the probe_hits[] slot without a list walk. damon_drains_ring_pf() and damon_drains_ring_perf() name which ring a context drives, so which ring is drained and which access-check path runs follow from the enabled primitive rather than from whether probe weights are set.
Build a per-target sorted region snapshot once per drain so each entry is matched to its region by binary search rather than a linear region walk. Reports straddling a region boundary are rejected. The address space of the monitoring target selects which address of a report is matched, so a context whose targets carry a pid matches the virtual address and the others match the physical address; a report carrying no address for that space is dropped. For pid-target contexts, filter by thread group id so entries from unrelated processes are not credited to the wrong target. The report carries that id in a field of its own, beside the thread id the threads type sample filter matches against.
Ownership of the global pf ring is enforced under damon_lock at damon_start(), on the live commit path, and cleared on kdamond exit. The perf ring needs no such guard because it is per-ctx. Staleness uses sample_interval rather than aggr_interval, so stale data does not inflate hot scores across aggregation boundaries.
--- include/linux/damon.h | 106 +++++- mm/damon/core.c | 820 ++++++++++++++++++++++++++++++++++++------ 2 files changed, 817 insertions(+), 109 deletions(-)
include/linux/damon.h +102 -4
@@ -6,6 +6,7 @@#ifndef _DAMON_H_#define _DAMON_H_ +#include <asm/local.h> #include <linux/math64.h> #include <linux/memcontrol.h> #include <linux/mutex.h>
@@ -17,9 +18,19 @@#define DAMON_MIN_REGION_SZ PAGE_SIZE/* Maximum number of monitoring probes. */ #define DAMON_MAX_PROBES (4) +/* +* Sentinel value for damon_access_report.probe_idx: 0 means no probe +* attribution (matches zero-init of struct damon_access_report on the stack). +* Perf-event probe indices start at 1. +*/ +#define DAMON_PROBE_IDX_NONE 0/* Max priority score for DAMON-based operation schemes */ #define DAMOS_MAX_SCORE (99) +/* Per-CPU SPSC ring: size must be a power of two. */ +#define DAMON_REPORT_RING_SIZE 256 +#define DAMON_REPORT_RING_MASK (DAMON_REPORT_RING_SIZE - 1) +/** * struct damon_addr_range - Represents an address region of [@start, @end). * @start: Start address of the region (inclusive).
@@ -110,7 +121,15 @@ struct damon_target {* @size: The size of the accessed address range. * @cpu: The id of the CPU that made the access. * @tid: The task id of the task that made the access. +* @tgid: The thread group id of the task that made the access. A +* monitoring target created for a process carries this id, +* so it is the id a report is matched against. * @is_write: Whether the access is write. +* @probe_idx: Index into probe_hits[] for the reporting probe; set by +* the perf-event overflow handler so the drain can credit +* the correct slot without a list walk. +* 0 is reserved (no probe attribution; matches zero-init); +* perf-event probe indices start at 1. * * Any DAMON API callers that notified access events can report the information * to DAMON using damon_report_access(). This struct contains the reporting
@@ -122,11 +141,53 @@ struct damon_access_report {unsigned long size;unsigned int cpu;pid_t tid; +pid_t tgid;bool is_write; +int probe_idx; +/* +* Owning context for perf-event reports (probe_idx >= 1): the overflow +* handler sets this so the producer enqueues into that ctx's own perf +* ring. NULL for page_fault reports (probe_idx == DAMON_PROBE_IDX_NONE), +* which route to the global pf ring by address at drain time. +*/ +struct damon_ctx *ctx;/* private: */ unsigned long report_jiffies;/* when this report is made */ }; +/** +* struct damon_report_ring - Per-CPU SPSC ring for NMI-safe access reports. +* +* @head: Write index; updated by the NMI producer. +* @tail: Read index; updated by the kdamond consumer. +* @entries: Ring buffer entries. +* +* One ring per CPU: page-fault reports use a global set, and each context +* with a perf-event probe has its own set. The producer (NMI overflow +* handler) writes to @head; the consumer (kdamond) reads from @tail. Both +* indices are unsigned and wrap modulo DAMON_REPORT_RING_SIZE. +*/ +struct damon_report_ring { +unsigned int head;/* written by producer (NMI) */ +unsigned int tail/* written by consumer (kdamond) */ +____cacheline_aligned_in_smp ; +struct damon_access_report entries[DAMON_REPORT_RING_SIZE ] +____cacheline_aligned_in_smp ; +}; + +/* +* struct damon_target_lookup - Cached, sorted region snapshot for one target. +* @regions: Array of region pointers, sorted by ar.start (address order). +* @nr_regions: Number of entries in @regions. +* +* Built once per aggregation tick by damon_build_target_lookup() so the ring +* drain can binary-search a target's regions instead of walking the list. +*/ +struct damon_target_lookup { +struct damon_region **regions; +unsigned int nr_regions; +}; +/** * enum damos_action - Represents an action of a Data Access Monitoring-based * Operation Scheme.
@@ -874,6 +935,7 @@ struct damon_filter {*/ struct damon_probe {unsigned int weight; +bool event_driven;/* hits arrive via ring drain, not apply_probes */ /* private: */ /* Preparation actions to apply to each probing memory. */ struct list_head preps;
@@ -1090,6 +1152,34 @@ struct damon_ctx {/* @rnd_state: Per-ctx PRNG state for damon_rand(). */ struct rnd_state rnd_state; + +/* Reusable drain-loop snapshot buffer (avoids per-tick kmalloc). */ +struct { +struct damon_target_lookup *lookups; +unsigned int nr_lookups; +struct damon_region **region_buf; +unsigned int region_buf_cap; + } drain_snapshot; + +/* +* Per-context perf-event report ring. Unlike the page_fault primitive +* (which has no ctx at report time and so uses a global ring), a perf +* overflow handler is armed by -- and carries a pointer to -- its owning +* ctx (damon_access_report.ctx), so its reports route to this per-ctx +* ring. This gives full per-context isolation (two perf-driven ctxs +* never share a ring) and removes any need for a cross-ctx perf ring +* owner guard. +* +* Allocated lazily when the first perf probe is armed +* (damon_ctx_alloc_perf_ring, from damon_perf_probe_setup) and freed in +* damon_destroy_ctx() AFTER all perf events are released, so no in-flight +* NMI can reach freed storage. perf_rings == NULL means "no perf ring +* yet" and any perf report is dropped, so a build/config without a perf +* source simply never allocates it (lazy alloc = zero cost when unused). +*/ +struct damon_report_ring __percpu *perf_rings; +int __percpu *perf_ring_busy; +cpumask_t perf_pending; };/* Get a random number in [@l, @r) using @ctx's lockless PRNG. */
@@ -1208,6 +1298,7 @@ void damon_destroy_filter(struct damon_filter *f);struct damon_probe *damon_new_probe (void );void damon_add_probe (struct damon_ctx *ctx,struct damon_probe *probe); +bool damon_has_event_driven_probes (struct damon_ctx *ctx);struct damon_region *damon_new_region (unsigned long start,unsigned long end); unsigned int damon_nr_accesses_mvsum(struct damon_region *r,
@@ -1297,7 +1388,8 @@ int damon_kdamond_pid(struct damon_ctx *ctx);int damon_call (struct damon_ctx *ctx,struct damon_call_control *control);int damos_walk (struct damon_ctx *ctx,struct damos_walk_control *control); -void damon_report_access (struct damon_access_report *report); +bool damon_report_access (struct damon_access_report *report); +int damon_ctx_alloc_perf_ring (struct damon_ctx *ctx);#ifdef CONFIG_MMU void damon_report_page_fault (struct vm_fault *vmf,bool huge_pmd);#else
@@ -1311,16 +1403,22 @@ int damon_set_region_system_rams_default(struct damon_target *t,unsigned long addr_unit, unsigned long min_region_sz); -#ifdef CONFIG_ACMA +unsigned long damon_get_report_overflow (void ); +unsigned long damon_get_report_ring_full (void ); +unsigned long damon_get_report_busy_drop (void ); +unsigned long damon_get_samples_drained (void ); +unsigned long damon_get_samples_stale_drained (void ); +unsigned long damon_get_samples_no_region (void ); +#ifdef CONFIG_ACMA unsigned long damon_alloced_bytes (void ); -#endif #else /* CONFIG_DAMON */ -static inline void damon_report_access (struct damon_access_report *report) +static inline bool damon_report_access (struct damon_access_report *report) { +return false; }static inline void damon_report_page_fault (struct vm_fault *vmf,bool huge_pmd) {
mm/damon/core.c +715 -105
981 lines not on this page · raw message
Signature · 1 line
2.43.0
RFC PATCH v2 6/9 mm/damon: add perf-event overflow handler feeding the report ring
In reply to Ravi JonnalagaddaAdd mm/damon/perf_source.c, an ops-agnostic perf-event source that turns PMU overflow samples into DAMON access reports. The overflow handler runs in NMI context and reports through damon_report_access(), so it works with either paddr or vaddr ops.
The handler sets whichever address fields the PMU provides: paddr when PERF_SAMPLE_PHYS_ADDR is set, vaddr when PERF_SAMPLE_ADDR is set, plus the CPU it runs on. The unified drain matches on whichever is present. It reports both the thread id and the thread group id, because a VA-only PMU samples whichever thread of a process happened to access memory while the drain matches a target created for that process.
Per-CPU perf events are armed and released through cpuhp callbacks, so a CPU coming online while a probe is armed gets a counter and a CPU going offline releases its own.
damon_perf_probe_alloc() allocates a perf-event-backed damon_probe with event_driven set, so its hits are credited through the report-ring drain rather than the apply_probes vtable; callers set the perf attributes after allocation. Both ops sets honour that flag in their probe vtables: an event-driven probe has no software prep action, and is skipped in the apply_probes loop, so the drain is the only thing that credits its probe_hits[].
damon_perf_probe_setup() computes probe_idx by walking ctx->probes, assigning 1-based indices because 0 is the zero-init sentinel DAMON_PROBE_IDX_NONE, and the overflow handler warns once if it sees 0.
A refcounted per-PMU owner tracks which context owns each PMU type: a second context claiming the same type gets -EBUSY, and ownership is released when the last probe for that type is torn down. Module exit refuses to unload while any owner remains.
--- mm/damon/Kconfig | 18 ++ mm/damon/Makefile | 1 + mm/damon/paddr.c | 19 +- mm/damon/perf_source.c | 402 +++++++++++++++++++++++++++++++++++++++++ mm/damon/perf_source.h | 26 +++ mm/damon/vaddr.c | 18 +- 6 files changed, 482 insertions(+), 2 deletions(-) create mode 100644 mm/damon/perf_source.c create mode 100644 mm/damon/perf_source.h
mm/damon/Kconfig +18 -0
@@ -64,6 +64,24 @@ config DAMON_PADDR This builds the default data access monitoring operations for DAMON that works for the physical address space. +config DAMON_PERF_SOURCE + bool "perf-event source for DAMON probes" + depends on DAMON && PERF_EVENTS + help + Provides a PMU-agnostic perf-event overflow handler that feeds + physical-address access reports into DAMON via damon_report_access(). + Required for DAMON probe-weighted tiering with any address-sampling PMU. + + The overflow handler is NMI-safe: it writes to a per-CPU SPSC + ring rather than taking any lock. The PMU is selected at probe + creation time via perf_event_attr (e.g. AMD IBS Op, Intel PEBS). + + A given PMU type may be driven by at most one DAMON context at a + time (hardware constraint); damon_perf_probe_setup() returns -EBUSY + if another context already owns that PMU type. Distinct contexts may + each drive different PMUs concurrently, each draining its own + per-context report ring. + config DAMON_VADDR_KUNIT_TEST bool "Test for DAMON operations" if !KUNIT_ALL_TESTS depends on DAMON_VADDR && KUNIT=y
mm/damon/Makefile +1 -0
@@ -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_SOURCE) += perf_source.o
mm/damon/paddr.c +18 -1
@@ -179,6 +179,16 @@ static void damon_pa_prep_probes_region(struct damon_region *r, {struct damon_prep *p; +/* +* Event-driven probes have no software prep action: their hits arrive +* asynchronously through the per-CPU report rings and are applied by +* the kdamond ring drain, so skip the software prep path here. +* Reaching this for an event-driven probe is normal (prep runs for +* every probe); it is silently skipped, not a warning. +*/ +if (probe->event_driven) +return ; +damon_for_each_prep (p, probe) {switch (p->action) {case DAMON_PREP_SET_PGIDLE :
@@ -268,7 +278,14 @@ static unsigned int damon_pa_apply_probes(struct damon_ctx *ctx, ctx->addr_unit); folio =damon_get_folio (PHYS_PFN (pa));damon_for_each_probe (p, ctx) { -if (damon_pa_filter_pass (folio, p)) +/* +* Event-driven probes skip this software apply +* path; their hits are counted asynchronously by +* the kdamond ring drain. Only sampling-based +* probes credit probe_hits[] here. +*/ +if (!p->event_driven && +damon_pa_filter_pass (folio, p)) r->probe_hits[i]++; i++; }
mm/damon/perf_source.c new file +402 -0
402 lines not on this page · raw message
mm/damon/perf_source.h new file +26 -0
@@ -0,0 +1,26 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* +* DAMON perf-event source - public interface +* +* Callers that register perf-event probes include this header. +*/ + +#ifndef _DAMON_PERF_SOURCE_H +#define _DAMON_PERF_SOURCE_H + +#ifdef CONFIG_DAMON_PERF_SOURCE + +#include <linux/damon.h> +#include <linux/perf_event.h> + +struct damon_perf_probe_event ; + +int damon_perf_probe_setup (struct damon_ctx *ctx, +struct damon_probe *probe, +struct damon_perf_probe_event *event); +void damon_perf_probe_teardown (struct damon_ctx *ctx, +struct damon_perf_probe_event *event); +struct damon_probe *damon_perf_probe_alloc (unsigned int weight); + +#endif /* CONFIG_DAMON_PERF_SOURCE */ +#endif /* _DAMON_PERF_SOURCE_H */
mm/damon/vaddr.c +17 -1
@@ -552,6 +552,16 @@ static void damon_va_prep_probe_region(struct damon_ctx *ctx, {struct damon_prep *p; +/* +* Event-driven probes have no software prep action: their hits arrive +* asynchronously through the per-CPU report rings and are applied by +* the kdamond ring drain, so skip the software prep path here. +* Reaching this for an event-driven probe is normal (prep runs for +* every probe); it is silently skipped, not a warning. +*/ +if (probe->event_driven) +return ; +damon_for_each_prep (p, probe) {switch (p->action) {case DAMON_PREP_SET_PGIDLE :
@@ -656,7 +666,13 @@ static void damon_va_probe_folio(struct damon_ctx *ctx,int i =0 ;damon_for_each_probe (probe, ctx) { -if (damon_va_filter_pass (folio, probe, pte, pmd, mm, +/* +* Event-driven probes skip this software apply path; their hits +* are counted asynchronously by the kdamond ring drain. Only +* sampling-based probes credit probe_hits[] here. +*/ +if (!probe->event_driven && +damon_va_filter_pass (folio, probe, pte, pmd, mm, r->sampling_addr)) r->probe_hits[i]++; i++;
Signature · 1 line
2.43.0
RFC PATCH v2 7/9 mm/damon/ops-common: use probe-weighted score when probe weights are set
In reply to Ravi JonnalagaddaWhen probe weights are configured (damon_has_probe_weights()), use damon_probe_hits_wsum() for the frequency subscore in damon_hot_score() instead of damon_nr_accesses_mvsum(). This routes hardware event counts from a perf-event probe into the tiering decision.
When no probe weights are set the frequency subscore comes from nr_accesses_mvsum, so page-table-only monitoring scores from the page-table access rate.
Export damon_has_probe_weights() so ops-common.c can call it without a static dependency on core.c internals.
Guard against integer overflow: clamp the probe-weighted subscore to DAMON_MAX_SUBSCORE via min_t after the mult_frac scaling so a large weighted-hit sum cannot overflow the subscore range.
--- include/linux/damon.h | 1 + mm/damon/core.c | 2 +- mm/damon/ops-common.c | 21 ++++++++++++++++++--- 3 files changed, 20 insertions(+), 4 deletions(-)
include/linux/damon.h +1 -0
@@ -1307,6 +1307,7 @@ unsigned char damon_probe_hits_mvsum(int probe_idx, struct damon_region *r, struct damon_ctx *ctx);unsigned int damon_probe_hits_wsum (struct damon_region *r,bool last,bool mv,struct damon_ctx *ctx); +bool damon_has_probe_weights (struct damon_ctx *c);int damon_set_regions (struct damon_target *t,struct damon_addr_range *ranges,unsigned int nr_ranges,unsigned long min_region_sz);
mm/damon/core.c +1 -1
@@ -391,7 +391,7 @@ static struct damon_probe *damon_nth_probe(int n, struct damon_ctx *ctx)return NULL ; } -static bool damon_has_probe_weights (struct damon_ctx *c) +bool damon_has_probe_weights (struct damon_ctx *c) {struct damon_probe *p;
mm/damon/ops-common.c +18 -3
@@ -170,9 +170,24 @@ int damon_hot_score(struct damon_ctx *c, struct damon_region *r,unsigned int age_weight = s->quota.weight_age;int hotness; - freq_subscore =mult_frac (damon_nr_accesses_mvsum (r, c), -DAMON_MAX_SUBSCORE , -damon_nr_samples_per_aggr (&c->attrs)); +if (damon_has_probe_weights (c)) { +unsigned int wsum =damon_probe_hits_wsum (r, false, true, c); + +/* +* Route perf-event hardware event counts into the score. +* Clamp to DAMON_MAX_SUBSCORE so a large weighted-hit sum +* cannot overflow the subscore range. +*/ + freq_subscore =min_t (int, +mult_frac (wsum,DAMON_MAX_SUBSCORE , +/* +1 guards divide-by-zero: samples-per-aggr can be 0 */ +damon_nr_samples_per_aggr (&c->attrs) +1 ), +DAMON_MAX_SUBSCORE ); + }else { + freq_subscore =mult_frac (damon_nr_accesses_mvsum (r, c), +DAMON_MAX_SUBSCORE , +damon_nr_samples_per_aggr (&c->attrs)); + } age_in_sec = (unsigned long )r->age * c->attrs.aggr_interval /1000000 ;if (age_in_sec)
Signature · 1 line
2.43.0
RFC PATCH v2 8/9 mm/damon: add perf_event prep for PMU-driven hotness probes
In reply to Ravi JonnalagaddaThe prep interface exposes a single preparation action, set_pgidle, which arms the page-idle scan used by the software access check. There is no way for user space to configure a probe whose access information comes from a hardware PMU sampling facility, even though the perf-event source (CONFIG_DAMON_PERF_SOURCE) already provides the NMI-safe overflow handler, the report ring, and the probe setup/teardown lifecycle to consume such samples.
Add a DAMON_PREP_PERF_EVENT prep action carrying a subset of perf_event_attr (type, config, config1, config2, sampling period or frequency, wakeup_events, precise_ip, sample_phys_addr, sample_weight_struct, exclude_kernel, exclude_hv), and wire it through include/linux/damon.h, mm/damon/perf_source.h, mm/damon/sysfs.c and mm/damon/core.c. A probe that has a perf_event prep is event driven:
DAMON does not walk it in the apply_probes vtable; instead the PMU samples memory accesses and feeds region hit counters through the report ring.
The prep also carries how many counters the PMU needs. A per-CPU PMU is armed by adding a cpuhp instance, which opens one kernel counter on every online CPU. A system-wide PMU is a single hardware unit served by exactly one counter, so a single_instance flag opens one kernel counter pinned to a fixed online CPU and bypasses the cpuhp fan-out. The pin names a real online CPU because such a PMU registers with perf_invalid_context, for which cpu = -1 would route to task context. If that CPU is later offlined the counter stops and is not migrated, which is acceptable for a dedicated monitoring host.
The PMU is only armed when building the context that will actually run. A param_ctx built for a commit carries the attributes but defers arming, because arming a throwaway context would collide with the running context's per-PMU ownership, and a commit_live flag distinguishes the dry-run validation pass from the real commit. The commit path hands the armed event between the running probe and the committed source probe, keeping the running event untouched on the common weight-only commit and re-arming only when the perf attributes change.
The armed event is released both when monitoring is turned off, from kdamond_fn()'s exit path after damon_destroy_targets(), and when the context is destroyed. Both run outside NMI context, so a provider's sleeping teardown is safe, and a later turn-on re-arms the event from the carried probe attributes. The teardown NMI barrier, an smp_store_release of event->ctx = NULL, covers the single-instance and per-CPU paths alike; the overflow handler reads its owning context with a matching smp_load_acquire.
After this change user space can configure a PMU hotness probe entirely through sysfs, for example:
echo perf_event > .../probes/0/preps/0/prep_action
echo 8 > .../probes/0/preps/0/type echo 0x100 > .../probes/0/preps/0/sample_period echo 1 > .../probes/0/preps/0/sample_phys_addr
--- include/linux/damon.h | 29 ++++ mm/damon/core.c | 134 +++++++++++++++++- mm/damon/perf_source.c | 195 ++++++++++++++++++-------- mm/damon/perf_source.h | 30 +++- mm/damon/sysfs.c | 266 ++++++++++++++++++++++++++++++++++-- mm/damon/tests/core-kunit.h | 2 +- 6 files changed, 583 insertions(+), 73 deletions(-)
include/linux/damon.h +29 -0
@@ -876,18 +876,45 @@ struct damon_intervals_goal {* enum damon_prep_action - DAMON probing preparation action. * * @DAMON_PREP_SET_PGIDLE: Set the probing memory as idle page. +* @DAMON_PREP_PERF_EVENT: Configure a perf-event hotness probe. */ enum damon_prep_action {DAMON_PREP_SET_PGIDLE , +DAMON_PREP_PERF_EVENT , };/** * struct damon_prep - DAMON probing preparation request. * * @action: Action to do to the probing memory for the preparation. +* @perf: perf_event_attr subset selecting the PMU and sampling +* parameters. Only valid when @action is DAMON_PREP_PERF_EVENT. +* +* A DAMON_PREP_PERF_EVENT prep turns the containing &struct damon_probe into +* an event-driven probe: instead of DAMON walking the address space each +* sampling interval, a per-CPU perf_event (e.g. AMD IBS Op, Intel PEBS) +* samples memory accesses and feeds them into the probe hit counters via the +* report ring. The @perf fields are copied into a perf_event_attr when the +* kdamond is turned on. */ struct damon_prep {enum damon_prep_action action; +struct { +u32 type; +u64 config; +u64 config1; +u64 config2; +u64 sample_period; +u64 sample_freq; +u32 wakeup_events; +u32 precise_ip; +bool sample_phys_addr; +bool sample_weight_struct; +bool exclude_kernel; +bool exclude_hv; +bool freq; +bool single_instance; + } perf;/* private: */ /* siblings list. */ struct list_head list;
@@ -936,6 +963,8 @@ struct damon_filter {struct damon_probe {unsigned int weight;bool event_driven;/* hits arrive via ring drain, not apply_probes */ +/* perf-event probe state (struct damon_perf_probe_event *) for teardown */ +void *perf_priv;/* private: */ /* Preparation actions to apply to each probing memory. */ struct list_head preps;
mm/damon/core.c +131 -3
208 lines not on this page · raw message
mm/damon/perf_source.c +141 -54
271 lines not on this page · raw message
mm/damon/perf_source.h +29 -1
@@ -13,7 +13,35 @@#include <linux/damon.h> #include <linux/perf_event.h> -struct damon_perf_probe_event ; +/* +* PMU event attributes for a perf-event probe. A subset of perf_event_attr +* chosen at probe creation time to select the PMU (AMD IBS, Intel PEBS, ...) +* and its sampling parameters. +*/ +struct damon_perf_event_attr { +u32 type; +u64 config; +u64 config1; +u64 config2; +bool sample_phys_addr; +bool sample_weight_struct; +bool exclude_kernel; +bool exclude_hv; +bool freq; +bool single_instance;/* system-wide PMU: open one counter, not per-CPU */ +u64 sample_freq; +u64 sample_period; +u32 wakeup_events; +u32 precise_ip; +}; + +struct damon_perf_probe_event { +struct damon_perf_event_attr attr; +struct damon_ctx *ctx;/* owning ctx for ring routing; set at setup */ +void *priv;/* struct damon_perf_probe_state * */ +struct hlist_node hlist_node; +int probe_idx;/* index into probe_hits[]; set at registration */ +}; int damon_perf_probe_setup(struct damon_ctx *ctx, struct damon_probe *probe,
mm/damon/sysfs.c +252 -14
367 lines not on this page · raw message
mm/damon/tests/core-kunit.h +1 -1
@@ -1477,7 +1477,7 @@ static void damon_test_commit_probes_for(struct kunit *test,kunit_skip (test,"src alloc fail" ); } - err =damon_commit_probes (dst, src); + err =damon_commit_probes (dst, src, false);KUNIT_EXPECT_EQ (test, err,0 );if (err) goto out;
Signature · 1 line
2.43.0
RFC PATCH v2 9/9 mm/damon/tests/drain-kunit: kunit for report rings and unified drain
In reply to Ravi JonnalagaddaAdd kunit coverage for the report rings (the global page-fault ring and a per-context perf ring) and the unified drain: ring inject/drain, overflow safety on wrap, the damon_report_access() producer + probe_idx routing (page-fault reports to the global pf ring, perf reports to the reporting ctx's own perf ring), ring partition, the pf-ring owner -EBUSY guard, vaddr thread-group-id filtering, and paddr crediting left unfiltered. Wire the suites into core.c (included after the drain and its counters are defined) so CONFIG_DAMON_KUNIT_TEST=y builds them.
Cover the page-fault primitive and an event-driven probe sharing one
context: a report on each ring credits the access rate of the region while only the probe report reaches the probe hits array feeding the weighted sum. Cover the address-space match by injecting a report whose virtual address falls inside a region of a physical address space target and whose physical address falls outside it, which is not credited. Cover the hot and cold sides of the page-fault signal by reporting an access on one of two regions and aging both.
--- mm/damon/core.c | 2 + mm/damon/tests/.kunitconfig | 4 + mm/damon/tests/drain-kunit.h | 1091 ++++++++++++++++++++++++++++++++++ mm/damon/tests/perf-kunit.h | 133 +++++ 4 files changed, 1230 insertions(+) create mode 100644 mm/damon/tests/drain-kunit.h create mode 100644 mm/damon/tests/perf-kunit.h
mm/damon/core.c +2 -0
@@ -5354,3 +5354,5 @@ struct damon_region *damon_search(unsigned long addr, struct pid *pid)subsys_initcall (damon_init);#include "tests/core-kunit.h" +#include "tests/drain-kunit.h" +#include "tests/perf-kunit.h"
mm/damon/tests/.kunitconfig +4 -0
@@ -16,3 +16,7 @@ CONFIG_DAMON_SYSFS_KUNIT_TEST=y # enable DAMON_DEBUG_SANITY to catch any bug CONFIG_DAMON_DEBUG_SANITY=y + +# for global report rings (pf/perf) and per-PMU exclusivity tests +CONFIG_PERF_EVENTS=y +CONFIG_DAMON_PERF_SOURCE=y
mm/damon/tests/drain-kunit.h new file +1091 -0
1,091 lines not on this page · raw message
mm/damon/tests/perf-kunit.h new file +133 -0
133 lines not on this page · raw message
Signature · 1 line
2.43.0
SJ Park
maintainerQuoted 16 lines · On Thu, 10 Sep 2026 10:16:14 -0700 Ravi Jonnalagadda <[email protected]>…
This series lets DAMON take its access information from a hardware sampler instead of from a page-table scan, and lets a scheme's score be weighted by what that sampler reported.
The change from v1 [1] is that it is now built on the data attribute probe infrastructure that has since landed in mm-new: a PMU is expressed as one more probe on a context, with its own weight and probe hits, rather than as a mechanism of its own beside the ops set. The rest of this posting is the same substrate reworked onto that, plus two consumers.
This is not a merge request. The series is based on damon/next at the base-commit below, which moves, so the tree it was built and tested from is also on
https://github.com/ravis-opensrc/linux/tree/damon/perf-rfc-v2-09-06-26
Quoted 8 lines
It is posted for design feedback, on the substrate and on where it belongs in the roadmap for extending DAMON beyond the pte-accessed bit [2]. That roadmap's second milestone, now open, is a first data attribute monitored through
damon_report_access(), and that is what a sampling PMU is here. So this series keeps that function and its callers and replaces its body: the reporting path a hardware sampler needs cannot take a mutex, and the drain has to reach a virtual-address context as well as a physical one. The shape of the ring, the drain and the sysfs surface are what is most useful to review.
Thank you for this series, Ravi! I will take time to thoughly read and reply this series by the end of this weekend.
Thanks,
SJ
[...]
SJ Park
maintainerHello Ravi,
Quoted 16 lines · On Thu, 10 Sep 2026 10:16:14 -0700 Ravi Jonnalagadda <[email protected]>…
This series lets DAMON take its access information from a hardware sampler instead of from a page-table scan, and lets a scheme's score be weighted by what that sampler reported.
The change from v1 [1] is that it is now built on the data attribute probe infrastructure that has since landed in mm-new: a PMU is expressed as one more probe on a context, with its own weight and probe hits, rather than as a mechanism of its own beside the ops set. The rest of this posting is the same substrate reworked onto that, plus two consumers.
This is not a merge request. The series is based on damon/next at the base-commit below, which moves, so the tree it was built and tested from is also on
https://github.com/ravis-opensrc/linux/tree/damon/perf-rfc-v2-09-06-26
Quoted 8 lines
It is posted for design feedback, on the substrate and on where it belongs in the roadmap for extending DAMON beyond the pte-accessed bit [2]. That roadmap's second milestone, now open, is a first data attribute monitored through
damon_report_access(), and that is what a sampling PMU is here. So this series keeps that function and its callers and replaces its body: the reporting path a hardware sampler needs cannot take a mutex, and the drain has to reach a virtual-address context as well as a physical one. The shape of the ring, the drain and the sysfs surface are what is most useful to review.
First of all, thank you very much for sharing this great series.
I think the high level direction, especially utilizing data attributes infrastructure and interface is good and aligned with our beyond-page table accessed-bit plan.
Nevertheless, this version is highly ambitious, optimized and big. We will land only essential parts in milestone 2. The goal of milestone 2 is only essential functionality. We will further extend its functionality and optimize its performance, in multiple and parallel steps, in the phase for the milestone 3 (or, simply phase 3).
I understand that's also what you are aligned with. And you had to post this big series mainly because we just started milestone 2, and therefore you have nothing to really base on.
Let me further provide some comments including what looks especially big and optimized to me, and how those could be split into small pieces.
Quoted 12 lines
## Why a unified perf-event substrate
DAMON derives its access information from the PTE Accessed bit. A sampling PMU carries what that bit cannot: which addresses the hardware went to, and how often it went there. Many machines already have such a unit, and more than one kind of it, so what this series is after is letting DAMON's regions be tuned from whichever perf-based hardware source a machine offers rather than from the Accessed bit alone. A sampler does not arrive on a kdamond's terms, though: it delivers an address when the hardware decides to, in NMI context, with no relation to the sampling interval and no mm to walk.
Quoted 7 lines
The alternative is a backend per PMU vendor, each owning its own configuration, sysfs knobs and lifecycle. The perf-event direction [3] avoids that: let DAMON register kernel-counter perf events and consume samples from any sampling PMU the perf core already knows about. This series follows it, adding one substrate below the ops sets rather than an ops set per PMU -- a report ring that any in-kernel access source can push into, and a drain that folds those reports into region probe hits on the aggregation boundary the kdamond already has.
This perfectly aligns with the plan. We will use perf event abstraction for AMD IBS-like h/w features based access monitoring primitives.
Quoted 4 lines
What running it
across vendors needed on top of that direction is:- per-CPU lockless rings between the NMI sample handler and the kdamond drain,
I understand we need to make it lockless. I wonder if we have to make it per-CPU. I understand it will be better in terms of performance, especially on machines having many CPUs. That said, this feels like somewhat we can discuss in phase 3. And it would deserve to have sufficient discussions and performance evaluations.
Quoted 2 lines
- a ring partitioned by probe class, so the page-fault primitive and a PMU can populate one context without either seeing the other's reports,
This also feels like something that we can discuss later in phase 3.
Particularly, page-fault primitive is out of the scope of milestone 2. The path forward and the timeline for it are quite unclear as of now. It requires alignments with other subsystems that would be challenging and taking time. I have a very rough plan to try it again with read-write protection, though. I think it might be safer to just keep it out of the scope of your project for now.
Quoted 6 lines
- per-CPU events that follow CPU hotplug, armed when the kdamond starts and disarmed and drained when it stops,
- a per-PMU owner, so two contexts cannot claim the same PMU type,
- whichever address a PMU does report carried on the report and matched against the context's own address space, so one source serves a paddr or a vaddr context without a backend per address space.
These all soudns making sense to me. Nonetheless, I think we can scope milestone 2 to support only physical address and defer these things to the phase 3.
Quoted 7 lines
This is tested with PEBS on Intel and IBS on AMD, both configured as
perf_eventattributes on a probe and using the perf core's event plumbing rather than per-vendor MSR code. A third source has already been written against the same
ring: Kunwu Chan's ARM SPE backend [5], which reaches it through an AUX buffer drained in process context instead of an overflow callback, and which the roadmap [2] places in its third milestone.
Awesome, appreciate your huge effort on this!
Quoted 5 lines
The partitioning is what lets promotion and demotion run in one context. A sampler says which regions are hot; it says nothing about which are cold, because a sampler that reports nothing about a page cannot distinguish untouched from unsampled.
I'm not really sure. I think absence of samples for an address range can also mean the address range is cold? Actually the page table accessed bit based monitoring also use a sort of sampling, so I don't show real distinction.
Maybe you're right, but I think this deserves sufficient discussions and testing that we could defer to the phase 3.
Quoted 2 lines
Region age is what a demotion scheme matches on, and age comes from the page-fault primitive.
We would have age in perf event based mode, too. Isn't it?
Quoted 3 lines
With the ring partitioned by class both are live at
once: the probe supplies hotness, the primitive supplies age, and two schemes over the same regions can move memory in both directions under one kdamond.
Unless the needs are clearly confirmed, I'd prefer having single class for simplicity.
Quoted 25 lines
The two classes are not symmetric, and the asymmetry is in where a report comes from rather than in what it carries. A page fault has no monitoring context at report time, so a per-context ring is not expressible for it: those reports go to one global set of per-CPU rings, drained by the single context whose page-fault primitive is enabled. A PMU overflow handler does carry the context that armed the event, so those reports go to that context's own per-CPU rings and each context drains only what it owns. Both classes share the ring layout and the producer side, so the barrier pairing cannot drift between them.
``` page-fault report PMU sample, NMI context no ctx at report time handler carries the owning ctx | | v v +----------------------------+ +----------------------------+ | one global per-CPU ring | | per-CPU rings per context | | set, shared by every ctx | | that armed a perf event | +----------------------------+ +----------------------------+ | drained by the one ctx | | each ctx drains only the | | with the pf primitive on | | rings it owns | +----------------------------+ +----------------------------+ | | +------------------+------------------+ |
Quoted 8 lines
drain on the aggregation boundary,
each report matched to a region
|
+---------------+---------------+
| |
region age region probe hits
demotion scheme promotion scheme
```Again, I'd suggest to simply ignore page-fault report for now.
Quoted 37 lines
That is the property this series exists to enable, and three things follow from it:
- One measurement drives both directions, at whichever scope the mode gives. Bandwidth comes from resctrl MBM, which is where the discussion on a platform-independent bandwidth signal for tiering has pointed [6]: a virtual-address context reads a monitoring group created for the processes it monitors, and a physical-address context reads the root monitoring group, so the same controller is scoped to a set of processes or to the whole machine without changing what it does. Reading it there also means a bandwidth allocation reserved for an application bounds what the controller optimises within. It spreads the hot set across nodes in the ratio that maximises achieved bandwidth when bandwidth is what the workload is short of, and it demotes cold pages off the near node in the same context at the same time -- either proactively under a fixed quota or only once DRAM is under memory pressure. Neither direction is configured as a target ratio; the proportion or weight the controller writes bounds how much memory moves while the score decides which pages move, and a hardware sample makes that score proportional to the traffic a region generates rather than one bit per page per scan. Earlier work on bandwidth-driven interleaving [4] measured the same effect from userspace.
- Bandwidth and capacity are expanded at once rather than one being chosen. Static weighted interleaving expands bandwidth, but it is a placement rule rather than a decision about any particular page, so cold pages keep their share of DRAM. NUMA balancing expands usable DRAM by promoting whatever is hot, but it does so without reference to what the far node can deliver, so it keeps promoting after the near node has stopped being the better place to read from. Here the hot set's distribution comes from a bandwidth measurement and the cold set is demoted on region age, in one context, so the far node's bandwidth is used and DRAM is not held by memory nothing is touching. The decision itself is a quota goal the kernel already carries:
node_eligible_mem_bp[8] bounds how much of a node a scheme may fill, so what the controller supplies is the bandwidth measurement, not the placement.
Quoted 7 lines
- When bandwidth is not the constraint it reverts to latency-first tiering on its own. The search starts with everything on the near node and moves memory outward only when the measurement improves, so on a workload that does not saturate that node no step outward improves anything: the hot set stays in DRAM for the lower access latency and cold pages keep being demoted. The same configuration covers both cases, with nothing to switch on and no ratio to revise.
Sounds very interesting.
Quoted 9 lines
## What the series adds
1.
mm/damon/vaddr: support page fault access check primitive-- implements the page-fault primitive for a virtual address space context; it was available for physical addresses only. The marker whose fault reports the access is installed through the target mm and the vma of the sampling address directly, andprepare_access_checks()dispatches on the enabled primitive as the physical address space one does.
As I mentioned above, I'd suggest to keep page fault primitive out of the scope for now.
Quoted 5 lines
2.
mm/damon/core: read the CPU number with preemption disabled-- the report path runs in fault context, which is preemptible. Read the CPU number with preemption disabled and keep it disabled across the report, so the recorded CPU stays consistent with the per-CPU ring the report is queued to.
Sounds good and will be needed for milestone 2, too. I will reuse or reference this in my milestone 2 implemenation. This will be very helpful, thank you for sharing.
Quoted 5 lines
3.
mm/damon/paddr: lock the folio for the page fault primitive rmap walk-- take the folio lock unconditionally around the walk, as the other reverse mapping walks in DAMON do, so every mapping type reaches it locked, and release the reference the lookup took on each exit path.
Again, let's keep page fault-based monitoring out of the scope for now.
Quoted 5 lines
4.
mm/damon: add damos_node_eligible_mem_bp tracepoint-- a per-tick tracepoint over the node-eligible-memory quota goal evaluation, exposing the goal's target and current values, so the loop a bandwidth-driven controller steers is visible to a tracer.
This seems doesn't need to wait anything. If this turned out to be helpful, please feel free to separately send patches for this.
Quoted 9 lines
5.
mm/damon/core: add per-probe-class report rings and unified drain-- the substrate. Per-CPU SPSC rings an NMI-context source can publish into, partitioned by probe index into a global page-fault ring and a per-context perf ring, plus the kdamond-side drain that matches each report to a region by binary search over a per-target snapshot and credits it to that region's probe hits on the aggregation boundary. The address space of the target selects which address of a report is matched, and pid targets are filtered by thread group id.
Sounds like good optimizations that we can discuss with tests in the phase 3.
Quoted 9 lines
6.
mm/damon: add perf-event overflow handler feeding the report ring-- an ops-agnostic perf-event source whose overflow handler turns a PMU sample into a report, setting each address field the PMU reported as valid. A PMU that reports a virtual address only can therefore drive a virtual-address context, one that reports a physical address a physical-address context, and the same source serves either without a backend per address space. Per-CPU events are armed and released through cpuhp callbacks, and a per-PMU owner keeps two contexts from claiming the same PMU type.
This should be what also essential for milestone 2. I will reuse or reference this in my milestone 2 implemenation. This will be very helpful, thank you for sharing.
Nonetheless, virtual-address context considered parts may be skipped in milestone 2. We can discuss such things in the phase 3.
Quoted 5 lines
7.
mm/damon/ops-common: use probe-weighted score when probe weights are set-- lets a scheme's frequency subscore come from the probe hits, weighted per probe class, so what the sampler reported reaches the tiering decision. With no weights set the subscore comes from the access rate as before.
I'm working on adding more attributes-only mode (probe weights are set) support to DAMOS. Hopefully we can converge on this. This is not a part of our planned milestone 2, but I will keep doing this in parallel, so hopefully full DAMOS support for attributes-only mode will be done around end of milestone 2. Let's keep discussion on the works.
Quoted 7 lines
8.
mm/damon: add perf_event prep for PMU-driven hotness probes-- the sysfs surface and the event lifecycle: aperf_eventprep action carrying the PMU type, the event config and the sample attributes per probe, with per-CPU or single-instance arming depending on how many counters the PMU needs. Arming is deferred on a context built for a commit, so a weight-only commit leaves the running event untouched.
This is also what would be essential for milestone 2. I will reuse or reference this in my milestone 2 implemenation. This will be very helpful, thank you for sharing.
Quoted 6 lines
9.
mm/damon/tests/drain-kunit: kunit for report rings and unified drain-- unit tests for the rings and the drain: inject and drain, overflow on wrap, producer routing by probe index, ring partitioning, pf-ring ownership, thread-group filtering, the address space match, and both primitives live on one context.
Testing is always important and nice. Thank you for doing this.
Quoted 166 lines
Patches 1-4 apply standalone on damon/next and are useful without the rest; 5-8 are the substrate and its first two consumers, and 9 covers them. If the first four read right, they need not wait on the rest of this series -- happy to repost them on their own for damon/next if that is easier to take. Two of them, 2 and 3, correct code that is already in mm-new rather than adding anything new, so they could instead go as a small fixes series carrying
Fixes: tags; guidance on which of the two routes is preferred is welcome.## Userspace setup model
The runs were driven by an
auto_tiersubcommand added to damo on the branch below, which reads achieved bandwidth from resctrl MBM and searches for the hot set's distribution that maximises it, while the demotion scheme runs alongside it in the same context. That tooling is not part of this posting; it is onhttps://github.com/ravis-opensrc/damo/tree/damo/auto-tier-bw-2026-09-08
-
--bw_source resctrlis the option this adds: the controller reads achieved bandwidth from an MBM monitoring group and searches, rather than being given a distribution to install.
- Everything else -- intervals, schemes, filters, the probe'sperf_eventattributes -- is what the generator writes from the command line shown: 5 ms sampling, 100 ms aggregation, a 1 s ops update.
- Both configurations select proactive demotion, so they demote continuously rather than waiting for pressure, and both were left closed loop throughout.
- The search is the algorithm described in [4].### Configuration A: AMD IBS Op, paddr ops, system-wide
``` $ sudo mount -t resctrl resctrl /sys/fs/resctrl $ sudo python3 tools/damon_tier_gen.py --hotness ibs \ --near_node 0 --far_node 4 \ --cold_demote --cold_demote_mode proactive \ -o tier.yaml $ sudo damo auto_tier tier.yaml --bw_source resctrl --verbose ```- Scope is the machine, not a process set. The distribution is steered through
node_eligible_mem_bpquota goals over each node's own physical ranges, which the generator reads from/proc/iomem.
- AMD Turin, DRAM on node 0 and a CXL node. IBS Op at asample_periodof 262144 withsample_phys_addrset, which is what a physical-address context needs from the sample.
- Workload is a hot-and-cold mix so both schemes have something to act on:
multiloaddrives the bandwidth-hungry hot set, and a second process allocates on the near node, touches it once and goes idle, leaving pages that age out for the demotion scheme to find.### Configuration B: Intel PEBS L3-miss, vaddr ops, per-PID
``` $ sudo mount -t resctrl resctrl /sys/fs/resctrl $ sudo python3 tools/damon_tier_gen.py --hotness pebs \ --pid $HOT_PID --pid $COLD_PID \ --near_node 0 --far_node 1 \ --cold_demote --cold_demote_mode proactive \ --min_nr_regions 1000 --max_nr_regions 20000 \ -o tier.yaml $ sudo damo auto_tier tier.yaml --bw_source resctrl --verbose ```- Scope is the processes the monitoring group names and nothing else. The distribution is steered through the hot scheme's
DamosDestweights [9].
- Intel Granite Rapids, DRAM on node 0 and CXL on node 1. PEBS L3-miss at`sample_freq` 5003 with `precise_ip` 2.- Same workload shape.
## What the runs show
Per-node reference, each figure measured by binding the same workload to one node:
``` Granite Rapids, 64 threads x 8 GiB node 0 1.0 TB DRAM DRAM only 269,885 MB/s node 1 2.0 TB CXL CXL only 249,903 MB/s Turin, 32 threads x 4 GiB node 0 386 GB DRAM DRAM only 113,596 MB/s node 4 1.0 TB CXL CXL only 33,253 MB/s ```- The two machines differ in the way that matters: on the Turin the far node is 3.4x slower than the near one, on the Granite Rapids the two are within 8%.
- Each configuration gets one graph, the climb: the run itself, one mark per decision taken from the bandwidth just measured, with the share it moved to under each mark. The settled line below it is the median over the samples the run went on to hold at that share.### Configuration B, virtual-address mode
``` MB/s climb 460k | * 440k | * 420k | * 400k | 380k | * 360k | * 340k | * * 320k | * 300k | * * 280k | * 260k | * * --------+------------+--------------+---------------+------------+-- 1 4 7 10 13 92 80 68 56 44 decision index, near-node share (%) settled share 44, 430,547 MB/s held ```- Thirteen decisions to settle at 44%, no target given: about 60% more than the better of the two nodes on its own, and the share does not change again over the 81 samples that follow.
- Cold demotion runs over the same interval, in the same context, actuated by the same kdamond: 34 regions totalling 15.4 GiB applied, and the idle process ends with 4,099,980 pages -- 15.6 GiB -- on the CXL node having started on DRAM.
- The promotion scheme's applied byte count is flat across the demotion ramp, 291.1 GiB before against 291.3 GiB after, so the recovered capacity is the demotion scheme's.### Configuration A, physical-address mode
``` MB/s climb 136k | 134k | * * 132k | * * 130k | * 128k | 126k | * 124k | 122k | * 120k | 118k | * +----+-----+-----+-----+-----+-----+-----+-----+-- 1 2 3 4 5 6 7 8 92 88 84 80 76 78 80 78 decision index, near-node share (%) settled share 78, 134,107 MB/s held ```- Eight decisions to settle at 78, from a start with almost everything on the near node: the same algorithm reaches the optimum share on its own from the bandwidth reading alone, and cold pages are demoted to the far node while it does so.
- A distribution beats either end because both nodes supply at once: at the settled share the near node is supplying 104,618 MB/s against the 113,596 it reaches with the whole working set bound to it, so the further 37,669 MB/s arriving over CXL is bandwidth it could not have supplied at any share.
- The idle process ends with 2,863,156 pages -- 10.9 GiB -- on the CXL node, out of the 32 GiB it allocated on DRAM and stopped touching. Residency is the attributable figure in this mode rather than a per-scheme applied count, because the schemes share physical ranges.### What the runs are meant to establish
Not the settled share and not the absolute bandwidth; those are properties of these machines and these workloads. What reproduces is:
Quoted 8 lines
- A controller reading achieved bandwidth converges unattended, from a configuration naming no target, to a distribution that beats either node on its own, and holds it once found.
- A demotion scheme recovers near-node capacity in the same context while that happens.
- One code path does both, steeringnode_eligible_mem_bpover physical ranges system-wide on one machine andDamosDestweights over a named process group on the other.
Nice testing and measurement, thank you for sharing! I'm happy the future DAMON could help this!
Quoted 17 lines
## Beyond a CPU PMU
Nothing above is specific to PEBS or IBS. A source qualifies if it can report an accessed address to the ring, which is the only thing the substrate asks of it. A CXL device's Hotness Monitoring Unit, whose r3.2 perf driver has been posted [7] exposing its hotlist through an AUX buffer, or a custom monitoring unit on an accelerator or a memory expander, reports exactly that, and a backend delivering those reports through a perf event reaches the same drain, the same probe hits and the same schemes already in the tree. Such a unit sees the traffic that reaches its own device rather than a system-wide view, so what it contributes is the hot set resident on that device: the tiering logic can promote those pages to DRAM without the host having to detect that hotness itself, and without depending on what produced the reports. That posting names driving tiering from such a unit as the intent and the in-kernel step as future work, and consuming it as a DAMON access-check primitive was raised there as one way to get it; a backend on this substrate is that path.
That makes sense to me. Thank you for sharing this nice survey and theories!
Quoted 23 lines
[1] v1 of this series
https://lore.kernel.org/damon/[email protected]/ [2] Roadmap for extending DAMON beyond pte-accessed bit https://lore.kernel.org/damon/[email protected]/ [3] mm/damon: introduce perf event based access check https://lore.kernel.org/damon/[email protected]/[4] B. Tabatabai, R. Jonnalagadda et al., "Bandwidth Speaks, We Listen:
Dynamic Interleaving for Tiered Memory", ISMM 2026.
https://dl.acm.org/doi/10.1145/3814942.3816137
[5]mm/damon/perf: add ARM SPE AUX backendhttps://lore.kernel.org/damon/[email protected]/[6] A platform-independent subsystem for bandwidth information, and resctrl as that source
https://lore.kernel.org/linux-mm/[email protected]/
[7] CXL Hotness Monitoring Unit perf driverhttps://lore.kernel.org/linux-mm/[email protected]/ [8] mm/damon: add node_eligible_mem_bp goal metric, merged for v7.2 https://lore.kernel.org/linux-mm/[email protected]/ [9] mm/damon/vaddr: allow interleaving in migrate_{hot,cold} actions, merged for v6.17 https://lore.kernel.org/linux-mm/[email protected]/
Quoted 11 lines
Ravi Jonnalagadda (9):
mm/damon/vaddr: support page fault access check primitivemm/damon/core: read the CPU number with preemption disabledmm/damon/paddr: lock the folio for the page fault primitive rmap walk mm/damon: adddamos_node_eligible_mem_bptracepointmm/damon/core: add per-probe-class report rings and unified drain mm/damon: add perf-event overflow handler feeding the report ringmm/damon/ops-common: use probe-weighted score when probe weights are
set
mm/damon: addperf_eventprep for PMU-driven hotness probesmm/damon/tests/drain-kunit: kunit for report rings and unified drain
As this series is for high level discussion, and the code will significantly changed after rebasing on milestone 2 output, I wouldn't go deep into each line of the code for now. Feel free to let me know if you want.
Quoted 23 lines
include/linux/damon.h | 136 ++++- include/trace/events/damon.h | 32 + mm/damon/Kconfig | 18 + mm/damon/Makefile | 1 + mm/damon/core.c | 995 +++++++++++++++++++++++++++---- mm/damon/ops-common.c | 21 +- mm/damon/paddr.c | 37 +- mm/damon/perf_source.c | 489 +++++++++++++++ mm/damon/perf_source.h | 54 ++ mm/damon/sysfs.c | 266 ++++++++- mm/damon/tests/.kunitconfig | 4 + mm/damon/tests/core-kunit.h | 2 +- mm/damon/tests/drain-kunit.h | 1091 ++++++++++++++++++++++++++++++++++ mm/damon/tests/perf-kunit.h | 133 +++++ mm/damon/vaddr.c | 83 ++- 15 files changed, 3220 insertions(+), 142 deletions(-) create mode 100644 mm/damon/perf_source.c create mode 100644 mm/damon/perf_source.h create mode 100644 mm/damon/tests/drain-kunit.h create mode 100644 mm/damon/tests/perf-kunit.h
Quoted 5 lines
base-commit:
e1f34dce183a96fc93bf9a42dcdc0ec0bf82a3d2Signature · 1 line
2.43.0
Thanks,
SJ
Quoted 30 lines · SJ Park
Hello Ravi,
Quoted 16 lines · On Thu, 10 Sep 2026 10:16:14 -0700 Ravi Jonnalagadda <[email protected]>…
This series lets DAMON take its access information from a hardware sampler instead of from a page-table scan, and lets a scheme's score be weighted by what that sampler reported.
The change from v1 [1] is that it is now built on the data attribute probe infrastructure that has since landed in mm-new: a PMU is expressed as one more probe on a context, with its own weight and probe hits, rather than as a mechanism of its own beside the ops set. The rest of this posting is the same substrate reworked onto that, plus two consumers.
This is not a merge request. The series is based on damon/next at the base-commit below, which moves, so the tree it was built and tested from is also on
https://github.com/ravis-opensrc/linux/tree/damon/perf-rfc-v2-09-06-26
Quoted 8 lines
It is posted for design feedback, on the substrate and on where it belongs in the roadmap for extending DAMON beyond the pte-accessed bit [2]. That roadmap's second milestone, now open, is a first data attribute monitored through
damon_report_access(), and that is what a sampling PMU is here. So this series keeps that function and its callers and replaces its body: the reporting path a hardware sampler needs cannot take a mutex, and the drain has to reach a virtual-address context as well as a physical one. The shape of the ring, the drain and the sysfs surface are what is most useful to review.
Quoted 2 lines
First of all, thank you very much for sharing this great series.
Thanks SJ for the detailed review and the milestone 2 / phase 3 clarifications.
Quoted 4 lines
I think the high level direction, especially utilizing data attributes infrastructure and interface is good and aligned with our beyond-page table accessed-bit plan.
Good to hear.
Quoted 6 lines
Nevertheless, this version is highly ambitious, optimized and big. We will land only essential parts in milestone 2. The goal of milestone 2 is only essential functionality. We will further extend its functionality and optimize its performance, in multiple and parallel steps, in the phase for the milestone 3 (or, simply phase 3).
Quoted 7 lines
I understand that's also what you are aligned with. And you had to post this big series mainly because we just started milestone 2, and therefore you have nothing to really base on.
Let me further provide some comments including what looks especially big and optimized to me, and how those could be split into small pieces.
Very much aligned. The comments below help a lot in identifying what is essential
for milestone 2 and what can wait for phase 3. v3 will be scoped accordingly.
Quoted 26 lines
Quoted 12 lines
## Why a unified perf-event substrate
DAMON derives its access information from the PTE Accessed bit. A sampling PMU carries what that bit cannot: which addresses the hardware went to, and how often it went there. Many machines already have such a unit, and more than one kind of it, so what this series is after is letting DAMON's regions be tuned from whichever perf-based hardware source a machine offers rather than from the Accessed bit alone. A sampler does not arrive on a kdamond's terms, though: it delivers an address when the hardware decides to, in NMI context, with no relation to the sampling interval and no mm to walk.
Quoted 7 lines
The alternative is a backend per PMU vendor, each owning its own configuration, sysfs knobs and lifecycle. The perf-event direction [3] avoids that: let DAMON register kernel-counter perf events and consume samples from any sampling PMU the perf core already knows about. This series follows it, adding one substrate below the ops sets rather than an ops set per PMU -- a report ring that any in-kernel access source can push into, and a drain that folds those reports into region probe hits on the aggregation boundary the kdamond already has.
This perfectly aligns with the plan. We will use perf event abstraction for AMD IBS-like h/w features based access monitoring primitives.
Quoted 3 lines
What running it
across vendors needed on top of that direction is:
Quoted 7 lines
Quoted 1 line
- per-CPU lockless rings between the NMI sample handler and the kdamond drain,
I understand we need to make it lockless. I wonder if we have to make it per-CPU. I understand it will be better in terms of performance, especially on machines having many CPUs. That said, this feels like somewhat we can discuss in phase 3. And it would deserve to have sufficient discussions and performance evaluations.
The per-CPU structure is not a performance optimization we can defer -- it is required by the calling context. A perf-event overflow handler runs in NMI context, which cannot take a mutex or any sleeping lock. damon_report_access() takes a mutex, so it cannot be called from there. Per-CPU SPSC rings are the correct primitive for a lockless producer (NMI overflow handler) to consumer (kdamond drain) handoff, not an optional improvement over a simpler shared ring. I am planning to retain per-CPU rings for v3 because of this reason.
Quoted 4 lines
Quoted 3 lines
- a ring partitioned by probe class, so the page-fault primitive and a PMU can populate one context without either seeing the other's reports,
Quoted 8 lines
This also feels like something that we can discuss later in phase 3.
Particularly, page-fault primitive is out of the scope of milestone 2. The path forward and the timeline for it are quite unclear as of now. It requires alignments with other subsystems that would be challenging and taking time. I have a very rough plan to try it again with read-write protection, though. I think it might be safer to just keep it out of the scope of your project for now.
Understood. I included the page-fault source in v2 because the December 2025 RFC [1] that introduced damon_report_access() had it as the primary consumer, and I wanted to carry forward that ability. Since it is out of scope for milestone 2, I will drop it from v3.
Patches 1, 2, and 3 will be dropped from the v3 submission since they are all tied to the page fault path.
Quoted 8 lines
Quoted 7 lines
- per-CPU events that follow CPU hotplug, armed when the kdamond starts and disarmed and drained when it stops,
- a per-PMU owner, so two contexts cannot claim the same PMU type,
- whichever address a PMU does report carried on the report and matched against the context's own address space, so one source serves a paddr or a vaddr context without a backend per address space.
Quoted 4 lines
These all soudns making sense to me. Nonetheless, I think we can scope milestone 2 to support only physical address and defer these things to the phase 3.
Got it. Will scope v3 to PA only. so included results for v3 would be based on AMD IBS testing.
Quoted 20 lines
Quoted 7 lines
This is tested with PEBS on Intel and IBS on AMD, both configured as
perf_eventattributes on a probe and using the perf core's event plumbing rather than per-vendor MSR code. A third source has already been written against the same
ring: Kunwu Chan's ARM SPE backend [5], which reaches it through an AUX buffer drained in process context instead of an overflow callback, and which the roadmap [2] places in its third milestone.Awesome, appreciate your huge effort on this!
Quoted 5 lines
The partitioning is what lets promotion and demotion run in one context. A sampler says which regions are hot; it says nothing about which are cold, because a sampler that reports nothing about a page cannot distinguish untouched from unsampled.
I'm not really sure. I think absence of samples for an address range can also mean the address range is cold? Actually the page table accessed bit based monitoring also use a sort of sampling, so I don't show real distinction.
Quoted 8 lines
Maybe you're right, but I think this deserves sufficient discussions and testing that we could defer to the phase 3.
Quoted 2 lines
Region age is what a demotion scheme matches on, and age comes from the page-fault primitive.
We would have age in perf event based mode, too. Isn't it?
You are right. Region age accumulates whenever nr_accesses stays at zero across aggregation boundaries, and that holds whether the zero comes from a PTE scan or from an empty perf-event drain.
I was initially concerned that a sparse sampling PMU might not cover every cold region in every aggregation window, leaving silence that could be mistaken for cold,
whereas page faults provide higher spatial coverage for confirming first-access. Having both together was intended as a defence against that gap.
Based on your observation I retested this on hardware. On AMD Turin the cold demotion scheme found and demoted the idle working set correctly using only the hardware-sampled IBS signal -- nr_accesses aged to zero for regions that genuinely had no traffic, and the scheme acted on age as expected. The combined design may still be worth exploring later when page fault
is considered to be reintroduced in phase 3.
Quoted 7 lines
Quoted 3 lines
With the ring partitioned by class both are live at
once: the probe supplies hotness, the primitive supplies age, and two schemes over the same regions can move memory in both directions under one kdamond.Unless the needs are clearly confirmed, I'd prefer having single class for simplicity.
With the page fault primitive out of scope there is no case for two ring classes. I will prepare v3 with a single class.
Quoted 93 lines
Quoted 25 lines
The two classes are not symmetric, and the asymmetry is in where a report comes from rather than in what it carries. A page fault has no monitoring context at report time, so a per-context ring is not expressible for it: those reports go to one global set of per-CPU rings, drained by the single context whose page-fault primitive is enabled. A PMU overflow handler does carry the context that armed the event, so those reports go to that context's own per-CPU rings and each context drains only what it owns. Both classes share the ring layout and the producer side, so the barrier pairing cannot drift between them.
``` page-fault report PMU sample, NMI context no ctx at report time handler carries the owning ctx | | v v +----------------------------+ +----------------------------+ | one global per-CPU ring | | per-CPU rings per context | | set, shared by every ctx | | that armed a perf event | +----------------------------+ +----------------------------+ | drained by the one ctx | | each ctx drains only the | | with the pf primitive on | | rings it owns | +----------------------------+ +----------------------------+ | | +------------------+------------------+ |Quoted 8 lines
drain on the aggregation boundary, each report matched to a region | +---------------+---------------+ | | region age region probe hits demotion scheme promotion scheme ```Again, I'd suggest to simply ignore page-fault report for now.
Quoted 37 lines
That is the property this series exists to enable, and three things follow from it:
- One measurement drives both directions, at whichever scope the mode gives. Bandwidth comes from resctrl MBM, which is where the discussion on a platform-independent bandwidth signal for tiering has pointed [6]: a virtual-address context reads a monitoring group created for the processes it monitors, and a physical-address context reads the root monitoring group, so the same controller is scoped to a set of processes or to the whole machine without changing what it does. Reading it there also means a bandwidth allocation reserved for an application bounds what the controller optimises within. It spreads the hot set across nodes in the ratio that maximises achieved bandwidth when bandwidth is what the workload is short of, and it demotes cold pages off the near node in the same context at the same time -- either proactively under a fixed quota or only once DRAM is under memory pressure. Neither direction is configured as a target ratio; the proportion or weight the controller writes bounds how much memory moves while the score decides which pages move, and a hardware sample makes that score proportional to the traffic a region generates rather than one bit per page per scan. Earlier work on bandwidth-driven interleaving [4] measured the same effect from userspace.
- Bandwidth and capacity are expanded at once rather than one being chosen. Static weighted interleaving expands bandwidth, but it is a placement rule rather than a decision about any particular page, so cold pages keep their share of DRAM. NUMA balancing expands usable DRAM by promoting whatever is hot, but it does so without reference to what the far node can deliver, so it keeps promoting after the near node has stopped being the better place to read from. Here the hot set's distribution comes from a bandwidth measurement and the cold set is demoted on region age, in one context, so the far node's bandwidth is used and DRAM is not held by memory nothing is touching. The decision itself is a quota goal the kernel already carries:
node_eligible_mem_bp[8] bounds how much of a node a scheme may fill, so what the controller supplies is the bandwidth measurement, not the placement.Quoted 7 lines
- When bandwidth is not the constraint it reverts to latency-first tiering on its own. The search starts with everything on the near node and moves memory outward only when the measurement improves, so on a workload that does not saturate that node no step outward improves anything: the hot set stays in DRAM for the lower access latency and cold pages keep being demoted. The same configuration covers both cases, with nothing to switch on and no ratio to revise.
Sounds very interesting.
Quoted 9 lines
## What the series adds
1.
mm/damon/vaddr: support page fault access check primitive-- implements the page-fault primitive for a virtual address space context; it was available for physical addresses only. The marker whose fault reports the access is installed through the target mm and the vma of the sampling address directly, andprepare_access_checks()dispatches on the enabled primitive as the physical address space one does.
Quoted 2 lines
As I mentioned above, I'd suggest to keep page fault primitive out of the scope for now.
Will do.
Quoted 10 lines
Quoted 6 lines
2.
mm/damon/core: read the CPU number with preemption disabled-- the report path runs in fault context, which is preemptible. Read the CPU number with preemption disabled and keep it disabled across the report, so the recorded CPU stays consistent with the per-CPU ring the report is queued to.Sounds good and will be needed for milestone 2, too. I will reuse or reference this in my milestone 2 implemenation. This will be very helpful, thank you for sharing.
Glad this is helpful.
Quoted 9 lines
Quoted 6 lines
3.
mm/damon/paddr: lock the folio for the page fault primitive rmap walk-- take the folio lock unconditionally around the walk, as the other reverse mapping walks in DAMON do, so every mapping type reaches it locked, and release the reference the lookup took on each exit path.Again, let's keep page fault-based monitoring out of the scope for now.
Understood. Will do.
Quoted 9 lines
Quoted 5 lines
4.
mm/damon: add damos_node_eligible_mem_bp tracepoint-- a per-tick tracepoint over the node-eligible-memory quota goal evaluation, exposing the goal's target and current values, so the loop a bandwidth-driven controller steers is visible to a tracer.This seems doesn't need to wait anything. If this turned out to be helpful, please feel free to separately send patches for this.
Yes. It is quite useful to track goal convergence. Will send a single patch targeting mm-new.
Quoted 11 lines
Quoted 9 lines
5.
mm/damon/core: add per-probe-class report rings and unified drain-- the substrate. Per-CPU SPSC rings an NMI-context source can publish into, partitioned by probe index into a global page-fault ring and a per-context perf ring, plus the kdamond-side drain that matches each report to a region by binary search over a per-target snapshot and credits it to that region's probe hits on the aggregation boundary. The address space of the target selects which address of a report is matched, and pid targets are filtered by thread group id.Sounds like good optimizations that we can discuss with tests in the phase 3.
Ring will remain single class for milestone 2 as page fault stays out of scope.
Quoted 11 lines
Quoted 10 lines
6.
mm/damon: add perf-event overflow handler feeding the report ring-- an ops-agnostic perf-event source whose overflow handler turns a PMU sample into a report, setting each address field the PMU reported as valid. A PMU that reports a virtual address only can therefore drive a virtual-address context, one that reports a physical address a physical-address context, and the same source serves either without a backend per address space. Per-CPU events are armed and released through cpuhp callbacks, and a per-PMU owner keeps two contexts from claiming the same PMU type.
Quoted 7 lines
This should be what also essential for milestone 2. I will reuse or reference this in my milestone 2 implemenation. This will be very helpful, thank you for sharing.
Nonetheless, virtual-address context considered parts may be skipped in milestone 2. We can discuss such things in the phase 3.
Got it.
Quoted 6 lines
Quoted 5 lines
7.
mm/damon/ops-common: use probe-weighted score when probe weights are set-- lets a scheme's frequency subscore come from the probe hits, weighted per probe class, so what the sampler reported reaches the tiering decision. With no weights set the subscore comes from the access rate as before.
Quoted 6 lines
I'm working on adding more attributes-only mode (probe weights are set) support to DAMOS. Hopefully we can converge on this. This is not a part of our planned milestone 2, but I will keep doing this in parallel, so hopefully full DAMOS support for attributes-only mode will be done around end of milestone 2. Let's keep discussion on the works.
Sure. will track changes around this on damon/next and lore postings.
Quoted 8 lines
Quoted 7 lines
8.
mm/damon: add perf_event prep for PMU-driven hotness probes-- the sysfs surface and the event lifecycle: aperf_eventprep action carrying the PMU type, the event config and the sample attributes per probe, with per-CPU or single-instance arming depending on how many counters the PMU needs. Arming is deferred on a context built for a commit, so a weight-only commit leaves the running event untouched.
Quoted 4 lines
This is also what would be essential for milestone 2. I will reuse or reference this in my milestone 2 implemenation. This will be very helpful, thank you for sharing.
Good to know. :-)
Quoted 9 lines
Quoted 6 lines
9.
mm/damon/tests/drain-kunit: kunit for report rings and unified drain-- unit tests for the rings and the drain: inject and drain, overflow on wrap, producer routing by probe index, ring partitioning, pf-ring ownership, thread-group filtering, the address space match, and both primitives live on one context.Testing is always important and nice. Thank you for doing this.
Thank you. The kunit coverage will be updated for the simplified v3 scope.
Quoted 196 lines
Quoted 166 lines
Patches 1-4 apply standalone on damon/next and are useful without the rest; 5-8 are the substrate and its first two consumers, and 9 covers them. If the first four read right, they need not wait on the rest of this series -- happy to repost them on their own for damon/next if that is easier to take. Two of them, 2 and 3, correct code that is already in mm-new rather than adding anything new, so they could instead go as a small fixes series carrying
Fixes: tags; guidance on which of the two routes is preferred is welcome.## Userspace setup model
The runs were driven by an
auto_tiersubcommand added to damo on the branch below, which reads achieved bandwidth from resctrl MBM and searches for the hot set's distribution that maximises it, while the demotion scheme runs alongside it in the same context. That tooling is not part of this posting; it is onhttps://github.com/ravis-opensrc/damo/tree/damo/auto-tier-bw-2026-09-08
-
--bw_source resctrlis the option this adds: the controller reads achieved bandwidth from an MBM monitoring group and searches, rather than being given a distribution to install.
- Everything else -- intervals, schemes, filters, the probe'sperf_eventattributes -- is what the generator writes from the command line shown: 5 ms sampling, 100 ms aggregation, a 1 s ops update.
- Both configurations select proactive demotion, so they demote continuously rather than waiting for pressure, and both were left closed loop throughout.
- The search is the algorithm described in [4].### Configuration A: AMD IBS Op, paddr ops, system-wide
``` $ sudo mount -t resctrl resctrl /sys/fs/resctrl $ sudo python3 tools/damon_tier_gen.py --hotness ibs \ --near_node 0 --far_node 4 \ --cold_demote --cold_demote_mode proactive \ -o tier.yaml $ sudo damo auto_tier tier.yaml --bw_source resctrl --verbose ```- Scope is the machine, not a process set. The distribution is steered through
node_eligible_mem_bpquota goals over each node's own physical ranges, which the generator reads from/proc/iomem.
- AMD Turin, DRAM on node 0 and a CXL node. IBS Op at asample_periodof 262144 withsample_phys_addrset, which is what a physical-address context needs from the sample.
- Workload is a hot-and-cold mix so both schemes have something to act on:
multiloaddrives the bandwidth-hungry hot set, and a second process allocates on the near node, touches it once and goes idle, leaving pages that age out for the demotion scheme to find.### Configuration B: Intel PEBS L3-miss, vaddr ops, per-PID
``` $ sudo mount -t resctrl resctrl /sys/fs/resctrl $ sudo python3 tools/damon_tier_gen.py --hotness pebs \ --pid $HOT_PID --pid $COLD_PID \ --near_node 0 --far_node 1 \ --cold_demote --cold_demote_mode proactive \ --min_nr_regions 1000 --max_nr_regions 20000 \ -o tier.yaml $ sudo damo auto_tier tier.yaml --bw_source resctrl --verbose ```- Scope is the processes the monitoring group names and nothing else. The distribution is steered through the hot scheme's
DamosDestweights [9].
- Intel Granite Rapids, DRAM on node 0 and CXL on node 1. PEBS L3-miss at`sample_freq` 5003 with `precise_ip` 2.- Same workload shape.
## What the runs show
Per-node reference, each figure measured by binding the same workload to one node:
``` Granite Rapids, 64 threads x 8 GiB node 0 1.0 TB DRAM DRAM only 269,885 MB/s node 1 2.0 TB CXL CXL only 249,903 MB/s Turin, 32 threads x 4 GiB node 0 386 GB DRAM DRAM only 113,596 MB/s node 4 1.0 TB CXL CXL only 33,253 MB/s ```- The two machines differ in the way that matters: on the Turin the far node is 3.4x slower than the near one, on the Granite Rapids the two are within 8%.
- Each configuration gets one graph, the climb: the run itself, one mark per decision taken from the bandwidth just measured, with the share it moved to under each mark. The settled line below it is the median over the samples the run went on to hold at that share.### Configuration B, virtual-address mode
``` MB/s climb 460k | * 440k | * 420k | * 400k | 380k | * 360k | * 340k | * * 320k | * 300k | * * 280k | * 260k | * * --------+------------+--------------+---------------+------------+-- 1 4 7 10 13 92 80 68 56 44 decision index, near-node share (%) settled share 44, 430,547 MB/s held ```- Thirteen decisions to settle at 44%, no target given: about 60% more than the better of the two nodes on its own, and the share does not change again over the 81 samples that follow.
- Cold demotion runs over the same interval, in the same context, actuated by the same kdamond: 34 regions totalling 15.4 GiB applied, and the idle process ends with 4,099,980 pages -- 15.6 GiB -- on the CXL node having started on DRAM.
- The promotion scheme's applied byte count is flat across the demotion ramp, 291.1 GiB before against 291.3 GiB after, so the recovered capacity is the demotion scheme's.### Configuration A, physical-address mode
``` MB/s climb 136k | 134k | * * 132k | * * 130k | * 128k | 126k | * 124k | 122k | * 120k | 118k | * +----+-----+-----+-----+-----+-----+-----+-----+-- 1 2 3 4 5 6 7 8 92 88 84 80 76 78 80 78 decision index, near-node share (%) settled share 78, 134,107 MB/s held ```- Eight decisions to settle at 78, from a start with almost everything on the near node: the same algorithm reaches the optimum share on its own from the bandwidth reading alone, and cold pages are demoted to the far node while it does so.
- A distribution beats either end because both nodes supply at once: at the settled share the near node is supplying 104,618 MB/s against the 113,596 it reaches with the whole working set bound to it, so the further 37,669 MB/s arriving over CXL is bandwidth it could not have supplied at any share.
- The idle process ends with 2,863,156 pages -- 10.9 GiB -- on the CXL node, out of the 32 GiB it allocated on DRAM and stopped touching. Residency is the attributable figure in this mode rather than a per-scheme applied count, because the schemes share physical ranges.### What the runs are meant to establish
Not the settled share and not the absolute bandwidth; those are properties of these machines and these workloads. What reproduces is:
Quoted 8 lines
- A controller reading achieved bandwidth converges unattended, from a configuration naming no target, to a distribution that beats either node on its own, and holds it once found.
- A demotion scheme recovers near-node capacity in the same context while that happens.
- One code path does both, steeringnode_eligible_mem_bpover physical ranges system-wide on one machine andDamosDestweights over a named process group on the other.Nice testing and measurement, thank you for sharing! I'm happy the future DAMON could help this!
Quoted 17 lines
## Beyond a CPU PMU
Nothing above is specific to PEBS or IBS. A source qualifies if it can report an accessed address to the ring, which is the only thing the substrate asks of it. A CXL device's Hotness Monitoring Unit, whose r3.2 perf driver has been posted [7] exposing its hotlist through an AUX buffer, or a custom monitoring unit on an accelerator or a memory expander, reports exactly that, and a backend delivering those reports through a perf event reaches the same drain, the same probe hits and the same schemes already in the tree. Such a unit sees the traffic that reaches its own device rather than a system-wide view, so what it contributes is the hot set resident on that device: the tiering logic can promote those pages to DRAM without the host having to detect that hotness itself, and without depending on what produced the reports. That posting names driving tiering from such a unit as the intent and the in-kernel step as future work, and consuming it as a DAMON access-check primitive was raised there as one way to get it; a backend on this substrate is that path.
Quoted 1 line
That makes sense to me. Thank you for sharing this nice survey and theories!
Thank you. Looking forward to seeing DAMON grow to support these use cases.
Summary of what v3 will contain (5 patches, former patches 5-9 of v2):
- paddr-only, single ring class, no page fault primitive
- per-CPU rings retained (required by NMI calling context, not an optimization)
- perf-event overflow handler (paddr path, vaddr parts deferred to phase 3)
- sysfs/lifecycle surface
- probe-weighted score
- kunit tests updated for the simplified scope
Separate send targeting mm-new:
- tracepoint (former patch 4)
Thanks,
Ravi
[1] RFC PATCH v3 00/37: mm/damon: introduce
per-CPUs/threads/write/read monitoring
https://lore.kernel.org/damon/[email protected]/
Quoted 72 lines
Quoted 24 lines
[1] v1 of this series
https://lore.kernel.org/damon/[email protected]/ [2] Roadmap for extending DAMON beyond pte-accessed bit https://lore.kernel.org/damon/[email protected]/ [3] mm/damon: introduce perf event based access check https://lore.kernel.org/damon/[email protected]/[4] B. Tabatabai, R. Jonnalagadda et al., "Bandwidth Speaks, We Listen:
Dynamic Interleaving for Tiered Memory", ISMM 2026.
https://dl.acm.org/doi/10.1145/3814942.3816137
[5]mm/damon/perf: add ARM SPE AUX backendhttps://lore.kernel.org/damon/[email protected]/[6] A platform-independent subsystem for bandwidth information, and resctrl as that source
https://lore.kernel.org/linux-mm/[email protected]/
[7] CXL Hotness Monitoring Unit perf driverhttps://lore.kernel.org/linux-mm/[email protected]/ [8] mm/damon: add node_eligible_mem_bp goal metric, merged for v7.2 https://lore.kernel.org/linux-mm/[email protected]/ [9] mm/damon/vaddr: allow interleaving in migrate_{hot,cold} actions, merged for v6.17 https://lore.kernel.org/linux-mm/[email protected]/Quoted 11 lines
Ravi Jonnalagadda (9):
mm/damon/vaddr: support page fault access check primitivemm/damon/core: read the CPU number with preemption disabledmm/damon/paddr: lock the folio for the page fault primitive rmap walk mm/damon: adddamos_node_eligible_mem_bptracepointmm/damon/core: add per-probe-class report rings and unified drain mm/damon: add perf-event overflow handler feeding the report ringmm/damon/ops-common: use probe-weighted score when probe weights are
set
mm/damon: addperf_eventprep for PMU-driven hotness probesmm/damon/tests/drain-kunit: kunit for report rings and unified drainAs this series is for high level discussion, and the code will significantly changed after rebasing on milestone 2 output, I wouldn't go deep into each line of the code for now. Feel free to let me know if you want.
Quoted 23 lines
include/linux/damon.h | 136 ++++- include/trace/events/damon.h | 32 + mm/damon/Kconfig | 18 + mm/damon/Makefile | 1 + mm/damon/core.c | 995 +++++++++++++++++++++++++++---- mm/damon/ops-common.c | 21 +- mm/damon/paddr.c | 37 +- mm/damon/perf_source.c | 489 +++++++++++++++ mm/damon/perf_source.h | 54 ++ mm/damon/sysfs.c | 266 ++++++++- mm/damon/tests/.kunitconfig | 4 + mm/damon/tests/core-kunit.h | 2 +- mm/damon/tests/drain-kunit.h | 1091 ++++++++++++++++++++++++++++++++++ mm/damon/tests/perf-kunit.h | 133 +++++ mm/damon/vaddr.c | 83 ++- 15 files changed, 3220 insertions(+), 142 deletions(-) create mode 100644 mm/damon/perf_source.c create mode 100644 mm/damon/perf_source.h create mode 100644 mm/damon/tests/drain-kunit.h create mode 100644 mm/damon/tests/perf-kunit.hQuoted 5 lines
base-commit:
e1f34dce183a96fc93bf9a42dcdc0ec0bf82a3d2--
2.43.0Thanks,
SJ
SJ Park
maintainerQuoted 9 lines · On Tue, 15 Sep 2026 14:23:02 -0700 Ravi Jonnalagadda <[email protected]>…
Quoted 8 lines · SJ Park
Hello Ravi,
Quoted 3 lines · On Thu, 10 Sep 2026 10:16:14 -0700 Ravi Jonnalagadda <[email protected]>…
This series lets DAMON take its access information from a hardware sampler instead of from a page-table scan, and lets a scheme's score be weighted by what that sampler reported.
[...]
Quoted 4 lines
Quoted 2 lines
First of all, thank you very much for sharing this great series.
Thanks SJ for the detailed review and the milestone 2 / phase 3 clarifications.
Glad to hear that!
[...]
Quoted 11 lines
Quoted 10 lines
Quoted 4 lines
What running it
across vendors needed on top of that direction is:- per-CPU lockless rings between the NMI sample handler and the kdamond drain,
I understand we need to make it lockless. I wonder if we have to make it per-CPU. I understand it will be better in terms of performance, especially on machines having many CPUs. That said, this feels like somewhat we can discuss in phase 3. And it would deserve to have sufficient discussions and performance evaluations.
Quoted 4 lines
The per-CPU structure is not a performance optimization we can defer -- it is required by the calling context. A perf-event overflow handler runs in NMI context, which cannot take a mutex or any sleeping lock.
damon_report_access()takes a mutex, so it cannot be called from there.
Thank you for clarifying, Ravi. But, what I wanted to say is, we could update damon_report_access() to not use mutex but atomic operations. Does that make sense? FYI, damon_report_access() will also be renamed, say, damon_report_attr().
[...]
Quoted 7 lines
Understood. I included the page-fault source in v2 because the December 2025 RFC [1] that introduced
damon_report_access()had it as the primary consumer, and I wanted to carry forward that ability. Since it is out of scope for milestone 2, I will drop it from v3.Patches 1, 2, and 3 will be dropped from the v3 submission since they are all tied to the page fault path.
Makes sense, thank you!
Quoted 9 lines
Quoted 9 lines
Quoted 8 lines
- per-CPU events that follow CPU hotplug, armed when the kdamond starts and disarmed and drained when it stops,
- a per-PMU owner, so two contexts cannot claim the same PMU type,
- whichever address a PMU does report carried on the report and matched against the context's own address space, so one source serves a paddr or a vaddr context without a backend per address space.
Quoted 7 lines
Quoted 4 lines
These all soudns making sense to me. Nonetheless, I think we can scope milestone 2 to support only physical address and defer these things to the phase 3.
Got it. Will scope v3 to PA only. so included results for v3 would be based on AMD IBS testing.
Sounds good!
Quoted 40 lines
Quoted 21 lines
Quoted 8 lines
This is tested with PEBS on Intel and IBS on AMD, both configured as
perf_eventattributes on a probe and using the perf core's event plumbing rather than per-vendor MSR code. A third source has already been written against the same
ring: Kunwu Chan's ARM SPE backend [5], which reaches it through an AUX buffer drained in process context instead of an overflow callback, and which the roadmap [2] places in its third milestone.Awesome, appreciate your huge effort on this!
Quoted 5 lines
The partitioning is what lets promotion and demotion run in one context. A sampler says which regions are hot; it says nothing about which are cold, because a sampler that reports nothing about a page cannot distinguish untouched from unsampled.
I'm not really sure. I think absence of samples for an address range can also mean the address range is cold? Actually the page table accessed bit based monitoring also use a sort of sampling, so I don't show real distinction.
Quoted 8 lines
Maybe you're right, but I think this deserves sufficient discussions and testing that we could defer to the phase 3.
Quoted 2 lines
Region age is what a demotion scheme matches on, and age comes from the page-fault primitive.
We would have age in perf event based mode, too. Isn't it?
You are right. Region age accumulates whenever
nr_accessesstays at zero across aggregation boundaries, and that holds whether the zero comes from a PTE scan or from an empty perf-event drain.I was initially concerned that a sparse sampling PMU might not cover every cold region in every aggregation window, leaving silence that could be mistaken for cold,
whereas page faults provide higher spatial coverage for confirming first-access. Having both together was intended as a defence against that gap.
Quoted 7 lines
Based on your observation I retested this on hardware. On AMD Turin the cold demotion scheme found and demoted the idle working set correctly using only the hardware-sampled IBS signal --
nr_accessesaged to zero for regions that genuinely had no traffic, and the scheme acted on age as expected. The combined design may still be worth exploring later when page fault
is considered to be reintroduced in phase 3.
Sounds good, thank you for the testing Ravi!
Quoted 11 lines
Quoted 8 lines
Quoted 4 lines
With the ring partitioned by class both are live at
once: the probe supplies hotness, the primitive supplies age, and two schemes over the same regions can move memory in both directions under one kdamond.Unless the needs are clearly confirmed, I'd prefer having single class for simplicity.
With the page fault primitive out of scope there is no case for two ring classes. I will prepare v3 with a single class.
Sounds good!
[...]
Quoted 11 lines
Quoted 8 lines
Quoted 4 lines
4.
mm/damon: add damos_node_eligible_mem_bp tracepoint-- a per-tick tracepoint over the node-eligible-memory quota goal evaluation, exposing the goal's target and current values, so the loop a bandwidth-driven controller steers is visible to a tracer.This seems doesn't need to wait anything. If this turned out to be helpful, please feel free to separately send patches for this.
Yes. It is quite useful to track goal convergence. Will send a single patch targeting mm-new.
Looking forward to the patch!
[...]
Quoted 8 lines
Summary of what v3 will contain (5 patches, former patches 5-9 of v2):
- paddr-only, single ring class, no page fault primitive
- per-CPU rings retained (required by NMI calling context, not an optimization)
- perf-event overflow handler (paddr path, vaddr parts deferred to phase 3)
- sysfs/lifecycle surface
- probe-weighted score
- kunit tests updated for the simplified scope
Makes sense. And regardless of my global ring idea, feel free to keep the per-CPU rings. As long as it is an RFC, please feel free to implement it in an easy-to-implement way.
Please also note that I'm still working on shaping the milestone 2 deliverables. I'm not yet in a stage that I can share how it will really look like. So the final version of your work might need a significant amount of change to rebase on it. I will also try to make it not unnecessarily delayed more than our planned timeline. But please bear in mind with me.
Quoted 3 lines
Separate send targeting mm-new:
- tracepoint (former patch 4)
Looking forward to it.
[...]
Quoted 3 lines
[1] RFC PATCH v3 00/37: mm/damon: introduce
per-CPUs/threads/write/read monitoringhttps://lore.kernel.org/damon/[email protected]/
Thanks,
SJ
[...]
From: "Lian Wang (ProcessMission)" <[email protected]>
Hi Ravi and SJ,
Thank you for sharing this work. The report path looks potentially useful for a DAMON observation-granularity problem that we are investigating.
The scenario originated from an issue reported by SXF, and we are planning to share and discuss it at LPC. We have now reproduced the underlying observation difference on both a local PC and an x86 server. The server run below is our larger, controlled reproduction.
Our test monitors a QEMU process whose guest memory is backed by a shared tmpfs file. The guest repeatedly writes one 4 KiB page in every 2 MiB range of a resident 48 GiB data set. The unique 4 KiB spatial coverage of one sweep is about 96 MiB, or 0.195% of that data set.
Using vaddr DAMON with a 500 ms sampling interval, 20 second aggregation interval, and 500--2000 regions, two opposite-order runs reported a mean hot ratio of 1.751% without shmem PMD mappings and 75.836% with them. A control that writes the full 2 MiB ranges reported 75.366% with PMD mappings. Hence, in this workload the current observation does not distinguish spatially sparse 4 KiB activity from dense activity once the large mappings are present.
We are testing existing DAMON strategy mitigations first. As a next step, we plan to replay recorded access addresses through the report/probe decision path, independently of a live PMU, and check whether DAMOS can distinguish the sparse and dense cases. In that experiment, raw report frequency and unique 4 KiB spatial coverage will remain separate quantities; lack of reports will remain unknown when source coverage or loss is insufficient.
If the report evidence makes the decision reliable, the response we want to evaluate is an internal, bounded PMD mapping demotion (splitting the mapping while retaining the underlying THP folio), rather than exposing physical folio split as a DAMOS action. We will first keep the response stat-only and archive the would-act ranges. After the replay result is repeatable, we can replace the replay producer with an appropriate hardware source and share the results.
This seems like a useful consumer experiment for the report interface, even if the vaddr and optimized transport parts belong to the later phase. Please let us know if this direction conflicts with the intended report semantics.
We will share the strategy, replay, and mapping-decision results once they are repeatable. We are also reviewing and testing this series, and will follow up with the appropriate tag(s) for the exact revision if that work justifies them.
Thanks,
Lian
Quoted 3 lines · On Thu, 10 Sep 2026 10:16:14 -0700 Ravi Jonnalagadda <[email protected]>…
This series lets DAMON take its access information from a hardware sampler instead of from a page-table scan, and lets a scheme's score be weighted by what that sampler reported.
Sent using hkml (https://github.com/sjp38/hackermail)
SJ Park
maintainerHi Lian,
Quoted 17 lines · On Tue, 15 Sep 2026 10:54:51 +0800 Lian Wang <[email protected]> wrote:
From: "Lian Wang (ProcessMission)" <[email protected]>
Hi Ravi and SJ,
Thank you for sharing this work. The report path looks potentially useful for a DAMON observation-granularity problem that we are investigating.
The scenario originated from an issue reported by SXF, and we are planning to share and discuss it at LPC. We have now reproduced the underlying observation difference on both a local PC and an x86 server. The server run below is our larger, controlled reproduction.
Our test monitors a QEMU process whose guest memory is backed by a shared tmpfs file. The guest repeatedly writes one 4 KiB page in every 2 MiB range of a resident 48 GiB data set. The unique 4 KiB spatial coverage of one sweep is about 96 MiB, or 0.195% of that data set.
Quoted 6 lines
Using vaddr DAMON with a 500 ms sampling interval, 20 second aggregation interval, and 500--2000 regions, two opposite-order runs reported a mean hot ratio of 1.751% without shmem PMD mappings and 75.836% with them. A control that writes the full 2 MiB ranges reported 75.366% with PMD mappings. Hence, in this workload the current observation does not distinguish spatially sparse 4 KiB activity from dense activity once the large mappings are present.
The observation makes sense. However, does the sparse access pattern realistic? If so, what is the purpose or expected benefit of using huge page for workloads having such access pattern?
Quoted 8 lines
We are testing existing DAMON strategy mitigations first. As a next step, we plan to replay recorded access addresses through the report/probe decision path, independently of a live PMU, and check whether DAMOS can distinguish the sparse and dense cases. In that experiment, raw report frequency and unique 4 KiB spatial coverage will remain separate quantities; lack of reports will remain unknown when source coverage or loss is insufficient.
Quoted 6 lines
If the report evidence makes the decision reliable, the response we want to evaluate is an internal, bounded PMD mapping demotion (splitting the mapping while retaining the underlying THP folio), rather than exposing physical folio split as a DAMOS action. We will first keep the response stat-only and archive the would-act ranges. After the replay result is repeatable, we can replace the replay producer with an appropriate hardware source and share the results.
I'm not very sure if this is the right direction. PMD mapping demotion sounds like you just don't want to use huge pages. If so, you could disable huge pages. Answers to my above questions would be helpful for me.
Quoted 4 lines
This seems like a useful consumer experiment for the report interface, even if the vaddr and optimized transport parts belong to the later phase. Please let us know if this direction conflicts with the intended report semantics.
I believe this doesn't conflict with the project, though. Nevertheless, technically speaking, it is not the report semantics. Reporting allows any information to be reported. Page faults like information could be coarse grained, same to the current page table accessed-bit based one. Only finer grained access primitive reports, like those from perf events, would increase the accuracy for the sparse access pattern monitoring.
Whether sparse access pattern is realistic and using huge pages on systems having such access pattern makes sense are different questions, though.
Quoted 4 lines
We will share the strategy, replay, and mapping-decision results once they are repeatable. We are also reviewing and testing this series, and will follow up with the appropriate tag(s) for the exact revision if that work justifies them.
Looking forward to! Nonetheless, it would better to make sure we are on the same page for the above questions before digging deeper, in my humble opinion.
Thanks,
SJ
[...]
Hi SJ,
Thank you for the questions. I mixed the original field scenario and our controlled reproducer in my previous mail. I also jumped from the observation problem to PMD mapping demotion before explaining why the deployment needs both huge pages and finer-grained observation. That made it sound as if our goal was simply to stop using huge pages.
Quoted 3 lines
The observation makes sense. However, does the sparse access pattern realistic? If so, what is the purpose or expected benefit of using huge page for workloads having such access pattern?
The original case is not a lab-created request to use huge pages for a sparse microbenchmark. It was reported by SXF from a KVM/QEMU deployment where the VM has a large memory allocation backed by a shared tmpfs file, host THP is enabled, and Oracle runs inside the guest. They use host-side DAMON to measure the VM's hot-memory proportion. With the same business memory use, they observed a much larger hot proportion when THP was enabled.
The guest tmpfs 4 KiB/2 MiB writer is their controlled diagnostic case for isolating that observation; it is not a claim that the Oracle workload is exactly a one-page-per-2-MiB loop. We then reproduced and quantified the same effect on a PC and on the x86 server.
For this deployment, keeping large pages is a real requirement at the host virtualization layer. QEMU owns a large resident guest-RAM mapping. Host THP allows that mapping to use PMDs and allows KVM to use large secondary mappings, reducing host page-table memory, TLB and nested-page-walk pressure, and KVM mapping/fault overhead. These benefits are independent of whether every 4 KiB page inside a particular 2 MiB range is hot at one observation time.
This host configuration should also not be confused with the guest database page policy. Oracle may separately use explicit HugePages for its SGA inside the guest; the guest policy and the host THP backing of QEMU RAM are different translation layers. The requirement reported to us is to retain the host large-page benefit while measuring the guest working-set proportion from the host. Disabling host THP merely to make DAMON's number smaller would change the deployed VM configuration and remove the benefit the user is trying to keep.
The monitoring requirement is different: DAMON is expected to estimate how much of the guest memory is actually hot. A host PMD is a translation unit for guest RAM, not a semantic hotness unit for Oracle. Guest allocation can place small accessed pages across many guest-physical 2 MiB ranges, and one access then makes each corresponding coarse mapping look accessed.
Our latest capture makes this mechanism concrete. The sparse diagnostic touched 24,576 guest 4 KiB pages spread over 24,575 guest-physical 2 MiB buckets, while the dense control touched 12,582,912 pages. They are 0.146% and 75% of the monitored 64 GiB backend, respectively, but host DAMON reported about 75% for both. This is why the user needs huge-page mapping for VM performance and, independently, finer-grained observation for a meaningful hot-memory ratio.
The performance requirement and monitoring use case come from the deployment, not from our lab model. We will also report the measured Oracle/VM benefit and the production access distribution when those data are ready. The diagnostic result establishes the observation mechanism, but it should not substitute for those workload-level measurements.
Quoted 3 lines
I'm not very sure if this is the right direction. PMD mapping demotion sounds like you just don't want to use huge pages. If so, you could disable huge pages.
I agree. My reasoning in the previous mail was too jumpy: I went from a coarse observation directly to a possible MM response, and that obscured the actual goal. PMD mapping demotion is not part of our current proposed solution. The current direction is to keep both the huge folio and PMD mapping unchanged and use a genuinely fine-grained access primitive to improve the observation and the stat-only decision. We should first find out whether that is sufficient before discussing any mapping change at all.
Quoted 5 lines
technically speaking, it is not the report semantics. Reporting allows any information to be reported. Page faults like information could be coarse grained, same to the current page table accessed-bit based one. Only finer grained access primitive reports, like those from perf events, would increase the accuracy for the sparse access pattern monitoring.
Agreed. The report interface is the transport, not the source of accuracy. Our recorded-address replay is intended only to test whether the DAMON consumer and decision can use complete fine-grained address evidence. The eventual accuracy has to come from a fine-grained primitive such as an appropriate perf event source, with its coverage and loss accounted for. We will use that terminology in the follow-up results.
With this context clarified, we will return to the immediate work: use the existing IBS/perf-event proposal as the concrete strategy under test, and see whether its fine-grained evidence can improve the observation and DAMOS decision for this case. We will use the results to test and review this series, report both improvements and remaining gaps, and not assume in advance that it is the final solution.
I hope this clarifies why keeping huge pages and requesting finer-grained observation are not contradictory requirements in this case.
If I am still misunderstanding any part of your questions, or if any part of this explanation remains unclear, please continue to correct me. We will keep sharing our findings and would like to make sure we are aligned before going further.
Thanks,
Lian
SJ Park
maintainerDAMON reporting more hot memory on huge pages (was: "Re: [RFC PATCH v2 0/9] mm/damon: hardware-sampled access reports")
Quoted 12 lines · On Wed, 16 Sep 2026 11:44:30 +0800 "Lian Wang (ProcessMission)"…
Hi SJ,
Thank you for the questions. I mixed the original field scenario and our controlled reproducer in my previous mail. I also jumped from the observation problem to PMD mapping demotion before explaining why the deployment needs both huge pages and finer-grained observation. That made it sound as if our goal was simply to stop using huge pages.
Quoted 3 lines
The observation makes sense. However, does the sparse access pattern realistic? If so, what is the purpose or expected benefit of using huge page for workloads having such access pattern?
Quoted 6 lines
The original case is not a lab-created request to use huge pages for a sparse microbenchmark. It was reported by SXF from a KVM/QEMU deployment where the VM has a large memory allocation backed by a shared tmpfs file, host THP is enabled, and Oracle runs inside the guest. They use host-side DAMON to measure the VM's hot-memory proportion. With the same business memory use, they observed a much larger hot proportion when THP was enabled.
Thank you for clarifying this. So I understand it is an issue from a real world workload. I'm still curious how sparse the real access pattern in the real world workload is, and if there is a low haning fruit. There might be rooms to improve in their DAMON usage.
Could you share more details about the real world setup and findings? More specifically, how they define the hot proportion and how they collect the number? What is DAMON setup? What are the real numbers they saw?
Also, I understand they want to understand real working set size of guests. Could you further share what they are planning to do with the monitored working set size? The required level of accuracy of the monitoring could depend on the purpose.
Quoted 6 lines
The guest tmpfs 4 KiB/2 MiB writer is their controlled diagnostic case for isolating that observation; it is not a claim that the Oracle workload is exactly a one-page-per-2-MiB loop. We then reproduced and quantified the same effect on a PC and on the x86 server.
Quoted 6 lines
For this deployment, keeping large pages is a real requirement at the host virtualization layer. QEMU owns a large resident guest-RAM mapping. Host THP allows that mapping to use PMDs and allows KVM to use large secondary mappings, reducing host page-table memory, TLB and nested-page-walk pressure, and KVM mapping/fault overhead. These benefits are independent of whether every 4 KiB page inside a particular 2 MiB range is hot at one observation time.
Thank you for clarifying this. Now I understand why they use THP, and I fully agree they should keep using THP.
[...]
Quoted 5 lines
The performance requirement and monitoring use case come from the deployment, not from our lab model. We will also report the measured Oracle/VM benefit and the production access distribution when those data are ready. The diagnostic result establishes the observation mechanism, but it should not substitute for those workload-level measurements.
Thanks to your detailed and nice explanation, I don't doubt about the needs to use huge pages. So I wouldn't insist on Oracle/VM benefit from huge pages.
I'm still curious the real access distribution, though. I understand you don't have the data yet. It would be very helpful if you could share that in future.
Quoted 5 lines
Quoted 4 lines
I'm not very sure if this is the right direction. PMD mapping demotion sounds like you just don't want to use huge pages. If so, you could disable huge pages.
Quoted 7 lines
I agree. My reasoning in the previous mail was too jumpy: I went from a coarse observation directly to a possible MM response, and that obscured the actual goal. PMD mapping demotion is not part of our current proposed solution. The current direction is to keep both the huge folio and PMD mapping unchanged and use a genuinely fine-grained access primitive to improve the observation and the stat-only decision. We should first find out whether that is sufficient before discussing any mapping change at all.
Thank you for clarifying. Yes, if you really need sub-pmd access, I believe the perf event based DAMON could be more useful. Nonetheless, as I requested above, knowing the DAMON usage and purpose in more detail might let us finding a low hanging fruit.
[...]
Quoted 6 lines
With this context clarified, we will return to the immediate work: use the existing IBS/perf-event proposal as the concrete strategy under test, and see whether its fine-grained evidence can improve the observation and DAMOS decision for this case. We will use the results to test and review this series, report both improvements and remaining gaps, and not assume in advance that it is the final solution.
That makes sense. Because Ravi thankfully already implemented this nice working version prototype, there is no reason to not test it right now.
Quoted 3 lines
I hope this clarifies why keeping huge pages and requesting finer-grained observation are not contradictory requirements in this case.
Yes, it is now clear to me. Thank you for patiently englightening me, Lian.
Quoted 5 lines
If I am still misunderstanding any part of your questions, or if any part of this explanation remains unclear, please continue to correct me. We will keep sharing our findings and would like to make sure we are aligned before going further.
I believe testing the perf event based monitoring with Ravi's implementation is a good path forward.
I'm curious about the real setup and results your clients got from their real world wokload. But that's not necessarily a blocker of the perf event based monitoring test.
Thanks,
SJ
[...]
SJ Park
maintainerRe: DAMON reporting more hot memory on huge pages (was: "Re: [RFC PATCH v2 0/9] mm/damon: hardware-sampled access reports")
Quoted 77 lines · On Tue, 15 Sep 2026 22:11:49 -0700 SJ Park <[email protected]> wrote:
Quoted 12 lines · On Wed, 16 Sep 2026 11:44:30 +0800 "Lian Wang (ProcessMission)"…
Hi SJ,
Thank you for the questions. I mixed the original field scenario and our controlled reproducer in my previous mail. I also jumped from the observation problem to PMD mapping demotion before explaining why the deployment needs both huge pages and finer-grained observation. That made it sound as if our goal was simply to stop using huge pages.
Quoted 3 lines
The observation makes sense. However, does the sparse access pattern realistic? If so, what is the purpose or expected benefit of using huge page for workloads having such access pattern?
Quoted 6 lines
The original case is not a lab-created request to use huge pages for a sparse microbenchmark. It was reported by SXF from a KVM/QEMU deployment where the VM has a large memory allocation backed by a shared tmpfs file, host THP is enabled, and Oracle runs inside the guest. They use host-side DAMON to measure the VM's hot-memory proportion. With the same business memory use, they observed a much larger hot proportion when THP was enabled.
Thank you for clarifying this. So I understand it is an issue from a real world workload. I'm still curious how sparse the real access pattern in the real world workload is, and if there is a low haning fruit. There might be rooms to improve in their DAMON usage.
Could you share more details about the real world setup and findings? More specifically, how they define the hot proportion and how they collect the number? What is DAMON setup? What are the real numbers they saw?
Also, I understand they want to understand real working set size of guests. Could you further share what they are planning to do with the monitored working set size? The required level of accuracy of the monitoring could depend on the purpose.
Quoted 6 lines
The guest tmpfs 4 KiB/2 MiB writer is their controlled diagnostic case for isolating that observation; it is not a claim that the Oracle workload is exactly a one-page-per-2-MiB loop. We then reproduced and quantified the same effect on a PC and on the x86 server.
Quoted 6 lines
For this deployment, keeping large pages is a real requirement at the host virtualization layer. QEMU owns a large resident guest-RAM mapping. Host THP allows that mapping to use PMDs and allows KVM to use large secondary mappings, reducing host page-table memory, TLB and nested-page-walk pressure, and KVM mapping/fault overhead. These benefits are independent of whether every 4 KiB page inside a particular 2 MiB range is hot at one observation time.
Thank you for clarifying this. Now I understand why they use THP, and I fully agree they should keep using THP.
[...]
Quoted 5 lines
The performance requirement and monitoring use case come from the deployment, not from our lab model. We will also report the measured Oracle/VM benefit and the production access distribution when those data are ready. The diagnostic result establishes the observation mechanism, but it should not substitute for those workload-level measurements.
Thanks to your detailed and nice explanation, I don't doubt about the needs to use huge pages. So I wouldn't insist on Oracle/VM benefit from huge pages.
I'm still curious the real access distribution, though. I understand you don't have the data yet. It would be very helpful if you could share that in future.
Quoted 5 lines
Quoted 4 lines
I'm not very sure if this is the right direction. PMD mapping demotion sounds like you just don't want to use huge pages. If so, you could disable huge pages.
Quoted 7 lines
I agree. My reasoning in the previous mail was too jumpy: I went from a coarse observation directly to a possible MM response, and that obscured the actual goal. PMD mapping demotion is not part of our current proposed solution. The current direction is to keep both the huge folio and PMD mapping unchanged and use a genuinely fine-grained access primitive to improve the observation and the stat-only decision. We should first find out whether that is sufficient before discussing any mapping change at all.
Quoted 4 lines
Thank you for clarifying. Yes, if you really need sub-pmd access, I believe the perf event based DAMON could be more useful. Nonetheless, as I requested above, knowing the DAMON usage and purpose in more detail might let us finding a low hanging fruit.
I still want to better understand the real world use case before digging into the specific solution. But, I was thinking perf event based monitoring might not feasible for the case. If that's the case, I think extending data attributes monitoring for splitting pmd mapping be fore the access check might be a path forward.
That is, we can request DAMON to do some preparation action for the data attribute sampling, using damon_prep. At the moment, set_pgidle prep action is supported. Maybe we could add a new prep action, say, split_pmd? Then, the following sampling memory access check probe (would be a kind of "allow pidle_unset" probe filter) will be able to show the access on exactly the pte accessed bit, not the pmd accessed bit.
Breaking pmd would cause the overhead, but it would be capped by the max_nr_regions. So the user could at least consider about the tradeoff. I think this is simpler and more intuitive than DAMOS-based pmd breaking, at least.
Quoted 32 lines
[...]
Quoted 6 lines
With this context clarified, we will return to the immediate work: use the existing IBS/perf-event proposal as the concrete strategy under test, and see whether its fine-grained evidence can improve the observation and DAMOS decision for this case. We will use the results to test and review this series, report both improvements and remaining gaps, and not assume in advance that it is the final solution.
That makes sense. Because Ravi thankfully already implemented this nice working version prototype, there is no reason to not test it right now.
Quoted 3 lines
I hope this clarifies why keeping huge pages and requesting finer-grained observation are not contradictory requirements in this case.
Yes, it is now clear to me. Thank you for patiently englightening me, Lian.
Quoted 5 lines
If I am still misunderstanding any part of your questions, or if any part of this explanation remains unclear, please continue to correct me. We will keep sharing our findings and would like to make sure we are aligned before going further.
I believe testing the perf event based monitoring with Ravi's implementation is a good path forward.
I'm curious about the real setup and results your clients got from their real world wokload. But that's not necessarily a blocker of the perf event based monitoring test.
Quoted 4 lines
Thanks,
SJ[...]
Thanks,
SJ
Re: DAMON reporting more hot memory on huge pages (was: "Re: [RFC PATCH v2 0/9] mm/damon: hardware-sampled access reports")
Hi SJ,
Thank you for thinking further about this and for suggesting the damon_prep direction. Let me first clarify the real-world use case and correct one expectation that I created in my previous mail.
SXF develops a hyperconverged infrastructure platform. The workloads running in the guests are customer workloads, and their types, memory sizes and access distributions are not controlled by us. Oracle is the workload in the case that exposed the problem, but we cannot define one Oracle access distribution as representative of the platform or provide one generally applicable workload-specific THP performance number.
The platform has two requirements:
1. Host THP is enabled by default. Large mappings generally provide useful translation and virtualization benefits, so disabling THP merely to make monitoring easier is not an acceptable general solution.
2. We also need an estimate of each VM's active memory as an input to later memory-tiering configuration. If a small number of distributed 4 KiB accesses makes many 2 MiB mappings appear fully active, the hot-tier requirement can be substantially overestimated. VM sizes vary, so the absolute error can become large on a large VM when accesses are spread over many huge mappings.
The reported setup is a KVM/QEMU VM whose large guest-RAM allocation is backed by a shared tmpfs file. Oracle runs in the guest, host THP is enabled, and host DAMON vaddr monitoring targets the QEMU process. The field observation is that, for the same business memory-use case, the measured hot proportion is much higher with THP. The guest-side tmpfs 4 KiB/2 MiB writer is a controlled diagnostic for isolating this mechanism, not a model of the complete Oracle access distribution.
The DAMON setup is:
operations=vaddr monitoring_attrs/nr_regions/min=500 monitoring_attrs/nr_regions/max=2000 monitoring_attrs/intervals/sample_us=500000 monitoring_attrs/intervals/aggr_us=20000000 monitoring_attrs/intervals/update_us=60000000 schemes/nr_schemes=1
schemes/0/action=stat schemes/0/access_pattern/age/min=0 schemes/0/access_pattern/age/max=18446744073709551615 schemes/0/access_pattern/nr_accesses/min=1 schemes/0/access_pattern/nr_accesses/max=18446744073709551615 schemes/0/access_pattern/sz/min=0 schemes/0/access_pattern/sz/max=18446744073709551615
The hot proportion is derived from the bytes selected by this stat-only scheme (at least one observed access in a 20-second aggregation window) relative to the monitored QEMU scope. It is used for later configuration, not as a request for DAMOS to split or migrate memory.
As shared in my previous mail, our controlled PC and server runs reproduced a large THP-off/on observation gap. The exact-address capture also showed a 512-times difference in unique 4 KiB spatial coverage while the two aggregate DAMON hot proportions remained nearly identical. The conclusion is that the current coarse observation can substantially inflate active-memory estimates, and configuration-only tuning cannot reconstruct the missing within-PMD spatial information.
Quoted 3 lines
I still want to better understand the real world use case before digging into the specific solution. But, I was thinking perf event based monitoring might not feasible for the case.
We agree that perf-event feasibility should be established rather than assumed. We will test the address space reported while a KVM vCPU is running, per-VM attribution, coverage and loss before relying on that path. If you had a specific feasibility concern in mind, please let us know so that we can include it in the test.
Quoted 11 lines
That is, we can request DAMON to do some preparation action for the data attribute sampling, using
damon_prep. At the moment,set_pgidleprep action is supported. Maybe we could add a new prep action, say,split_pmd? Then, the following sampling memory access check probe (would be a kind of "allowpidle_unset" probe filter) will be able to show the access on exactly the pte accessed bit, not the pmd accessed bit.Breaking pmd would cause the overhead, but it would be capped by the
max_nr_regions. So the user could at least consider about the tradeoff. I think this is simpler and more intuitive than DAMOS-based pmd breaking, at least.
This placement makes sense to us. A split_pmd preparation for access sampling is clearer than exposing PMD breaking as a DAMOS action. We understand it as refining the mapping used for observation, without splitting the underlying huge folio, and then letting the following probe observe PTE accessed bits.
We will follow this idea and validate it. In particular, we will measure the active-memory result and mapping cost, check whether max_nr_regions also bounds the cumulative number of mappings that remain PTE-mapped over time, and verify whether splitting the host process PMD is sufficient in the QEMU case or KVM's large secondary mapping remains the limiting layer. We will compare it with the perf-event path, stat-only first, without proposing a user-visible DAMOS split action or physical folio split.
Please rest assured that we see this as a long-term technical discussion and collaboration, not an attempt to rush one mechanism into the tree. We will continue sharing both positive and negative results. I also plan to present the scenario, evidence and tradeoffs in more detail at LPC. Please feel free to share any concerns or ideas at any time; we are happy to keep iterating on them together.
This topic has now become broader than Ravi's hardware-sampled report series. If you think it would keep the discussions clearer, I can start a separate thread focused on huge-page observation and the damon_prep approach, while keeping perf-event implementation and testing comments on Ravi's thread.
Please let me know whether this clarifies the intended use and whether our reading of the split_pmd damon_prep proposal matches what you had in mind.
Thanks,
Lian
SJ Park
maintainerRe: DAMON reporting more hot memory on huge pages (was: "Re: [RFC PATCH v2 0/9] mm/damon: hardware-sampled access reports")
Quoted 19 lines · On Thu, 17 Sep 2026 10:06:37 +0800 Lian Wang <[email protected]> wrote:
Hi SJ,
Thank you for thinking further about this and for suggesting the
damon_prepdirection. Let me first clarify the real-world use case and correct one expectation that I created in my previous mail.SXF develops a hyperconverged infrastructure platform. The workloads running in the guests are customer workloads, and their types, memory sizes and access distributions are not controlled by us. Oracle is the workload in the case that exposed the problem, but we cannot define one Oracle access distribution as representative of the platform or provide one generally applicable workload-specific THP performance number.
The platform has two requirements:
1. Host THP is enabled by default. Large mappings generally provide useful translation and virtualization benefits, so disabling THP merely to make monitoring easier is not an acceptable general solution.
Quoted 6 lines
2. We also need an estimate of each VM's active memory as an input to later memory-tiering configuration. If a small number of distributed 4 KiB accesses makes many 2 MiB mappings appear fully active, the hot-tier requirement can be substantially overestimated. VM sizes vary, so the absolute error can become large on a large VM when accesses are spread over many huge mappings.
Thank you for sharing these. Especially the second requirement is very interesting to me.
As you may already know, DAMON can provide not only the observability but also such tiering like operation. SK hynix and Micron are developing their tiering solution using DAMON. I also developed [1] TPP-based solution. I'm wondering if they considered directly using tiering solution instead of doing observation and operation separately.
Quoted 26 lines
The reported setup is a KVM/QEMU VM whose large guest-RAM allocation is backed by a shared tmpfs file. Oracle runs in the guest, host THP is enabled, and host DAMON vaddr monitoring targets the QEMU process. The field observation is that, for the same business memory-use case, the measured hot proportion is much higher with THP. The guest-side tmpfs 4 KiB/2 MiB writer is a controlled diagnostic for isolating this mechanism, not a model of the complete Oracle access distribution.
The DAMON setup is:
operations=vaddr monitoring_attrs/nr_regions/min=500 monitoring_attrs/nr_regions/max=2000 monitoring_attrs/intervals/sample_us=500000 monitoring_attrs/intervals/aggr_us=20000000 monitoring_attrs/intervals/update_us=60000000 schemes/nr_schemes=1
schemes/0/action=stat schemes/0/access_pattern/age/min=0 schemes/0/access_pattern/age/max=18446744073709551615 schemes/0/access_pattern/nr_accesses/min=1 schemes/0/access_pattern/nr_accesses/max=18446744073709551615 schemes/0/access_pattern/sz/min=0 schemes/0/access_pattern/sz/max=18446744073709551615
Quoted 4 lines
The hot proportion is derived from the bytes selected by this stat-only scheme (at least one observed access in a 20-second aggregation window) relative to the monitored QEMU scope. It is used for later configuration, not as a request for DAMOS to split or migrate memory.
Thank you very much for sharing these details. I now get much clearer picture.
I think the DAMON setup may have rooms to improve. When someone asks me recommeded setup for access pattern observability, I recommend to use intervals auto-tuning and read idle time percentiles, similar to DAMON_STAT. If you haven't tried that, I'd recommend to try it. I presented [2] this basic idea at Lst year LPC refereed track. Hopefully that could give you more ideas. If you tried it and found some problems, and that resulted you in this setup, knowing the detailed context would be helpful for me.
[...]
Quoted 9 lines
Quoted 3 lines
I still want to better understand the real world use case before digging into the specific solution. But, I was thinking perf event based monitoring might not feasible for the case.
We agree that perf-event feasibility should be established rather than assumed. We will test the address space reported while a KVM vCPU is running, per-VM attribution, coverage and loss before relying on that path. If you had a specific feasibility concern in mind, please let us know so that we can include it in the test.
I'm still in a very early stage of perf event based monitoring prototyping. Hence I have no expertise to find a real concern at the moment. I was just thinking it _might_.
Quoted 18 lines
Quoted 8 lines
That is, we can request DAMON to do some preparation action for the data attribute sampling, using
damon_prep. At the moment,set_pgidleprep action is supported. Maybe we could add a new prep action, say,split_pmd? Then, the following sampling memory access check probe (would be a kind of "allowpidle_unset" probe filter) will be able to show the access on exactly the pte accessed bit, not the pmd accessed bit.Quoted 4 lines
Breaking pmd would cause the overhead, but it would be capped by the
max_nr_regions. So the user could at least consider about the tradeoff. I think this is simpler and more intuitive than DAMOS-based pmd breaking, at least.This placement makes sense to us. A
split_pmdpreparation for access sampling is clearer than exposing PMD breaking as a DAMOS action. We understand it as refining the mapping used for observation, without splitting the underlying huge folio, and then letting the following probe observe PTE accessed bits.
Quoted 7 lines
We will follow this idea and validate it. In particular, we will measure the active-memory result and mapping cost, check whether
max_nr_regionsalso bounds the cumulative number of mappings that remain PTE-mapped over time, and verify whether splitting the host process PMD is sufficient in the QEMU case or KVM's large secondary mapping remains the limiting layer. We will compare it with the perf-event path, stat-only first, without proposing a user-visible DAMOS split action or physical folio split.
Sounds good, looking forward to the progress.
Quoted 7 lines
Please rest assured that we see this as a long-term technical discussion and collaboration, not an attempt to rush one mechanism into the tree. We will continue sharing both positive and negative results. I also plan to present the scenario, evidence and tradeoffs in more detail at LPC. Please feel free to share any concerns or ideas at any time; we are happy to keep iterating on them together.
No worry. Nothing needs to be rushed.
Quoted 5 lines
This topic has now become broader than Ravi's hardware-sampled report series. If you think it would keep the discussions clearer, I can start a separate thread focused on huge-page observation and the
damon_prepapproach, while keeping perf-event implementation and testing comments on Ravi's thread.
Indeed we might hijacking Ravi's thread and adding unexpected mails to recipients' inbox. Please feel free to drop recipients or start a new thread, whatever you prefer.
Quoted 3 lines
Please let me know whether this clarifies the intended use and whether our reading of the
split_pmddamon_prepproposal matches what you had in mind.
Yes, this is very helpful. I have raised followup questions above, though ;) No pressure. I understand not every detail can always be revealed, and sometimes it takes time. Please answer only if you want, and when you get the chance.
[1] https://lkml.kernel.org/r/[email protected]
[2] https://lpc.events/event/19/contributions/2075/
Thanks,
SJ
[...]