mm/damon: add node_eligible_mem_bp goal metric
Subsystems (3)
PATCH v7 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
The metric iterates through each scheme's eligible regions and uses damon_get_folio() to determine NUMA node placement of each folio.
Two-Scheme Setup for Hot Page Distribution
==========================================
For maintaining hot memory on DRAM (node 0) and CXL (node 1) in a 6:4 ratio (per SeongJae Park's suggestion):
PULL scheme: migrate_hot to node 0
goal: node_eligible_mem_bp, nid=0, target=6000
"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 "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
======================
Key implementation considerations:
- Uses phys_addr_t for byte calculations to prevent overflow on 32-bit architectures with large memory configurations
- Includes cond_resched() per region to prevent CPU soft lockups when processing large numbers of regions
- Validates that ops type is DAMON_OPS_PADDR in both damon_commit_ctx() (for clear error feedback) and the metric function (defensive check)
- Properly handles folio alignment when region addresses fall within large folios by calculating exact overlap
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.
--- include/linux/damon.h | 3 + mm/damon/core.c | 171 +++++++++++++++++++++++++++++++++++---- mm/damon/sysfs-schemes.c | 7 ++ 3 files changed, 165 insertions(+), 16 deletions(-)
include/linux/damon.h +3 -0
@@ -159,6 +159,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.
@@ -172,6 +174,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 +155 -16
272 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,
@@ -2685,6 +2689,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: 277a7fae172bb4a598e0d4144ffb2ff6207fedb3
Signature · 1 line
2.43.0
[email protected]
AI reviewSashiko AI review found 4 potential issue(s): - [High] Integer Truncation and Arithmetic Overflow on 32-bit Architectures - [High] CPU Soft Lockups due to Unbounded Physical Address Iteration…
Sashiko AI review found 4 potential issue(s):
- [High] Integer Truncation and Arithmetic Overflow on 32-bit Architectures
- [High] CPU Soft Lockups due to Unbounded Physical Address Iteration
- [Medium] Missing Core Filters Evaluation Causes Eligible Memory Over-counting
- [Medium] Unaligned Address Advancement Truncates Exact Overlap Calculation --
commit d92efd581a7b3318becfa32765dc87547acb91c6
Author: Ravi Jonnalagadda <[email protected]>
Subject: mm/damon: add node_eligible_mem_bp goal metric
This commit introduces a new metric node_eligible_mem_bp for DAMON schemes to control memory distribution across NUMA nodes based on the ratio of scheme-eligible memory. It calculates physical bytes per node using PADDR ops and integrates with the DAMOS quota goal system.
Quoted 4 lines
mm/damon/core.c +0
-0
[ ... ]
Quoted 26 lines
mm/damon/core.c +21
-1@@ -2287,7 +2305,112 @@ static unsigned long damos_get_node_memcg_used_bp( numerator = i.totalram - used_pages;return mult_frac (numerator,10000 , i.totalram); } -#else + +#ifdef CONFIG_DAMON_PADDR +/* +* damos_calc_eligible_bytes() - Calculate raw eligible bytes per node. +* @c: The DAMON context. +* @s: The scheme. +* @nid: The target NUMA node id. +* @total: Output for total eligible bytes across all nodes. +* +* Iterates through each folio in eligible regions to accurately determine +* which node the memory resides on. Returns eligible bytes on the specified +* node and sets *total to the sum across all nodes. +* +* Note: This function requires damon_get_folio() from ops-common.c, which is +* only available when CONFIG_DAMON_PADDR is enabled. It also requires the +* context to be using PADDR operations for meaningful results. +*/ +static phys_addr_t damos_calc_eligible_bytes (struct damon_ctx *c, +struct damos *s,int nid,phys_addr_t *total) +{ +struct damon_target *t;
mm/damon/core.c static unsigned long damos_get_node_memcg_used_bp( · line 2338
Quoted 10 lines
mm/damon/core.c +10
-0+struct damon_region *r; +phys_addr_t total_eligible =0 ; +phys_addr_t node_eligible =0 ; + +damon_for_each_target (t, c) { +damon_for_each_region (r, t) { +phys_addr_t addr, end_addr; + +if (!__damos_valid_target (r, s)) +continue ;
Does this overcount eligible memory by skipping core filters?
The quota adjustment logic uses damos_core_filter_out() to exclude explicitly filtered regions, but it appears this calculation only checks __damos_valid_target(). Should damos_core_filter_out() be evaluated here as well?
Quoted 5 lines
mm/damon/core.c +5
-0+ +/* Convert from core address units to physical bytes */ + addr = (phys_addr_t )r->ar.start * c->addr_unit; + end_addr = (phys_addr_t )r->ar.end * c->addr_unit; +while (addr < end_addr) {
mm/damon/core.c static unsigned long damos_get_node_memcg_used_bp( · line 2353
Quoted 10 lines
mm/damon/core.c +10
-0+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)); +if (!folio) { + addr +=PAGE_SIZE ; +continue ; + }
If addr is unaligned, does adding PAGE_SIZE preserve the unaligned offset and cause under-counting on the next valid folio?
If an unaligned fallback happens, the address might skip past a valid page boundary instead of aligning to it. When advancing the address without a valid folio, would it be better to align it to the next page boundary?
Quoted 7 lines
mm/damon/core.c +7
-0+ +/* +* 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. +*/
[ ... ]
Quoted 4 lines
mm/damon/core.c +4
-0+if (overlap_end > overlap_start) { + counted = overlap_end - overlap_start; + total_eligible += counted; +if (folio_nid (folio) == nid)
mm/damon/core.c static unsigned long damos_get_node_memcg_used_bp( · line 2380
Quoted 10 lines
mm/damon/core.c +10
-0+ node_eligible += counted; + } + +/* Advance past the entire folio */ + addr = folio_end; +folio_put (folio); + } +cond_resched (); + } + }
Could this cause CPU soft lockups for very large regions?
Since the while loop can iterate millions of times for large memory regions, should cond_resched() be placed inside the while loop instead of outside it?
Quoted 15 lines
mm/damon/core.c +15
-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))
mm/damon/core.c static unsigned long damos_get_node_memcg_used_bp( · line 2405
Quoted 10 lines
mm/damon/core.c +10
-0+return 0 ; + + node_eligible =damos_calc_eligible_bytes (c, s, nid, &total_eligible); + +if (!total_eligible) +return 0 ; + +return mult_frac ((unsigned long )node_eligible,10000 , + (unsigned long )total_eligible); +}
Can this cast cause truncation and overflow on 32-bit architectures?
Both node_eligible and total_eligible are phys_addr_t, which are 64-bit on PAE systems, but unsigned long is 32-bit.
If total_eligible is an exact multiple of 4GB, it would cast to 0 and trigger a divide-by-zero panic in mult_frac(). Additionally, mult_frac() multiplies by 10000 using 32-bit math, which might overflow for values larger than roughly 429 KB.
Should this use 64-bit division or shift the byte sizes down to page counts before calculating the fraction?
[ ... ]
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=1
SeongJae Park
maintainerQuoted 2 lines · On Fri, 24 Apr 2026 20:59:07 +0000 [email protected] wrote:
Sashiko AI review found 4 potential issue(s):
- [High] Integer Truncation and Arithmetic Overflow on 32-bit Architectures
No real problem.
Quoted 1 line
- [High] CPU Soft Lockups due to Unbounded Physical Address Iteration
Unlikely.
Quoted 1 line
- [Medium] Missing Core Filters Evaluation Causes Eligible Memory Over-counting
This is intentional.
Quoted 1 line
- [Medium] Unaligned Address Advancement Truncates Exact Overlap Calculation
I agree to Sashiko.
Quoted 57 lines
--
commit
d92efd581a7b3318becfa32765dc87547acb91c6
Author: Ravi Jonnalagadda <[email protected]>
Subject: mm/damon: addnode_eligible_mem_bpgoal metricThis commit introduces a new metric
node_eligible_mem_bpfor DAMON schemes to control memory distribution across NUMA nodes based on the ratio of scheme-eligible memory. It calculates physical bytes per node using PADDR ops and integrates with the DAMOS quota goal system.Quoted 4 lines
mm/damon/core.c +0
-0[ ... ]
Quoted 26 lines
mm/damon/core.c +21
-1@@ -2287,7 +2305,112 @@ static unsigned long damos_get_node_memcg_used_bp( numerator = i.totalram - used_pages;return mult_frac (numerator,10000 , i.totalram); } -#else + +#ifdef CONFIG_DAMON_PADDR +/* +* damos_calc_eligible_bytes() - Calculate raw eligible bytes per node. +* @c: The DAMON context. +* @s: The scheme. +* @nid: The target NUMA node id. +* @total: Output for total eligible bytes across all nodes. +* +* Iterates through each folio in eligible regions to accurately determine +* which node the memory resides on. Returns eligible bytes on the specified +* node and sets *total to the sum across all nodes. +* +* Note: This function requires damon_get_folio() from ops-common.c, which is +* only available when CONFIG_DAMON_PADDR is enabled. It also requires the +* context to be using PADDR operations for meaningful results. +*/ +static phys_addr_t damos_calc_eligible_bytes (struct damon_ctx *c, +struct damos *s,int nid,phys_addr_t *total) +{ +struct damon_target *t;mm/damon/core.c static unsigned long damos_get_node_memcg_used_bp( · line 2338
Quoted 10 lines
mm/damon/core.c +10
-0+struct damon_region *r; +phys_addr_t total_eligible =0 ; +phys_addr_t node_eligible =0 ; + +damon_for_each_target (t, c) { +damon_for_each_region (r, t) { +phys_addr_t addr, end_addr; + +if (!__damos_valid_target (r, s)) +continue ;Does this overcount eligible memory by skipping core filters?
Quoted 4 lines
The quota adjustment logic uses
damos_core_filter_out()to exclude explicitly filtered regions, but it appears this calculation only checks__damos_valid_target(). Shoulddamos_core_filter_out()be evaluated here as well?
This is an intentional behavior as we discussed [1] on the previous version.
Quoted 20 lines
Quoted 6 lines
mm/damon/core.c +5
-0+ +/* Convert from core address units to physical bytes */ + addr = (phys_addr_t )r->ar.start * c->addr_unit; + end_addr = (phys_addr_t )r->ar.end * c->addr_unit; +while (addr < end_addr) {mm/damon/core.c static unsigned long damos_get_node_memcg_used_bp( · line 2353
Quoted 10 lines
mm/damon/core.c +10
-0+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)); +if (!folio) { + addr +=PAGE_SIZE ; +continue ; + }If addr is unaligned, does adding
PAGE_SIZEpreserve the unaligned offset and cause under-counting on the next valid folio?
Quoted 3 lines
If an unaligned fallback happens, the address might skip past a valid page boundary instead of aligning to it. When advancing the address without a valid folio, would it be better to align it to the next page boundary?
Makes sense. I suggested to use PAGE_ALING_DOWN() on the reply to the patch.
Quoted 28 lines
Quoted 8 lines
mm/damon/core.c +7
-0+ +/* +* 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. +*/ [ ... ]
Quoted 4 lines
mm/damon/core.c +4
-0+if (overlap_end > overlap_start) { + counted = overlap_end - overlap_start; + total_eligible += counted; +if (folio_nid (folio) = nid)mm/damon/core.c static unsigned long damos_get_node_memcg_used_bp( · line 2380
Quoted 10 lines
mm/damon/core.c +10
-0+ node_eligible += counted; + } + +/* Advance past the entire folio */ + addr = folio_end; +folio_put (folio); + } +cond_resched (); + } + }Could this cause CPU soft lockups for very large regions?
Quoted 2 lines
Since the while loop can iterate millions of times for large memory regions, should
cond_resched()be placed inside the while loop instead of outside it?
Not likely. Users could set min_nr_regions to control the maximum size of each region. We might be able to make this more robust, but seems not a blocker of this patch in my opinion.
Quoted 32 lines
Quoted 16 lines
mm/damon/core.c +15
-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))mm/damon/core.c static unsigned long damos_get_node_memcg_used_bp( · line 2405
Quoted 10 lines
mm/damon/core.c +10
-0+return 0 ; + + node_eligible =damos_calc_eligible_bytes (c, s, nid, &total_eligible); + +if (!total_eligible) +return 0 ; + +return mult_frac ((unsigned long )node_eligible,10000 , + (unsigned long )total_eligible); +}Can this cast cause truncation and overflow on 32-bit architectures?
Both
node_eligibleandtotal_eligiblearephys_addr_t, which are 64-bit on PAE systems, but unsigned long is 32-bit.
Quoted 7 lines
If
total_eligibleis an exact multiple of 4GB, it would cast to 0 and trigger a divide-by-zero panic inmult_frac(). Additionally,mult_frac()multiplies by 10000 using 32-bit math, which might overflow for values larger than roughly 429 KB.Should this use 64-bit division or shift the byte sizes down to page counts before calculating the fraction?
Too much overflow concern in my opinion. The overflow makes no real critical problem. Let's keep this as is for now.
Quoted 6 lines
[ ... ]
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=1
[1] https://lore.kernel.org/[email protected]
Thanks,
SJ
SeongJae Park
maintainerHello Ravi,
This version looks good, except very trivial nits.
Quoted 25 lines · On Fri, 24 Apr 2026 13:34:48 -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* 10000The metric iterates through each scheme's eligible regions and uses
damon_get_folio()to determine NUMA node placement of each folio.Two-Scheme Setup for Hot Page Distribution
==========================================
Quoted 6 lines
For maintaining hot memory on DRAM (node 0) and CXL (node 1) in a 6:4 ratio (per SeongJae Park's suggestion):
PULL scheme:
migrate_hotto node 0
goal:node_eligible_mem_bp, nid=0, target=6000
"Move hot pages to DRAM if less than 60% of hot data is in DRAM"
You also set a damos filter of node type with node id 1 for this scheme, right?
Quoted 4 lines
PUSH scheme:
migrate_hotto node 1
goal:node_eligible_mem_bp, nid=1, target=4000 "Move hot pages to CXL if less than 40% of hot data is in CXL"
And you set damos filter of node type with node id 0 here, right?
I think it would good to mention that here, too.
Quoted 9 lines
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
======================
Quoted 9 lines
Key implementation considerations:
- Usesphys_addr_tfor byte calculations to prevent overflow on 32-bit architectures with large memory configurations
- Includescond_resched()per region to prevent CPU soft lockups when processing large numbers of regions
- Validates that ops type isDAMON_OPS_PADDRin bothdamon_commit_ctx()(for clear error feedback) and the metric function (defensive check)
- Properly handles folio alignment when region addresses fall within large folios by calculating exact overlap
This is changelog from the previous version [1], right? Let's put all changelog from all previous version on the commentary area [2].
Quoted 79 lines
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.
Note: This metric works with both TEMPORAL and CONSIST goal tuners.
Suggested-by SeongJae Park <[email protected]>Signed-off-by Ravi Jonnalagadda <[email protected]>--- include/linux/damon.h | 3 + mm/damon/core.c | 171 +++++++++++++++++++++++++++++++++++---- mm/damon/sysfs-schemes.c | 7 ++ 3 files changed, 165 insertions(+), 16 deletions(-)include/linux/damon.h +3
-0@@ -159,6 +159,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. @@ -172,6 +174,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 +6
-0@@ -13,10 +13,14 @@#include <linux/memcontrol.h> #include <linux/mm.h> #include <linux/psi.h> +#include <linux/sched.h> #include <linux/slab.h> #include <linux/string.h> #include <linux/string_choices.h> +/* for damon_get_folio() used by node eligible memory metrics */ +#include "ops-common.h" +#define CREATE_TRACE_POINTS #include <trace/events/damon.h> @@ -1326,11 +1330,25 @@ static int damon_commit_targets(int damon_commit_ctx (struct damon_ctx *dst,struct damon_ctx *src) {int err; +struct damos *scheme; +struct damos_quota_goal *goal; dst->maybe_corrupted = true;
mm/damon/core.c static int damon_commit_targets( · line 1346
Quoted 10 lines
mm/damon/core.c +7
-0if (!is_power_of_2 (src->min_region_sz))return -EINVAL ; +/* node_eligible_mem_bp metric requires PADDR ops */ +if (src->ops.id !=DAMON_OPS_PADDR ) { +damon_for_each_scheme (scheme, src) { +struct damos_quota *quota = &scheme->quota; + +damos_for_each_quota_goal (goal, quota) { +if (goal->metric ==DAMOS_QUOTA_NODE_ELIGIBLE_MEM_BP )
Let's wrap this line to keep the 80 columns limit.
Quoted 47 lines
mm/damon/core.c +39
-1+return -EINVAL ; + } + } + } + err =damon_commit_schemes (dst, src);if (err)return err;@@ -2287,7 +2305,112 @@ static unsigned long damos_get_node_memcg_used_bp( numerator = i.totalram - used_pages;return mult_frac (numerator,10000 , i.totalram); } -#else + +#ifdef CONFIG_DAMON_PADDR +/* +* damos_calc_eligible_bytes() - Calculate raw eligible bytes per node. +* @c: The DAMON context. +* @s: The scheme. +* @nid: The target NUMA node id. +* @total: Output for total eligible bytes across all nodes. +* +* Iterates through each folio in eligible regions to accurately determine +* which node the memory resides on. Returns eligible bytes on the specified +* node and sets *total to the sum across all nodes. +* +* Note: This function requires damon_get_folio() from ops-common.c, which is +* only available when CONFIG_DAMON_PADDR is enabled. It also requires the +* context to be using PADDR operations for meaningful results. +*/ +static phys_addr_t damos_calc_eligible_bytes (struct damon_ctx *c, +struct damos *s,int nid,phys_addr_t *total) +{ +struct damon_target *t; +struct damon_region *r; +phys_addr_t total_eligible =0 ; +phys_addr_t node_eligible =0 ; + +damon_for_each_target (t, c) { +damon_for_each_region (r, t) { +phys_addr_t addr, end_addr; + +if (!__damos_valid_target (r, s)) +continue ; + +/* Convert from core address units to physical bytes */ + addr = (phys_addr_t )r->ar.start * c->addr_unit;
mm/damon/core.c static unsigned long damos_get_node_memcg_used_bp( · line 2351
Quoted 10 lines
mm/damon/core.c +10
-0+ end_addr = (phys_addr_t )r->ar.end * c->addr_unit; +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)); +if (!folio) { + addr +=PAGE_SIZE ;
Sashiko found [3] this may better to be aligned. It is trivial but I agree. How about using PAGE_ALIGN_DOWN(addr + PAGE_SIZE)?
Quoted 35 lines
mm/damon/core.c +35
-0+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; + } + +/* Advance past the entire folio */ + addr = folio_end; +folio_put (folio); + } +cond_resched (); + } + } + + *total = total_eligible; +return node_eligible; +} + +static unsigned long damos_get_node_eligible_mem_bp(struct damon_ctx *c,
mm/damon/core.c static unsigned long damos_get_node_memcg_used_bp( · line 2396
Quoted 10 lines
mm/damon/core.c +10
-0+ 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 ;
Trivial nit. I'd prefer using '!node_state(nid, N_MEMORY)' to be consistent with the similar one in damon_migrate_pages(). But this one seems fine to me.
Quoted 73 lines
mm/damon/core.c +33
-4+ + node_eligible =damos_calc_eligible_bytes (c, s, nid, &total_eligible); + +if (!total_eligible) +return 0 ; + +return mult_frac ((unsigned long )node_eligible,10000 , + (unsigned long)total_eligible); +} +#else /* CONFIG_DAMON_PADDR */ +static unsigned long damos_get_node_eligible_mem_bp (struct damon_ctx *c, +struct damos *s,int nid) +{ +return 0 ; +} +#endif /* CONFIG_DAMON_PADDR */ +#else /* CONFIG_NUMA */ static __kernel_ulong_t damos_get_node_mem_bp (struct damos_quota_goal *goal) {@@ -2299,7 +2422,13 @@ static unsigned long damos_get_node_memcg_used_bp( {return 0 ; } -#endif + +static unsigned long damos_get_node_eligible_mem_bp (struct damon_ctx *c, +struct damos *s,int nid) +{ +return 0 ; +} +#endif /* CONFIG_NUMA */ /* * Returns LRU-active or inactive memory to total LRU memory size ratio. @@ -2319,7 +2448,8 @@ static unsigned int damos_get_in_active_mem_bp(bool active_ratio)return mult_frac (inactive,10000 , total); } -static void damos_set_quota_goal_current_value (struct damos_quota_goal *goal) +static void damos_set_quota_goal_current_value (struct damon_ctx *c, +struct damos *s,struct damos_quota_goal *goal) {u64 now_psi_total;@@ -2345,19 +2475,24 @@ static void damos_set_quota_goal_current_value(struct damos_quota_goal *goal) goal->current_value =damos_get_in_active_mem_bp ( goal->metric ==DAMOS_QUOTA_ACTIVE_MEM_BP );break ; +case DAMOS_QUOTA_NODE_ELIGIBLE_MEM_BP : + goal->current_value =damos_get_node_eligible_mem_bp (c, s, + goal->nid); +break ;default :break ; } }/* Return the highest score since it makes schemes least aggressive */ -static unsigned long damos_quota_score (struct damos_quota *quota) +static unsigned long damos_quota_score (struct damon_ctx *c,struct damos *s) {struct damos_quota_goal *goal; +struct damos_quota *quota = &s->quota;unsigned long highest_score =0 ;damos_for_each_quota_goal (goal, quota) { -damos_set_quota_goal_current_value (goal); +damos_set_quota_goal_current_value (c, s, goal); highest_score =max (highest_score,mult_frac (goal->current_value,10000 , goal->target_value));
mm/damon/core.c static unsigned long damos_quota_score(struct damos_quota *quota) · line 2504
Quoted 5 lines
mm/damon/core.c +1
-1@@ -2366,17 +2501,19 @@ static unsigned long damos_quota_score(struct damos_quota *quota)return highest_score; } -static void damos_goal_tune_esz_bp_consist (struct damos_quota *quota) +static void damos_goal_tune_esz_bp_consist (struct damon_ctx *c,struct damos *s)
80 columns limit?
Quoted 52 lines
mm/damon/core.c +10
-8{ -unsigned long score =damos_quota_score (quota); +struct damos_quota *quota = &s->quota; +unsigned long score =damos_quota_score (c, s); quota->esz_bp =damon_feed_loop_next_input (max (quota->esz_bp,10000UL ), score); } -static void damos_goal_tune_esz_bp_temporal (struct damos_quota *quota) +static void damos_goal_tune_esz_bp_temporal (struct damon_ctx *c,struct damos *s) { -unsigned long score =damos_quota_score (quota); +struct damos_quota *quota = &s->quota; +unsigned long score =damos_quota_score (c, s);if (score >=10000 ) quota->esz_bp =0 ;@@ -2389,9 +2526,9 @@ static void damos_goal_tune_esz_bp_temporal(struct damos_quota *quota)/* * Called only if quota->ms, or quota->sz are set, or quota->goals is not empty */ -static void damos_set_effective_quota (struct damos_quota *quota, -struct damon_ctx *ctx) +static void damos_set_effective_quota (struct damon_ctx *c,struct damos *s) { +struct damos_quota *quota = &s->quota;unsigned long throughput;unsigned long esz =ULONG_MAX ;@@ -2402,9 +2539,9 @@ static void damos_set_effective_quota(struct damos_quota *quota,if (!list_empty ("a->goals)) {if (quota->goal_tuner ==DAMOS_QUOTA_GOAL_TUNER_CONSIST ) -damos_goal_tune_esz_bp_consist (quota); +damos_goal_tune_esz_bp_consist (c, s);else if (quota->goal_tuner ==DAMOS_QUOTA_GOAL_TUNER_TEMPORAL ) -damos_goal_tune_esz_bp_temporal (quota); +damos_goal_tune_esz_bp_temporal (c, s); esz = quota->esz_bp /10000 ; }@@ -2415,7 +2552,7 @@ static void damos_set_effective_quota(struct damos_quota *quota,else throughput =PAGE_SIZE *1024 ; esz =min (throughput * quota->ms, esz); - esz =max (ctx->min_region_sz, esz); + esz =max (c->min_region_sz, esz); }if (quota->sz && quota->sz < esz)
mm/damon/core.c static void damos_adjust_quota(struct damon_ctx *c, struct damos *s) · line 2594
Quoted 7 lines
mm/damon/core.c +3
-1@@ -2452,7 +2589,9 @@ static void damos_adjust_quota(struct damon_ctx *c, struct damos *s)/* First charge window */ if (!quota->total_charged_sz && !quota->charged_from) { quota->charged_from = jiffies; -damos_set_effective_quota (quota, c); +damos_set_effective_quota (c, s); +if (trace_damos_esz_enabled ()) +damos_trace_esz (c, s, quota);
damos_trace_esz() seems an unintended change?
Quoted 38 lines
mm/damon/core.c +1
-1}/* New charge window starts */ @@ -2467,7 +2606,7 @@ static void damos_adjust_quota(struct damon_ctx *c, struct damos *s) quota->charged_sz =0 ;if (trace_damos_esz_enabled ()) cached_esz = quota->esz; -damos_set_effective_quota (quota, c); +damos_set_effective_quota (c, s);if (trace_damos_esz_enabled () && quota->esz != cached_esz)damos_trace_esz (c, s, quota); }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,@@ -2685,6 +2689,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 ; }
Quoted 3 lines
base-commit:
277a7fae172bb4a598e0d4144ffb2ff6207fedb3Signature · 1 line
2.43.0
I also reviewed Sashiko review [3] and I find no real problem except the addr alignment.
So this looks good to me, except the trivial things I commented above. Could you please revision for the last time?
[1] https://lore.kernel.org/[email protected]
[2] https://docs.kernel.org/process/submitting-patches.html#commentary
[3] https://lore.kernel.org/[email protected]
Thanks,
SJ
Quoted 5 lines · SeongJae Park
Hello Ravi,
This version looks good, except very trivial nits.
Hi SJ,
Thanks for the super quick review! Very helpful.
Quoted 38 lines · On Fri, 24 Apr 2026 13:34:48 -0700 Ravi Jonnalagadda <[email protected]>…
Quoted 26 lines
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* 10000The metric iterates through each scheme's eligible regions and uses
damon_get_folio()to determine NUMA node placement of each folio.Two-Scheme Setup for Hot Page Distribution
==========================================Quoted 6 lines
For maintaining hot memory on DRAM (node 0) and CXL (node 1) in a 6:4 ratio (per SeongJae Park's suggestion):
PULL scheme:
migrate_hotto node 0
goal:node_eligible_mem_bp, nid=0, target=6000
"Move hot pages to DRAM if less than 60% of hot data is in DRAM"You also set a damos filter of node type with node id 1 for this scheme, right?
Quoted 1 line
Quoted 8 lines
Quoted 3 lines
PUSH scheme:
migrate_hotto node 1
goal:node_eligible_mem_bp, nid=1, target=4000 "Move hot pages to CXL if less than 40% of hot data is in CXL"And you set damos filter of node type with node id 0 here, right?
I think it would good to mention that here, too.
Good point. I'll add a note explaining that each scheme uses DAMOS
addr filters
to restrict the source node - PULL uses an addr filter for node 1's
address
range, and PUSH uses an addr filter for node 0's address range.
Quoted 19 lines
Quoted 9 lines
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
======================Quoted 9 lines
Key implementation considerations:
- Usesphys_addr_tfor byte calculations to prevent overflow on 32-bit architectures with large memory configurations
- Includescond_resched()per region to prevent CPU soft lockups when processing large numbers of regions
- Validates that ops type isDAMON_OPS_PADDRin bothdamon_commit_ctx()(for clear error feedback) and the metric function (defensive check)
- Properly handles folio alignment when region addresses fall within large folios by calculating exact overlap
Quoted 3 lines
This is changelog from the previous version [1], right? Let's put all changelog from all previous version on the commentary area [2].
Understood. Will move this to the commentary area and include the full changelog history from RFC v1.
Quoted 82 lines
Quoted 82 lines
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.
Note: This metric works with both TEMPORAL and CONSIST goal tuners.
Suggested-by SeongJae Park <[email protected]>Signed-off-by Ravi Jonnalagadda <[email protected]>--- include/linux/damon.h | 3 + mm/damon/core.c | 171 +++++++++++++++++++++++++++++++++++---- mm/damon/sysfs-schemes.c | 7 ++ 3 files changed, 165 insertions(+), 16 deletions(-)include/linux/damon.h +3
-0@@ -159,6 +159,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. @@ -172,6 +174,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 +6
-0@@ -13,10 +13,14 @@#include <linux/memcontrol.h> #include <linux/mm.h> #include <linux/psi.h> +#include <linux/sched.h> #include <linux/slab.h> #include <linux/string.h> #include <linux/string_choices.h> +/* for damon_get_folio() used by node eligible memory metrics */ +#include "ops-common.h" +#define CREATE_TRACE_POINTS #include <trace/events/damon.h> @@ -1326,11 +1330,25 @@ static int damon_commit_targets(int damon_commit_ctx (struct damon_ctx *dst,struct damon_ctx *src) {int err; +struct damos *scheme; +struct damos_quota_goal *goal; dst->maybe_corrupted = true;if (!is_power_of_2 (src->min_region_sz))return -EINVAL ;
Quoted 9 lines
mm/damon/core.c static int damon_commit_targets( · line 1346
Quoted 7 lines
mm/damon/core.c +7
-0+/* node_eligible_mem_bp metric requires PADDR ops */ +if (src->ops.id !=DAMON_OPS_PADDR ) { +damon_for_each_scheme (scheme, src) { +struct damos_quota *quota = &scheme->quota; + +damos_for_each_quota_goal (goal, quota) { +if (goal->metric ==DAMOS_QUOTA_NODE_ELIGIBLE_MEM_BP )Let's wrap this line to keep the 80 columns limit.
Will wrap this in v8.
Quoted 53 lines
Quoted 53 lines
mm/damon/core.c +44
-1+return -EINVAL ; + } + } + } + err =damon_commit_schemes (dst, src);if (err)return err;@@ -2287,7 +2305,112 @@ static unsigned long damos_get_node_memcg_used_bp( numerator = i.totalram - used_pages;return mult_frac (numerator,10000 , i.totalram); } -#else + +#ifdef CONFIG_DAMON_PADDR +/* +* damos_calc_eligible_bytes() - Calculate raw eligible bytes per node. +* @c: The DAMON context. +* @s: The scheme. +* @nid: The target NUMA node id. +* @total: Output for total eligible bytes across all nodes. +* +* Iterates through each folio in eligible regions to accurately determine +* which node the memory resides on. Returns eligible bytes on the specified +* node and sets *total to the sum across all nodes. +* +* Note: This function requires damon_get_folio() from ops-common.c, which is +* only available when CONFIG_DAMON_PADDR is enabled. It also requires the +* context to be using PADDR operations for meaningful results. +*/ +static phys_addr_t damos_calc_eligible_bytes (struct damon_ctx *c, +struct damos *s,int nid,phys_addr_t *total) +{ +struct damon_target *t; +struct damon_region *r; +phys_addr_t total_eligible =0 ; +phys_addr_t node_eligible =0 ; + +damon_for_each_target (t, c) { +damon_for_each_region (r, t) { +phys_addr_t addr, end_addr; + +if (!__damos_valid_target (r, s)) +continue ; + +/* Convert from core address units to physical bytes */ + addr = (phys_addr_t )r->ar.start * c->addr_unit; + end_addr = (phys_addr_t )r->ar.end * c->addr_unit; +while (addr < end_addr) { +struct folio *folio; +phys_addr_t folio_start, folio_end; +phys_addr_t overlap_start, overlap_end;
Quoted 8 lines
mm/damon/core.c static unsigned long damos_get_node_memcg_used_bp( · line 2351
Quoted 5 lines
mm/damon/core.c +5
-0+phys_addr_t counted; + + folio =damon_get_folio (PHYS_PFN (addr)); +if (!folio) { + addr +=PAGE_SIZE ;Sashiko found [3] this may better to be aligned. It is trivial but I agree. How about using
PAGE_ALIGN_DOWN(addr + PAGE_SIZE)?
Agreed, will change to PAGE_ALIGN_DOWN(addr + PAGE_SIZE) in v8.
Quoted 41 lines
Quoted 41 lines
mm/damon/core.c +40
-0+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; + } + +/* Advance past the entire folio */ + addr = folio_end; +folio_put (folio); + } +cond_resched (); + } + } + + *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; +
Quoted 8 lines
mm/damon/core.c static unsigned long damos_get_node_memcg_used_bp( · line 2396
Quoted 5 lines
mm/damon/core.c +5
-0+if (c->ops.id !=DAMON_OPS_PADDR ) +return 0 ; + +if (nid <0 || nid >=MAX_NUMNODES || !node_online (nid)) +return 0 ;Trivial nit. I'd prefer using '!node_state(nid,
N_MEMORY)' to be consistent with the similar one indamon_migrate_pages(). But this one seems fine to me.
Thanks for the suggestion. I'll keep node_online() for now as you indicated
it's fine, but happy to change it later, if you prefer consistency.
Quoted 77 lines
Quoted 77 lines
mm/damon/core.c +33
-4+ + node_eligible =damos_calc_eligible_bytes (c, s, nid, &total_eligible); + +if (!total_eligible) +return 0 ; + +return mult_frac ((unsigned long )node_eligible,10000 , + (unsigned long)total_eligible); +} +#else /* CONFIG_DAMON_PADDR */ +static unsigned long damos_get_node_eligible_mem_bp (struct damon_ctx *c, +struct damos *s,int nid) +{ +return 0 ; +} +#endif /* CONFIG_DAMON_PADDR */ +#else /* CONFIG_NUMA */ static __kernel_ulong_t damos_get_node_mem_bp (struct damos_quota_goal *goal) {@@ -2299,7 +2422,13 @@ static unsigned long damos_get_node_memcg_used_bp( {return 0 ; } -#endif + +static unsigned long damos_get_node_eligible_mem_bp (struct damon_ctx *c, +struct damos *s,int nid) +{ +return 0 ; +} +#endif /* CONFIG_NUMA */ /* * Returns LRU-active or inactive memory to total LRU memory size ratio. @@ -2319,7 +2448,8 @@ static unsigned int damos_get_in_active_mem_bp(bool active_ratio)return mult_frac (inactive,10000 , total); } -static void damos_set_quota_goal_current_value (struct damos_quota_goal *goal) +static void damos_set_quota_goal_current_value (struct damon_ctx *c, +struct damos *s,struct damos_quota_goal *goal) {u64 now_psi_total;@@ -2345,19 +2475,24 @@ static void damos_set_quota_goal_current_value(struct damos_quota_goal *goal) goal->current_value =damos_get_in_active_mem_bp ( goal->metric ==DAMOS_QUOTA_ACTIVE_MEM_BP );break ; +case DAMOS_QUOTA_NODE_ELIGIBLE_MEM_BP : + goal->current_value =damos_get_node_eligible_mem_bp (c, s, + goal->nid); +break ;default :break ; } }/* Return the highest score since it makes schemes least aggressive */ -static unsigned long damos_quota_score (struct damos_quota *quota) +static unsigned long damos_quota_score (struct damon_ctx *c,struct damos *s) {struct damos_quota_goal *goal; +struct damos_quota *quota = &s->quota;unsigned long highest_score =0 ;damos_for_each_quota_goal (goal, quota) { -damos_set_quota_goal_current_value (goal); +damos_set_quota_goal_current_value (c, s, goal); highest_score =max (highest_score,mult_frac (goal->current_value,10000 , goal->target_value));@@ -2366,17 +2501,19 @@ static unsigned long damos_quota_score(struct damos_quota *quota)return highest_score; }
Quoted 4 lines
mm/damon/core.c static unsigned long damos_quota_score(struct damos_quota *quota) · line 2504
Quoted 2 lines
mm/damon/core.c +1
-1-static void damos_goal_tune_esz_bp_consist (struct damos_quota *quota) +static void damos_goal_tune_esz_bp_consist (struct damon_ctx *c,struct damos *s)80 columns limit?
I checked - damos_goal_tune_esz_bp_consist is exactly 80 characters, so it's
fine as-is. However, damos_goal_tune_esz_bp_temporal below is 81 characters, so I'll wrap that one in v8.
Quoted 51 lines
Quoted 51 lines
mm/damon/core.c +10
-8{ -unsigned long score =damos_quota_score (quota); +struct damos_quota *quota = &s->quota; +unsigned long score =damos_quota_score (c, s); quota->esz_bp =damon_feed_loop_next_input (max (quota->esz_bp,10000UL ), score); } -static void damos_goal_tune_esz_bp_temporal (struct damos_quota *quota) +static void damos_goal_tune_esz_bp_temporal (struct damon_ctx *c,struct damos *s) { -unsigned long score =damos_quota_score (quota); +struct damos_quota *quota = &s->quota; +unsigned long score =damos_quota_score (c, s);if (score >=10000 ) quota->esz_bp =0 ;@@ -2389,9 +2526,9 @@ static void damos_goal_tune_esz_bp_temporal(struct damos_quota *quota)/* * Called only if quota->ms, or quota->sz are set, or quota->goals is not empty */ -static void damos_set_effective_quota (struct damos_quota *quota, -struct damon_ctx *ctx) +static void damos_set_effective_quota (struct damon_ctx *c,struct damos *s) { +struct damos_quota *quota = &s->quota;unsigned long throughput;unsigned long esz =ULONG_MAX ;@@ -2402,9 +2539,9 @@ static void damos_set_effective_quota(struct damos_quota *quota,if (!list_empty ("a->goals)) {if (quota->goal_tuner ==DAMOS_QUOTA_GOAL_TUNER_CONSIST ) -damos_goal_tune_esz_bp_consist (quota); +damos_goal_tune_esz_bp_consist (c, s);else if (quota->goal_tuner ==DAMOS_QUOTA_GOAL_TUNER_TEMPORAL ) -damos_goal_tune_esz_bp_temporal (quota); +damos_goal_tune_esz_bp_temporal (c, s); esz = quota->esz_bp /10000 ; }@@ -2415,7 +2552,7 @@ static void damos_set_effective_quota(struct damos_quota *quota,else throughput =PAGE_SIZE *1024 ; esz =min (throughput * quota->ms, esz); - esz =max (ctx->min_region_sz, esz); + esz =max (c->min_region_sz, esz); }
Quoted 11 lines
mm/damon/core.c static void damos_adjust_quota(struct damon_ctx *c, struct damos *s) · line 2594
Quoted 9 lines
mm/damon/core.c +3
-1if (quota->sz && quota->sz < esz)@@ -2452,7 +2589,9 @@ static void damos_adjust_quota(struct damon_ctx *c, struct damos *s)/* First charge window */ if (!quota->total_charged_sz && !quota->charged_from) { quota->charged_from = jiffies; -damos_set_effective_quota (quota, c); +damos_set_effective_quota (c, s); +if (trace_damos_esz_enabled ()) +damos_trace_esz (c, s, quota);
damos_trace_esz()seems an unintended change?
Yes, this was unintended. Will remove it in v8.
Quoted 46 lines
Quoted 39 lines
mm/damon/core.c +1
-1}/* New charge window starts */ @@ -2467,7 +2606,7 @@ static void damos_adjust_quota(struct damon_ctx *c, struct damos *s) quota->charged_sz =0 ;if (trace_damos_esz_enabled ()) cached_esz = quota->esz; -damos_set_effective_quota (quota, c); +damos_set_effective_quota (c, s);if (trace_damos_esz_enabled () && quota->esz != cached_esz)damos_trace_esz (c, s, quota); }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,@@ -2685,6 +2689,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 ; }Quoted 3 lines
base-commit:
277a7fae172bb4a598e0d4144ffb2ff6207fedb3--
2.43.0I also reviewed Sashiko review [3] and I find no real problem except the addr alignment.
Quoted 2 lines
So this looks good to me, except the trivial things I commented above. Could you please revision for the last time?
Will do. Summary of changes for v8:
1. Mention addr filters for source-node filtering in Two-Scheme
Setup
2. Move implementation details to commentary area with full
changelog
3. Wrap the 80-column violation in damon_commit_ctx()
4. Use PAGE_ALIGN_DOWN(addr + PAGE_SIZE) for alignment
5. Wrap damos_goal_tune_esz_bp_temporal() (81 chars)
6. Remove unintended damos_trace_esz() from first charge window
Thanks,
Ravi
SeongJae Park
maintainerQuoted 10 lines · On Fri, 24 Apr 2026 21:18:57 -0700 Ravi Jonnalagadda <[email protected]>…
Quoted 5 lines · SeongJae Park
Hello Ravi,
This version looks good, except very trivial nits.
Hi SJ,
Thanks for the super quick review! Very helpful.
My pleasure!
[...]
Quoted 12 lines
Quoted 2 lines
So this looks good to me, except the trivial things I commented above. Could you please revision for the last time?
Will do. Summary of changes for v8:
1. Mention addr filters for source-node filtering in Two-Scheme
Setup
2. Move implementation details to commentary area with full
changelog
3. Wrap the 80-column violation indamon_commit_ctx()
4. UsePAGE_ALIGN_DOWN(addr + PAGE_SIZE)for alignment
5. Wrapdamos_goal_tune_esz_bp_temporal()(81 chars)
6. Remove unintendeddamos_trace_esz()from first charge window
Sounds good, looking forward to the v8!
Thanks,
SJ
[...]