From: Lian Wang <lianux.mm@gmail.com>
To: damon@lists.linux.dev, linux-mm@kvack.org
Cc: sj@kernel.org, akpm@linux-foundation.org,
linux-kernel@vger.kernel.org, david@kernel.org, ljs@kernel.org,
liam@infradead.org, vbabka@kernel.org, rppt@kernel.org,
surenb@google.com, mhocko@suse.com, npache@redhat.com,
ziy@nvidia.com, baolin.wang@linux.alibaba.com,
ryan.roberts@arm.com, daichaobing@sangfor.com.cn,
wangkefeng.wang@huawei.com, gutierrez.asier@huawei-partners.com,
zengheng4@huawei.com, kasong@tencent.com, corbet@lwn.net,
skhan@linuxfoundation.org, linux-doc@vger.kernel.org,
linux-kselftest@vger.kernel.org, lianux.mm@gmail.com,
lianux.wang@processmission.com, kunwu.chan@linux.dev
Subject: [RFC PATCH v3 3/3] selftests/damon: add functional test for DAMOS_SPLIT
Date: Mon, 20 Jul 2026 11:03:27 +0800 [thread overview]
Message-ID: <20260720030327.80153-4-lianux.mm@gmail.com> (raw)
In-Reply-To: <20260720030327.80153-1-lianux.mm@gmail.com>
Add damos_split.py, which allocates a MADV_HUGEPAGE-backed region in a
child process and runs a DAMON/DAMOS scheme with action 'split' and
target_order 0 against it, then verifies the huge pages are split into
base pages (the child's AnonHugePages drops).
Extend the _damon_sysfs.py Damos helper with the target_order parameter
so a scheme's split target can be expressed.
Co-developed-by: Kunwu Chan <kunwu.chan@linux.dev>
Signed-off-by: Kunwu Chan <kunwu.chan@linux.dev>
Signed-off-by: Lian Wang (Processmission) <lianux.mm@gmail.com>
---
tools/testing/selftests/damon/Makefile | 1 +
tools/testing/selftests/damon/_damon_sysfs.py | 9 +-
tools/testing/selftests/damon/damos_split.py | 125 ++++++++++++++++++
3 files changed, 134 insertions(+), 1 deletion(-)
create mode 100644 tools/testing/selftests/damon/damos_split.py
diff --git a/tools/testing/selftests/damon/Makefile b/tools/testing/selftests/damon/Makefile
index ece244e5c5b9..a623c271355f 100644
--- a/tools/testing/selftests/damon/Makefile
+++ b/tools/testing/selftests/damon/Makefile
@@ -12,6 +12,7 @@ TEST_PROGS += sysfs.sh
TEST_PROGS += sysfs.py
TEST_PROGS += sysfs_update_schemes_tried_regions_wss_estimation.py
TEST_PROGS += damos_quota.py damos_quota_goal.py damos_apply_interval.py
+TEST_PROGS += damos_split.py
TEST_PROGS += damos_tried_regions.py damon_nr_regions.py
TEST_PROGS += sysfs_refresh.py
TEST_PROGS += reclaim.sh lru_sort.sh
diff --git a/tools/testing/selftests/damon/_damon_sysfs.py b/tools/testing/selftests/damon/_damon_sysfs.py
index e6a2265d721e..5a01f31ad9f2 100644
--- a/tools/testing/selftests/damon/_damon_sysfs.py
+++ b/tools/testing/selftests/damon/_damon_sysfs.py
@@ -419,6 +419,7 @@ class Damos:
filters = None
apply_interval_us = None
target_nid = None
+ target_order = None
dests = None
idx = None
context = None
@@ -429,7 +430,7 @@ class Damos:
def __init__(self, action='stat', access_pattern=DamosAccessPattern(),
quota=DamosQuota(), watermarks=DamosWatermarks(),
core_filters=[], ops_filters=[], filters=[], target_nid=0,
- dests=DamosDests(), apply_interval_us=0):
+ target_order=0, dests=DamosDests(), apply_interval_us=0):
self.action = action
self.access_pattern = access_pattern
self.access_pattern.scheme = self
@@ -448,6 +449,7 @@ class Damos:
self.filters.scheme = self
self.target_nid = target_nid
+ self.target_order = target_order
self.dests = dests
self.dests.scheme = self
@@ -492,6 +494,11 @@ class Damos:
if err is not None:
return err
+ err = write_file(os.path.join(self.sysfs_dir(), 'target_order'), '%d' %
+ self.target_order)
+ if err is not None:
+ return err
+
err = self.dests.stage()
if err is not None:
return err
diff --git a/tools/testing/selftests/damon/damos_split.py b/tools/testing/selftests/damon/damos_split.py
new file mode 100644
index 000000000000..089fbe0f0d4d
--- /dev/null
+++ b/tools/testing/selftests/damon/damos_split.py
@@ -0,0 +1,125 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+#
+# Functional test for the DAMOS_SPLIT action.
+#
+# A child process allocates a MADV_HUGEPAGE-backed anonymous region and
+# faults it in so that it is backed by (m)THPs. The parent then runs a
+# DAMON/DAMOS scheme with action 'split' and target_order 0 against the
+# child and checks that the huge pages are split into base pages, i.e. the
+# child's AnonHugePages (as reported by /proc/<pid>/smaps) drops.
+
+import ctypes
+import os
+import signal
+import sys
+import time
+
+import _damon_sysfs
+
+PMD_SIZE = 2 * 1024 * 1024
+MADV_HUGEPAGE = 14
+PROT_READ_WRITE = 0x1 | 0x2
+MAP_PRIVATE_ANON = 0x2 | 0x20
+REGION_SIZE = 32 * PMD_SIZE
+
+def child_workload():
+ '''Allocate a PMD-aligned, THP-backed region, fault it in, then idle.'''
+ libc = ctypes.CDLL('libc.so.6', use_errno=True)
+ libc.mmap.restype = ctypes.c_void_p
+ libc.mmap.argtypes = [ctypes.c_void_p, ctypes.c_size_t, ctypes.c_int,
+ ctypes.c_int, ctypes.c_int, ctypes.c_long]
+ libc.madvise.argtypes = [ctypes.c_void_p, ctypes.c_size_t, ctypes.c_int]
+
+ # Over-allocate so that a PMD-aligned window is available.
+ raw = libc.mmap(None, REGION_SIZE + PMD_SIZE, PROT_READ_WRITE,
+ MAP_PRIVATE_ANON, -1, 0)
+ if raw is None or raw == ctypes.c_void_p(-1).value:
+ os._exit(2)
+ base = (raw + PMD_SIZE - 1) & ~(PMD_SIZE - 1)
+ libc.madvise(ctypes.c_void_p(base), REGION_SIZE, MADV_HUGEPAGE)
+
+ buf = (ctypes.c_char * REGION_SIZE).from_address(base)
+ for off in range(0, REGION_SIZE, 4096):
+ buf[off] = 1
+
+ # Ready; idle until the parent tears us down.
+ signal.pause()
+
+def anon_huge_kb(pid):
+ total = 0
+ try:
+ with open('/proc/%d/smaps' % pid) as f:
+ for line in f:
+ if line.startswith('AnonHugePages:'):
+ total += int(line.split()[1])
+ except FileNotFoundError:
+ return -1
+ return total
+
+def main():
+ if not os.path.exists('/sys/kernel/mm/transparent_hugepage/enabled'):
+ print('SKIP: transparent hugepage is not available')
+ exit(0)
+
+ pid = os.fork()
+ if pid == 0:
+ child_workload()
+ os._exit(0)
+
+ try:
+ # Give the child time to fault in its huge pages.
+ time.sleep(2)
+ before = anon_huge_kb(pid)
+ if before <= 0:
+ print('SKIP: workload did not get any THP (AnonHugePages=%d)'
+ % before)
+ os.kill(pid, signal.SIGKILL)
+ exit(0)
+
+ # Split every large folio in the target down to order-0 base pages.
+ kdamonds = _damon_sysfs.Kdamonds([_damon_sysfs.Kdamond(
+ contexts=[_damon_sysfs.DamonCtx(
+ ops='vaddr',
+ targets=[_damon_sysfs.DamonTarget(pid=pid)],
+ schemes=[_damon_sysfs.Damos(
+ action='split',
+ target_order=0,
+ # match every region regardless of access/age/size, so
+ # the ARM64 stale-TLB blind spot cannot mask the target
+ access_pattern=_damon_sysfs.DamosAccessPattern(
+ size=[0, 2**64 - 1],
+ nr_accesses=[0, 2**64 - 1],
+ age=[0, 2**64 - 1]),
+ apply_interval_us=0)])])])
+ err = kdamonds.start()
+ if err is not None:
+ print('kdamonds start failed: %s' % err)
+ os.kill(pid, signal.SIGKILL)
+ exit(1)
+
+ # Let the scheme find and split the regions.
+ after = before
+ for _ in range(50):
+ time.sleep(0.2)
+ after = anon_huge_kb(pid)
+ if after == 0:
+ break
+
+ kdamonds.stop()
+ os.kill(pid, signal.SIGKILL)
+
+ if after >= before:
+ print('FAIL: AnonHugePages did not shrink: before=%d KiB '
+ 'after=%d KiB' % (before, after))
+ exit(1)
+ print('PASS: AnonHugePages %d KiB -> %d KiB after DAMOS_SPLIT'
+ % (before, after))
+ finally:
+ try:
+ os.kill(pid, signal.SIGKILL)
+ except ProcessLookupError:
+ pass
+
+if __name__ == '__main__':
+ main()
next prev parent reply other threads:[~2026-07-20 3:04 UTC|newest]
Thread overview: 14+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-07-20 3:03 [RFC PATCH v3 0/3] mm/damon: introduce DAMOS_SPLIT action Lian Wang
2026-07-20 3:03 ` [RFC PATCH v3 1/3] " Lian Wang
2026-07-20 9:47 ` Gutierrez Asier
2026-07-20 10:02 ` Lian Wang
2026-07-20 3:03 ` [RFC PATCH v3 2/3] mm/damon/vaddr: implement DAMOS_SPLIT handler Lian Wang
2026-07-20 3:03 ` Lian Wang [this message]
2026-07-20 9:28 ` [RFC PATCH v3 0/3] mm/damon: introduce DAMOS_SPLIT action Gutierrez Asier
2026-07-20 9:43 ` Lian Wang
2026-07-20 9:44 ` David Hildenbrand (Arm)
2026-07-20 9:56 ` Lian Wang
2026-07-20 19:12 ` Zi Yan
2026-07-21 0:47 ` SJ Park
2026-07-21 1:34 ` Lian Wang
2026-07-21 1:08 ` SJ Park
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260720030327.80153-4-lianux.mm@gmail.com \
--to=lianux.mm@gmail.com \
--cc=akpm@linux-foundation.org \
--cc=baolin.wang@linux.alibaba.com \
--cc=corbet@lwn.net \
--cc=daichaobing@sangfor.com.cn \
--cc=damon@lists.linux.dev \
--cc=david@kernel.org \
--cc=gutierrez.asier@huawei-partners.com \
--cc=kasong@tencent.com \
--cc=kunwu.chan@linux.dev \
--cc=liam@infradead.org \
--cc=lianux.wang@processmission.com \
--cc=linux-doc@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-kselftest@vger.kernel.org \
--cc=linux-mm@kvack.org \
--cc=ljs@kernel.org \
--cc=mhocko@suse.com \
--cc=npache@redhat.com \
--cc=rppt@kernel.org \
--cc=ryan.roberts@arm.com \
--cc=sj@kernel.org \
--cc=skhan@linuxfoundation.org \
--cc=surenb@google.com \
--cc=vbabka@kernel.org \
--cc=wangkefeng.wang@huawei.com \
--cc=zengheng4@huawei.com \
--cc=ziy@nvidia.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox