Skip to contentSkip to conversation

mm/damon: add kunit and selftests for probes and probe weights

RFC PATCH In review SJ Park 2 participants 15 messages Jul 29 – Jul 30

Subsystems (3)
lore
Share & tools
lore

SJ Park

author maintainer
#0 lore raw

cover 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.

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                   | 121 +++++++++++++++++
 tools/testing/selftests/damon/_damon_sysfs.py | 123 +++++++++++++++++-
 .../selftests/damon/drgn_dump_damon_status.py |  32 +++++
 tools/testing/selftests/damon/sysfs.py        |  35 ++++-
 4 files changed, 309 insertions(+), 2 deletions(-)

base-commit: 2f558ff382eed84b9ffde4dbd630a1fc4c57fab4

Signature · 1 line
2.47.3
author maintainer
#1 lore raw

RFC 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
index c7bda8aadef3f..ff864402e9c75 100644
@@ -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 DAMOS_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
#2 lore raw

Re: [RFC PATCH 1/6] mm/damon/tests/core-kunit: test damon_commit_filter()

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [Medium] The kunit test uses the wrong enum type `DAMOS_FILTER_TYPE_MEMCG` instead of…

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] The kunit test uses the wrong enum type DAMOS_FILTER_TYPE_MEMCG instead of DAMON_FILTER_TYPE_MEMCG when checking src->type, causing the memcg_id assertion to be silently skipped. --

--- Patch [1]: [RFC PATCH 1/6] mm/damon/tests/core-kunit: test damon_commit_filter() --- commit e16ea26a19f85858bdb608d81842e976d30c3270
Author: SJ Park <[email protected]>

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.

Quoted 5 lines
mm/damon/tests/core-kunit.h +0 -0
index 4a536d41cdb2d..6cbe22accabfb 100644
@@ -1306,6 +1306,45 @@ static void damon_test_commit_target_regions(struct kunit *test)

[ ... ]

mm/damon/tests/core-kunit.h static void damon_test_commit_target_regions(struct kunit *test) · line 1367

Quoted 11 lines
mm/damon/tests/core-kunit.h +11 -0
+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 DAMOS_FILTER_TYPE_MEMCG:
+		KUNIT_EXPECT_EQ(test, dst->memcg_id, src->memcg_id);
+		break;

[Severity: Medium]
Will this case label ever be matched?

This code checks against DAMOS_FILTER_TYPE_MEMCG, but damon_test_commit_filter() below initializes the source filter with type DAMON_FILTER_TYPE_MEMCG.

Because of this mismatch, the switch statement falls through to the default case, and the test silently skips the assertion for memcg_id.

Quoted 7 lines
mm/damon/tests/core-kunit.h +7 -0
+	default:
+		break;
+	}
+}
+
+static void damon_test_commit_filter(struct kunit *test)
+{

[ ... ]

Quoted 8 lines
mm/damon/tests/core-kunit.h +8 -0
+	damon_test_commit_filter_for(test, &dst,
+			&(struct damon_filter){
+			.type = DAMON_FILTER_TYPE_MEMCG,
+			.matching = false,
+			.allow = false,
+			.memcg_id = 123,
+			});
+}
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=1
author maintainer
#3 lore raw

Re: [RFC PATCH 1/6] mm/damon/tests/core-kunit: test damon_commit_filter()

Quoted 36 lines · On Wed, 29 Jul 2026 14:52:57 +0000 [email protected] wrote:

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] The kunit test uses the wrong enum type DAMOS_FILTER_TYPE_MEMCG instead of DAMON_FILTER_TYPE_MEMCG when checking src->type, causing the memcg_id assertion to be silently skipped. --

--- Patch [1]: [RFC PATCH 1/6] mm/damon/tests/core-kunit: test damon_commit_filter() --- commit e16ea26a19f85858bdb608d81842e976d30c3270
Author: SJ Park <[email protected]>

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.

