Changes from RFC
(https://lore.kernel.org/damon/[email protected]/)
- Make the working set size estimation test more reliable
- Wordsmith coverletter and commit messages
- Rename _damon.py to _damon_sysfs.py
DAMON exports most of its functionality via its sysfs interface. Hence
most DAMON functionality tests could be implemented using the interface.
However, because the interfaces require simple but multiple operations
for many controls, writing all such tests from the scratch could be
repetitive and time consuming.
Implement a minimum DAMON sysfs control module, and a couple of DAMON
functionality tests using the control module. The first test is for
ensuring minimum accuracy of data access monitoring, and the second test
is for finding if a previously found and fixed bug is introduced again.
Note that the DAMON sysfs control module is only for avoiding
duplicating code in tests. For convenient and general control of DAMON,
users should use DAMON user-space tools that developed for the purpose,
such as damo[1].
[1] https://github.com/damonitor/damo
Patches Sequence
----------------
This patchset is constructed with five patches. The first three patches
implement a Python-written test implementation-purpose DAMON sysfs
control module. The implementation is incrementally done in the
sequence of the basic data structure (first patch) first, kdamonds start
command (second patch) next, and finally DAMOS tried bytes update
command (third patch).
Then two patches for implementing selftests using the module follows.
The fourth patch implements a basic functionality test of DAMON for
working set estimation accuracy. Finally, the fifth patch implements a
corner case test for a previously found bug.
SeongJae Park (5):
selftests/damon: implement a python module for test-purpose DAMON
sysfs controls
selftests/damon/_damon_sysfs: implement kdamonds start function
selftests/damon/_damon_sysfs: implement updat_schemes_tried_bytes
command
selftests/damon: add a test for update_schemes_tried_regions sysfs
command
selftests/damon: add a test for update_schemes_tried_regions hang bug
tools/testing/selftests/damon/Makefile | 3 +
tools/testing/selftests/damon/_damon_sysfs.py | 322 ++++++++++++++++++
tools/testing/selftests/damon/access_memory.c | 41 +++
...sysfs_update_schemes_tried_regions_hang.py | 33 ++
...te_schemes_tried_regions_wss_estimation.py | 55 +++
5 files changed, 454 insertions(+)
create mode 100644 tools/testing/selftests/damon/_damon_sysfs.py
create mode 100644 tools/testing/selftests/damon/access_memory.c
create mode 100755 tools/testing/selftests/damon/sysfs_update_schemes_tried_regions_hang.py
create mode 100755 tools/testing/selftests/damon/sysfs_update_schemes_tried_regions_wss_estimation.py
base-commit: 5794dfaf6d1be564b0912d51d8a714baff329495
--
2.34.1
Implement update_schemes_tried_bytes command of DAMON sysfs interface in
_damon_sysfs.py. It is not only making the update, but also read the
updated value from the sysfs interface and store it in the Kdamond
python objects so that the user of the module can easily get the value.
Signed-off-by: SeongJae Park <[email protected]>
---
tools/testing/selftests/damon/_damon_sysfs.py | 14 ++++++++++++++
1 file changed, 14 insertions(+)
diff --git a/tools/testing/selftests/damon/_damon_sysfs.py b/tools/testing/selftests/damon/_damon_sysfs.py
index 6b99f87a5f1e..e98cf4b6a4b7 100644
--- a/tools/testing/selftests/damon/_damon_sysfs.py
+++ b/tools/testing/selftests/damon/_damon_sysfs.py
@@ -76,6 +76,7 @@ class Damos:
# todo: Support quotas, watermarks, stats, tried_regions
idx = None
context = None
+ tried_bytes = None
def __init__(self, action='stat', access_pattern=DamosAccessPattern()):
self.action = action
@@ -284,6 +285,19 @@ class Kdamond:
err = write_file(os.path.join(self.sysfs_dir(), 'state'), 'on')
return err
+ def update_schemes_tried_bytes(self):
+ err = write_file(os.path.join(self.sysfs_dir(), 'state'),
+ 'update_schemes_tried_bytes')
+ if err != None:
+ return err
+ for context in self.contexts:
+ for scheme in context.schemes:
+ content, err = read_file(os.path.join(scheme.sysfs_dir(),
+ 'tried_regions', 'total_bytes'))
+ if err != None:
+ return err
+ scheme.tried_bytes = int(content)
+
class Kdamonds:
kdamonds = []
--
2.34.1
Implement a python module for DAMON sysfs controls. The module is aimed
to be useful for writing DAMON functionality tests in future.
Nonetheless, this module is only representing a subset of DAMON sysfs
files. Following commits will implement more DAMON sysfs controls.
Signed-off-by: SeongJae Park <[email protected]>
---
tools/testing/selftests/damon/_damon_sysfs.py | 102 ++++++++++++++++++
1 file changed, 102 insertions(+)
create mode 100644 tools/testing/selftests/damon/_damon_sysfs.py
diff --git a/tools/testing/selftests/damon/_damon_sysfs.py b/tools/testing/selftests/damon/_damon_sysfs.py
new file mode 100644
index 000000000000..78101846ab66
--- /dev/null
+++ b/tools/testing/selftests/damon/_damon_sysfs.py
@@ -0,0 +1,102 @@
+# SPDX-License-Identifier: GPL-2.0
+
+class DamosAccessPattern:
+ size = None
+ nr_accesses = None
+ age = None
+ scheme = None
+
+ def __init__(self, size=None, nr_accesses=None, age=None):
+ self.size = size
+ self.nr_accesses = nr_accesses
+ self.age = age
+
+ if self.size == None:
+ self.size = [0, 2**64 - 1]
+ if self.nr_accesses == None:
+ self.nr_accesses = [0, 2**64 - 1]
+ if self.age == None:
+ self.age = [0, 2**64 - 1]
+
+class Damos:
+ action = None
+ access_pattern = None
+ # todo: Support quotas, watermarks, stats, tried_regions
+ idx = None
+ context = None
+
+ def __init__(self, action='stat', access_pattern=DamosAccessPattern()):
+ self.action = action
+ self.access_pattern = access_pattern
+ self.access_pattern.scheme = self
+
+class DamonTarget:
+ pid = None
+ # todo: Support target regions if test is made
+ idx = None
+ context = None
+
+ def __init__(self, pid):
+ self.pid = pid
+
+class DamonAttrs:
+ sample_us = None
+ aggr_us = None
+ update_us = None
+ min_nr_regions = None
+ max_nr_regions = None
+ context = None
+
+ def __init__(self, sample_us=5000, aggr_us=100000, update_us=1000000,
+ min_nr_regions=10, max_nr_regions=1000):
+ self.sample_us = sample_us
+ self.aggr_us = aggr_us
+ self.update_us = update_us
+ self.min_nr_regions = min_nr_regions
+ self.max_nr_regions = max_nr_regions
+
+class DamonCtx:
+ ops = None
+ monitoring_attrs = None
+ targets = None
+ schemes = None
+ kdamond = None
+ idx = None
+
+ def __init__(self, ops='paddr', monitoring_attrs=DamonAttrs(), targets=[],
+ schemes=[]):
+ self.ops = ops
+ self.monitoring_attrs = monitoring_attrs
+ self.monitoring_attrs.context = self
+
+ self.targets = targets
+ for idx, target in enumerate(self.targets):
+ target.idx = idx
+ target.context = self
+
+ self.schemes = schemes
+ for idx, scheme in enumerate(self.schemes):
+ scheme.idx = idx
+ scheme.context = self
+
+class Kdamond:
+ state = None
+ pid = None
+ contexts = None
+ idx = None # index of this kdamond between siblings
+ kdamonds = None # parent
+
+ def __init__(self, contexts=[]):
+ self.contexts = contexts
+ for idx, context in enumerate(self.contexts):
+ context.idx = idx
+ context.kdamond = self
+
+class Kdamonds:
+ kdamonds = []
+
+ def __init__(self, kdamonds=[]):
+ self.kdamonds = kdamonds
+ for idx, kdamond in enumerate(self.kdamonds):
+ kdamond.idx = idx
+ kdamond.kdamonds = self
--
2.34.1
Extend the tests-writing-purpose DAMON sysfs control module to support
the kdamonds start functionality.
Signed-off-by: SeongJae Park <[email protected]>
---
tools/testing/selftests/damon/_damon_sysfs.py | 206 ++++++++++++++++++
1 file changed, 206 insertions(+)
diff --git a/tools/testing/selftests/damon/_damon_sysfs.py b/tools/testing/selftests/damon/_damon_sysfs.py
index 78101846ab66..6b99f87a5f1e 100644
--- a/tools/testing/selftests/damon/_damon_sysfs.py
+++ b/tools/testing/selftests/damon/_damon_sysfs.py
@@ -1,5 +1,28 @@
# SPDX-License-Identifier: GPL-2.0
+import os
+
+sysfs_root = '/sys/kernel/mm/damon/admin'
+
+def write_file(path, string):
+ "Returns error string if failed, or None otherwise"
+ string = '%s' % string
+ try:
+ with open(path, 'w') as f:
+ f.write(string)
+ except Exception as e:
+ return '%s' % e
+ return None
+
+def read_file(path):
+ '''Returns the read content and error string. The read content is None if
+ the reading failed'''
+ try:
+ with open(path, 'r') as f:
+ return f.read(), None
+ except Exception as e:
+ return None, '%s' % e
+
class DamosAccessPattern:
size = None
nr_accesses = None
@@ -18,6 +41,35 @@ class DamosAccessPattern:
if self.age == None:
self.age = [0, 2**64 - 1]
+ def sysfs_dir(self):
+ return os.path.join(self.scheme.sysfs_dir(), 'access_pattern')
+
+ def stage(self):
+ err = write_file(
+ os.path.join(self.sysfs_dir(), 'sz', 'min'), self.size[0])
+ if err != None:
+ return err
+ err = write_file(
+ os.path.join(self.sysfs_dir(), 'sz', 'max'), self.size[1])
+ if err != None:
+ return err
+ err = write_file(os.path.join(self.sysfs_dir(), 'nr_accesses', 'min'),
+ self.nr_accesses[0])
+ if err != None:
+ return err
+ err = write_file(os.path.join(self.sysfs_dir(), 'nr_accesses', 'max'),
+ self.nr_accesses[1])
+ if err != None:
+ return err
+ err = write_file(
+ os.path.join(self.sysfs_dir(), 'age', 'min'), self.age[0])
+ if err != None:
+ return err
+ err = write_file(
+ os.path.join(self.sysfs_dir(), 'age', 'max'), self.age[1])
+ if err != None:
+ return err
+
class Damos:
action = None
access_pattern = None
@@ -30,6 +82,39 @@ class Damos:
self.access_pattern = access_pattern
self.access_pattern.scheme = self
+ def sysfs_dir(self):
+ return os.path.join(
+ self.context.sysfs_dir(), 'schemes', '%d' % self.idx)
+
+ def stage(self):
+ err = write_file(os.path.join(self.sysfs_dir(), 'action'), self.action)
+ if err != None:
+ return err
+ err = self.access_pattern.stage()
+ if err != None:
+ return err
+
+ # disable quotas
+ err = write_file(os.path.join(self.sysfs_dir(), 'quotas', 'ms'), '0')
+ if err != None:
+ return err
+ err = write_file(
+ os.path.join(self.sysfs_dir(), 'quotas', 'bytes'), '0')
+ if err != None:
+ return err
+
+ # disable watermarks
+ err = write_file(
+ os.path.join(self.sysfs_dir(), 'watermarks', 'metric'), 'none')
+ if err != None:
+ return err
+
+ # disable filters
+ err = write_file(
+ os.path.join(self.sysfs_dir(), 'filters', 'nr_filters'), '0')
+ if err != None:
+ return err
+
class DamonTarget:
pid = None
# todo: Support target regions if test is made
@@ -39,6 +124,18 @@ class DamonTarget:
def __init__(self, pid):
self.pid = pid
+ def sysfs_dir(self):
+ return os.path.join(
+ self.context.sysfs_dir(), 'targets', '%d' % self.idx)
+
+ def stage(self):
+ err = write_file(
+ os.path.join(self.sysfs_dir(), 'regions', 'nr_regions'), '0')
+ if err != None:
+ return err
+ return write_file(
+ os.path.join(self.sysfs_dir(), 'pid_target'), self.pid)
+
class DamonAttrs:
sample_us = None
aggr_us = None
@@ -55,6 +152,40 @@ class DamonAttrs:
self.min_nr_regions = min_nr_regions
self.max_nr_regions = max_nr_regions
+ def interval_sysfs_dir(self):
+ return os.path.join(self.context.sysfs_dir(), 'monitoring_attrs',
+ 'intervals')
+
+ def nr_regions_range_sysfs_dir(self):
+ return os.path.join(self.context.sysfs_dir(), 'monitoring_attrs',
+ 'nr_regions')
+
+ def stage(self):
+ err = write_file(os.path.join(self.interval_sysfs_dir(), 'sample_us'),
+ self.sample_us)
+ if err != None:
+ return err
+ err = write_file(os.path.join(self.interval_sysfs_dir(), 'aggr_us'),
+ self.aggr_us)
+ if err != None:
+ return err
+ err = write_file(os.path.join(self.interval_sysfs_dir(), 'update_us'),
+ self.update_us)
+ if err != None:
+ return err
+
+ err = write_file(
+ os.path.join(self.nr_regions_range_sysfs_dir(), 'min'),
+ self.min_nr_regions)
+ if err != None:
+ return err
+
+ err = write_file(
+ os.path.join(self.nr_regions_range_sysfs_dir(), 'max'),
+ self.max_nr_regions)
+ if err != None:
+ return err
+
class DamonCtx:
ops = None
monitoring_attrs = None
@@ -79,6 +210,46 @@ class DamonCtx:
scheme.idx = idx
scheme.context = self
+ def sysfs_dir(self):
+ return os.path.join(self.kdamond.sysfs_dir(), 'contexts',
+ '%d' % self.idx)
+
+ def stage(self):
+ err = write_file(
+ os.path.join(self.sysfs_dir(), 'operations'), self.ops)
+ if err != None:
+ return err
+ err = self.monitoring_attrs.stage()
+ if err != None:
+ return err
+
+ nr_targets_file = os.path.join(
+ self.sysfs_dir(), 'targets', 'nr_targets')
+ content, err = read_file(nr_targets_file)
+ if err != None:
+ return err
+ if int(content) != len(self.targets):
+ err = write_file(nr_targets_file, '%d' % len(self.targets))
+ if err != None:
+ return err
+ for target in self.targets:
+ err = target.stage()
+ if err != None:
+ return err
+
+ nr_schemes_file = os.path.join(
+ self.sysfs_dir(), 'schemes', 'nr_schemes')
+ content, err = read_file(nr_schemes_file)
+ if int(content) != len(self.schemes):
+ err = write_file(nr_schemes_file, '%d' % len(self.schemes))
+ if err != None:
+ return err
+ for scheme in self.schemes:
+ err = scheme.stage()
+ if err != None:
+ return err
+ return None
+
class Kdamond:
state = None
pid = None
@@ -92,6 +263,27 @@ class Kdamond:
context.idx = idx
context.kdamond = self
+ def sysfs_dir(self):
+ return os.path.join(self.kdamonds.sysfs_dir(), '%d' % self.idx)
+
+ def start(self):
+ nr_contexts_file = os.path.join(self.sysfs_dir(),
+ 'contexts', 'nr_contexts')
+ content, err = read_file(nr_contexts_file)
+ if err != None:
+ return err
+ if int(content) != len(self.contexts):
+ err = write_file(nr_contexts_file, '%d' % len(self.contexts))
+ if err != None:
+ return err
+
+ for context in self.contexts:
+ err = context.stage()
+ if err != None:
+ return err
+ err = write_file(os.path.join(self.sysfs_dir(), 'state'), 'on')
+ return err
+
class Kdamonds:
kdamonds = []
@@ -100,3 +292,17 @@ class Kdamonds:
for idx, kdamond in enumerate(self.kdamonds):
kdamond.idx = idx
kdamond.kdamonds = self
+
+ def sysfs_dir(self):
+ return os.path.join(sysfs_root, 'kdamonds')
+
+ def start(self):
+ err = write_file(os.path.join(self.sysfs_dir(), 'nr_kdamonds'),
+ '%s' % len(self.kdamonds))
+ if err != None:
+ return err
+ for kdamond in self.kdamonds:
+ err = kdamond.start()
+ if err != None:
+ return err
+ return None
--
2.34.1
Add a test for reproducing the update_schemes_tried_{regions,bytes}
command-causing indefinite hang bug that fixed by commit 7d6fa31a2fd7
("mm/damon/sysfs-schemes: add timeout for
update_schemes_tried_regions"), to avoid mistakenly re-introducing the
bug. Refer to the fix commit for more details of the bug.
Signed-off-by: SeongJae Park <[email protected]>
---
tools/testing/selftests/damon/Makefile | 1 +
...sysfs_update_schemes_tried_regions_hang.py | 33 +++++++++++++++++++
2 files changed, 34 insertions(+)
create mode 100755 tools/testing/selftests/damon/sysfs_update_schemes_tried_regions_hang.py
diff --git a/tools/testing/selftests/damon/Makefile b/tools/testing/selftests/damon/Makefile
index 1363987709c6..d90bdba28ff4 100644
--- a/tools/testing/selftests/damon/Makefile
+++ b/tools/testing/selftests/damon/Makefile
@@ -12,6 +12,7 @@ TEST_PROGS += debugfs_empty_targets.sh debugfs_huge_count_read_write.sh
TEST_PROGS += debugfs_duplicate_context_creation.sh
TEST_PROGS += debugfs_rm_non_contexts.sh
TEST_PROGS += sysfs.sh sysfs_update_removed_scheme_dir.sh
+TEST_PROGS += sysfs_update_schemes_tried_regions_hang.py
TEST_PROGS += sysfs_update_schemes_tried_regions_wss_estimation.py
TEST_PROGS += reclaim.sh lru_sort.sh
TEST_PROGS += dbgfs_target_ids_read_before_terminate_race.sh
diff --git a/tools/testing/selftests/damon/sysfs_update_schemes_tried_regions_hang.py b/tools/testing/selftests/damon/sysfs_update_schemes_tried_regions_hang.py
new file mode 100755
index 000000000000..8c690ba1a573
--- /dev/null
+++ b/tools/testing/selftests/damon/sysfs_update_schemes_tried_regions_hang.py
@@ -0,0 +1,33 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+
+import subprocess
+import time
+
+import _damon_sysfs
+
+def main():
+ proc = subprocess.Popen(['sleep', '2'])
+ kdamonds = _damon_sysfs.Kdamonds([_damon_sysfs.Kdamond(
+ contexts=[_damon_sysfs.DamonCtx(
+ ops='vaddr',
+ targets=[_damon_sysfs.DamonTarget(pid=proc.pid)],
+ schemes=[_damon_sysfs.Damos(
+ access_pattern=_damon_sysfs.DamosAccessPattern(
+ nr_accesses=[200, 200]))] # schemes
+ )] # contexts
+ )]) # kdamonds
+
+ err = kdamonds.start()
+ if err != None:
+ print('kdmaond start failed: %s' % err)
+ exit(1)
+
+ while proc.poll() == None:
+ err = kdamonds.kdamonds[0].update_schemes_tried_bytes()
+ if err != None:
+ print('tried bytes update failed: %s' % err)
+ exit(1)
+
+if __name__ == '__main__':
+ main()
--
2.34.1
Add a selftest for verifying the accuracy of DAMON's access monitoring
functionality. The test starts a program of artificial access pattern,
monitor the access pattern using DAMON, and check if DAMON finds
expected amount of hot data region (working set size) with only
acceptable error rate.
Note that the acceptable error rate is set with only naive assumptions
and small number of tests. Hence failures of the test may not always
mean DAMON is broken. Rather than that, those could be a signal to
better understand the real accuracy level of DAMON in wider
environments. Based on further finding, we could optimize DAMON or
adjust the expectation of the test.
Signed-off-by: SeongJae Park <[email protected]>
---
tools/testing/selftests/damon/Makefile | 2 +
tools/testing/selftests/damon/access_memory.c | 41 ++++++++++++++
...te_schemes_tried_regions_wss_estimation.py | 55 +++++++++++++++++++
3 files changed, 98 insertions(+)
create mode 100644 tools/testing/selftests/damon/access_memory.c
create mode 100755 tools/testing/selftests/damon/sysfs_update_schemes_tried_regions_wss_estimation.py
diff --git a/tools/testing/selftests/damon/Makefile b/tools/testing/selftests/damon/Makefile
index d2105d41ea25..1363987709c6 100644
--- a/tools/testing/selftests/damon/Makefile
+++ b/tools/testing/selftests/damon/Makefile
@@ -4,6 +4,7 @@
TEST_GEN_FILES += huge_count_read_write
TEST_GEN_FILES += dbgfs_target_ids_read_before_terminate_race
TEST_GEN_FILES += dbgfs_target_ids_pid_leak
+TEST_GEN_FILES += access_memory
TEST_FILES = _chk_dependency.sh _debugfs_common.sh
TEST_PROGS = debugfs_attrs.sh debugfs_schemes.sh debugfs_target_ids.sh
@@ -11,6 +12,7 @@ TEST_PROGS += debugfs_empty_targets.sh debugfs_huge_count_read_write.sh
TEST_PROGS += debugfs_duplicate_context_creation.sh
TEST_PROGS += debugfs_rm_non_contexts.sh
TEST_PROGS += sysfs.sh sysfs_update_removed_scheme_dir.sh
+TEST_PROGS += sysfs_update_schemes_tried_regions_wss_estimation.py
TEST_PROGS += reclaim.sh lru_sort.sh
TEST_PROGS += dbgfs_target_ids_read_before_terminate_race.sh
TEST_PROGS += dbgfs_target_ids_pid_leak.sh
diff --git a/tools/testing/selftests/damon/access_memory.c b/tools/testing/selftests/damon/access_memory.c
new file mode 100644
index 000000000000..585a2fa54329
--- /dev/null
+++ b/tools/testing/selftests/damon/access_memory.c
@@ -0,0 +1,41 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Artificial memory access program for testing DAMON.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+int main(int argc, char *argv[])
+{
+ char **regions;
+ clock_t start_clock;
+ int nr_regions;
+ int sz_region;
+ int access_time_ms;
+ int i;
+
+ if (argc != 4) {
+ printf("Usage: %s <number> <size (bytes)> <time (ms)>\n",
+ argv[0]);
+ return -1;
+ }
+
+ nr_regions = atoi(argv[1]);
+ sz_region = atoi(argv[2]);
+ access_time_ms = atoi(argv[3]);
+
+ regions = malloc(sizeof(*regions) * nr_regions);
+ for (i = 0; i < nr_regions; i++)
+ regions[i] = malloc(sz_region);
+
+ for (i = 0; i < nr_regions; i++) {
+ start_clock = clock();
+ while ((clock() - start_clock) * 1000 / CLOCKS_PER_SEC <
+ access_time_ms)
+ memset(regions[i], i, 1024 * 1024 * 10);
+ }
+ return 0;
+}
diff --git a/tools/testing/selftests/damon/sysfs_update_schemes_tried_regions_wss_estimation.py b/tools/testing/selftests/damon/sysfs_update_schemes_tried_regions_wss_estimation.py
new file mode 100755
index 000000000000..cdbf19b442c9
--- /dev/null
+++ b/tools/testing/selftests/damon/sysfs_update_schemes_tried_regions_wss_estimation.py
@@ -0,0 +1,55 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+
+import subprocess
+import time
+
+import _damon_sysfs
+
+def main():
+ # access two 10 MiB memory regions, 2 second per each
+ sz_region = 10 * 1024 * 1024
+ proc = subprocess.Popen(['./access_memory', '2', '%d' % sz_region, '2000'])
+ kdamonds = _damon_sysfs.Kdamonds([_damon_sysfs.Kdamond(
+ contexts=[_damon_sysfs.DamonCtx(
+ ops='vaddr',
+ targets=[_damon_sysfs.DamonTarget(pid=proc.pid)],
+ schemes=[_damon_sysfs.Damos(
+ access_pattern=_damon_sysfs.DamosAccessPattern(
+ # >= 25% access rate, >= 200ms age
+ nr_accesses=[5, 20], age=[2, 2**64 - 1]))] # schemes
+ )] # contexts
+ )]) # kdamonds
+
+ err = kdamonds.start()
+ if err != None:
+ print('kdmaond start failed: %s' % err)
+ exit(1)
+
+ wss_collected = []
+ while proc.poll() == None:
+ time.sleep(0.1)
+ err = kdamonds.kdamonds[0].update_schemes_tried_bytes()
+ if err != None:
+ print('tried bytes update failed: %s' % err)
+ exit(1)
+
+ wss_collected.append(
+ kdamonds.kdamonds[0].contexts[0].schemes[0].tried_bytes)
+
+ wss_collected.sort()
+ acceptable_error_rate = 0.2
+ for percentile in [50, 75]:
+ sample = wss_collected[int(len(wss_collected) * percentile / 100)]
+ error_rate = abs(sample - sz_region) / sz_region
+ print('%d-th percentile (%d) error %f' %
+ (percentile, sample, error_rate))
+ if error_rate > acceptable_error_rate:
+ print('the error rate is not acceptable (> %f)' %
+ acceptable_error_rate)
+ print('samples are as below')
+ print('\n'.join(['%d' % wss for wss in wss_collected]))
+ exit(1)
+
+if __name__ == '__main__':
+ main()
--
2.34.1
This and fifth patch of this patchset may not cleanly applicable, since those
are made on top of my out-of-tree experimental changes. I will rebase these
and send v2. Sorry for the noise.
Thanks,
SJ
On 2023-12-12T19:12:05+00:00 SeongJae Park <[email protected]> wrote:
> Add a selftest for verifying the accuracy of DAMON's access monitoring
> functionality. The test starts a program of artificial access pattern,
> monitor the access pattern using DAMON, and check if DAMON finds
> expected amount of hot data region (working set size) with only
> acceptable error rate.
>
> Note that the acceptable error rate is set with only naive assumptions
> and small number of tests. Hence failures of the test may not always
> mean DAMON is broken. Rather than that, those could be a signal to
> better understand the real accuracy level of DAMON in wider
> environments. Based on further finding, we could optimize DAMON or
> adjust the expectation of the test.
>
> Signed-off-by: SeongJae Park <[email protected]>
> ---
> tools/testing/selftests/damon/Makefile | 2 +
> tools/testing/selftests/damon/access_memory.c | 41 ++++++++++++++
> ...te_schemes_tried_regions_wss_estimation.py | 55 +++++++++++++++++++
> 3 files changed, 98 insertions(+)
> create mode 100644 tools/testing/selftests/damon/access_memory.c
> create mode 100755 tools/testing/selftests/damon/sysfs_update_schemes_tried_regions_wss_estimation.py
>
> diff --git a/tools/testing/selftests/damon/Makefile b/tools/testing/selftests/damon/Makefile
> index d2105d41ea25..1363987709c6 100644
> --- a/tools/testing/selftests/damon/Makefile
> +++ b/tools/testing/selftests/damon/Makefile
> @@ -4,6 +4,7 @@
> TEST_GEN_FILES += huge_count_read_write
> TEST_GEN_FILES += dbgfs_target_ids_read_before_terminate_race
> TEST_GEN_FILES += dbgfs_target_ids_pid_leak
> +TEST_GEN_FILES += access_memory
>
> TEST_FILES = _chk_dependency.sh _debugfs_common.sh
> TEST_PROGS = debugfs_attrs.sh debugfs_schemes.sh debugfs_target_ids.sh
> @@ -11,6 +12,7 @@ TEST_PROGS += debugfs_empty_targets.sh debugfs_huge_count_read_write.sh
> TEST_PROGS += debugfs_duplicate_context_creation.sh
> TEST_PROGS += debugfs_rm_non_contexts.sh
> TEST_PROGS += sysfs.sh sysfs_update_removed_scheme_dir.sh
> +TEST_PROGS += sysfs_update_schemes_tried_regions_wss_estimation.py
> TEST_PROGS += reclaim.sh lru_sort.sh
> TEST_PROGS += dbgfs_target_ids_read_before_terminate_race.sh
> TEST_PROGS += dbgfs_target_ids_pid_leak.sh
> diff --git a/tools/testing/selftests/damon/access_memory.c b/tools/testing/selftests/damon/access_memory.c
> new file mode 100644
> index 000000000000..585a2fa54329
> --- /dev/null
> +++ b/tools/testing/selftests/damon/access_memory.c
> @@ -0,0 +1,41 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Artificial memory access program for testing DAMON.
> + */
> +
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +#include <time.h>
> +
> +int main(int argc, char *argv[])
> +{
> + char **regions;
> + clock_t start_clock;
> + int nr_regions;
> + int sz_region;
> + int access_time_ms;
> + int i;
> +
> + if (argc != 4) {
> + printf("Usage: %s <number> <size (bytes)> <time (ms)>\n",
> + argv[0]);
> + return -1;
> + }
> +
> + nr_regions = atoi(argv[1]);
> + sz_region = atoi(argv[2]);
> + access_time_ms = atoi(argv[3]);
> +
> + regions = malloc(sizeof(*regions) * nr_regions);
> + for (i = 0; i < nr_regions; i++)
> + regions[i] = malloc(sz_region);
> +
> + for (i = 0; i < nr_regions; i++) {
> + start_clock = clock();
> + while ((clock() - start_clock) * 1000 / CLOCKS_PER_SEC <
> + access_time_ms)
> + memset(regions[i], i, 1024 * 1024 * 10);
> + }
> + return 0;
> +}
> diff --git a/tools/testing/selftests/damon/sysfs_update_schemes_tried_regions_wss_estimation.py b/tools/testing/selftests/damon/sysfs_update_schemes_tried_regions_wss_estimation.py
> new file mode 100755
> index 000000000000..cdbf19b442c9
> --- /dev/null
> +++ b/tools/testing/selftests/damon/sysfs_update_schemes_tried_regions_wss_estimation.py
> @@ -0,0 +1,55 @@
> +#!/usr/bin/env python3
> +# SPDX-License-Identifier: GPL-2.0
> +
> +import subprocess
> +import time
> +
> +import _damon_sysfs
> +
> +def main():
> + # access two 10 MiB memory regions, 2 second per each
> + sz_region = 10 * 1024 * 1024
> + proc = subprocess.Popen(['./access_memory', '2', '%d' % sz_region, '2000'])
> + kdamonds = _damon_sysfs.Kdamonds([_damon_sysfs.Kdamond(
> + contexts=[_damon_sysfs.DamonCtx(
> + ops='vaddr',
> + targets=[_damon_sysfs.DamonTarget(pid=proc.pid)],
> + schemes=[_damon_sysfs.Damos(
> + access_pattern=_damon_sysfs.DamosAccessPattern(
> + # >= 25% access rate, >= 200ms age
> + nr_accesses=[5, 20], age=[2, 2**64 - 1]))] # schemes
> + )] # contexts
> + )]) # kdamonds
> +
> + err = kdamonds.start()
> + if err != None:
> + print('kdmaond start failed: %s' % err)
> + exit(1)
> +
> + wss_collected = []
> + while proc.poll() == None:
> + time.sleep(0.1)
> + err = kdamonds.kdamonds[0].update_schemes_tried_bytes()
> + if err != None:
> + print('tried bytes update failed: %s' % err)
> + exit(1)
> +
> + wss_collected.append(
> + kdamonds.kdamonds[0].contexts[0].schemes[0].tried_bytes)
> +
> + wss_collected.sort()
> + acceptable_error_rate = 0.2
> + for percentile in [50, 75]:
> + sample = wss_collected[int(len(wss_collected) * percentile / 100)]
> + error_rate = abs(sample - sz_region) / sz_region
> + print('%d-th percentile (%d) error %f' %
> + (percentile, sample, error_rate))
> + if error_rate > acceptable_error_rate:
> + print('the error rate is not acceptable (> %f)' %
> + acceptable_error_rate)
> + print('samples are as below')
> + print('\n'.join(['%d' % wss for wss in wss_collected]))
> + exit(1)
> +
> +if __name__ == '__main__':
> + main()
> --
> 2.34.1