mm/damon: add kunit and selftests for probes and probe weights
Subsystems (3)
SJ Park
maintainercover mm/damon: add kunit and selftests for probes and probe weights
DAMON recently introduced probes and probe weights. Add kunit and selftests for ensuring the parameters for features can be set using the core API and the sysfs ABI, respectively.
Changes from RFC v1
- RFC v1: https://lore.kernel.org/[email protected]
- Fix a typo, memory leak and NULL dereference in kunit test.
- Fix ignored error in selftest.
SJ Park (6):
mm/damon/tests/core-kunit: test damon_commit_filter() mm/damon/tests/core-kunit: add damon_commit_probes() test selftests/damon/_damon_sysfs: implement DamonProbes selftests/damon/drgn_dump_damon_status: dump probes selftests/damon/sysfs.py: extend commit assertion function for probes selftests/damon/sysfs.py: test damon probes
mm/damon/tests/core-kunit.h | 124 +++++++++++++++++ tools/testing/selftests/damon/_damon_sysfs.py | 125 +++++++++++++++++- .../selftests/damon/drgn_dump_damon_status.py | 32 +++++ tools/testing/selftests/damon/sysfs.py | 35 ++++- 4 files changed, 314 insertions(+), 2 deletions(-)
base-commit: 1e413a0c40e2aca4a55f9ba664b2b7ac2a03c9da
Signature · 1 line
2.47.3
SJ Park
maintainerRFC PATCH 1/6 mm/damon/tests/core-kunit: test damon_commit_filter()
Add kunit test to ensure damon_commit_filter() updates destination filter as expected for valid inputs.
--- mm/damon/tests/core-kunit.h | 40 +++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+)
mm/damon/tests/core-kunit.h +40 -0
@@ -1354,6 +1354,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;
@@ -1705,6 +1744,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 (damos_test_filter_out), KUNIT_CASE(damon_test_feed_loop_next_input),
Signature · 1 line
2.47.3
SJ Park
maintainerRFC PATCH 2/6 mm/damon/tests/core-kunit: add damon_commit_probes() test
In reply to SJ ParkAdd kunit test to ensure damon_commit_probes() updates destination DAMON context with source probes as expected.
--- mm/damon/tests/core-kunit.h | 84 +++++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+)
mm/damon/tests/core-kunit.h +84 -0
@@ -1393,6 +1393,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;
@@ -1745,6 +1828,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 (damos_test_filter_out), KUNIT_CASE(damon_test_feed_loop_next_input),
Signature · 1 line
2.47.3
SJ Park
maintainerRFC PATCH 3/6 selftests/damon/_damon_sysfs: implement DamonProbes
In reply to SJ ParkExtend _damon_sysfs.py to support staging and committing DAMON probes. It will be used for setting DAMON probes via sysfs changes for testing purposes.
--- tools/testing/selftests/damon/_damon_sysfs.py | 125 +++++++++++++++++- 1 file changed, 124 insertions(+), 1 deletion(-)
tools/testing/selftests/damon/_damon_sysfs.py +124 -1
@@ -558,6 +558,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
@@ -565,11 +676,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=IntervalsGoal(), update_us=1000000, - min_nr_regions=10, max_nr_regions=1000): + min_nr_regions=10, max_nr_regions=1000, probes=None): self.sample_us = sample_us self.aggr_us = aggr_us self.intervals_goal = intervals_goal
@@ -577,6 +689,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',
@@ -586,6 +702,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)
@@ -615,6 +734,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
Signature · 1 line
2.47.3
SJ Park
maintainerRFC PATCH 4/6 selftests/damon/drgn_dump_damon_status: dump probes
In reply to SJ ParkExtend 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.
--- .../selftests/damon/drgn_dump_damon_status.py | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+)
tools/testing/selftests/damon/drgn_dump_damon_status.py +32 -0
@@ -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],
Signature · 1 line
2.47.3
SJ Park
maintainerRFC PATCH 5/6 selftests/damon/sysfs.py: extend commit assertion function for probes
In reply to SJ ParkExtend DAMON sysfs testing commit assertion helper function to check probes too.
--- tools/testing/selftests/damon/sysfs.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+)
tools/testing/selftests/damon/sysfs.py +19 -0
@@ -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)
Signature · 1 line
2.47.3
SJ Park
maintainerRFC PATCH 6/6 selftests/damon/sysfs.py: test damon probes
In reply to SJ ParkExtend sysfs.py to commit DAMON probes via sysfs, and see if it changed in-kernel DAMON status as expected using drgn.
--- tools/testing/selftests/damon/sysfs.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-)
tools/testing/selftests/damon/sysfs.py +15 -1
@@ -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(
Signature · 1 line
2.47.3