mm/damon/core: fix the size charged for a filter-trimmed region
Subsystems (4)
SJ Park
maintainerv2 cover mm/damon/core: fix the size charged for a filter-trimmed region
From: Donggeun Yoo <[email protected]>
An address range DAMOS filter that partially overlaps a monitoring region splits it, so the scheme action reaches only one side of the boundary. damos_apply_scheme() reads the region size before that split and never re-reads it, so the quota charge and schemes/<S>/stats/sz_tried account for the whole original region.
Patch 1 re-reads the size after the core filters have run. Patch 2 adds KUnit coverage for what the function charges and reports, for the trimmed cases and for the cases that must not change.
Changes from v1
- v1: https://lore.kernel.org/[email protected]
- Add user impact clarification to patch 1.
- Collec R-b: from SJ.
- Rebase to the latest mm-new.
Donggeun Yoo (2):
mm/damon/core: charge only the part of a region the filter left mm/damon/tests/core-kunit: test the size charged for a filter-trimmed
region
mm/damon/core.c | 1 + mm/damon/tests/core-kunit.h | 316 ++++++++++++++++++++++++++++++++++++ 2 files changed, 317 insertions(+)
base-commit: 99528f68cc4066167e5e7135c1172be1e6315102
Signature · 1 line
2.47.3
SJ Park
maintainerPATCH v2 1/2 mm/damon/core: charge only the part of a region the filter left
From: Donggeun Yoo <[email protected]>
An address range DAMOS filter that partially overlaps a monitoring region splits the region at the filter boundary, so the scheme action is applied to only one side of it. damos_apply_scheme() reads the region size once on entry, before damos_core_filter_out() performs that split.
The quota charge and the statistics update therefore account for the region as it was before the trim. schemes/<S>/stats/sz_tried counts memory the filter excluded, which Documentation/mm/damon/design.rst says is not counted as tried, and with quotas/bytes set the excluded part is charged against the budget, throttling the scheme to a fraction of what was configured.
User impact is that DAMOS could unexpectedly slowly run, due to over-charged quota. DAMOS stat could also be confusing. No critical events such as crashes or leask happen.
Re-read the region size after the core filters have run.
--- mm/damon/core.c | 1 + 1 file changed, 1 insertion(+)
mm/damon/core.c +1 -0
@@ -2784,6 +2784,7 @@ static void damos_apply_scheme(struct damon_ctx *c, struct damon_target *t, }if (damos_core_filter_out (c, t, r, s))return ; + sz =damon_sz_region (r);ktime_get_coarse_ts64 (&begin);trace_damos_before_apply (cidx, sidx, tidx, r, nr_accesses,damon_nr_regions (t), do_trace);
Signature · 1 line
2.47.3
[email protected]
AI reviewRe: [PATCH v2 1/2] mm/damon/core: charge only the part of a region the filter left
Sashiko has reviewed this patch and found no issues. It looks great!
Quoted 18 lines
From: Donggeun Yoo <[email protected]>
An address range DAMOS filter that partially overlaps a monitoring region splits the region at the filter boundary, so the scheme action is applied to only one side of it.
damos_apply_scheme()reads the region size once on entry, beforedamos_core_filter_out()performs that split.The quota charge and the statistics update therefore account for the region as it was before the trim. schemes/<S>/stats/sz_tried counts memory the filter excluded, which
Documentation/mm/damon/design.rstsays is not counted as tried, and with quotas/bytes set the excluded part is charged against the budget, throttling the scheme to a fraction of what was configured.User impact is that DAMOS could unexpectedly slowly run, due to over-charged quota. DAMOS stat could also be confusing. No critical events such as crashes or leask happen.
Quoted 7 lines
Re-read the region size after the core filters have run.
Fixes ab9bda001b68 ("mm/damon/core: introduce address range type damos filter")Cc <[email protected]> # 6.6.xSigned-off-by Donggeun Yoo <[email protected]>Reviewee-by SJ Park <[email protected]>Signed-off-by SJ Park <[email protected]>
Sashiko has reviewed this patch and found no issues. It looks great!
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=1
SJ Park
maintainerPATCH v2 2/2 mm/damon/tests/core-kunit: test the size charged for a filter-trimmed region
In reply to SJ ParkFrom: Donggeun Yoo <[email protected]>
damos_test_filter_out() checks that an address range filter splits a region at the filter boundary, and stops there. Nothing checks what damos_apply_scheme() then charges to the quota and reports as tried.
damos_test_apply_scheme_filtered_sz() covers the two ways such a filter trims a region: one that starts before the filter's range, and one that starts inside it.
damos_test_apply_scheme_filter_sz_unchanged() and damos_test_apply_scheme_quota_sz() cover the cases whose accounting must not change: a region wholly inside a reject range, one wholly inside an allow range, two filters trimming in a single call, a filter type that never splits, no filter at all, a region the quota trims, and a quota remainder too small for one region.
--- mm/damon/tests/core-kunit.h | 316 ++++++++++++++++++++++++++++++++++++ 1 file changed, 316 insertions(+)
mm/damon/tests/core-kunit.h +316 -0
@@ -1706,6 +1706,319 @@ static void damos_test_filter_out(struct kunit *test)damos_free_filter (f); } +static unsigned long damos_test_apply_scheme_stub (struct damon_ctx *c, +struct damon_target *t,struct damon_region *r, +struct damos *s,unsigned long *sz_filter_passed) +{ +return 0 ; +} + +static void damos_test_apply_scheme_filtered_sz (struct kunit *test) +{ +struct damos_access_pattern pattern = { + .min_sz_region =0 , + .max_sz_region =ULONG_MAX , + .min_nr_accesses =0 , + .max_nr_accesses =UINT_MAX , + .min_age_region =0 , + .max_age_region =UINT_MAX , + }; +unsigned long min_sz =DAMON_MIN_REGION_SZ ; +struct damos_watermarks wmarks = {}; +struct damos_quota quota = {}; +struct damon_ctx *c; +struct damon_target *t; +struct damon_region *r; +struct damos_filter *f; +struct damos *s; + + c =damon_new_ctx (); +if (!c) +kunit_skip (test,"ctx alloc fail" ); + c->ops.apply_scheme = damos_test_apply_scheme_stub; + + s =damon_new_scheme (&pattern,DAMOS_STAT ,0 , "a, &wmarks, +NUMA_NO_NODE ); +if (!s) { +damon_destroy_ctx (c); +kunit_skip (test,"scheme alloc fail" ); + } +damon_add_scheme (c, s); + + t =damon_new_target (); +if (!t) { +damon_destroy_ctx (c); +kunit_skip (test,"target alloc fail" ); + } +damon_add_target (c, t); + + f =damos_new_filter (DAMOS_FILTER_TYPE_ADDR , true, false); +if (!f) { +damon_destroy_ctx (c); +kunit_skip (test,"filter alloc fail" ); + } + f->addr_range = (struct damon_addr_range ){ + .start =2 * min_sz, + .end =8 * min_sz + }; +damos_add_filter (s, f); +damos_set_filters_default_reject (s); + +/* reject filter, region starting before the range */ + r =damon_new_region (0 ,4 * min_sz); +if (!r) { +damon_destroy_ctx (c); +kunit_skip (test,"region alloc fail" ); + } +damon_add_region (r, t); + +damos_apply_scheme (c, t, r, s); +KUNIT_EXPECT_EQ (test,damon_nr_regions (t),2 ); +KUNIT_EXPECT_EQ (test, r->ar.end,2 * min_sz); +KUNIT_EXPECT_EQ (test, s->stat.sz_tried,2 * min_sz); + +if (damon_nr_regions (t) !=2 ) + goto out; +damon_destroy_region (damon_next_region (r), t); +damon_destroy_region (r, t); + s->stat = (struct damos_stat ){}; + +/* allow filter, region starting inside the range */ + f->allow = true; +damos_set_filters_default_reject (s); + r =damon_new_region (2 * min_sz,10 * min_sz); +if (!r) { +damon_destroy_ctx (c); +kunit_skip (test,"region alloc fail" ); + } +damon_add_region (r, t); + +damos_apply_scheme (c, t, r, s); +KUNIT_EXPECT_EQ (test,damon_nr_regions (t),2 ); +KUNIT_EXPECT_EQ (test, r->ar.end,8 * min_sz); +KUNIT_EXPECT_EQ (test, s->stat.sz_tried,6 * min_sz); + +out: +damon_destroy_ctx (c); +} + +static void damos_test_apply_scheme_filter_sz_unchanged (struct kunit *test) +{ +struct damos_access_pattern pattern = { + .min_sz_region =0 , + .max_sz_region =ULONG_MAX , + .min_nr_accesses =0 , + .max_nr_accesses =UINT_MAX , + .min_age_region =0 , + .max_age_region =UINT_MAX , + }; +unsigned long min_sz =DAMON_MIN_REGION_SZ ; +struct damos_watermarks wmarks = {}; +struct damos_quota quota = {}; +struct damos_filter *f, *f2; +struct damon_ctx *c; +struct damon_target *t; +struct damon_region *r; +struct damos *s; + + c =damon_new_ctx (); +if (!c) +kunit_skip (test,"ctx alloc fail" ); + c->ops.apply_scheme = damos_test_apply_scheme_stub; + + s =damon_new_scheme (&pattern,DAMOS_STAT ,0 , "a, &wmarks, +NUMA_NO_NODE ); +if (!s) { +damon_destroy_ctx (c); +kunit_skip (test,"scheme alloc fail" ); + } +damon_add_scheme (c, s); + + t =damon_new_target (); +if (!t) { +damon_destroy_ctx (c); +kunit_skip (test,"target alloc fail" ); + } +damon_add_target (c, t); + + f =damos_new_filter (DAMOS_FILTER_TYPE_ADDR , true, false); +if (!f) { +damon_destroy_ctx (c); +kunit_skip (test,"filter alloc fail" ); + } + f->addr_range = (struct damon_addr_range ){ + .start =2 * min_sz, .end =8 * min_sz}; +damos_add_filter (s, f); +damos_set_filters_default_reject (s); + +/* wholly inside a reject range: not counted at all */ + r =damon_new_region (4 * min_sz,6 * min_sz); +if (!r) { +damon_destroy_ctx (c); +kunit_skip (test,"region alloc fail" ); + } +damon_add_region (r, t); + +damos_apply_scheme (c, t, r, s); +KUNIT_EXPECT_EQ (test, s->stat.nr_tried,0 ); +KUNIT_EXPECT_EQ (test, s->stat.sz_tried,0 ); +KUNIT_EXPECT_EQ (test,damon_nr_regions (t),1 ); + +/* wholly inside an allow range: counted whole, not split */ + f->allow = true; +damos_set_filters_default_reject (s); + +damos_apply_scheme (c, t, r, s); +KUNIT_EXPECT_EQ (test, s->stat.sz_tried,2 * min_sz); +KUNIT_EXPECT_EQ (test,damon_nr_regions (t),1 ); + +/* two filters, each trimming: the size left after both is counted */ +damon_destroy_region (r, t); + s->stat = (struct damos_stat ){}; + f->allow = false; + f->addr_range = (struct damon_addr_range ){ + .start =4 * min_sz, .end =12 * min_sz}; + f2 =damos_new_filter (DAMOS_FILTER_TYPE_ADDR , true, false); +if (!f2) { +damon_destroy_ctx (c); +kunit_skip (test,"filter alloc fail" ); + } + f2->addr_range = (struct damon_addr_range ){ + .start =2 * min_sz, .end =3 * min_sz}; +damos_add_filter (s, f2); +damos_set_filters_default_reject (s); + + r =damon_new_region (0 ,8 * min_sz); +if (!r) { +damon_destroy_ctx (c); +kunit_skip (test,"region alloc fail" ); + } +damon_add_region (r, t); + +damos_apply_scheme (c, t, r, s); +KUNIT_EXPECT_EQ (test,damon_nr_regions (t),3 ); +KUNIT_EXPECT_EQ (test, r->ar.end,2 * min_sz); +KUNIT_EXPECT_EQ (test, s->stat.sz_tried,2 * min_sz); + +/* a core filter that never splits: counted whole */ +damos_destroy_filter (f2); +damos_destroy_filter (f); + f =damos_new_filter (DAMOS_FILTER_TYPE_TARGET , true, true); +if (!f) { +damon_destroy_ctx (c); +kunit_skip (test,"filter alloc fail" ); + } + f->target_idx =0 ; +damos_add_filter (s, f); +damos_set_filters_default_reject (s); + s->stat = (struct damos_stat ){}; + +damos_apply_scheme (c, t, r, s); +KUNIT_EXPECT_EQ (test,damon_nr_regions (t),3 ); +KUNIT_EXPECT_EQ (test, r->ar.end,2 * min_sz); +KUNIT_EXPECT_EQ (test, s->stat.sz_tried,2 * min_sz); + +damon_destroy_ctx (c); +} + +static void damos_test_apply_scheme_quota_sz (struct kunit *test) +{ +struct damos_access_pattern pattern = { + .min_sz_region =0 , + .max_sz_region =ULONG_MAX , + .min_nr_accesses =0 , + .max_nr_accesses =UINT_MAX , + .min_age_region =0 , + .max_age_region =UINT_MAX , + }; +unsigned long min_sz =DAMON_MIN_REGION_SZ ; +struct damos_watermarks wmarks = {}; +struct damos_quota quota = {}; +struct damon_region *r, *next; +struct damon_ctx *c; +struct damon_target *t; +struct damos *s; + + c =damon_new_ctx (); +if (!c) +kunit_skip (test,"ctx alloc fail" ); + + s =damon_new_scheme (&pattern,DAMOS_STAT ,0 , "a, &wmarks, +NUMA_NO_NODE ); +if (!s) { +damon_destroy_ctx (c); +kunit_skip (test,"scheme alloc fail" ); + } +damon_add_scheme (c, s); +damos_set_filters_default_reject (s); + + t =damon_new_target (); +if (!t) { +damon_destroy_ctx (c); +kunit_skip (test,"target alloc fail" ); + } +damon_add_target (c, t); + +/* no apply_scheme operation: the whole region is counted */ + r =damon_new_region (0 ,4 * min_sz); +if (!r) { +damon_destroy_ctx (c); +kunit_skip (test,"region alloc fail" ); + } +damon_add_region (r, t); + +damos_apply_scheme (c, t, r, s); +KUNIT_EXPECT_EQ (test, s->stat.sz_tried,4 * min_sz); +KUNIT_EXPECT_EQ (test,damon_nr_regions (t),1 ); + +/* no filter and no quota: the whole region is counted */ + c->ops.apply_scheme = damos_test_apply_scheme_stub; + s->stat = (struct damos_stat ){}; + +damos_apply_scheme (c, t, r, s); +KUNIT_EXPECT_EQ (test, s->stat.sz_tried,4 * min_sz); +KUNIT_EXPECT_EQ (test,damon_nr_regions (t),1 ); + +/* the quota trims the region: the trimmed size is counted */ +damon_for_each_region_safe (r, next, t) +damon_destroy_region (r, t); + s->stat = (struct damos_stat ){}; + s->quota.esz =2 * min_sz; + s->quota.charged_sz =0 ; + + r =damon_new_region (0 ,8 * min_sz); +if (!r) { +damon_destroy_ctx (c); +kunit_skip (test,"region alloc fail" ); + } +damon_add_region (r, t); + +damos_apply_scheme (c, t, r, s); +KUNIT_EXPECT_EQ (test, r->ar.end,2 * min_sz); +KUNIT_EXPECT_EQ (test, s->stat.sz_tried,2 * min_sz); + +/* quota remainder below one region: tried, but nothing counted */ +damon_for_each_region_safe (r, next, t) +damon_destroy_region (r, t); + s->stat = (struct damos_stat ){}; + s->quota.esz =1 ; + s->quota.charged_sz =0 ; + + r =damon_new_region (0 ,4 * min_sz); +if (!r) { +damon_destroy_ctx (c); +kunit_skip (test,"region alloc fail" ); + } +damon_add_region (r, t); + +damos_apply_scheme (c, t, r, s); +KUNIT_EXPECT_EQ (test, s->stat.nr_tried,1 ); +KUNIT_EXPECT_EQ (test, s->stat.sz_tried,0 ); +KUNIT_EXPECT_EQ (test, r->ar.end,4 * min_sz); + +damon_destroy_ctx (c); +} +static void damon_test_feed_loop_next_input (struct kunit *test) {unsigned long last_input =900000 , current_score =200 ;
@@ -1959,6 +2272,9 @@ static struct kunit_case damon_test_cases[] = {KUNIT_CASE (damon_test_commit_ctx),KUNIT_CASE (damon_test_valid_probe_params),KUNIT_CASE (damos_test_filter_out), +KUNIT_CASE (damos_test_apply_scheme_filtered_sz), +KUNIT_CASE (damos_test_apply_scheme_filter_sz_unchanged), +KUNIT_CASE (damos_test_apply_scheme_quota_sz),KUNIT_CASE (damon_test_feed_loop_next_input),KUNIT_CASE (damon_test_set_filters_default_reject), KUNIT_CASE(damon_test_apply_min_nr_regions),
Signature · 1 line
2.47.3
[email protected]
AI reviewRe: [PATCH v2 2/2] mm/damon/tests/core-kunit: test the size charged for a filter-trimmed region
Sashiko has reviewed this patch and found no issues. It looks great!
Quoted 17 lines
From: Donggeun Yoo <[email protected]>
damos_test_filter_out()checks that an address range filter splits a region at the filter boundary, and stops there. Nothing checks whatdamos_apply_scheme()then charges to the quota and reports as tried.
damos_test_apply_scheme_filtered_sz()covers the two ways such a filter trims a region: one that starts before the filter's range, and one that starts inside it.
damos_test_apply_scheme_filter_sz_unchanged()anddamos_test_apply_scheme_quota_sz()cover the cases whose accounting must not change: a region wholly inside a reject range, one wholly inside an allow range, two filters trimming in a single call, a filter type that never splits, no filter at all, a region the quota trims, and a quota remainder too small for one region.
Quoted 3 lines
Signed-off-by Donggeun Yoo <[email protected]>Reviewed-by SJ Park <[email protected]>Signed-off-by SJ Park <[email protected]>
Sashiko has reviewed this patch and found no issues. It looks great!
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=2