Add kunit test to ensure damon_commit_filter() updates destination filter as expected for valid inputs. Signed-off-by: SJ Park --- mm/damon/tests/core-kunit.h | 40 +++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/mm/damon/tests/core-kunit.h b/mm/damon/tests/core-kunit.h index d0bed01a5b7fe..c2ddef3c76d35 100644 --- a/mm/damon/tests/core-kunit.h +++ b/mm/damon/tests/core-kunit.h @@ -1364,6 +1364,45 @@ static void damon_test_commit_target_regions(struct kunit *test) (unsigned long[][2]) {{3, 8}, {8, 10}}, 2); } +static void damon_test_commit_filter_for(struct kunit *test, + struct damon_filter *dst, struct damon_filter *src) +{ + damon_commit_filter(dst, src); + KUNIT_EXPECT_EQ(test, dst->type, src->type); + KUNIT_EXPECT_EQ(test, dst->matching, src->matching); + KUNIT_EXPECT_EQ(test, dst->allow, src->allow); + switch (src->type) { + case DAMON_FILTER_TYPE_MEMCG: + KUNIT_EXPECT_EQ(test, dst->memcg_id, src->memcg_id); + break; + default: + break; + } +} + +static void damon_test_commit_filter(struct kunit *test) +{ + struct damon_filter dst = { + .type = DAMON_FILTER_TYPE_ANON, + .matching = false, + .allow = false, + }; + + damon_test_commit_filter_for(test, &dst, + &(struct damon_filter){ + .type = DAMON_FILTER_TYPE_ANON, + .matching = true, + .allow = true, + }); + damon_test_commit_filter_for(test, &dst, + &(struct damon_filter){ + .type = DAMON_FILTER_TYPE_MEMCG, + .matching = false, + .allow = false, + .memcg_id = 123, + }); +} + static void damon_test_commit_ctx(struct kunit *test) { struct damon_ctx *src, *dst; @@ -1771,6 +1810,7 @@ static struct kunit_case damon_test_cases[] = { KUNIT_CASE(damos_test_commit_pageout), KUNIT_CASE(damos_test_commit_migrate_hot), KUNIT_CASE(damon_test_commit_target_regions), + KUNIT_CASE(damon_test_commit_filter), KUNIT_CASE(damon_test_commit_ctx), KUNIT_CASE(damon_test_valid_probe_params), KUNIT_CASE(damos_test_filter_out), -- 2.47.3 Add kunit test to ensure damon_commit_probes() updates destination DAMON context with source probes as expected. Signed-off-by: SJ Park --- mm/damon/tests/core-kunit.h | 84 +++++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/mm/damon/tests/core-kunit.h b/mm/damon/tests/core-kunit.h index c2ddef3c76d35..c01e6a75cadc1 100644 --- a/mm/damon/tests/core-kunit.h +++ b/mm/damon/tests/core-kunit.h @@ -1403,6 +1403,89 @@ static void damon_test_commit_filter(struct kunit *test) }); } +static struct damon_ctx *damon_test_help_setup_probes(unsigned int weights[], + int nr_weights) +{ + struct damon_ctx *ctx; + struct damon_probe *probe; + int i; + + ctx = damon_new_ctx(); + if (!ctx) + return NULL; + for (i = 0; i < nr_weights; i++) { + probe = damon_new_probe(); + if (!probe) { + damon_destroy_ctx(ctx); + return NULL; + } + probe->weight = weights[i]; + damon_add_probe(ctx, probe); + } + return ctx; +} + +static void damon_test_commit_probes_for(struct kunit *test, + unsigned int dst_weights[], int nr_dst_probes, + unsigned int src_weights[], int nr_src_probes) +{ + struct damon_ctx *dst, *src; + int err; + struct damon_probe *dst_probe, *src_probe; + + dst = damon_test_help_setup_probes(dst_weights, nr_dst_probes); + if (!dst) + kunit_skip(test, "dst alloc fail"); + src = damon_test_help_setup_probes(src_weights, nr_src_probes); + if (!src) { + damon_destroy_ctx(dst); + kunit_skip(test, "src alloc fail"); + } + + err = damon_commit_probes(dst, src); + KUNIT_EXPECT_EQ(test, err, 0); + if (err) + goto out; + nr_dst_probes = 0; + damon_for_each_probe(dst_probe, dst) + nr_dst_probes++; + nr_src_probes = 0; + damon_for_each_probe(src_probe, src) + nr_src_probes++; + KUNIT_EXPECT_EQ(test, nr_dst_probes, nr_src_probes); + if (nr_dst_probes != nr_src_probes) + goto out; + nr_dst_probes = 0; + damon_for_each_probe(dst_probe, dst) { + src_probe = damon_nth_probe(nr_dst_probes, src); + KUNIT_EXPECT_EQ(test, src_probe->weight, dst_probe->weight); + nr_dst_probes++; + } +out: + damon_destroy_ctx(dst); + damon_destroy_ctx(src); +} + +static void damon_test_commit_probes(struct kunit *test) +{ + damon_test_commit_probes_for(test, + (unsigned int[]){}, 0, (unsigned int[]){}, 0); + damon_test_commit_probes_for(test, + (unsigned int[]){}, 0, (unsigned int[]){1}, 1); + damon_test_commit_probes_for(test, + (unsigned int[]){}, 0, (unsigned int[]){1, 2}, 2); + damon_test_commit_probes_for(test, + (unsigned int[]){1}, 1, (unsigned int[]){2}, 1); + damon_test_commit_probes_for(test, + (unsigned int[]){1}, 1, (unsigned int[]){2, 3}, 2); + damon_test_commit_probes_for(test, + (unsigned int[]){2, 3}, 2, (unsigned int[]){1}, 1); + damon_test_commit_probes_for(test, + (unsigned int[]){2, 3}, 2, (unsigned int[]){}, 0); + damon_test_commit_probes_for(test, + (unsigned int[]){2}, 1, (unsigned int[]){}, 0); +} + static void damon_test_commit_ctx(struct kunit *test) { struct damon_ctx *src, *dst; @@ -1811,6 +1894,7 @@ static struct kunit_case damon_test_cases[] = { KUNIT_CASE(damos_test_commit_migrate_hot), KUNIT_CASE(damon_test_commit_target_regions), KUNIT_CASE(damon_test_commit_filter), + KUNIT_CASE(damon_test_commit_probes), KUNIT_CASE(damon_test_commit_ctx), KUNIT_CASE(damon_test_valid_probe_params), KUNIT_CASE(damos_test_filter_out), -- 2.47.3 Extend _damon_sysfs.py to support staging and committing DAMON probes. It will be used for setting DAMON probes via sysfs changes for testing purposes. Signed-off-by: SJ Park --- tools/testing/selftests/damon/_damon_sysfs.py | 125 +++++++++++++++++- 1 file changed, 124 insertions(+), 1 deletion(-) diff --git a/tools/testing/selftests/damon/_damon_sysfs.py b/tools/testing/selftests/damon/_damon_sysfs.py index f604b7d6530b3..e7095c3365245 100644 --- a/tools/testing/selftests/damon/_damon_sysfs.py +++ b/tools/testing/selftests/damon/_damon_sysfs.py @@ -570,6 +570,117 @@ class IntervalsGoal: return err return None +class DamonFilter: + type_ = None + matching = None + allow = None + filters = None + path = None + idx = None + + def __init__(self, type_='anon', matching=False, allow=False, path=None): + self.type_ = type_ + self.matching = matching + self.allow = allow + self.path = path + + def sysfs_dir(self): + return os.path.join(self.filters.sysfs_dir(), '%d' % self.idx) + + def stage(self): + err = write_file(os.path.join(self.sysfs_dir(), 'type'), self.type_) + if err is not None: + return err + err = write_file(os.path.join(self.sysfs_dir(), 'matching'), + 'Y' if self.matching else 'N') + if err is not None: + return err + err = write_file(os.path.join(self.sysfs_dir(), 'allow'), + 'Y' if self.allow else 'N') + if err is not None: + return err + if self.type_ == 'memcg': + err = write_file(os.path.join(self.sysfs_dir(), 'path'), self.path) + if err is not None: + return err + return None + +class DamonFilters: + filters = None + probe = None + + def __init__(self, filters=None): + if filters is None: + filters = [] + self.filters = filters + for idx, filter in enumerate(self.filters): + filter.filters = self + filter.idx = idx + + def sysfs_dir(self): + return os.path.join(self.probe.sysfs_dir(), 'filters') + + def stage(self): + err = write_file( + os.path.join(self.sysfs_dir(), 'nr_filters'), + len(self.filters)) + if err is not None: + return err + for filter in self.filters: + err = filter.stage() + if err is not None: + return err + return None + +class DamonProbe: + weight = None + filters = None + probes = None + idx = None + + def __init__(self, weight=0, filters=None): + self.weight = weight + if filters is None: + filters = DamonFilters() + self.filters = filters + self.filters.probe = self + + def sysfs_dir(self): + return os.path.join(self.probes.sysfs_dir(), '%d' % self.idx) + + def stage(self): + err = write_file( + os.path.join(self.sysfs_dir(), 'weight'), '%d' % self.weight) + if err is not None: + return err + return self.filters.stage() + +class DamonProbes: + probes = None + attrs = None + + def __init__(self, probes=None): + if probes is None: + probes = [] + self.probes = probes + for idx, probe in enumerate(self.probes): + probe.probes = self + probe.idx = idx + + def sysfs_dir(self): + return os.path.join(self.attrs.sysfs_dir(), 'probes') + + def stage(self): + err = write_file(os.path.join(self.sysfs_dir(), 'nr_probes'), + len(self.probes)) + if err is not None: + return err + for probe in self.probes: + err = probe.stage() + if err is not None: + return err + return None + class DamonAttrs: sample_us = None aggr_us = None @@ -577,11 +688,12 @@ class DamonAttrs: update_us = None min_nr_regions = None max_nr_regions = None + probes = None context = None def __init__(self, sample_us=5000, aggr_us=100000, intervals_goal=None, update_us=1000000, min_nr_regions=10, - max_nr_regions=1000): + max_nr_regions=1000, probes=None): self.sample_us = sample_us self.aggr_us = aggr_us if intervals_goal is None: @@ -591,6 +703,10 @@ class DamonAttrs: self.update_us = update_us self.min_nr_regions = min_nr_regions self.max_nr_regions = max_nr_regions + if probes is None: + probes = DamonProbes() + self.probes = probes + self.probes.attrs = self def interval_sysfs_dir(self): return os.path.join(self.context.sysfs_dir(), 'monitoring_attrs', @@ -600,6 +716,9 @@ class DamonAttrs: return os.path.join(self.context.sysfs_dir(), 'monitoring_attrs', 'nr_regions') + def sysfs_dir(self): + return os.path.join(self.context.sysfs_dir(), 'monitoring_attrs') + def stage(self): err = write_file(os.path.join(self.interval_sysfs_dir(), 'sample_us'), self.sample_us) @@ -629,6 +748,10 @@ class DamonAttrs: if err is not None: return err + err = self.probes.stage() + if err is not None: + return err + class DamonCtx: ops = None monitoring_attrs = None -- 2.47.3 Extend drgn_dump_damon_status.py to dump damon_ctx->probes. It will be used to see if in-kernel DAMON status are changed as the user sets the probes via sysfs. Signed-off-by: SJ Park --- .../selftests/damon/drgn_dump_damon_status.py | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/tools/testing/selftests/damon/drgn_dump_damon_status.py b/tools/testing/selftests/damon/drgn_dump_damon_status.py index 09552e91bc782..4622046fd0118 100755 --- a/tools/testing/selftests/damon/drgn_dump_damon_status.py +++ b/tools/testing/selftests/damon/drgn_dump_damon_status.py @@ -48,6 +48,37 @@ def attrs_to_dict(attrs): ['max_nr_regions', int], ]) +def filter_to_dict(damon_filter): + filter_type_keyword = { + 0: 'anon', + 1: 'memcg', + } + dict_ = { + 'type': filter_type_keyword[int(damon_filter.type)], + 'matching': bool(damon_filter.matching), + 'allow': bool(damon_filter.allow), + } + type_ = dict_['type'] + if type_ == 'memcg': + dict_['memcg_id'] = int(damon_filter.memcg_id) + return dict_ + +def filters_to_list(filters): + return [filter_to_dict(f) + for f in list_for_each_entry( + 'struct damon_filter', filters.address_of_(), 'list')] + +def probe_to_dict(probe): + return to_dict(probe, [ + ['weight', int], + ['filters', filters_to_list], + ]) + +def probes_to_list(probes): + return [probe_to_dict(p) + for p in list_for_each_entry( + 'struct damon_probe', probes.address_of_(), 'list')] + def addr_range_to_dict(addr_range): return to_dict(addr_range, [ ['start', int], @@ -199,6 +230,7 @@ def damon_ctx_to_dict(ctx): return to_dict(ctx, [ ['ops', ops_to_dict], ['attrs', attrs_to_dict], + ['probes', probes_to_list], ['adaptive_targets', targets_to_list], ['schemes', schemes_to_list], ['pause', bool], -- 2.47.3 Extend DAMON sysfs testing commit assertion helper function to check probes too. Signed-off-by: SJ Park --- tools/testing/selftests/damon/sysfs.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tools/testing/selftests/damon/sysfs.py b/tools/testing/selftests/damon/sysfs.py index 88a26422ff44c..159cbeac067f8 100755 --- a/tools/testing/selftests/damon/sysfs.py +++ b/tools/testing/selftests/damon/sysfs.py @@ -178,6 +178,24 @@ def assert_monitoring_attrs_committed(attrs, dump): assert_true(dump['max_nr_regions'] == attrs.max_nr_regions, 'max_nr_regions', dump) +def assert_damon_filters_committed(filters, dump): + assert_true(len(dump) == len(filters.filters), 'probe filters', dump) + for idx, damon_filter in enumerate(filters.filters): + filter_dump = dump[idx] + assert_true(filter_dump['type'] == damon_filter.type_, 'type', + filter_dump) + assert_true(filter_dump['matching'] == damon_filter.matching, + 'matching', filter_dump) + assert_true(filter_dump['allow'] == damon_filter.allow, 'allow', + filter_dump) + +def assert_probes_committed(probes, dump): + assert_true(len(dump) == len(probes.probes), 'probes length', dump) + for idx, probe in enumerate(probes.probes): + probe_dump = dump[idx] + assert_true(probe.weight == probe_dump['weight'], 'weight', probe_dump) + assert_damon_filters_committed(probe.filters, probe_dump['filters']) + def assert_monitoring_target_committed(target, dump): # target.pid is the pid "number", while dump['pid'] is 'struct pid' # pointer, and hence cannot be compared. @@ -196,6 +214,7 @@ def assert_ctx_committed(ctx, dump): } assert_true(dump['ops']['id'] == ops_val[ctx.ops], 'ops_id', dump) assert_monitoring_attrs_committed(ctx.monitoring_attrs, dump['attrs']) + assert_probes_committed(ctx.monitoring_attrs.probes, dump['probes']) assert_monitoring_targets_committed(ctx.targets, dump['adaptive_targets']) assert_schemes_committed(ctx.schemes, dump['schemes']) assert_true(dump['pause'] == ctx.pause, 'pause', dump) -- 2.47.3 Extend sysfs.py to commit DAMON probes via sysfs, and see if it changed in-kernel DAMON status as expected using drgn. Signed-off-by: SJ Park --- tools/testing/selftests/damon/sysfs.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/tools/testing/selftests/damon/sysfs.py b/tools/testing/selftests/damon/sysfs.py index 159cbeac067f8..66c826189320d 100755 --- a/tools/testing/selftests/damon/sysfs.py +++ b/tools/testing/selftests/damon/sysfs.py @@ -319,7 +319,21 @@ def main(): intervals_goal=_damon_sysfs.IntervalsGoal( access_bp=400, aggrs=3, min_sample_us=5000, max_sample_us=10000000), - update_us=2000000), + update_us=2000000, + probes=_damon_sysfs.DamonProbes( + probes=[_damon_sysfs.DamonProbe( + weight=42, + filters=_damon_sysfs.DamonFilters( + filters=[ + _damon_sysfs.DamonFilter( + type_='anon', + matching=True, + allow=True, + ), + ]), + ), + ]), + ), schemes=[_damon_sysfs.Damos( action='pageout', access_pattern=_damon_sysfs.DamosAccessPattern( -- 2.47.3