mm/damon: add node_eligible_mem_bp goal metric
Subsystems (3)
PATCH 1/1 mm/damon: add node_eligible_mem_bp goal metric
Background and Motivation
=========================
In heterogeneous memory systems, controlling memory distribution across NUMA nodes is essential for performance optimization. This patch enables system-wide page distribution with target-state goals such as "maintain 60% of scheme-eligible memory on DRAM" using PA-mode DAMON schemes.
Rather than using absolute thresholds, this metric tracks the ratio of memory that matches each scheme's access pattern filters on a target node, enabling the quota system to automatically adjust migration aggressiveness to maintain the desired distribution.
What This Metric Measures
=========================
node_eligible_mem_bp:
scheme_eligible_bytes_on_node / total_scheme_eligible_bytes * 10000
Two-Scheme Setup for Hot Page Distribution
==========================================
For maintaining 60% of hot memory on DRAM (node 0) and 40% on CXL (node 1):
PULL scheme: migrate_hot to node 0
goal: node_eligible_mem_bp, nid=0, target=6000
addr filter: node 1 address range (only migrate FROM CXL) "Move hot pages to DRAM if less than 60% of hot data is in DRAM"
PUSH scheme: migrate_hot to node 1
goal: node_eligible_mem_bp, nid=1, target=4000
addr filter: node 0 address range (only migrate FROM DRAM) "Move hot pages to CXL if less than 40% of hot data is in CXL"
Each scheme independently measures its own eligible memory and adjusts its quota to achieve its target ratio. The schemes work in concert through DAMON's unified monitoring context, with the quota autotuner balancing their relative aggressiveness.
Implementation Details
======================
The implementation adds a new quota goal metric type DAMOS_QUOTA_NODE_ELIGIBLE_MEM_BP to the existing DAMOS quota goal framework. When this metric is configured for a scheme:
1. During each quota adjustment cycle, damos_get_node_eligible_mem_bp() is called to calculate the current memory distribution.
2. The function iterates through all regions that match the scheme's access pattern (via __damos_valid_target()) and calculates:
- Total eligible bytes across all nodes
- Eligible bytes specifically on the target node (goal->nid)
3. For each eligible region, damos_calc_eligible_bytes() walks through the physical address range, using damon_get_folio() to look up each folio and determine its NUMA node via folio_nid().
4. Large folios are handled by calculating the exact overlap between the region boundaries and folio boundaries, ensuring accurate byte counts even when regions partially span folios.
5. The ratio (node_eligible / total_eligible * 10000) is returned as basis points, which the quota autotuner uses to adjust the scheme's effective quota size (esz).
The implementation requires CONFIG_DAMON_PADDR since damon_get_folio() is only available for physical address space monitoring.
Testing Results
===============
Functionally tested on a two-node heterogeneous memory system with DRAM (node 0) and CXL memory (node 1). A PUSH+PULL scheme configuration using migrate_hot actions was used to reach a target hot memory ratio between the two tiers.
With the TEMPORAL tuner, the system converges quickly to the target distribution. The tuner drives esz to maximum when under goal and to zero once the goal is met, forming a simple on/off feedback loop that stabilizes at the desired ratio.
With the CONSIST tuner, the scheme still converges but more slowly, as it migrates and then throttles itself based on quota feedback. The time to reach the goal varies depending on workload intensity.
Note: This metric works with both TEMPORAL and CONSIST goal tuners.
---
Changes since v9.1:
===================
https://lore.kernel.org/linux-mm/[email protected]/
- Break calc_eligible_bytes() loop if addr overflows to prevent infinite loop when addr + PAGE_SIZE wraps around (SJ's fixup based on Sashiko review)
Changes since v9:
=================
https://lore.kernel.org/linux-mm/[email protected]/
- Cast phys_addr_t to unsigned long before mult_frac() to avoid __udivdi3 linking error on 32-bit systems with CONFIG_PHYS_ADDR_T_64BIT (SJ's fixup suggestion based on Sashiko review)
- Check !(unsigned long)total_eligible to avoid divide-by-zero after casting (SJ review)
Changes since v8:
=================
https://lore.kernel.org/linux-mm/[email protected]/
- Removed unnecessary casts in mult_frac() call to fix potential divide-by-zero on 32-bit PAE systems (Sashiko/SJ review)
- Kept original parameter name 'ctx' in damos_set_effective_quota() to avoid unnecessary diff (SJ review)
Changes since v7:
=================
https://lore.kernel.org/linux-mm/[email protected]/
- Wrapped if condition for goal->metric check to fix 80-column violation
- Used PAGE_ALIGN_DOWN(addr + PAGE_SIZE) instead of addr += PAGE_SIZE for proper page-aligned advancement
- Wrapped damos_goal_tune_esz_bp_temporal() function signature for 80-column compliance
- Removed unintended damos_trace_esz() call from first charge window
- Added addr filter usage note in two-scheme setup documentation
Changes since v6:
=================
https://lore.kernel.org/linux-mm/[email protected]/
- Dropped node_ineligible_mem_bp metric per maintainer feedback
- Updated two-scheme setup to use 60%/40% split with both schemes using node_eligible_mem_bp (per SeongJae Park's suggestion)
Changes since v5:
=================
https://lore.kernel.org/linux-mm/[email protected]/
- Rebased onto mm-new instead of damon/next for sashiko review
- Removed Reported-by/Closes tags per maintainer feedback (not needed for bugs found before merge)
Changes since v4:
=================
https://lore.kernel.org/linux-mm/[email protected]/
- Fixed commit message description for DAMOS_QUOTA_NODE_INELIGIBLE_MEM_BP per review feedback
- Added clarifying comment for ops-common.h include (for damon_get_folio())
- Fixed build error when CONFIG_DAMON_PADDR is disabled by adding #ifdef CONFIG_DAMON_PADDR guards around functions using damon_get_folio()
- Dropped RFC tag per maintainer feedback
Changes since RFC v3:
=====================
https://lore.kernel.org/linux-mm/[email protected]/
- Fixed phys_addr_t overflow on 32-bit PAE systems by using phys_addr_t for intermediate byte calculations
- Added cond_resched() per region to prevent CPU soft lockups
- Improved folio overlap calculation for accurate byte counting
Changes since RFC v2:
=====================
https://lore.kernel.org/linux-mm/[email protected]/
- Added node_ineligible_mem_bp complementary metric
- Fixed DAMON_OPS_PADDR validation in damon_commit_ctx()
- Improved commit message with two-scheme setup documentation
Changes since RFC v1:
=====================
https://lore.kernel.org/linux-mm/[email protected]/
- Initial implementation based on SeongJae Park's suggestion
- Added basic node_eligible_mem_bp metric
--- include/linux/damon.h | 3 + mm/damon/core.c | 172 +++++++++++++++++++++++++++++++++++---- mm/damon/sysfs-schemes.c | 7 ++ 3 files changed, 167 insertions(+), 15 deletions(-)
include/linux/damon.h +3 -0
@@ -161,6 +161,8 @@ enum damos_action {* @DAMOS_QUOTA_NODE_MEMCG_FREE_BP: MemFree ratio of a node for a cgroup. * @DAMOS_QUOTA_ACTIVE_MEM_BP: Active to total LRU memory ratio. * @DAMOS_QUOTA_INACTIVE_MEM_BP: Inactive to total LRU memory ratio. +* @DAMOS_QUOTA_NODE_ELIGIBLE_MEM_BP: Scheme-eligible memory ratio of a +* node in basis points (0-10000). * @NR_DAMOS_QUOTA_GOAL_METRICS: Number of DAMOS quota goal metrics. * * Metrics equal to larger than @NR_DAMOS_QUOTA_GOAL_METRICS are unsupported.
@@ -174,6 +176,7 @@ enum damos_quota_goal_metric {DAMOS_QUOTA_NODE_MEMCG_FREE_BP ,DAMOS_QUOTA_ACTIVE_MEM_BP ,DAMOS_QUOTA_INACTIVE_MEM_BP , +DAMOS_QUOTA_NODE_ELIGIBLE_MEM_BP , NR_DAMOS_QUOTA_GOAL_METRICS, };
mm/damon/core.c +157 -15
267 lines not on this page · raw message
mm/damon/sysfs-schemes.c +7 -0
@@ -1094,6 +1094,10 @@ struct damos_sysfs_qgoal_metric_name damos_sysfs_qgoal_metric_names[] = { .metric = DAMOS_QUOTA_INACTIVE_MEM_BP, .name = "inactive_mem_bp", }, + { + .metric = DAMOS_QUOTA_NODE_ELIGIBLE_MEM_BP, + .name = "node_eligible_mem_bp", + }, }; static ssize_t target_metric_show(struct kobject *kobj,
@@ -2690,6 +2694,9 @@ static int damos_sysfs_add_quota_score( } goal->nid = sysfs_goal->nid;break ; +case DAMOS_QUOTA_NODE_ELIGIBLE_MEM_BP : + goal->nid = sysfs_goal->nid; +break ;default :break ; }
base-commit: a1316f1941695b84c9b9eff946bee67b8c8ff0d8
Signature · 1 line
2.43.0
[email protected]
AI reviewSashiko AI review found 4 potential issue(s): - [High] Missing `cond_resched()` inside the inner PFN-scanning loop causes CPU soft lockups. - [High] Casting 64-bit `phys_addr_t` to 32-bit `unsigned…
Sashiko AI review found 4 potential issue(s):
- [High] Missing cond_resched() inside the inner PFN-scanning loop causes CPU soft lockups.
- [High] Casting 64-bit phys_addr_t to 32-bit unsigned long causes massive quota calculation errors on 32-bit PAE systems.
- [High] Physical address wraparound of folio_end causes an infinite loop and system hang.
- [High] Pre-existing VM_BUG_ON in speculative folio access within damon_get_folio().
--
commit 377bf0664129c87741fa640c563456b379c8a816
Author: Ravi Jonnalagadda <[email protected]>
mm/damon: add node_eligible_mem_bp goal metric
This patch enables system-wide page distribution with target-state goals using PA-mode DAMON schemes. It tracks the ratio of scheme-eligible memory on a target node to adjust migration aggressiveness. The implementation calculates the current memory distribution across regions that match the scheme's access pattern.
Quoted 4 lines
mm/damon/core.c +0
-0
[ ... ]
Quoted 1 line
mm/damon/core.c +0
-0@@ -2287,7 +2306,115 @@ static unsigned long damos_get_node_memcg_used_bp(
[ ... ]
mm/damon/core.c static unsigned long damos_get_node_memcg_used_bp( · line 2351
Quoted 7 lines
mm/damon/core.c +7
-0+while (addr < end_addr) { +struct folio *folio; +phys_addr_t folio_start, folio_end; +phys_addr_t overlap_start, overlap_end; +phys_addr_t counted; + + folio =damon_get_folio (PHYS_PFN (addr));
Since this code heavily invokes damon_get_folio() in a new PFN scanner, could the speculative folio access inside damon_get_folio() trigger a VM_BUG_ON? If damon_get_folio() tests folio_test_lru() before acquiring a reference with folio_try_get(), the page could be concurrently split or reallocated as a tail page.
Quoted 19 lines
mm/damon/core.c +19
-0+if (!folio) { + addr =PAGE_ALIGN_DOWN (addr + +PAGE_SIZE ); +if (!addr) +break ; +continue ; + } + +/* +* Calculate exact overlap between the region +* [addr, end_addr) and the folio range. +* The folio may start before addr if addr is +* in the middle of a large folio. +*/ + folio_start =PFN_PHYS (folio_pfn (folio)); + folio_end = folio_start +folio_size (folio); + + overlap_start =max (addr, folio_start); + overlap_end =min (end_addr, folio_end);
mm/damon/core.c static unsigned long damos_get_node_memcg_used_bp( · line 2380
Quoted 10 lines
mm/damon/core.c +10
-0+ +if (overlap_end > overlap_start) { + counted = overlap_end - overlap_start; + total_eligible += counted; +if (folio_nid (folio) == nid) + node_eligible += counted; + } + +/* Advance past the entire folio */ + addr = folio_end;
If a folio is located at the very end of the physical address space, could folio_end wrap around to 0? If addr becomes 0, could it cause an infinite loop since addr < end_addr would remain true?
mm/damon/core.c static unsigned long damos_get_node_memcg_used_bp( · line 2383
Quoted 3 lines
mm/damon/core.c +3
-0+folio_put (folio); + } +cond_resched ();
Could scanning a large DAMON region without yielding the CPU in the inner loop cause soft lockups? Moving the cond_resched() inside the while loop would prevent it from monopolizing the processor.
Quoted 16 lines
mm/damon/core.c +16
-0+ } + } + + *total = total_eligible; +return node_eligible; +} + +static unsigned long damos_get_node_eligible_mem_bp (struct damon_ctx *c, +struct damos *s,int nid) +{ +phys_addr_t total_eligible =0 ; +phys_addr_t node_eligible; + +if (c->ops.id !=DAMON_OPS_PADDR ) +return 0 ; +
mm/damon/core.c static unsigned long damos_get_node_memcg_used_bp( · line 2409
Quoted 10 lines
mm/damon/core.c +10
-0+if (nid <0 || nid >=MAX_NUMNODES || !node_online (nid)) +return 0 ; + + node_eligible =damos_calc_eligible_bytes (c, s, nid, &total_eligible); + +if (!(unsigned long )total_eligible) +return 0 ; + +return mult_frac ((unsigned long )node_eligible,10000 , + (unsigned long )total_eligible);
Does casting these phys_addr_t variables to unsigned long cause truncation errors on 32-bit PAE systems where physical addresses are 64-bit? For example, if the eligible memory size exceeds 4GB, the truncation could corrupt the ratio metrics and break the quota autotuner. Would converting byte counts to page counts before casting avoid this?
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=1
SeongJae Park
maintainerQuoted 5 lines · On Tue, 28 Apr 2026 03:25:42 +0000 [email protected] wrote:
Sashiko AI review found 4 potential issue(s):
- [High] Missingcond_resched()inside the inner PFN-scanning loop causes CPU soft lockups.
- [High] Casting 64-bitphys_addr_tto 32-bitunsigned longcauses massive quota calculation errors on 32-bit PAE systems.
- [High] Physical address wraparound offolio_endcauses an infinite loop and system hang.
- [High] Pre-existingVM_BUG_ONin speculative folio access withindamon_get_folio().
I find none of above as a blocker of this patch. Please read below for more details.
Quoted 20 lines
--
commit
377bf0664129c87741fa640c563456b379c8a816
Author: Ravi Jonnalagadda <[email protected]>mm/damon: add
node_eligible_mem_bpgoal metricThis patch enables system-wide page distribution with target-state goals using PA-mode DAMON schemes. It tracks the ratio of scheme-eligible memory on a target node to adjust migration aggressiveness. The implementation calculates the current memory distribution across regions that match the scheme's access pattern.
Quoted 4 lines
mm/damon/core.c +0
-0[ ... ]
Quoted 1 line
mm/damon/core.c +0
-0@@ -2287,7 +2306,115 @@ static unsigned long damos_get_node_memcg_used_bp([ ... ]
Quoted 13 lines
mm/damon/core.c static unsigned long damos_get_node_memcg_used_bp( · line 2351
Quoted 7 lines
mm/damon/core.c +7
-0+while (addr < end_addr) { +struct folio *folio; +phys_addr_t folio_start, folio_end; +phys_addr_t overlap_start, overlap_end; +phys_addr_t counted; + + folio =damon_get_folio (PHYS_PFN (addr));Since this code heavily invokes
damon_get_folio()in a new PFN scanner, could the speculative folio access insidedamon_get_folio()trigger aVM_BUG_ON? Ifdamon_get_folio()testsfolio_test_lru()before acquiring a reference withfolio_try_get(), the page could be concurrently split or reallocated as a tail page.
I'm not sure what VM_BUG_ON() Sashiko is saying about. Even if Sashiko is correct, seems Sashiko argues the issue is inside damon_get_folio(), which is not modified by this patch. So this is not a blocker of this patch.
Quoted 26 lines
Quoted 26 lines
mm/damon/core.c +25
-0+if (!folio) { + addr =PAGE_ALIGN_DOWN (addr + +PAGE_SIZE ); +if (!addr) +break ; +continue ; + } + +/* +* Calculate exact overlap between the region +* [addr, end_addr) and the folio range. +* The folio may start before addr if addr is +* in the middle of a large folio. +*/ + folio_start =PFN_PHYS (folio_pfn (folio)); + folio_end = folio_start +folio_size (folio); + + overlap_start =max (addr, folio_start); + overlap_end =min (end_addr, folio_end); + +if (overlap_end > overlap_start) { + counted = overlap_end - overlap_start; + total_eligible += counted; +if (folio_nid (folio) == nid) + node_eligible += counted;
Quoted 8 lines
mm/damon/core.c static unsigned long damos_get_node_memcg_used_bp( · line 2380
Quoted 4 lines
mm/damon/core.c +4
-0+ } + +/* Advance past the entire folio */ + addr = folio_end;If a folio is located at the very end of the physical address space, could
folio_endwrap around to 0? If addr becomes 0, could it cause an infinite loop since addr <end_addrwould remain true?
The folio is a valid one, so should located on a valid physical address range. And to my understanding, no real world machine allows 2**64 - 1 address. So this should be no blocker of this patch.
Quoted 8 lines
mm/damon/core.c static unsigned long damos_get_node_memcg_used_bp( · line 2383
Quoted 4 lines
mm/damon/core.c +3
-0+folio_put (folio); + } +cond_resched ();Could scanning a large DAMON region without yielding the CPU in the inner loop cause soft lockups? Moving the
cond_resched()inside the while loop would prevent it from monopolizing the processor.
As replied on previous versions, this is unlikely to happen. We can revisit if this really causes such issue.
Quoted 25 lines
Quoted 25 lines
mm/damon/core.c +24
-0+ } + } + + *total = total_eligible; +return node_eligible; +} + +static unsigned long damos_get_node_eligible_mem_bp (struct damon_ctx *c, +struct damos *s,int nid) +{ +phys_addr_t total_eligible =0 ; +phys_addr_t node_eligible; + +if (c->ops.id !=DAMON_OPS_PADDR ) +return 0 ; + +if (nid <0 || nid >=MAX_NUMNODES || !node_online (nid)) +return 0 ; + + node_eligible =damos_calc_eligible_bytes (c, s, nid, &total_eligible); + +if (!(unsigned long )total_eligible) +return 0 ; +
Quoted 8 lines
mm/damon/core.c static unsigned long damos_get_node_memcg_used_bp( · line 2409
Quoted 2 lines
mm/damon/core.c +2
-0+return mult_frac ((unsigned long )node_eligible,10000 , + (unsigned long )total_eligible);Does casting these
phys_addr_tvariables to unsigned long cause truncation errors on 32-bit PAE systems where physical addresses are 64-bit? For example, if the eligible memory size exceeds 4GB, the truncation could corrupt the ratio metrics and break the quota autotuner. Would converting byte counts to page counts before casting avoid this?
As replied on previous versions, the user impact is trivial. No blocker.
Thanks,
SJ
[...]
SeongJae Park
maintainerQuoted 87 lines · On Mon, 27 Apr 2026 20:05:20 -0700 Ravi Jonnalagadda <[email protected]>…
Background and Motivation
=========================In heterogeneous memory systems, controlling memory distribution across NUMA nodes is essential for performance optimization. This patch enables system-wide page distribution with target-state goals such as "maintain 60% of scheme-eligible memory on DRAM" using PA-mode DAMON schemes.
Rather than using absolute thresholds, this metric tracks the ratio of memory that matches each scheme's access pattern filters on a target node, enabling the quota system to automatically adjust migration aggressiveness to maintain the desired distribution.
What This Metric Measures
=========================
node_eligible_mem_bp:
scheme_eligible_bytes_on_node/total_scheme_eligible_bytes* 10000Two-Scheme Setup for Hot Page Distribution
==========================================For maintaining 60% of hot memory on DRAM (node 0) and 40% on CXL (node 1):
PULL scheme:
migrate_hotto node 0
goal:node_eligible_mem_bp, nid=0, target=6000
addr filter: node 1 address range (only migrate FROM CXL) "Move hot pages to DRAM if less than 60% of hot data is in DRAM"PUSH scheme:
migrate_hotto node 1
goal:node_eligible_mem_bp, nid=1, target=4000
addr filter: node 0 address range (only migrate FROM DRAM) "Move hot pages to CXL if less than 40% of hot data is in CXL"Each scheme independently measures its own eligible memory and adjusts its quota to achieve its target ratio. The schemes work in concert through DAMON's unified monitoring context, with the quota autotuner balancing their relative aggressiveness.
Implementation Details
======================The implementation adds a new quota goal metric type
DAMOS_QUOTA_NODE_ELIGIBLE_MEM_BPto the existing DAMOS quota goal framework. When this metric is configured for a scheme:1. During each quota adjustment cycle,
damos_get_node_eligible_mem_bp()is called to calculate the current memory distribution.2. The function iterates through all regions that match the scheme's access pattern (via
__damos_valid_target()) and calculates:
- Total eligible bytes across all nodes
- Eligible bytes specifically on the target node (goal->nid)3. For each eligible region,
damos_calc_eligible_bytes()walks through the physical address range, usingdamon_get_folio()to look up each folio and determine its NUMA node viafolio_nid().4. Large folios are handled by calculating the exact overlap between the region boundaries and folio boundaries, ensuring accurate byte counts even when regions partially span folios.
5. The ratio (
node_eligible/total_eligible* 10000) is returned as basis points, which the quota autotuner uses to adjust the scheme's effective quota size (esz).The implementation requires
CONFIG_DAMON_PADDRsincedamon_get_folio()is only available for physical address space monitoring.Testing Results
===============Functionally tested on a two-node heterogeneous memory system with DRAM (node 0) and CXL memory (node 1). A PUSH+PULL scheme configuration using
migrate_hotactions was used to reach a target hot memory ratio between the two tiers.With the TEMPORAL tuner, the system converges quickly to the target distribution. The tuner drives esz to maximum when under goal and to zero once the goal is met, forming a simple on/off feedback loop that stabilizes at the desired ratio.
With the CONSIST tuner, the scheme still converges but more slowly, as it migrates and then throttles itself based on quota feedback. The time to reach the goal varies depending on workload intensity.
Quoted 5 lines
Note: This metric works with both TEMPORAL and CONSIST goal tuners.
Suggested-by SeongJae Park <[email protected]>Signed-off-by Ravi Jonnalagadda <[email protected]>Reviewed-by SeongJae Park <[email protected]>
So, this looks good to me. I find no blocker [1] from Sashiko review, too. I added this to damon/next tree. Unless others find concerns or this is added to mm-new by Andrew soon, I will repost this for mm-new merge.
[1] https://lore.kernel.org/[email protected]
Thanks,
SJ
[...]