Add a node-scoped "system bp" goal metric for PA-based migration control. This metric represents basis points (bp, 1/10,000) for (scheme-eligible bytes on node nid) / (system total bytes) Signed-off-by: Ravi Jonnalagadda --- include/linux/damon.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/include/linux/damon.h b/include/linux/damon.h index 3813373a9200..ec5ed1a217fc 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_SYS_BP: Scheme-eligible bytes 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_SYS_BP, NR_DAMOS_QUOTA_GOAL_METRICS, }; -- 2.43.0 Introduce an optional damon_operations callback `get_goal_metric()` that lets ops providers compute goal metrics requiring address-space knowledge. Provide a PA implementation that handles DAMOS_QUOTA_NODE_SYS_BP by iterating the monitored PFN regions and attributing bytes to the goal's nid. Core remains generic and asks ops only when needed. Signed-off-by: Ravi Jonnalagadda --- include/linux/damon.h | 3 +++ mm/damon/paddr.c | 58 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+) diff --git a/include/linux/damon.h b/include/linux/damon.h index ec5ed1a217fc..67233898c27c 100644 --- a/include/linux/damon.h +++ b/include/linux/damon.h @@ -649,6 +649,9 @@ struct damon_operations { bool (*target_valid)(struct damon_target *t); void (*cleanup_target)(struct damon_target *t); void (*cleanup)(struct damon_ctx *context); + unsigned long (*get_goal_metric)(struct damon_ctx *ctx, + struct damos *scheme, + const struct damos_quota_goal *goal); }; /* diff --git a/mm/damon/paddr.c b/mm/damon/paddr.c index 07a8aead439e..30e4e5663dcb 100644 --- a/mm/damon/paddr.c +++ b/mm/damon/paddr.c @@ -14,6 +14,8 @@ #include #include #include +#include +#include #include "../internal.h" #include "ops-common.h" @@ -148,6 +150,48 @@ static bool damon_pa_invalid_damos_folio(struct folio *folio, struct damos *s) return false; } +/* System total RAM in bytes (denominator for bp computation) */ +static unsigned long damon_pa_totalram_bytes(void) +{ + return (unsigned long)totalram_pages() << PAGE_SHIFT; +} + +/* + * Compute node-scoped system bp for PA contexts: + * bp = (bytes attributed to goal->nid across monitored PA regions) / + * (system total bytes) * 10000 + */ +static unsigned long damon_pa_get_node_sys_bp(struct damon_ctx *ctx, + struct damos *scheme, + const struct damos_quota_goal *goal) +{ + int nid = goal ? goal->nid : -1; + unsigned long node_bytes = 0; + unsigned long total_bytes = damon_pa_totalram_bytes(); + struct damon_target *t; + struct damon_region *r; + + if (nid < 0 || !total_bytes) + return 0; + + damon_for_each_target(t, ctx) { + damon_for_each_region(r, t) { + unsigned long start_pfn = r->ar.start >> PAGE_SHIFT; + unsigned long end_pfn = r->ar.end >> PAGE_SHIFT; + unsigned long pfn; + + for (pfn = start_pfn; pfn < end_pfn; pfn++) { + if (!pfn_valid(pfn)) + continue; + if (page_to_nid(pfn_to_page(pfn)) == nid) + node_bytes += PAGE_SIZE; + } + } + } + + return div64_u64((u64)node_bytes * 10000ULL, total_bytes); +} + static unsigned long damon_pa_pageout(struct damon_region *r, unsigned long addr_unit, struct damos *s, unsigned long *sz_filter_passed) @@ -344,6 +388,19 @@ static unsigned long damon_pa_apply_scheme(struct damon_ctx *ctx, return 0; } +/* Generic goal-metric provider for PA */ +static unsigned long damon_pa_get_goal_metric(struct damon_ctx *ctx, + struct damos *scheme, + const struct damos_quota_goal *goal) +{ + switch (goal ? goal->metric : -1) { + case DAMOS_QUOTA_NODE_SYS_BP: + return damon_pa_get_node_sys_bp(ctx, scheme, goal); + default: + return 0; + } +} + static int damon_pa_scheme_score(struct damon_ctx *context, struct damon_target *t, struct damon_region *r, struct damos *scheme) @@ -378,6 +435,7 @@ static int __init damon_pa_initcall(void) .cleanup = NULL, .apply_scheme = damon_pa_apply_scheme, .get_scheme_score = damon_pa_scheme_score, + .get_goal_metric = damon_pa_get_goal_metric, }; return damon_register_ops(&ops); -- 2.43.0 Convert static functions in core.c to pass damon_ctx* and damos* down to `damos_set_quota_goal_current_value()`. This keeps all goal metrics (PSI, node_mem*, node_memcg*, and the new node_sys_bp) computed in one place, while core remains generic and ops-specific computation is delegated via ctx->ops.get_goal_metric(). Only static functions in this file are touched; no external ABI changes. Signed-off-by: Ravi Jonnalagadda --- mm/damon/core.c | 34 +++++++++++++++++++++++++++------- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/mm/damon/core.c b/mm/damon/core.c index 84f80a20f233..d898bfcbef72 100644 --- a/mm/damon/core.c +++ b/mm/damon/core.c @@ -2119,8 +2119,20 @@ static unsigned long damos_get_node_memcg_used_bp( } #endif +static unsigned long +damos_get_goal_metric_from_ops(struct damon_ctx *ctx, struct damos *scheme, + const struct damos_quota_goal *goal) +{ + if (!ctx || !goal) + return 0; + if (!ctx->ops.get_goal_metric) + return 0; + return ctx->ops.get_goal_metric(ctx, scheme, goal); +} -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 +2153,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_SYS_BP: + goal->current_value = damos_get_goal_metric_from_ops(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 +2183,9 @@ 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 +2196,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 +2247,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 +2260,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 Clamp effective target to node capacity (bp) and skip in-migration if the node already meets/exceeds it. This avoids oscillation and unnecessary work in two-context DRAM/CXL setups when quota goals (e.g., node_sys_bp) are met. Signed-off-by: Ravi Jonnalagadda --- mm/damon/paddr.c | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/mm/damon/paddr.c b/mm/damon/paddr.c index 30e4e5663dcb..64dbdd2196a5 100644 --- a/mm/damon/paddr.c +++ b/mm/damon/paddr.c @@ -300,10 +300,54 @@ static unsigned long damon_pa_deactivate_pages(struct damon_region *r, sz_filter_passed); } +static unsigned long damon_pa_node_capacity_bp(int nid) +{ + struct pglist_data *pgdat; + unsigned long sys_total = damon_pa_totalram_bytes(); + unsigned long node_pages, node_total; + + if (nid < 0 || !sys_total) + return 0; + pgdat = NODE_DATA(nid); + if (!pgdat) + return 0; + node_pages = pgdat->node_spanned_pages; + node_total = node_pages << PAGE_SHIFT; + return div64_u64((u64)node_total * 10000ULL, sys_total); +} + static unsigned long damon_pa_migrate(struct damon_region *r, unsigned long addr_unit, struct damos *s, unsigned long *sz_filter_passed) { + /* + * Capacity clamp + directional early-exit for node_sys_bp goals: + * If we are migrating INTO g->nid and the current bp for that node is + * already >= min(target_bp, capacity_bp), skip work this interval. + */ + { + struct damos_quota_goal *g; + + list_for_each_entry(g, &s->quota.goals, list) { + unsigned long cap_bp, effective_target_bp; + + if (g->metric != DAMOS_QUOTA_NODE_SYS_BP) + continue; + if (g->nid < 0) + continue; + + cap_bp = damon_pa_node_capacity_bp(g->nid); + if (!cap_bp) + break; + + effective_target_bp = min(g->target_value, cap_bp); + if (s->target_nid == g->nid && + g->current_value >= effective_target_bp) + return 0; + break; + } + } + phys_addr_t addr, applied; LIST_HEAD(folio_list); struct folio *folio; -- 2.43.0 Allow userspace to select the new goal metric "node_sys_bp" by writing it to goals//target_metric. Also set goal->nid for this metric when committing goals from sysfs to the running schemes. Signed-off-by: Ravi Jonnalagadda --- mm/damon/sysfs-schemes.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/mm/damon/sysfs-schemes.c b/mm/damon/sysfs-schemes.c index 3a699dcd5a7f..37cd5d715821 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_SYS_BP, + .name = "node_sys_bp", + }, }; static ssize_t target_metric_show(struct kobject *kobj, @@ -2566,6 +2570,9 @@ static int damos_sysfs_add_quota_score( } goal->nid = sysfs_goal->nid; break; + case DAMOS_QUOTA_NODE_SYS_BP: + goal->nid = sysfs_goal->nid; + break; default: break; } -- 2.43.0