Skip to contentSkip to conversation

mm/damon: introduce DAMOS failed region quota charge ratio

RFC PATCH Superseded SeongJae Park 1 participant 14 messages Apr 9 – Apr 10

Subsystems (5)

v4 of 5 Superseded Latest is v5 →

lore
Share & tools
lore

SeongJae Park

author maintainer
#0 lore raw

v4 cover mm/damon: introduce DAMOS failed region quota charge ratio

TL; DR: Let users set different DAMOS quota charge ratios for DAMOS action failed regions, for deterministic and consistent DAMOS action progress.

Common Reports: Unexpectedly Slow DAMOS
=======================================

One common issue report that we get from DAMON users is that DAMOS action applying progress speed is sometimes much slower than expected. And one common root cause is that the DAMOS quota is exceeded by the action applying failed memory regions.

For example, a group of users tried to run DAMOS-based proactive memory reclamation (DAMON_RECLAIM) with 100 MiB per second DAMOS quota. They ran it on a system having no active workload which means all memory of the system is cold. The expectation was that the system will show 100 MiB per second reclamation until (nearly) all memory is reclaimed. But what they found is that the speed is quite inconsistent and sometimes it becomes very slower than the expectation, sometimes even no reclamation at all for about tens of seconds. The upper limit of the speed (100 MiB per second) was being kept as expected, though.

By monitoring the qt_exceeds (number of DAMOS quota exceed events) DAMOS stat, we found DAMOS quota is always exceeded when the speed is slow. By monitoring sz_tried and sz_applied (the total amount of DAMOS action tried memory and succeeded memory) DAMOS stats together, we found the reclamation attempts nearly always failed when the speed is slow.

DAMOS quota charges DAMOS action tried regions regardless of the successfulness of the try. Hence in the example reported case, there was unreclaimable memory spread around the system memory. Sometimes nearly 100 MiB of memory that DAMOS tried to reclaim in the given quota interval was reclaimable, and therefore showed nearly 100 MiB per second speed. Sometimes nearly 99 MiB of memory that DAMOS was trying to reclaim in the given quota interval was unreclaimable, and therefore showing only about 1 MiB per second reclaim speed.

We explained it is an expected behavior of the feature rather than a bug, as DAMOS quota is there for only the upper-limit of the speed. The users agreed and later reported a huge win from the adoption of DAMON_RECLAIM on their products.

It is Not a Bug but a Feature; But...
=====================================

So nothing is broken. DAMOS quota is working as intended, as the upper limit of the speed. It also provides its behavior observability via DAMOS stat. In the real world production environment that runs long term active workloads and matters stability, the speed sometimes being slow is not a real problem.

But, the non-deterministic behavior is sometimes annoying, especially in lab environments. Even in a realistic production environment, when there is a huge amount of DAMOS action unapplicable memory, the speed could be problematically slow. Let's suppose a virtual machines provider that setup 99% of the host memory as hugetlb pages that cannot be reclaimed, to give it to virtual machines. Also, when aim-oriented DAMOS auto-tuning is applied, this could also make the internal feedback loop confused.

The intention of the current behavior was that trying DAMOS action to regions would anyway impose some overhead, and therefore somehow be charged. But in the real world, the overhead for failed action is much lighter than successful action. Charging those at the same ratio may be unfair, or at least suboptimum in some environments.

DAMOS Action Failed Region Quota Charge Ratio
=============================================

Let users set the charge ratio for the action-failed memory, for more optimal and deterministic use of DAMOS. It allows users to specify the numerator and the denominator of the ratio for flexible setup. For example, let's suppose the numerator and the denominator are set to 1 and 4,096, respectively. The ratio is 1 / 4,096. A DAMOS scheme action is applied to 5 GiB memory. For 1 GiB of the memory, the action is succeeded. For the rest (4 GiB), the action is failed. Then, only 1 GiB and 1 MiB quota is charged.

The optimal charge ratio will depend on the use case and system/workload. I'd recommend starting from setting the nominator as 1 and the denominator as PAGE_SIZE and tune based on the results, because many DAMOS actions are applied at page level.

Tests
=====

I tested this feature in the steps below.

1. Allocate 50% of system memory and mlock() it using a test program.
2. Fill up the page cache to exhaust nearly all free memory.
3. Start DAMON-based proactive reclamation with 100 MiB/second DAMOS hard-quota. Auto-tune the DAMOS soft-quota under the hard-quota for achieving 40% free memory of the system with 'temporal' tuner.

For step 1, I run a simple C program that is written by Gemini. It is quite straightforward, so I'm not sharing the code here.

For step 2, I use dd command like below:

dd if=/dev/zero of=foo bs=1M count=$50_percent_of_system_memory

For step 3, I use the latest version of DAMON user-space tool (damo) like below.

    sudo damo start --damos_action pageout \
            ` # Do the pageout only up to 100 MiB per second ` \
            --damos_quota_space 100M --damos_quota_interval 1s \
            ` # Auto-tune the quota below the hard quota aiming` \
            ` # 40% free memory of the node 0 ` \
            ` # (entire node of the test system)` \
            --damos_quota_goal node_mem_free_bp 40% 0 \
            ` # use temporal tuner, which is easy to understnd ` \
            --damos_quota_goal_tuner temporal

As expected, the progress of the reclamation is not consistent, because the quota is exceeded for the failed reclamation of the unreclaimable memory.

I do this again, but with the failed region charge ratio feature. For this, the above 'damo' command is used, after appending command line option for setup of the charge ratio like below. Note that the option was added to 'damo' after v3.1.9.

    sudo ./damo start --damos_action pageout \
            [...]
            ` # quota-charge only 1/4096 for pageout-failed regions ` \
            --damos_quota_fail_charge_ratio 1 4096

The progress of the reclamation was nearly 100 MiB per second until the goal was achieved, meeting the expectation.

Patches Sequence
================

First two patches make preparational changes. Patch 1 updates fully charged quota check to handle <min_region_sz remaining quota, which will be able to exist after this series is applied. Patch 2 merges regions that split out for quota as soon as possible, since the split can happen much more frequently under a corner case that this series will make available.

