Add a new quota goal metric DAMOS_QUOTA_NODE_TARGET_MEM_BP for physical address space-based DAMON operation schemes. This metric represents the ratio of scheme-eligible memory on a specific NUMA node to that node's total capacity, expressed in basis points (1/10000). The metric enables auto-tuning of DAMOS quotas based on how much memory on a node matches the scheme's access pattern criteria, which is essential for controlling memory migration in heterogeneous memory systems (e.g., DRAM and CXL memory tiering). Suggested-by: SeongJae Park Signed-off-by: Ravi Jonnalagadda --- include/linux/damon.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/include/linux/damon.h b/include/linux/damon.h index 3813373a9200..5eebb8aaab85 100644 --- a/include/linux/damon.h +++ b/include/linux/damon.h @@ -155,6 +155,7 @@ enum damos_action { * @DAMOS_QUOTA_NODE_MEM_FREE_BP: MemFree ratio of a node. * @DAMOS_QUOTA_NODE_MEMCG_USED_BP: MemUsed ratio of a node for a cgroup. * @DAMOS_QUOTA_NODE_MEMCG_FREE_BP: MemFree ratio of a node for a cgroup. + * @DAMOS_QUOTA_NODE_TARGET_MEM_BP: Scheme-eligible memory ratio of a node. * @NR_DAMOS_QUOTA_GOAL_METRICS: Number of DAMOS quota goal metrics. * * Metrics equal to larger than @NR_DAMOS_QUOTA_GOAL_METRICS are unsupported. @@ -166,6 +167,7 @@ enum damos_quota_goal_metric { DAMOS_QUOTA_NODE_MEM_FREE_BP, DAMOS_QUOTA_NODE_MEMCG_USED_BP, DAMOS_QUOTA_NODE_MEMCG_FREE_BP, + DAMOS_QUOTA_NODE_TARGET_MEM_BP, NR_DAMOS_QUOTA_GOAL_METRICS, }; @@ -193,6 +195,9 @@ enum damos_quota_goal_metric { * * If @metric is DAMOS_QUOTA_NODE_MEMCG_{USED,FREE}_BP, @nid and @memcg_id * represents the node id and the cgroup to account the used memory for. + * + * If @metric is DAMOS_QUOTA_NODE_TARGET_MEM_BP, @nid represents the node + * to measure scheme-eligible memory ratio against its capacity. */ struct damos_quota_goal { enum damos_quota_goal_metric metric; -- 2.43.0 Add damos_get_node_target_mem_bp() function that calculates the ratio of scheme-eligible memory on a specific NUMA node to the total node capacity, expressed in basis points (bp, 1/10000). The function iterates through all regions that match the scheme's access pattern criteria (checked via __damos_valid_target) and counts how many pages from those regions reside on the specified node. This enables quota auto-tuning based on the actual distribution of hot/cold pages across NUMA nodes. To support this new metric which requires access to both the DAMON context and scheme: - Update damos_set_quota_goal_current_value() signature - Update damos_quota_score() to pass ctx and scheme through - Update damos_set_effective_quota() and its callers This metric is particularly useful for heterogeneous memory systems (e.g., DRAM + CXL) where controlling the distribution of hot pages across nodes can optimize memory bandwidth utilization. Suggested-by: SeongJae Park Signed-off-by: Ravi Jonnalagadda --- mm/damon/core.c | 66 +++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 59 insertions(+), 7 deletions(-) diff --git a/mm/damon/core.c b/mm/damon/core.c index 84f80a20f233..1482c97828e8 100644 --- a/mm/damon/core.c +++ b/mm/damon/core.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -2119,8 +2120,52 @@ static unsigned long damos_get_node_memcg_used_bp( } #endif +static unsigned long damos_get_node_target_mem_bp( + struct damon_ctx *ctx, struct damos *scheme, + struct damos_quota_goal *goal) +{ + int nid = goal->nid; + unsigned long node_capacity, scheme_node_bytes = 0; + unsigned long addr_unit = ctx->addr_unit; + struct damon_target *t; + struct damon_region *r; + unsigned long start_pfn, end_pfn, pfn; + + /* Only supported for physical address space monitoring */ + if (ctx->ops.id != DAMON_OPS_PADDR) + return 0; + + if (nid < 0 || nid >= MAX_NUMNODES || !node_online(nid)) + return 0; + + node_capacity = NODE_DATA(nid)->node_spanned_pages << PAGE_SHIFT; + if (!node_capacity) + return 0; + + damon_for_each_target(t, ctx) { + damon_for_each_region(r, t) { + if (!__damos_valid_target(r, scheme)) + continue; + + start_pfn = (phys_addr_t)r->ar.start * + addr_unit >> PAGE_SHIFT; + end_pfn = (phys_addr_t)r->ar.end * + addr_unit >> PAGE_SHIFT; + + for (pfn = start_pfn; pfn < end_pfn; pfn++) { + if (pfn_valid(pfn) && + page_to_nid(pfn_to_page(pfn)) == nid) + scheme_node_bytes += PAGE_SIZE; + } + } + } + + return mult_frac(scheme_node_bytes, 10000, node_capacity); +} -static void damos_set_quota_goal_current_value(struct damos_quota_goal *goal) +static void damos_set_quota_goal_current_value( + struct damon_ctx *ctx, struct damos *scheme, + struct damos_quota_goal *goal) { u64 now_psi_total; @@ -2141,19 +2186,25 @@ static void damos_set_quota_goal_current_value(struct damos_quota_goal *goal) case DAMOS_QUOTA_NODE_MEMCG_FREE_BP: goal->current_value = damos_get_node_memcg_used_bp(goal); break; + case DAMOS_QUOTA_NODE_TARGET_MEM_BP: + goal->current_value = damos_get_node_target_mem_bp( + ctx, scheme, goal); + 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 *ctx, struct damos *scheme, + struct damos_quota *quota) { struct damos_quota_goal *goal; 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(ctx, scheme, goal); highest_score = max(highest_score, goal->current_value * 10000 / goal->target_value); @@ -2165,7 +2216,8 @@ static unsigned long damos_quota_score(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) +static void damos_set_effective_quota(struct damon_ctx *ctx, + struct damos *scheme, struct damos_quota *quota) { unsigned long throughput; unsigned long esz = ULONG_MAX; @@ -2176,7 +2228,7 @@ static void damos_set_effective_quota(struct damos_quota *quota) } if (!list_empty("a->goals)) { - unsigned long score = damos_quota_score(quota); + unsigned long score = damos_quota_score(ctx, scheme, quota); quota->esz_bp = damon_feed_loop_next_input( max(quota->esz_bp, 10000UL), @@ -2227,7 +2279,7 @@ 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); + damos_set_effective_quota(c, s, quota); } /* New charge window starts */ @@ -2240,7 +2292,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); + damos_set_effective_quota(c, s, quota); if (trace_damos_esz_enabled() && quota->esz != cached_esz) damos_trace_esz(c, s, quota); } -- 2.43.0 Add sysfs support for the new NODE_TARGET_MEM_BP quota goal metric. This exposes the metric as 'node_target_mem_bp' under the quota goal's target_metric attribute. The metric measures the ratio of scheme-eligible memory (regions matching the scheme's access pattern) on a specified NUMA node to the node's total capacity, expressed in basis points (bp, 1/10000). Users can configure this metric by: 1. Setting target_metric to 'node_target_mem_bp' 2. Setting nid to the target NUMA node 3. Setting target_value to the desired ratio in basis points The current_value attribute shows the measured ratio, which can be used by userspace to compute the actual bytes of scheme-eligible memory on the node: bytes = current_value * node_capacity / 10000. This is particularly useful for tiered memory systems to monitor and control the distribution of hot pages across NUMA nodes. Suggested-by: SeongJae Park Signed-off-by: Ravi Jonnalagadda --- mm/damon/sysfs-schemes.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/mm/damon/sysfs-schemes.c b/mm/damon/sysfs-schemes.c index 3a699dcd5a7f..50133263c592 100644 --- a/mm/damon/sysfs-schemes.c +++ b/mm/damon/sysfs-schemes.c @@ -1038,6 +1038,10 @@ struct damos_sysfs_qgoal_metric_name damos_sysfs_qgoal_metric_names[] = { .metric = DAMOS_QUOTA_NODE_MEMCG_FREE_BP, .name = "node_memcg_free_bp", }, + { + .metric = DAMOS_QUOTA_NODE_TARGET_MEM_BP, + .name = "node_target_mem_bp", + }, }; static ssize_t target_metric_show(struct kobject *kobj, @@ -2554,6 +2558,7 @@ static int damos_sysfs_add_quota_score( break; case DAMOS_QUOTA_NODE_MEM_USED_BP: case DAMOS_QUOTA_NODE_MEM_FREE_BP: + case DAMOS_QUOTA_NODE_TARGET_MEM_BP: goal->nid = sysfs_goal->nid; break; case DAMOS_QUOTA_NODE_MEMCG_USED_BP: -- 2.43.0