Quoted 5 lines
mm/damon/tests/core-kunit.h +0 -0
index 4a536d41cdb2d..6cbe22accabfb 100644
@@ -1306,6 +1306,45 @@ static void damon_test_commit_target_regions(struct kunit *test)

[ ... ]

mm/damon/tests/core-kunit.h static void damon_test_commit_target_regions(struct kunit *test) · line 1367

Quoted 11 lines
mm/damon/tests/core-kunit.h +11 -0
+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 DAMOS_FILTER_TYPE_MEMCG:
+		KUNIT_EXPECT_EQ(test, dst->memcg_id, src->memcg_id);
+		break;

[Severity: Medium]
Will this case label ever be matched?

Quoted 5 lines

This code checks against DAMOS_FILTER_TYPE_MEMCG, but damon_test_commit_filter() below initializes the source filter with type DAMON_FILTER_TYPE_MEMCG.

Because of this mismatch, the switch statement falls through to the default case, and the test silently skips the assertion for memcg_id.

Good catch, I will fix.

Quoted 11 lines
Quoted 8 lines
mm/damon/tests/core-kunit.h +7 -0
+	default:
+		break;
+	}
+}
+
+static void damon_test_commit_filter(struct kunit *test)
+{

[ ... ]

Quoted 12 lines
Quoted 8 lines
mm/damon/tests/core-kunit.h +8 -0
+	damon_test_commit_filter_for(test, &dst,
+			&(struct damon_filter){
+			.type = DAMON_FILTER_TYPE_MEMCG,
+			.matching = false,
+			.allow = false,
+			.memcg_id = 123,
+			});
+}
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=1

Thanks,
SJ

author maintainer
#4 lore raw

RFC PATCH 2/6 mm/damon/tests/core-kunit: add damon_commit_probes() test

In reply to SJ Park

Add kunit test to ensure damon_commit_probes() updates destination DAMON context with source probes as expected.

---
 mm/damon/tests/core-kunit.h | 81 +++++++++++++++++++++++++++++++++++++
 1 file changed, 81 insertions(+)
mm/damon/tests/core-kunit.h +81 -0
index ff864402e9c75..d1e8c612fea03 100644
@@ -1393,6 +1393,86 @@ 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)
+		return;
+	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);
+	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++;
+	}
+	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 +1825,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
#5 lore raw

Re: [RFC PATCH 2/6] mm/damon/tests/core-kunit: add damon_commit_probes() test

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider: - [Medium] Missing cleanup in the error path of `damon_test_commit_probes_for` causes a memory leak of…

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Medium] Missing cleanup in the error path of damon_test_commit_probes_for causes a memory leak of dynamically allocated test structures.
- [Medium] Missing bounds check in damon_test_commit_probes_for can lead to a NULL pointer dereference when a test assertion fails. --

--- Patch [2]: [RFC PATCH 2/6] mm/damon/tests/core-kunit: add damon_commit_probes() test --- commit 490c9d2d6b4090811aaa428df3f6d9ce89e2d406
Author: SJ Park <[email protected]>

mm/damon/tests/core-kunit: add damon_commit_probes() test

Add kunit test to ensure damon_commit_probes() updates destination DAMON context with source probes as expected.

Quoted 4 lines
mm/damon/tests/core-kunit.h +0 -0
index 6cbe22accabfb..6577410ace3da 100644

[ ... ]

Quoted 11 lines
mm/damon/tests/core-kunit.h +11 -0
+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");

mm/damon/tests/core-kunit.h static void damon_test_commit_filter(struct kunit *test) · line 1438

Quoted 10 lines
mm/damon/tests/core-kunit.h +10 -0
+	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)
+		return;

[Severity: Medium]
Does this error path leak the dst and src context structures?

If damon_commit_probes() returns an error, the early return here skips the cleanup calls to damon_destroy_ctx(dst) and damon_destroy_ctx(src) at the end of the function.

