* [PATCH 2/2] mm/damon/sysfs-schemes: report the number of tried regions
2026-07-27 9:54 [PATCH 1/2] mm/damon/core: cover discrete System RAM areas with per-range regions Jiayuan Chen
@ 2026-07-27 9:54 ` Jiayuan Chen
2026-07-27 14:34 ` SJ Park
2026-07-27 10:07 ` [PATCH 1/2] mm/damon/core: cover discrete System RAM areas with per-range regions sashiko-bot
2026-07-27 14:26 ` SJ Park
2 siblings, 1 reply; 6+ messages in thread
From: Jiayuan Chen @ 2026-07-27 9:54 UTC (permalink / raw)
To: damon
Cc: jiayuan.chen, Jiayuan Chen, SeongJae Park, Andrew Morton,
David Hildenbrand, Lorenzo Stoakes, Liam R. Howlett,
Vlastimil Babka, Mike Rapoport, Suren Baghdasaryan, Michal Hocko,
Jonathan Corbet, Shuah Khan, linux-mm, linux-kernel, linux-doc
From: Jiayuan Chen <jiayuan.chen@shopee.com>
The 'tried_regions' directory of each DAMON sysfs scheme exposes the memory
regions that the scheme's action has been tried to be applied to, as
per-region subdirectories. It also has a 'total_bytes' file that reports
the total size of those regions without materializing the per-region
subdirectories, so that users can cheaply retrieve the aggregated result.
The number of the tried regions is another useful aggregated metric. When
the scheme's access pattern is not restrictive, it approximates the number
of the adaptive monitoring regions of the context, which users may want to
watch, e.g., to see how well the monitoring is refined under a given
max_nr_regions, or to feed fleet wide access pattern dashboards.
Retrieving it currently requires materializing all the per-region
subdirectories (via writing 'update_schemes_tried_regions') and counting
them, which is unnecessarily expensive for users that only need the count.
Add a 'nr_regions' file to the 'tried_regions' directory. Like
'total_bytes', it is updated by both 'update_schemes_tried_bytes' and
'update_schemes_tried_regions', so it can be read as a lightweight counter
without materializing the per-region subdirectories.
Suggested-by: SeongJae Park <sj@kernel.org>
Cc: Jiayuan Chen <jiayuan.chen@linux.dev>
Signed-off-by: Jiayuan Chen <jiayuan.chen@shopee.com>
---
.../ABI/testing/sysfs-kernel-mm-damon | 10 +++++++
Documentation/admin-guide/mm/damon/usage.rst | 27 ++++++++++---------
mm/damon/sysfs-schemes.c | 23 +++++++++++++---
3 files changed, 45 insertions(+), 15 deletions(-)
diff --git a/Documentation/ABI/testing/sysfs-kernel-mm-damon b/Documentation/ABI/testing/sysfs-kernel-mm-damon
index 786be4537da1..a6e8f5036555 100644
--- a/Documentation/ABI/testing/sysfs-kernel-mm-damon
+++ b/Documentation/ABI/testing/sysfs-kernel-mm-damon
@@ -622,6 +622,16 @@ Description: Writing a number to this file sets the upper limit of
nr_snapshots that deactivates the scheme when the limit is
reached or exceeded.
+What: /sys/kernel/mm/damon/admin/kdamonds/<K>/contexts/<C>/schemes/<S>/tried_regions/nr_regions
+Date: Aug 2026
+Contact: SJ Park <sj@kernel.org>
+Description: Reading this file returns the number of regions that
+ corresponding DAMON-based Operation Scheme's action has tried
+ to be applied. The number is updated together with
+ '.../tried_regions/total_bytes' by writing 'update_schemes_tried_bytes'
+ or 'update_schemes_tried_regions' to the relevant 'state' file, and
+ hence can be read without materializing the per-region directories.
+
What: /sys/kernel/mm/damon/admin/kdamonds/<K>/contexts/<C>/schemes/<S>/tried_regions/total_bytes
Date: Jul 2023
Contact: SJ Park <sj@kernel.org>
diff --git a/Documentation/admin-guide/mm/damon/usage.rst b/Documentation/admin-guide/mm/damon/usage.rst
index f6048fc04263..9152ce09de9d 100644
--- a/Documentation/admin-guide/mm/damon/usage.rst
+++ b/Documentation/admin-guide/mm/damon/usage.rst
@@ -108,7 +108,7 @@ comma (",").
│ │ │ │ │ │ │ :ref:`dests <damon_sysfs_dests>`/nr_dests
│ │ │ │ │ │ │ │ 0/id,weight
│ │ │ │ │ │ │ :ref:`stats <sysfs_schemes_stats>`/nr_tried,sz_tried,nr_applied,sz_applied,sz_ops_filter_passed,qt_exceeds,nr_snapshots,max_nr_snapshots
- │ │ │ │ │ │ │ :ref:`tried_regions <sysfs_schemes_tried_regions>`/total_bytes
+ │ │ │ │ │ │ │ :ref:`tried_regions <sysfs_schemes_tried_regions>`/nr_regions,total_bytes
│ │ │ │ │ │ │ │ 0/start,end,nr_accesses,age,sz_filter_passed
│ │ │ │ │ │ │ │ │ probes
│ │ │ │ │ │ │ │ │ │ 0/hits
@@ -671,21 +671,24 @@ relevant ``kdamonds/<N>/state`` file. Refer to :ref:`kdamond directory
schemes/<N>/tried_regions/
--------------------------
-This directory initially has one file, ``total_bytes``.
+This directory initially has two files, ``nr_regions`` and ``total_bytes``.
When a special keyword, ``update_schemes_tried_regions``, is written to the
-relevant ``kdamonds/<N>/state`` file, DAMON updates the ``total_bytes`` file so
-that reading it returns the total size of the scheme tried regions, and creates
-directories named integer starting from ``0`` under this directory. Each
-directory contains files exposing detailed information about each of the memory
-region that the corresponding scheme's ``action`` has tried to be applied under
-this directory, during next :ref:`apply interval <damon_design_damos>` of the
-corresponding scheme. The information includes address range, ``nr_accesses``,
-and ``age`` of the region.
+relevant ``kdamonds/<N>/state`` file, DAMON updates the ``nr_regions`` and
+``total_bytes`` files so that reading them returns the number and the total size
+of the scheme tried regions, respectively, and creates directories named
+integer starting from ``0`` under this directory. Each directory contains files
+exposing detailed information about each of the memory region that the
+corresponding scheme's ``action`` has tried to be applied under this directory,
+during next :ref:`apply interval <damon_design_damos>` of the corresponding
+scheme. The information includes address range, ``nr_accesses``, and ``age`` of
+the region.
Writing ``update_schemes_tried_bytes`` to the relevant ``kdamonds/<N>/state``
-file will only update the ``total_bytes`` file, and will not create the
-subdirectories.
+file will only update the ``nr_regions`` and ``total_bytes`` files, and will not
+create the subdirectories. Hence ``nr_regions`` can be used as a lightweight way
+to read the number of the scheme tried regions without materializing the
+per-region subdirectories.
The directories will be removed when another special keyword,
``clear_schemes_tried_regions``, is written to the relevant
diff --git a/mm/damon/sysfs-schemes.c b/mm/damon/sysfs-schemes.c
index 54ff196f8e24..b1df9f21c267 100644
--- a/mm/damon/sysfs-schemes.c
+++ b/mm/damon/sysfs-schemes.c
@@ -297,6 +297,7 @@ static const struct kobj_type damon_sysfs_scheme_region_ktype = {
struct damon_sysfs_scheme_regions {
struct kobject kobj;
struct list_head regions_list;
+ int nr_region_dirs;
int nr_regions;
unsigned long total_bytes;
};
@@ -311,11 +312,21 @@ damon_sysfs_scheme_regions_alloc(void)
regions->kobj = (struct kobject){};
INIT_LIST_HEAD(®ions->regions_list);
+ regions->nr_region_dirs = 0;
regions->nr_regions = 0;
regions->total_bytes = 0;
return regions;
}
+static ssize_t nr_regions_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ struct damon_sysfs_scheme_regions *regions = container_of(kobj,
+ struct damon_sysfs_scheme_regions, kobj);
+
+ return sysfs_emit(buf, "%d\n", regions->nr_regions);
+}
+
static ssize_t total_bytes_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
@@ -335,7 +346,7 @@ static void damon_sysfs_scheme_regions_rm_dirs(
list_del(&r->list);
kobject_del(&r->kobj);
kobject_put(&r->kobj);
- regions->nr_regions--;
+ regions->nr_region_dirs--;
}
}
@@ -344,10 +355,14 @@ static void damon_sysfs_scheme_regions_release(struct kobject *kobj)
kfree(container_of(kobj, struct damon_sysfs_scheme_regions, kobj));
}
+static struct kobj_attribute damon_sysfs_scheme_regions_nr_regions_attr =
+ __ATTR_RO_MODE(nr_regions, 0400);
+
static struct kobj_attribute damon_sysfs_scheme_regions_total_bytes_attr =
__ATTR_RO_MODE(total_bytes, 0400);
static struct attribute *damon_sysfs_scheme_regions_attrs[] = {
+ &damon_sysfs_scheme_regions_nr_regions_attr.attr,
&damon_sysfs_scheme_regions_total_bytes_attr.attr,
NULL,
};
@@ -3133,6 +3148,7 @@ void damos_sysfs_populate_region_dir(struct damon_sysfs_schemes *sysfs_schemes,
return;
sysfs_regions = sysfs_schemes->schemes_arr[schemes_idx]->tried_regions;
+ sysfs_regions->nr_regions++;
sysfs_regions->total_bytes += r->ar.end - r->ar.start;
if (total_bytes_only)
return;
@@ -3144,13 +3160,13 @@ void damos_sysfs_populate_region_dir(struct damon_sysfs_schemes *sysfs_schemes,
if (kobject_init_and_add(®ion->kobj,
&damon_sysfs_scheme_region_ktype,
&sysfs_regions->kobj, "%d",
- sysfs_regions->nr_regions))
+ sysfs_regions->nr_region_dirs))
goto out;
if (damos_sysfs_region_add_dirs(region, ctx, r))
goto del_out;
list_add_tail(®ion->list, &sysfs_regions->regions_list);
- sysfs_regions->nr_regions++;
+ sysfs_regions->nr_region_dirs++;
return;
del_out:
@@ -3170,6 +3186,7 @@ int damon_sysfs_schemes_clear_regions(
sysfs_scheme = sysfs_schemes->schemes_arr[i];
damon_sysfs_scheme_regions_rm_dirs(
sysfs_scheme->tried_regions);
+ sysfs_scheme->tried_regions->nr_regions = 0;
sysfs_scheme->tried_regions->total_bytes = 0;
}
return 0;
--
2.43.0
^ permalink raw reply related [flat|nested] 6+ messages in thread* Re: [PATCH 1/2] mm/damon/core: cover discrete System RAM areas with per-range regions
2026-07-27 9:54 [PATCH 1/2] mm/damon/core: cover discrete System RAM areas with per-range regions Jiayuan Chen
2026-07-27 9:54 ` [PATCH 2/2] mm/damon/sysfs-schemes: report the number of tried regions Jiayuan Chen
@ 2026-07-27 10:07 ` sashiko-bot
2026-07-27 11:04 ` Jiayuan Chen
2026-07-27 14:26 ` SJ Park
2 siblings, 1 reply; 6+ messages in thread
From: sashiko-bot @ 2026-07-27 10:07 UTC (permalink / raw)
To: Jiayuan Chen; +Cc: damon
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] Time-of-check to time-of-use (TOCTOU) race condition in `damon_find_system_rams()` leads to a heap out-of-bounds write during concurrent memory hotplug.
- [High] DAMON initialization fails with `-EINVAL` on systems with sub-page System RAM gaps due to alignment overlap in `damon_set_regions()`.
--
--- Patch [1]: [PATCH 1/2] mm/damon/core: cover discrete System RAM areas with per-range regions ---
commit 20fb01bf6e8e4cefc6016dfc073fb77c95ca3511
Author: Jiayuan Chen <jiayuan.chen@shopee.com>
mm/damon/core: cover discrete System RAM areas with per-range regions
This commit changes how DAMON sets default monitoring regions for System RAM.
Instead of a single region covering all RAM (and the holes between), it now
creates separate regions for each discrete System RAM area. This excludes
large memory holes from being monitored.
> diff --git a/mm/damon/core.c b/mm/damon/core.c
> index 644daf5a16560..365c43271c0c6 100644
> --- a/mm/damon/core.c
> +++ b/mm/damon/core.c
[ ... ]
> +static int damon_find_system_rams(struct damon_addr_range **ranges_out,
> + unsigned long addr_unit)
> +{
> + struct damon_system_ram_range_walk_arg arg = { .addr_unit = addr_unit };
> + struct damon_addr_range *ranges;
>
> + /* First pass: count the coalesced ranges. */
> walk_system_ram_res(0, -1, &arg, damon_system_ram_walk_fn);
> - if (!arg.walked)
> - return false;
> - *start = damon_res_to_core_addr(arg.res.start, addr_unit);
> - *end = damon_res_to_core_addr(arg.res.end + 1, addr_unit);
> - if (*end <= *start)
> - return false;
> - return true;
> + if (!arg.nr_ranges)
> + return 0;
> + ranges = kvmalloc_objs(*ranges, arg.nr_ranges, GFP_KERNEL);
> + if (!ranges)
> + return -ENOMEM;
> + /* Second pass: fill in the coalesced ranges. */
> + arg.ranges = ranges;
> + arg.nr_ranges = 0;
> + arg.has_prev = false;
> + walk_system_ram_res(0, -1, &arg, damon_system_ram_walk_fn);
[Severity: High]
Can this race with memory hotplug events and cause an out-of-bounds write?
walk_system_ram_res() is called twice without holding memory hotplug locks.
If a new System RAM region is hotplugged between or during the second pass,
the number of visited regions could exceed the allocated buffer size.
If that happens, damon_system_ram_walk_fn() would write past the end of the
kvmalloc-allocated buffer:
mm/damon/core.c:damon_system_ram_walk_fn() {
...
if (a->ranges) {
a->ranges[a->nr_ranges].start = start;
a->ranges[a->nr_ranges].end = end;
}
a->nr_ranges++;
...
}
> + *ranges_out = ranges;
> + return arg.nr_ranges;
> }
[ ... ]
> @@ -3984,14 +4021,25 @@ int damon_set_region_system_rams_default(struct damon_target *t,
> unsigned long addr_unit, unsigned long min_region_sz)
> {
> struct damon_addr_range addr_range;
> + struct damon_addr_range *ranges;
> + int nr_ranges, err;
>
> - if (!*start && !*end &&
> - !damon_find_system_rams_range(start, end, addr_unit))
> - return -EINVAL;
> + if (*start || *end) {
> + addr_range.start = *start;
> + addr_range.end = *end;
> + return damon_set_regions(t, &addr_range, 1, min_region_sz);
> + }
>
> - addr_range.start = *start;
> - addr_range.end = *end;
> - return damon_set_regions(t, &addr_range, 1, min_region_sz);
> + nr_ranges = damon_find_system_rams(&ranges, addr_unit);
> + if (nr_ranges < 0)
> + return nr_ranges;
> + if (!nr_ranges)
> + return -EINVAL;
> + *start = ranges[0].start;
> + *end = ranges[nr_ranges - 1].end;
> + err = damon_set_regions(t, ranges, nr_ranges, min_region_sz);
[Severity: High]
Could this fail with -EINVAL during initialization on systems with sub-page
memory gaps?
If the system has System RAM fragmented by small non-RAM holes (like sub-page
BIOS or EFI reserved regions), damon_system_ram_walk_fn() will create separate
discrete regions.
When these regions are passed to damon_set_regions(), it aligns the boundaries
to min_region_sz (typically PAGE_SIZE):
mm/damon/core.c:damon_set_regions() {
for (i = 0; i < nr_ranges; i++) {
...
start = ALIGN_DOWN(ranges[i].start, min_region_sz);
end = ALIGN(ranges[i].end, min_region_sz);
if (start >= end)
return -EINVAL;
if (i > 0 && last_end > start)
return -EINVAL;
last_end = end;
}
...
}
If the gap between two RAM areas is smaller than PAGE_SIZE, the ALIGN of the
first region's end will evaluate to a higher address than the ALIGN_DOWN of
the second region's start. Does this cause the strictly-ascending overlap
check (last_end > start) to trigger and reject valid configurations?
> + kvfree(ranges);
> + return err;
> }
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260727095429.143527-1-jiayuan.chen@linux.dev?part=1
^ permalink raw reply [flat|nested] 6+ messages in thread* Re: [PATCH 1/2] mm/damon/core: cover discrete System RAM areas with per-range regions
2026-07-27 9:54 [PATCH 1/2] mm/damon/core: cover discrete System RAM areas with per-range regions Jiayuan Chen
2026-07-27 9:54 ` [PATCH 2/2] mm/damon/sysfs-schemes: report the number of tried regions Jiayuan Chen
2026-07-27 10:07 ` [PATCH 1/2] mm/damon/core: cover discrete System RAM areas with per-range regions sashiko-bot
@ 2026-07-27 14:26 ` SJ Park
2 siblings, 0 replies; 6+ messages in thread
From: SJ Park @ 2026-07-27 14:26 UTC (permalink / raw)
To: Jiayuan Chen
Cc: SJ Park, damon, Jiayuan Chen, Andrew Morton, David Hildenbrand,
Lorenzo Stoakes, Liam R. Howlett, Vlastimil Babka, Mike Rapoport,
Suren Baghdasaryan, Michal Hocko, Jonathan Corbet, Shuah Khan,
linux-mm, linux-kernel, linux-doc
Hello Jiayuan,
On Mon, 27 Jul 2026 17:54:22 +0800 Jiayuan Chen <jiayuan.chen@linux.dev> wrote:
> From: Jiayuan Chen <jiayuan.chen@shopee.com>
>
> damon_set_region_system_rams_default(), introduced by commit 70d8797c15d6
> ("mm/damon: introduce damon_set_region_system_rams_default()"), is used by
> DAMON_RECLAIM, DAMON_LRU_SORT and DAMON_STAT to set the default monitoring
> target address range covering all 'System RAM' when the user does not
> specify a range. It walks the 'System RAM' resources but keeps only the
> start of the first resource and the end of the last one, and then sets a
> single monitoring region spanning that whole [first_start, last_end] range.
>
> On systems whose RAM is split into discrete areas that are far apart in the
> physical address space, that single region also covers the holes between
> them. For example:
>
> $ sudo cat /proc/iomem | grep RAM
> 00001000-0009ffff : System RAM
> 00100000-4848c017 : System RAM
> 4848c018-48550c57 : System RAM
> 48550c58-48551017 : System RAM
> 48551018-48615c57 : System RAM
> 48615c58-48616017 : System RAM
> 48616018-486dac57 : System RAM
> 486dac58-4e563017 : System RAM
> 4e563018-4e627c57 : System RAM
> 4e627c58-4ef39017 : System RAM
> 4ef39018-4ef3f057 : System RAM
> 4ef3f058-4efe6017 : System RAM
> 4efe6018-4efec057 : System RAM
> 4efec058-50247fff : System RAM
> 50317000-56720fff : System RAM
> 56722000-59c19fff : System RAM
> 6bbfe000-6bbfefff : System RAM
> 6bc00000-777fffff : System RAM
> 100000000-1007effffff : System RAM
> 67e80000000-77e7fffffff : System RAM
>
> Here the last two areas (about 1TB starting at 4GiB, and about 1.1TB
> starting at ~6.5TB) are separated by a ~5.5TB hole, and the single-region
> setup makes DAMON treat that entire hole as if it were memory.
>
> This is harmful in a few ways. The monitoring target regions are limited
> by max_nr_regions, so regions that fall into the hole waste that budget and
> leave fewer regions for the real RAM, coarsening the adaptive regions and
> degrading the monitoring accuracy.
The hole would look like not accessed. As a result, the whole region will be a
few regions that very cold. That wouldn't waste the budget that much. Do you
have some specific setups that this cannot help?
> In addition, DAMOS actions on the paddr
> operations set walk such a region page by page, so a region that covers the
> hole is walked for its entire (empty) span on every application.
That makes sense.
>
> Set a separate monitoring region for each discrete System RAM area instead,
> coalescing only truly adjacent (no gap in between) resources into one
> range, so holes between the areas are excluded. The reported *start and
> *end still carry the overall first-start and last-end, so the user-visible
> default range reported via the module parameters is unchanged.
I'm concerned if this could result in having too many regions. The gap between
user-visible parameters and internal state is also a concern.
A quick workaround would be adjusting the memory layout in BIOS, using DAMON
sysfs interface instead, or setting the monitor_region_{start,end} to cover
only the single area. Have you considered such workarounds?
Let's complete this high level discussion first.
Thanks,
SJ
[...]
^ permalink raw reply [flat|nested] 6+ messages in thread