Patch 3 implements the feature and exposes it via DAMON core API. Patch 4 implements DAMON sysfs ABI for the feature. Three following patches (5-7) document the feature and ABI on design, usage, and ABI documents, respectively. Four patches for testing of the new feature follow. Patch 8 implements a kunit test for the feature. Patches 9 and 10 extend DAMON selftest helpers for DAMON sysfs control and internal state dumping for adding a new selftest for the feature. Patch 11 extends existing DAMON sysfs interface selftest to test the new feature using the extended helper scripts.

Changelog
=========

Changes from RFC v3
(https://lore.kernel.org/[email protected])
- Make damos_quota_is_full() safe from overflow and easier to read.
- Avoid quota-based region split making too many new regions. Changes from RFC v2
(https://lore.kernel.org/[email protected])
- Handle <min_region_sz remaining quota.
- Document zero denum behavior.
- Fix typos: s/selftets/selftests/
Changes from RFC v1
(https://lore.kernel.org/[email protected])
- Avoid overflows in charge amount calculation.
- Fix/wordsmith documentation for grammar, typo, and wrong examples.
- Improve unit test for more consistent comparison source use.

SeongJae Park (11):
mm/damon/core: handle <min_region_sz remaining quota as empty mm/damon/core: merge quota-sliced regions back mm/damon/core: introduce failed region quota charge ratio mm/damon/sysfs-schemes: implement fail_charge_{num,denom} files Docs/mm/damon/design: document fail_charge_{num,denom} Docs/admin-guide/mm/damon/usage: document fail_charge_{num,denom}
files
Docs/ABI/damon: document fail_charge_{num,denom} mm/damon/tests/core-kunit: test fail_charge_{num,denom} committing selftests/damon/_damon_sysfs: support failed region quota charge ratio selftests/damon/drgn_dump_damon_status: support failed region quota
charge ratio
selftests/damon/sysfs.py: test failed region quota charge ratio

 .../ABI/testing/sysfs-kernel-mm-damon         | 12 +++
 Documentation/admin-guide/mm/damon/usage.rst  | 18 ++++-
 Documentation/mm/damon/design.rst             | 22 ++++++
 include/linux/damon.h                         |  9 +++
 mm/damon/core.c                               | 76 ++++++++++++++++---
 mm/damon/sysfs-schemes.c                      | 54 +++++++++++++
 mm/damon/tests/core-kunit.h                   |  6 ++
 tools/testing/selftests/damon/_damon_sysfs.py | 21 ++++-
 .../selftests/damon/drgn_dump_damon_status.py |  2 +
 tools/testing/selftests/damon/sysfs.py        |  6 ++
 10 files changed, 209 insertions(+), 17 deletions(-)

base-commit: dcd9caafdc7ec3f936d87d9698a6c126f77e9750

Signature · 1 line
2.47.3
author maintainer
#1 lore raw

RFC PATCH v4 1/11 mm/damon/core: handle <min_region_sz remaining quota as empty

Less than min_region_sz remaining quota effectively means the quota is fully charged. In other words, no remaining quota. This is because DAMOS actions are applied in the region granularity, and each region should have min_region_sz or larger size. However the existing fully charged quota check, which is also used for setting charge_target_from and charge_addr_from of the quota, is not aware of the case. For the reason, charge_target_from and charge_addr_from of the quota will not be updated in the case. This can result in DAMOS action being applied more frequently to a specific area of the memory.

The case is unreal because quota charging is also made in the region granularity. It could be changed in future, though. Actually, the following commit will make the change, by allowing users to set arbitrary quota charging ratio for action-failed regions. To be prepared for the change, update the fully charged quota checks to treat having less than min_region_sz remaining quota as fully charged.

---
 mm/damon/core.c | 23 +++++++++++++++++------
 1 file changed, 17 insertions(+), 6 deletions(-)
mm/damon/core.c +17 -6
index e680716972506..c7d05d2385fe8 100644
@@ -2042,6 +2042,20 @@ static void damos_walk_cancel(struct damon_ctx *ctx)
 	mutex_unlock(&ctx->walk_control_lock);
 }
 
+static bool damos_quota_is_full(struct damos_quota *quota,
+		unsigned long min_region_sz)
+{
+	if (!damos_quota_is_set(quota))
+		return false;
+	if (quota->charged_sz >= quota->esz)
+		return true;
+	/*
+	 * DAMOS action is applied per region, so <min_region_sz remaining
+	 * quota means the quota is effectively full.
+	 */
+	return quota->esz - quota->charged_sz < min_region_sz;
+}
+
 static void damos_apply_scheme(struct damon_ctx *c, struct damon_target *t,
 		struct damon_region *r, struct damos *s)
 {
@@ -2099,8 +2113,7 @@ static void damos_apply_scheme(struct damon_ctx *c, struct damon_target *t,
 		quota->total_charged_ns += timespec64_to_ns(&end) -
 			timespec64_to_ns(&begin);
 		quota->charged_sz += sz;
-		if (damos_quota_is_set(quota) &&
-				quota->charged_sz >= quota->esz) {
+		if (damos_quota_is_full(quota, c->min_region_sz)) {
 			quota->charge_target_from = t;
 			quota->charge_addr_from = r->ar.end + 1;
 		}
@@ -2128,8 +2141,7 @@ static void damon_do_apply_schemes(struct damon_ctx *c,
 			continue;
 
 		/* Check the quota */
-		if (damos_quota_is_set(quota) &&
-				quota->charged_sz >= quota->esz)
+		if (damos_quota_is_full(quota, c->min_region_sz))
 			continue;
 
 		if (damos_skip_charged_region(t, r, s, c->min_region_sz))
@@ -2455,8 +2467,7 @@ static void damos_adjust_quota(struct damon_ctx *c, struct damos *s)
 	if (!time_in_range_open(jiffies, quota->charged_from,
 				quota->charged_from +
 				msecs_to_jiffies(quota->reset_interval))) {
-		if (damos_quota_is_set(quota) &&
-				quota->charged_sz >= quota->esz)
+		if (damos_quota_is_full(quota, c->min_region_sz))
 			s->stat.qt_exceeds++;
 		quota->total_charged_sz += quota->charged_sz;
 		quota->charged_from = jiffies;
Signature · 1 line
2.47.3
author maintainer
#2 lore raw

RFC PATCH v4 2/11 mm/damon/core: merge quota-sliced regions back

In reply to SeongJae Park

damos_apply_scheme() splits the given region to avoid applying action to the region results in applying action to more than the quota limit memory. When such a split happens multiple times, theoretically that could result in violating the user-set max_nr_regions limit. If that happens, DAMON could impose overhead larger than the max_nr_regions based expectation.

Such cases should be rare since usually the real number of DAMON regions are much lower than the max_nr_regions. Even if it happens, because the split operations could be made only up to the number of schemes per scheme apply interval, the impact will be negligible. The impact could be higher after the following commit, though. The following commit will allow the action-failed region to be charged in a different (less than region size) ratio. As a result, the split operation could be made more frequently in a corner case.

Still it is a theoretical corner case. But it would still be better to be avoided unless it causes other issues, as max_nr_regions is one of the important user parameters.

Avoid the violation and resulting overhead by merging the sliced regions back, as soon as the schemes handling for the slices are done.

---
 mm/damon/core.c | 32 ++++++++++++++++++++++++++++----
 1 file changed, 28 insertions(+), 4 deletions(-)
mm/damon/core.c +28 -4
index c7d05d2385fe8..98ee776d98cd0 100644
@@ -2159,6 +2159,33 @@ static void damon_do_apply_schemes(struct damon_ctx *c,
 	}
 }
 
+static void damos_apply_target(struct damon_ctx *c, struct damon_target *t)
+{
+	struct damon_region *r, *next, *orig_region = NULL;
+	unsigned long orig_end_addr;
+
+	damon_for_each_region_safe(r, next, t) {
+		/*
+		 * damon_do_apply_schemes() split the region if applying the
+		 * action to the whole region can make quota exceeded.  That
+		 * split can result in DAMON snapshot having more than
+		 * max_nr_regions regions.
+		 *
+		 * Merge back the sliced regions to the original region, as
+		 * soon as the schemes-handling of the slice is completed.
+		 */
+		if (!orig_region || orig_end_addr <= r->ar.start) {
+			orig_region = r;
+			orig_end_addr = r->ar.end;
+		}
+		damon_do_apply_schemes(c, t, r);
+		if (r == orig_region)
+			continue;
+		orig_region->ar.end = r->ar.end;
+		damon_destroy_region(r, t);
+	}
+}
+
 /*
  * damon_feed_loop_next_input() - get next input to achieve a target score.
  * @last_input	The last input.
@@ -2528,7 +2555,6 @@ static void damos_trace_stat(struct damon_ctx *c, struct damos *s)
 static void kdamond_apply_schemes(struct damon_ctx *c)
 {
 	struct damon_target *t;
-	struct damon_region *r;
 	struct damos *s;
 	bool has_schemes_to_apply = false;
 
@@ -2551,9 +2577,7 @@ static void kdamond_apply_schemes(struct damon_ctx *c)
 	damon_for_each_target(t, c) {
 		if (c->ops.target_valid && c->ops.target_valid(t) == false)
 			continue;
-
-		damon_for_each_region(r, t)
-			damon_do_apply_schemes(c, t, r);
+		damos_apply_target(c, t);
 	}
 
 	damon_for_each_scheme(s, c) {
Signature · 1 line
2.47.3
author maintainer
#3 lore raw

Re: (sashiko review) [RFC PATCH v4 02/11] mm/damon/core: merge quota-sliced regions back

TL; DR: sashiko found clear issues. I will fix those in the next revision.

Forwarding full sashiko review in a reply format with my inline comments below, for sharing details of my view and doing followup discussions via mails if needed.

Quoted 29 lines
# review url: https://sashiko.dev/#/patchset/[email protected]
# start of sashiko.dev inline review
commit e2cfd0aa5e77f600726ed8baabf9eb6c27b3546d
Author: SeongJae Park <[email protected]>

mm/damon/core: merge quota-sliced regions back

This commit modifies the DAMON core to merge back regions that were sliced due to quota limits during scheme application. This aims to prevent the total number of regions from exceeding the user-defined max_nr_regions limit.

Quoted 4 lines
mm/damon/core.c +0 -0
@@ -2158,6 +2158,33 @@ static void damon_do_apply_schemes(struct damon_ctx *c,

[ ... ]

mm/damon/core.c static void damon_do_apply_schemes(struct damon_ctx *c, · line 2167

Quoted 6 lines
mm/damon/core.c +6 -0
+static void damos_apply_target(struct damon_ctx *c, struct damon_target *t)
+{
+	struct damon_region *r, *next, *orig_region = NULL;
+	unsigned long orig_end_addr;
+
+	damon_for_each_region_safe(r, next, t) {

Since damon_for_each_region_safe uses list_for_each_entry_safe, it will cache the next element before executing the loop body. If damon_do_apply_schemes splits the region r by inserting a new slice immediately after r, won't the loop advance to the cached next element and skip evaluating the newly inserted slice?

Quoted 3 lines

If the newly inserted slice is bypassed, it looks like the merge-back logic below would become dead code for that slice, leaving the slice stranded in the list and defeating the patch's goal of avoiding max_nr_regions violations.

That's correct. I will fix this on the next revision.

Quoted 28 lines
Quoted 9 lines
mm/damon/core.c +8 -0
+		/*
+		 * damon_do_apply_schemes() split the region if applying the
+		 * action to the whole region can make quota exceeded.  That
+		 * split can result in DAMON snapshot having more than
+		 * max_nr_regions regions.
+		 *
+		 * Merge back the sliced regions to the original region, as
+		 * soon as the schemes-handling of the slice is completed.

mm/damon/core.c static void damon_do_apply_schemes(struct damon_ctx *c, · line 2185

Quoted 10 lines
mm/damon/core.c +10 -0
+		 */
+		if (!orig_region || orig_end_addr <= r->ar.start) {
+			orig_region = r;
+			orig_end_addr = r->ar.end;
+		}
+		damon_do_apply_schemes(c, t, r);
+		if (r == orig_region)
+			continue;
+		orig_region->ar.end = r->ar.end;
+		damon_destroy_region(r, t);

Does this merge-back logic permanently destroy the monitoring state of the unapplied portion of the region?

When damon_do_apply_schemes applies a scheme to the first part of a region, its state might be modified (for example, resetting its age). The second part of the region that was split off and skipped due to quota limits is supposed to retain its original state.

Quoted 4 lines

By destroying the unapplied slice and extending the applied region's end address, the unapplied slice's original state appears to be clobbered by the applied slice's modified state. Could this prevent the unapplied memory from meeting thresholds like min_age in subsequent intervals?

Again, good finding. I will fix this in the next revision.

Quoted 7 lines

mm/damon/core.c

Quoted 3 lines
mm/damon/core.c +2 -0
+	}
+}
# end of sashiko.dev inline review
# review url: https://sashiko.dev/#/patchset/[email protected]

Thanks,
SJ

# hkml [1] generated a draft of this mail.  You can regenerate
# this using below command:
#
#     hkml patch sashiko_dev --for_forwarding \
#             [email protected]
#
# [1] https://github.com/sjp38/hackermail
author maintainer
#4 lore raw

RFC PATCH v4 3/11 mm/damon/core: introduce failed region quota charge ratio

In reply to SeongJae Park

DAMOS quota is charged to all DAMOS action application attempted memory, regardless of how much of the memory the action was successful and failed. This makes understanding quota behavior without DAMOS stat but only with end level metrics (e.g., increased amount of free memory for DAMOS_PAGEOUT action) difficult. Also, charging action-failed memory same as action-successful memory is somewhat unfair, as successful action application will induce more overhead in most cases.

Introduce DAMON core API for setting the charge ratio for such action-failed memory. It allows API callers to specify the ratio in a flexible way, by setting the numerator and the denominator.

---
 include/linux/damon.h |  9 +++++++++
 mm/damon/core.c       | 21 ++++++++++++++++++++-
 2 files changed, 29 insertions(+), 1 deletion(-)
include/linux/damon.h +9 -0
index 4b69f4553267d..9ab7331775b9e 100644
@@ -233,6 +233,8 @@ enum damos_quota_goal_tuner {
  * @goals:		Head of quota tuning goals (&damos_quota_goal) list.
  * @goal_tuner:		Goal-based @esz tuning algorithm to use.
  * @esz:		Effective size quota in bytes.
+ * @fail_charge_num:	Failed regions charge rate numerator.
+ * @fail_charge_denom:	Failed regions charge rate denominator.
  *
  * @weight_sz:		Weight of the region's size for prioritization.
  * @weight_nr_accesses:	Weight of the region's nr_accesses for prioritization.
@@ -262,6 +264,10 @@ enum damos_quota_goal_tuner {
  *
  * The resulting effective size quota in bytes is set to @esz.
  *
+ * For DAMOS action applying failed amount of regions, charging those same to
+ * those that the action has successfully applied may be unfair.  For the
+ * reason, 'the size * @fail_charge_num / @fail_charge_denom' is charged.
+ *
  * For selecting regions within the quota, DAMON prioritizes current scheme's
  * target memory regions using the &struct damon_operations->get_scheme_score.
  * You could customize the prioritization logic by setting &weight_sz,
@@ -276,6 +282,9 @@ struct damos_quota {
 	enum damos_quota_goal_tuner goal_tuner;
 	unsigned long esz;
 
+	unsigned int fail_charge_num;
+	unsigned int fail_charge_denom;
+
 	unsigned int weight_sz;
 	unsigned int weight_nr_accesses;
 	unsigned int weight_age;
mm/damon/core.c +20 -1
index 98ee776d98cd0..9b9b124e867d5 100644
@@ -918,6 +918,8 @@ static int damos_commit_quota(struct damos_quota *dst, struct damos_quota *src)
 	if (err)
 		return err;
 	dst->goal_tuner = src->goal_tuner;
+	dst->fail_charge_num = src->fail_charge_num;
+	dst->fail_charge_denom = src->fail_charge_denom;
 	dst->weight_sz = src->weight_sz;
 	dst->weight_nr_accesses = src->weight_nr_accesses;
 	dst->weight_age = src->weight_age;
@@ -2042,6 +2044,23 @@ static void damos_walk_cancel(struct damon_ctx *ctx)
 	mutex_unlock(&ctx->walk_control_lock);
 }
 
+static void damos_charge_quota(struct damos_quota *quota,
+		unsigned long sz_region, unsigned long sz_applied)
+{
+	/*
+	 * sz_applied could be bigger than sz_region, depending on ops
+	 * implementation of the action, e.g., damos_pa_pageout().  Charge only
+	 * the region size in the case.
+	 */
+	if (!quota->fail_charge_denom || sz_applied > sz_region)
+		quota->charged_sz += sz_region;
+	else
+		quota->charged_sz += sz_applied + mult_frac(
+				(sz_region - sz_applied),
+				quota->fail_charge_num,
+				quota->fail_charge_denom);
+}
+
 static bool damos_quota_is_full(struct damos_quota *quota,
 		unsigned long min_region_sz)
 {
@@ -2112,7 +2131,7 @@ static void damos_apply_scheme(struct damon_ctx *c, struct damon_target *t,
 		ktime_get_coarse_ts64(&end);
 		quota->total_charged_ns += timespec64_to_ns(&end) -
 			timespec64_to_ns(&begin);
-		quota->charged_sz += sz;
+		damos_charge_quota(quota, sz, sz_applied);
 		if (damos_quota_is_full(quota, c->min_region_sz)) {
 			quota->charge_target_from = t;
 			quota->charge_addr_from = r->ar.end + 1;
Signature · 1 line
2.47.3
author maintainer
#5 lore raw

Re: (sashiko review) [RFC PATCH v4 03/11] mm/damon/core: introduce failed region quota charge ratio

# sashiko review suggestions
#
# 1. Consider reducing recipients.  Maybe the author,
#    maintainers, reviewers, and mailing list of the
#    direct subsystem and parent susystem mailing lists
#    could be a starting point.
# 2. Add short summary of your opinion at the beginning.
#    For example:
#
#      TL;DR: sashiko found an issue.  I will respin.
#      TL;DR: sashiko found no issue on this patch.
#
# Please DON'T FORGET removing this comment block before
# sending this!

Forwarding full sashiko review in a reply format with my inline comments below, for sharing details of my view and doing followup discussions via mails if needed.

Quoted 52 lines
# review url: https://sashiko.dev/#/patchset/[email protected]
# start of sashiko.dev inline review
commit 55afcaa519ab8046d8aa94af44973c3cff0c2a26
Author: SeongJae Park <[email protected]>

mm/damon/core: introduce failed region quota charge ratio

DAMOS quota is charged to all DAMOS action application attempted memory, regardless of how much of the memory the action was successful and failed. This makes understanding quota behavior without DAMOS stat but only with end level metrics (e.g., increased amount of free memory for DAMOS_PAGEOUT action) difficult. Also, charging action-failed memory same as action-successful memory is somewhat unfair, as successful action application will induce more overhead in most cases.

Introduce DAMON core API for setting the charge ratio for such action-failed memory. It allows API callers to specify the ratio in a flexible way, by setting the numerator and the denominator.

Quoted 4 lines
mm/damon/core.c +0 -0
index 31ba739d28e2f..1f171665509b8 100644

[ ... ]

Quoted 9 lines
mm/damon/core.c +5 -0
@@ -2041,6 +2043,23 @@ static void damos_walk_cancel(struct damon_ctx *ctx)
 	mutex_unlock(&ctx->walk_control_lock);
 }
 
+static void damos_charge_quota(struct damos_quota *quota,
+		unsigned long sz_region, unsigned long sz_applied)
+{
+	/*
+	 * sz_applied could be bigger than sz_region, depending on ops

mm/damon/core.c static void damos_walk_cancel(struct damon_ctx *ctx) · line 2060

Quoted 10 lines
mm/damon/core.c +10 -0
+	 * implementation of the action, e.g., damos_pa_pageout().  Charge only
+	 * the region size in the case.
+	 */
+	if (!quota->fail_charge_denom || sz_applied > sz_region)
+		quota->charged_sz += sz_region;
+	else
+		quota->charged_sz += sz_applied + mult_frac(
+				(sz_region - sz_applied),
+				quota->fail_charge_num,
+				quota->fail_charge_denom);

Could this calculation lead to an integer overflow?

The mult_frac() macro evaluates the numerator multiplication internally using the type of the first argument, which is unsigned long. The sysfs interface in the subsequent patch ("mm/damon/sysfs-schemes: implement fail_charge_{num,denom} files") populates these values using kstrtouint, allowing inputs up to UINT_MAX.

Quoted 5 lines

If a user configures large values for fail_charge_num, could the multiplication silently overflow the 32-bit unsigned long result on 32-bit platforms, or on 64-bit platforms for sufficiently large memory regions?

This might produce a wrapped-around charged_sz and affect quota enforcement.

Sashiko asked same question in the previous version. I decided to keep this as is for code simplicity, as the issue is quite minor, unless other humans make some voices.

Quoted 19 lines

mm/damon/core.c static void damos_apply_scheme(struct damon_ctx *c, struct damon_target *t, · line 2133

Quoted 12 lines
mm/damon/core.c +3 -1
+}
+
 static bool damos_quota_is_full(struct damos_quota *quota,
 		unsigned long min_region_sz)
 {
@@ -2111,7 +2130,7 @@ static void damos_apply_scheme(struct damon_ctx *c, struct damon_target *t,
 		ktime_get_coarse_ts64(&end);
 		quota->total_charged_ns += timespec64_to_ns(&end) -
 			timespec64_to_ns(&begin);
-		quota->charged_sz += sz;
+		damos_charge_quota(quota, sz, sz_applied);

Does discounting charged_sz for failed actions affect time quota enforcement?

DAMON converts the time quota (ms) into an effective size quota (esz) using throughput. If fail_charge_num is small or 0, the charged bytes accumulate very slowly, which could drive the throughput estimate to near zero.

Quoted 5 lines

This would cause esz to bottom out at min_region_sz. Since kdamond stops only when charged_sz >= esz, and charged_sz increases only by heavily discounted bytes, is it possible that charged_sz never reaches esz? This might cause kdamond to endlessly process the entire target memory space in a single cycle and consume unbounded CPU time.

Action failure is expected to be faster than successful action applying. Hence the denominator of the throughput, the total time took for the charging, should also be reduced. Users may set the ratio based on their expected overhead difference between the time for failure and success of the actions. So, this is an intended behavior, not a bug.

Quoted 6 lines

Additionally, does the discounting affect region splitting earlier in damos_apply_scheme()?

The region is split using:

Quoted 7 lines
    sz = ALIGN_DOWN(quota->esz - quota->charged_sz, c->min_region_sz);

Here, esz and charged_sz track discounted bytes, but the resulting sz is passed to damon_split_region_at() to split the physical memory region. Could using a discounted byte limit as a physical byte size cause the action to be unnecessarily restricted to a tiny chunk, preventing the scheme from being applied to the full allowed physical area?

Yes. And therefore the previous patch is made, though the current version of the patch is completely wrong.

Quoted 14 lines

mm/damon/core.c static void damos_apply_scheme(struct damon_ctx *c, struct damon_target *t, · line 2137

Quoted 4 lines
mm/damon/core.c +0 -0
 		if (damos_quota_is_full(quota, c->min_region_sz)) {
 			quota->charge_target_from = t;
 			quota->charge_addr_from = r->ar.end + 1;

As a separate note regarding a preceding patch ("mm/damon/core: merge quota-sliced regions back"), are regions split due to quota limits being skipped and orphaned during the target loop?

In damos_apply_target(), the loop uses damon_for_each_region_safe(r, next, t) which fetches next before executing the body. If damon_do_apply_schemes() splits r, damon_split_region_at() inserts the new remainder region immediately after r.

Quoted 4 lines

Because next was pre-fetched to the original next region, the newly inserted remainder appears to be bypassed by the iterator. This might prevent the remainder region from being processed by other schemes in the current cycle, and the intended merge-back logic might not execute for it.

Yes, as I mentioned to the earlier question just above.

Quoted 4 lines

# end of sashiko.dev inline review
# review url: https://sashiko.dev/#/patchset/[email protected]

Thanks,
SJ

# hkml [1] generated a draft of this mail.  You can regenerate
# this using below command:
#
#     hkml patch sashiko_dev --for_forwarding \
#             [email protected]
#
# [1] https://github.com/sjp38/hackermail
author maintainer
#6 lore raw

RFC PATCH v4 4/11 mm/damon/sysfs-schemes: implement fail_charge_{num,denom} files

In reply to SeongJae Park

Implement the user-space ABI for the DAMOS action failed region quota-charge ratio setup. For this, add two new sysfs files under the DAMON sysfs interface for DAMOS quotas. Names of the files are fail_charge_num and fail_charge_denom, and work for reading and setting the numerator and denominator of the failed regions charge ratio.

---
 mm/damon/sysfs-schemes.c | 54 ++++++++++++++++++++++++++++++++++++++++
 1 file changed, 54 insertions(+)
mm/damon/sysfs-schemes.c +54 -0
index 5186966dafb35..d5bfba79f105f 100644
@@ -1489,6 +1489,8 @@ struct damon_sysfs_quotas {
 	unsigned long reset_interval_ms;
 	unsigned long effective_sz;	/* Effective size quota in bytes */
 	enum damos_quota_goal_tuner goal_tuner;
+	unsigned int fail_charge_num;
+	unsigned int fail_charge_denom;
 };
 
 static struct damon_sysfs_quotas *damon_sysfs_quotas_alloc(void)
@@ -1663,6 +1665,48 @@ static ssize_t goal_tuner_store(struct kobject *kobj,
 	return -EINVAL;
 }
 
+static ssize_t fail_charge_num_show(struct kobject *kobj,
+		struct kobj_attribute *attr, char *buf)
+{
+	struct damon_sysfs_quotas *quotas = container_of(kobj,
+			struct damon_sysfs_quotas, kobj);
+
+	return sysfs_emit(buf, "%u\n", quotas->fail_charge_num);
+}
+
+static ssize_t fail_charge_num_store(struct kobject *kobj,
+		struct kobj_attribute *attr, const char *buf, size_t count)
+{
+	struct damon_sysfs_quotas *quotas = container_of(kobj,
+			struct damon_sysfs_quotas, kobj);
+	int err = kstrtouint(buf, 0, &quotas->fail_charge_num);
+
+	if (err)
+		return -EINVAL;
+	return count;
+}
+
+static ssize_t fail_charge_denom_show(struct kobject *kobj,
+		struct kobj_attribute *attr, char *buf)
+{
+	struct damon_sysfs_quotas *quotas = container_of(kobj,
+			struct damon_sysfs_quotas, kobj);
+
+	return sysfs_emit(buf, "%u\n", quotas->fail_charge_denom);
+}
+
+static ssize_t fail_charge_denom_store(struct kobject *kobj,
+		struct kobj_attribute *attr, const char *buf, size_t count)
+{
+	struct damon_sysfs_quotas *quotas = container_of(kobj,
+			struct damon_sysfs_quotas, kobj);
+	int err = kstrtouint(buf, 0, &quotas->fail_charge_denom);
+
+	if (err)
+		return -EINVAL;
+	return count;
+}
+
 static void damon_sysfs_quotas_release(struct kobject *kobj)
 {
 	kfree(container_of(kobj, struct damon_sysfs_quotas, kobj));
@@ -1683,12 +1727,20 @@ static struct kobj_attribute damon_sysfs_quotas_effective_bytes_attr =
 static struct kobj_attribute damon_sysfs_quotas_goal_tuner_attr =
 		__ATTR_RW_MODE(goal_tuner, 0600);
 
+static struct kobj_attribute damon_sysfs_quotas_fail_charge_num_attr =
+		__ATTR_RW_MODE(fail_charge_num, 0600);
+
+static struct kobj_attribute damon_sysfs_quotas_fail_charge_denom_attr =
+		__ATTR_RW_MODE(fail_charge_denom, 0600);
+
 static struct attribute *damon_sysfs_quotas_attrs[] = {
 	&damon_sysfs_quotas_ms_attr.attr,
 	&damon_sysfs_quotas_sz_attr.attr,
 	&damon_sysfs_quotas_reset_interval_ms_attr.attr,
 	&damon_sysfs_quotas_effective_bytes_attr.attr,
 	&damon_sysfs_quotas_goal_tuner_attr.attr,
+	&damon_sysfs_quotas_fail_charge_num_attr.attr,
+	&damon_sysfs_quotas_fail_charge_denom_attr.attr,
 	NULL,
 };
 ATTRIBUTE_GROUPS(damon_sysfs_quotas);
@@ -2776,6 +2828,8 @@ static struct damos *damon_sysfs_mk_scheme(
 		.weight_nr_accesses = sysfs_weights->nr_accesses,
 		.weight_age = sysfs_weights->age,
 		.goal_tuner = sysfs_quotas->goal_tuner,
+		.fail_charge_num = sysfs_quotas->fail_charge_num,
+		.fail_charge_denom = sysfs_quotas->fail_charge_denom,
 	};
 	struct damos_watermarks wmarks = {
 		.metric = sysfs_wmarks->metric,
Signature · 1 line
2.47.3
author maintainer
#7 lore raw

RFC PATCH v4 5/11 Docs/mm/damon/design: document fail_charge_{num,denom}

In reply to SeongJae Park

Update DAMON design document for the DAMOS action failed region quota charge ratio.

---
 Documentation/mm/damon/design.rst | 22 ++++++++++++++++++++++
 1 file changed, 22 insertions(+)
Documentation/mm/damon/design.rst +22 -0
index 510ec6375178d..94e898b671d15 100644
@@ -572,6 +572,28 @@ interface <sysfs_interface>`, refer to :ref:`weights <sysfs_quotas>` part of
 the documentation.
 
 
+.. _damon_design_damos_quotas_failed_memory_charging_ratio:
+
+Action-failed Memory Charging Ratio
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+DAMOS action to a given region can fail for some subsets of the memory of the
+region.  For example, if the action is ``pageout`` and the region has some
+unreclaimable pages, applying the action to the pages will fail.  The amount of
+system resource that is taken for such failed action applications is usually
+different from that for successful action applications.  For such cases, users
+can set different charging ratio for such failed memory.  The ratio can be
+specified using ``fail_charge_num`` and ``fail_charge_denom`` parameters.  The
+two parameters represent the numerator and denominator of the ratio.  The
+feature is enabled only if ``fail_charge_denom`` is not zero.
+
+For example, let's suppose a DAMOS action is applied to a region of 1,000 MiB
+size.  The action is successfully applied to only 700 MiB of the region.
+``fail_charge_num`` and ``fail_charge_denom`` are set to ``1`` and ``1024``,
+respectively.  Then only 700 MiB and 300 KiB of size (``700 MiB + 300 MiB * 1 /
+1024``) will be charged.
+
+
 .. _damon_design_damos_quotas_auto_tuning:
 
 Aim-oriented Feedback-driven Auto-tuning
Signature · 1 line
2.47.3
author maintainer
#8 lore raw

RFC PATCH v4 6/11 Docs/admin-guide/mm/damon/usage: document fail_charge_{num,denom} files

In reply to SeongJae Park

Update DAMON usage document for the DAMOS action failed regions quota charge ratio control sysfs files.

---
 Documentation/admin-guide/mm/damon/usage.rst | 18 ++++++++++++++----
 1 file changed, 14 insertions(+), 4 deletions(-)
Documentation/admin-guide/mm/damon/usage.rst +14 -4
index bfdb717441f05..d5548e460857c 100644
@@ -84,7 +84,9 @@ comma (",").
     │ │ │ │ │ │ │ │ sz/min,max
     │ │ │ │ │ │ │ │ nr_accesses/min,max
     │ │ │ │ │ │ │ │ age/min,max
-    │ │ │ │ │ │ │ :ref:`quotas <sysfs_quotas>`/ms,bytes,reset_interval_ms,effective_bytes,goal_tuner
+    │ │ │ │ │ │ │ :ref:`quotas <sysfs_quotas>`/ms,bytes,reset_interval_ms,
+    │ │ │ │ │ │ │     effective_bytes,goal_tuner,
+    │ │ │ │ │ │ │     fail_charge_num,fail_charge_denom
     │ │ │ │ │ │ │ │ weights/sz_permil,nr_accesses_permil,age_permil
     │ │ │ │ │ │ │ │ :ref:`goals <sysfs_schemes_quota_goals>`/nr_goals
     │ │ │ │ │ │ │ │ │ 0/target_metric,target_value,current_value,nid,path
@@ -381,9 +383,10 @@ schemes/<N>/quotas/
 The directory for the :ref:`quotas <damon_design_damos_quotas>` of the given
 DAMON-based operation scheme.
 
-Under ``quotas`` directory, five files (``ms``, ``bytes``,
-``reset_interval_ms``, ``effective_bytes`` and ``goal_tuner``) and two
-directories (``weights`` and ``goals``) exist.
+Under ``quotas`` directory, seven files (``ms``, ``bytes``,
+``reset_interval_ms``, ``effective_bytes``, ``goal_tuner``, ``fail_charge_num``
+and ``fail_charge_denom``) and two directories (``weights`` and ``goals``)
+exist.
 
 You can set the ``time quota`` in milliseconds, ``size quota`` in bytes, and
 ``reset interval`` in milliseconds by writing the values to the three files,
@@ -402,6 +405,13 @@ the background design of the feature and the name of the selectable algorithms.
 Refer to :ref:`goals directory <sysfs_schemes_quota_goals>` for the goals
 setup.
 
+You can set the action-failed memory quota charging ratio by writing the
+numerator and the denominator for the ratio to ``fail_charge_num`` and
+``fail_charge_denom`` files, respectively.  Reading those files will return the
+current set values.  Refer to :ref:`design
+<damon_design_damos_quotas_failed_memory_charging_ratio>` for more details of
+the ratio feature.
+
 The time quota is internally transformed to a size quota.  Between the
 transformed size quota and user-specified size quota, smaller one is applied.
 Based on the user-specified :ref:`goal <sysfs_schemes_quota_goals>`, the
Signature · 1 line
2.47.3
author maintainer
#9 lore raw

RFC PATCH v4 7/11 Docs/ABI/damon: document fail_charge_{num,denom}

In reply to SeongJae Park

Update DAMON ABI document for the DAMOS action failed regions quota charge ratio control sysfs files.

---
 Documentation/ABI/testing/sysfs-kernel-mm-damon | 12 ++++++++++++
 1 file changed, 12 insertions(+)
Documentation/ABI/testing/sysfs-kernel-mm-damon +12 -0
index 7059f540940f0..971c22e34e722 100644
@@ -329,6 +329,18 @@ Contact:	SeongJae Park <[email protected]>
 Description:	Writing to and reading from this file sets and gets the
 		goal-based effective quota auto-tuning algorithm to use.
 
+What:		/sys/kernel/mm/damon/admin/kdamonds/<K>/contexts/<C>/schemes/<S>/quotas/fail_charge_num
+Date:		Mar 2026
+Contact:	SeongJae Park <[email protected]>
+Description:	Writing to and reading from this file sets and gets the
+		action-failed memory quota charging ratio numerator.
+
+What:		/sys/kernel/mm/damon/admin/kdamonds/<K>/contexts/<C>/schemes/<S>/quotas/fail_charge_denom
+Date:		Mar 2026
+Contact:	SeongJae Park <[email protected]>
+Description:	Writing to and reading from this file sets and gets the
+		action-failed memory quota charging ratio denominator.
+
 What:		/sys/kernel/mm/damon/admin/kdamonds/<K>/contexts/<C>/schemes/<S>/quotas/weights/sz_permil
 Date:		Mar 2022
 Contact:	SeongJae Park <[email protected]>
Signature · 1 line
2.47.3
author maintainer
#10 lore raw

RFC PATCH v4 8/11 mm/damon/tests/core-kunit: test fail_charge_{num,denom} committing

In reply to SeongJae Park

Extend damos_test_commit_quotas() kunit test to ensure damos_commit_quota() handles fail_charge_{num,denom} parameters.

---
 mm/damon/tests/core-kunit.h | 6 ++++++
 1 file changed, 6 insertions(+)
mm/damon/tests/core-kunit.h +6 -0
index 0030f682b23b7..1b23a22ac04c4 100644
@@ -694,6 +694,8 @@ static void damos_test_commit_quota(struct kunit *test)
 		.ms = 2,
 		.sz = 3,
 		.goal_tuner = DAMOS_QUOTA_GOAL_TUNER_CONSIST,
+		.fail_charge_num = 2,
+		.fail_charge_denom = 3,
 		.weight_sz = 4,
 		.weight_nr_accesses = 5,
 		.weight_age = 6,
@@ -703,6 +705,8 @@ static void damos_test_commit_quota(struct kunit *test)
 		.ms = 8,
 		.sz = 9,
 		.goal_tuner = DAMOS_QUOTA_GOAL_TUNER_TEMPORAL,
+		.fail_charge_num = 1,
+		.fail_charge_denom = 1024,
 		.weight_sz = 10,
 		.weight_nr_accesses = 11,
 		.weight_age = 12,
@@ -717,6 +721,8 @@ static void damos_test_commit_quota(struct kunit *test)
 	KUNIT_EXPECT_EQ(test, dst.ms, src.ms);
 	KUNIT_EXPECT_EQ(test, dst.sz, src.sz);
 	KUNIT_EXPECT_EQ(test, dst.goal_tuner, src.goal_tuner);
+	KUNIT_EXPECT_EQ(test, dst.fail_charge_num, src.fail_charge_num);
+	KUNIT_EXPECT_EQ(test, dst.fail_charge_denom, src.fail_charge_denom);
 	KUNIT_EXPECT_EQ(test, dst.weight_sz, src.weight_sz);
 	KUNIT_EXPECT_EQ(test, dst.weight_nr_accesses, src.weight_nr_accesses);
 	KUNIT_EXPECT_EQ(test, dst.weight_age, src.weight_age);
Signature · 1 line
2.47.3
author maintainer
#11 lore raw

RFC PATCH v4 9/11 selftests/damon/_damon_sysfs: support failed region quota charge ratio

In reply to SeongJae Park

Extend _damon_sysfs.py for DAMOS action failed regions quota charge ratio setup, so that we can add kselftest for the new feature.

---
 tools/testing/selftests/damon/_damon_sysfs.py | 21 +++++++++++++++++--
 1 file changed, 19 insertions(+), 2 deletions(-)
tools/testing/selftests/damon/_damon_sysfs.py +19 -2
index 120b96ecbd741..8b12cc0484405 100644
@@ -132,14 +132,17 @@ class DamosQuota:
     goals = None                # quota goals
     goal_tuner = None           # quota goal tuner
     reset_interval_ms = None    # quota reset interval
+    fail_charge_num = None
+    fail_charge_denom = None
     weight_sz_permil = None
     weight_nr_accesses_permil = None
     weight_age_permil = None
     scheme = None               # owner scheme
 
     def __init__(self, sz=0, ms=0, goals=None, goal_tuner='consist',
-                 reset_interval_ms=0, weight_sz_permil=0,
-                 weight_nr_accesses_permil=0, weight_age_permil=0):
+                 reset_interval_ms=0, fail_charge_num=0, fail_charge_denom=0,
+                 weight_sz_permil=0, weight_nr_accesses_permil=0,
+                 weight_age_permil=0):
         self.sz = sz
         self.ms = ms
         self.reset_interval_ms = reset_interval_ms
@@ -151,6 +154,8 @@ class DamosQuota:
         for idx, goal in enumerate(self.goals):
             goal.idx = idx
             goal.quota = self
+        self.fail_charge_num = fail_charge_num
+        self.fail_charge_denom = fail_charge_denom
 
     def sysfs_dir(self):
         return os.path.join(self.scheme.sysfs_dir(), 'quotas')
@@ -197,6 +202,18 @@ class DamosQuota:
                 os.path.join(self.sysfs_dir(), 'goal_tuner'), self.goal_tuner)
         if err is not None:
             return err
+
+        err = write_file(
+                os.path.join(self.sysfs_dir(), 'fail_charge_num'),
+                self.fail_charge_num)
+        if err is not None:
+            return err
+        err = write_file(
+                os.path.join(self.sysfs_dir(), 'fail_charge_denom'),
+                self.fail_charge_denom)
+        if err is not None:
+            return err
+
         return None
 
 class DamosWatermarks:
Signature · 1 line
2.47.3
author maintainer
#12 lore raw

RFC PATCH v4 10/11 selftests/damon/drgn_dump_damon_status: support failed region quota charge ratio

In reply to SeongJae Park

Extend drgn_dump_damon_status.py to dump DAMON internal state for DAMOS action failed regions quota charge ratio, to be able to show if the internal state for the feature is working, with future DAMON selftests.

---
 tools/testing/selftests/damon/drgn_dump_damon_status.py | 2 ++
 1 file changed, 2 insertions(+)
tools/testing/selftests/damon/drgn_dump_damon_status.py +2 -0
index 5b90eb8e7ef88..972948e6215f1 100755
@@ -112,6 +112,8 @@ def damos_quota_to_dict(quota):
         ['goals', damos_quota_goals_to_list],
         ['goal_tuner', int],
         ['esz', int],
+        ['fail_charge_num', int],
+        ['fail_charge_denom', int],
         ['weight_sz', int],
         ['weight_nr_accesses', int],
         ['weight_age', int],
Signature · 1 line
2.47.3
author maintainer
#13 lore raw

RFC PATCH v4 11/11 selftests/damon/sysfs.py: test failed region quota charge ratio

In reply to SeongJae Park

Extend sysfs.py DAMON selftest to setup DAMOS action failed region quota charge ratio and assert the setup is made into DAMON internal state.

---
 tools/testing/selftests/damon/sysfs.py | 6 ++++++
 1 file changed, 6 insertions(+)
tools/testing/selftests/damon/sysfs.py +6 -0
index b8d6e0f8fd906..d8d4717128898 100755
@@ -73,6 +73,10 @@ def assert_quota_committed(quota, dump):
             }
     assert_true(dump['goal_tuner'] == tuner_val[quota.goal_tuner],
                 'goal_tuner', dump)
+    assert_true(dump['fail_charge_num'] == quota.fail_charge_num,
+                'fail_charge_num', dump)
+    assert_true(dump['fail_charge_denom'] == quota.fail_charge_denom,
+                'fail_charge_denom', dump)
     assert_true(dump['weight_sz'] == quota.weight_sz_permil, 'weight_sz', dump)
     assert_true(dump['weight_nr_accesses'] == quota.weight_nr_accesses_permil,
                 'weight_nr_accesses', dump)
@@ -277,6 +281,8 @@ def main():
                         nid=1)],
                     goal_tuner='temporal',
                     reset_interval_ms=1500,
+                    fail_charge_num=1,
+                    fail_charge_denom=4096,
                     weight_sz_permil=20,
                     weight_nr_accesses_permil=200,
                     weight_age_permil=1000),
Signature · 1 line
2.47.3