mm/damon/tests/core-kunit.h static void damon_test_commit_filter(struct kunit *test) · line 1449

Quoted 11 lines
mm/damon/tests/core-kunit.h +11 -0
+	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);
+	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);

[Severity: Medium]
Could this result in a NULL pointer dereference if the destination context ends up with more probes than the source context?

KUNIT_EXPECT_EQ() does not abort test execution if the previous check for nr_dst_probes == nr_src_probes fails. If dst has more probes, damon_nth_probe() will return NULL, and the next line accessing src_probe->weight will crash the test runner.

Quoted 5 lines
mm/damon/tests/core-kunit.h +5 -0
+		nr_dst_probes++;
+	}
+	damon_destroy_ctx(dst);
+	damon_destroy_ctx(src);
+}
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=2
author maintainer
#6 lore raw

Re: [RFC PATCH 2/6] mm/damon/tests/core-kunit: add damon_commit_probes() test

Quoted 46 lines · On Wed, 29 Jul 2026 15:00:27 +0000 [email protected] wrote:

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Medium] Missing cleanup in the error path of damon_test_commit_probes_for causes a memory leak of dynamically allocated test structures.
- [Medium] Missing bounds check in damon_test_commit_probes_for can lead to a NULL pointer dereference when a test assertion fails. --

--- Patch [2]: [RFC PATCH 2/6] mm/damon/tests/core-kunit: add damon_commit_probes() test --- commit 490c9d2d6b4090811aaa428df3f6d9ce89e2d406
Author: SJ Park <[email protected]>

mm/damon/tests/core-kunit: add damon_commit_probes() test

Add kunit test to ensure damon_commit_probes() updates destination DAMON context with source probes as expected.

Quoted 4 lines
mm/damon/tests/core-kunit.h +0 -0
index 6cbe22accabfb..6577410ace3da 100644

[ ... ]

Quoted 11 lines
mm/damon/tests/core-kunit.h +11 -0
+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");

mm/damon/tests/core-kunit.h static void damon_test_commit_filter(struct kunit *test) · line 1438

Quoted 10 lines
mm/damon/tests/core-kunit.h +10 -0
+	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)
+		return;

[Severity: Medium]
Does this error path leak the dst and src context structures?

Quoted 3 lines

If damon_commit_probes() returns an error, the early return here skips the cleanup calls to damon_destroy_ctx(dst) and damon_destroy_ctx(src) at the end of the function.

Good catch, I will fix.

Quoted 17 lines

mm/damon/tests/core-kunit.h static void damon_test_commit_filter(struct kunit *test) · line 1449

Quoted 12 lines
mm/damon/tests/core-kunit.h +11 -0
+	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);
+	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);

[Severity: Medium]
Could this result in a NULL pointer dereference if the destination context ends up with more probes than the source context?

Quoted 4 lines

KUNIT_EXPECT_EQ() does not abort test execution if the previous check for nr_dst_probes = nr_src_probes fails. If dst has more probes, damon_nth_probe() will return NULL, and the next line accessing src_probe->weight will crash the test runner.

Good catch, I will fix.

Quoted 10 lines
Quoted 6 lines
mm/damon/tests/core-kunit.h +5 -0
+		nr_dst_probes++;
+	}
+	damon_destroy_ctx(dst);
+	damon_destroy_ctx(src);
+}
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=2

Thanks,
SJ

author maintainer
#7 lore raw

RFC PATCH 3/6 selftests/damon/_damon_sysfs: implement DamonProbes

In reply to SJ Park

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.

---
 tools/testing/selftests/damon/_damon_sysfs.py | 123 +++++++++++++++++-
 1 file changed, 122 insertions(+), 1 deletion(-)
tools/testing/selftests/damon/_damon_sysfs.py +122 -1
index e6a2265d721e8..d6afb5b2f0a4a 100644
@@ -558,6 +558,115 @@ 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)
+        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 +674,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 +687,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 +700,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 +732,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
#8 lore raw

Re: [RFC PATCH 3/6] selftests/damon/_damon_sysfs: implement DamonProbes

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [Medium] The error result of `write_file()` for setting the probe weight is silently discarded. --

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] The error result of write_file() for setting the probe weight is silently discarded. --

--- Patch [3]: [RFC PATCH 3/6] selftests/damon/_damon_sysfs: implement DamonProbes --- commit 12ef2c079bfc3ec5e9f25fc085e18c6bca931488
Author: SJ Park <[email protected]>

selftests/damon/_damon_sysfs: implement DamonProbes

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.

Quoted 3 lines
tools/testing/selftests/damon/_damon_sysfs.py +0 -0

[ ... ]

Quoted 10 lines
tools/testing/selftests/damon/_damon_sysfs.py +10 -0
+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()

tools/testing/selftests/damon/_damon_sysfs.py class IntervalsGoal: · line 642

Quoted 10 lines
tools/testing/selftests/damon/_damon_sysfs.py +10 -0
+        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)
+        return self.filters.stage()

[Severity: Medium]
Is err intentionally ignored here? If write_file() fails to write the weight to sysfs, it returns an error string. Returning self.filters.stage() directly without checking err will swallow the error and might cause tests to proceed with incorrect assumptions about the probe configuration.

Should this check err before returning, similar to how it is handled in other stage() methods?

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=3
author maintainer
#9 lore raw

Re: [RFC PATCH 3/6] selftests/damon/_damon_sysfs: implement DamonProbes

Quoted 47 lines · On Wed, 29 Jul 2026 15:04:39 +0000 [email protected] wrote:

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] The error result of write_file() for setting the probe weight is silently discarded. --

--- Patch [3]: [RFC PATCH 3/6] selftests/damon/_damon_sysfs: implement DamonProbes --- commit 12ef2c079bfc3ec5e9f25fc085e18c6bca931488
Author: SJ Park <[email protected]>

selftests/damon/_damon_sysfs: implement DamonProbes

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.

Quoted 3 lines
tools/testing/selftests/damon/_damon_sysfs.py +0 -0

[ ... ]

Quoted 10 lines
tools/testing/selftests/damon/_damon_sysfs.py +10 -0
+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()

tools/testing/selftests/damon/_damon_sysfs.py class IntervalsGoal: · line 642

Quoted 10 lines
tools/testing/selftests/damon/_damon_sysfs.py +10 -0
+        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)
+        return self.filters.stage()

[Severity: Medium]
Is err intentionally ignored here? If write_file() fails to write the weight to sysfs, it returns an error string. Returning self.filters.stage() directly without checking err will swallow the error and might cause tests to proceed with incorrect assumptions about the probe configuration.

Quoted 2 lines

Should this check err before returning, similar to how it is handled in other stage() methods?

Good catch, I will fix.
'

Quoted 4 lines
Signature · 2 lines
-- 
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=3

Thanks,
SJ

author maintainer
#10 lore raw

RFC PATCH 4/6 selftests/damon/drgn_dump_damon_status: dump probes

In reply to SJ Park

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.

---
 .../selftests/damon/drgn_dump_damon_status.py | 32 +++++++++++++++++++
 1 file changed, 32 insertions(+)
tools/testing/selftests/damon/drgn_dump_damon_status.py +32 -0
index 09552e91bc782..4622046fd0118 100755
@@ -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
author maintainer
#11 lore raw

RFC PATCH 5/6 selftests/damon/sysfs.py: extend commit assertion function for probes

In reply to SJ Park

Extend 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
index 88a26422ff44c..159cbeac067f8 100755
@@ -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
author maintainer
#12 lore raw

RFC PATCH 6/6 selftests/damon/sysfs.py: test damon probes

In reply to SJ Park

Extend 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
index 159cbeac067f8..66c826189320d 100755
@@ -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
#13 lore raw

Re: [RFC PATCH 6/6] selftests/damon/sysfs.py: test damon probes

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [Medium] DamonProbe.stage() completely ignores the error returned when writing to the 'weight' sysfs…

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] DamonProbe.stage() completely ignores the error returned when writing to the 'weight' sysfs file, masking potential write failures. --

--- Patch [6]: [RFC PATCH 6/6] selftests/damon/sysfs.py: test damon probes --- commit e47c18c7c1e4b19237b2738bc5c072c3c570dcc9
Author: SJ Park <[email protected]>

selftests/damon/sysfs.py: test damon probes

This commit extends sysfs.py to test DAMON probes by committing them via sysfs and checking the in-kernel status using drgn.

tools/testing/selftests/damon/sysfs.py def main(): · line 325

Quoted 13 lines
tools/testing/selftests/damon/sysfs.py +4 -1
index 8c2dfc5ed8e04..852b6887a75ce 100755
@@ -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,

[Severity: Medium]
Will this properly catch setup failures when setting the probe weight?

When testing DamonProbes, DamonProbe.stage() writes the weight to sysfs but overwrites the returned error status without checking it:

tools/testing/selftests/damon/_damon_sysfs.py:DamonProbe.stage()

    def stage(self):
        err = write_file(
                os.path.join(self.sysfs_dir(), 'weight'), '%d' % self.weight)
        return self.filters.stage()

If the sysfs write fails, such as the kernel rejecting the weight value or the sysfs node being absent, the error is silently ignored. Could this cause the test suite to proceed as if setup succeeded, leading to false positive passes or confusing downstream errors?

[ ... ]

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=6
author maintainer
#14 lore raw

Re: [RFC PATCH 6/6] selftests/damon/sysfs.py: test damon probes

Quoted 40 lines · On Wed, 29 Jul 2026 15:19:33 +0000 [email protected] wrote:

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] DamonProbe.stage() completely ignores the error returned when writing to the 'weight' sysfs file, masking potential write failures. --

--- Patch [6]: [RFC PATCH 6/6] selftests/damon/sysfs.py: test damon probes --- commit e47c18c7c1e4b19237b2738bc5c072c3c570dcc9
Author: SJ Park <[email protected]>

selftests/damon/sysfs.py: test damon probes

This commit extends sysfs.py to test DAMON probes by committing them via sysfs and checking the in-kernel status using drgn.

tools/testing/selftests/damon/sysfs.py def main(): · line 325

Quoted 13 lines
tools/testing/selftests/damon/sysfs.py +4 -1
index 8c2dfc5ed8e04..852b6887a75ce 100755
@@ -319,7 +319,21 @@ def main():
                 intervals_goal=_damon_sysfs.IntervalsGoal(
                     access_bp@0, aggrs=3, min_sample_usP00,
                     max_sample_us000000),
-                update_us 00000),
+                update_us 00000,
+                probes=_damon_sysfs.DamonProbes(
+                    probes=[_damon_sysfs.DamonProbe(
+                        weightB,

[Severity: Medium]
Will this properly catch setup failures when setting the probe weight?

When testing DamonProbes, DamonProbe.stage() writes the weight to sysfs but overwrites the returned error status without checking it:

tools/testing/selftests/damon/_damon_sysfs.py:DamonProbe.stage()

    def stage(self):
        err = write_file(
                os.path.join(self.sysfs_dir(), 'weight'), '%d' % self.weight)
        return self.filters.stage()
Quoted 4 lines

If the sysfs write fails, such as the kernel rejecting the weight value or the sysfs node being absent, the error is silently ignored. Could this cause the test suite to proceed as if setup succeeded, leading to false positive passes or confusing downstream errors?

Yes, I will fix it.

Quoted 6 lines

[ ... ]

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=6

Thanks,
SJ