Skip to contentSkip to conversation

mm/damon: fix damos quota walk-position tracking

RFC PATCH Liew Rui Yan 2 participants 3 messages Aug 27

Subsystems (2)
lore
Share & tools
lore
#0 lore raw
Quoted 86 lines · On Wed, 26 Aug 2026 17:44:38 -0700 SJ Park <[email protected]> wrote:
Quoted 49 lines · On Wed, 26 Aug 2026 07:05:08 -0700 SJ Park <[email protected]> wrote:
Quoted 37 lines · On Wed, 26 Aug 2026 18:24:13 +0800 Liew Rui Yan <[email protected]>…
Quoted 6 lines · On Tue, 25 Aug 2026 06:54:57 -0700 SJ Park <[email protected]> wrote:
Quoted 4 lines · On Tue, 25 Aug 2026 20:46:16 +0800 Liew Rui Yan <[email protected]>…

DAMOS uses charge_target_from/charge_addr_from to remember how far a quota-limited walk has progressed. The current implementation has two problems:

Quoted 7 lines
Quoted 3 lines

1. Once set, the cursor unconditionally skips and resets at the last region of the tracked target, so the last region can be skipped even when it has not been processed.

I don't fully understand this. Could you please clarify more? Maybe adding a realistic example scenario would be helpful.

Problem: Unconditional skip of the last region

In the current damos_skip_charged_region(), there is this logic:

    if (r == damon_last_region(t)) {
        quota->charge_target_from = NULL;
        quota->charge_addr_from = 0;
        return true; /* Skip */
    }

Scenario:
1. Target has 2 regions: R1 (0-100 bytes) and R2 (100-200 bytes).

2. Quota is configured to process only 50 bytes per window.

3. Window 1: Processes R1 (0-50). Quota is full. Cursor is saved at (Target, 50).

4. Window 2: Skips R1 (0-50). Processes R1 (50-100). Quota is full. Cursor is saved at (Target, 100), which is exactly the start of R2.

Quoted 6 lines

5. Window 3: The loop reaches R2. Because R2 is damon_last_region(t), the old code unconditionally returns true, skipping R2 entirely and resetting the cursor.

Result: R2 is permanently skipped even though it has never been processed.

Ok, makes sense. The user impact should be not that big, though.

Quoted 1 line
Quoted 7 lines
Quoted 4 lines

To fix this, the patch advances the cursor every time a region is walked, regardless of whether it is applied or filtered out. This allows DAMON to accurately track whether the last region has already been visited, eliminating the need for the unconditional reset.

Sounds like a big change compared to the problem. Why we cannot modify the last region case? Have you also considered other possible simpler approaches?

For example,

'''

mm/damon/core.c +7 -6
@@ -2686,14 +2686,15 @@ static bool damos_skip_charged_region(struct damon_target *t,
        if (quota->charge_target_from) {
                if (t != quota->charge_target_from)
                        return true;
-               if (r == damon_last_region(t)) {
-                       quota->charge_target_from = NULL;
-                       quota->charge_addr_from = 0;
-                       return true;
-               }
                if (quota->charge_addr_from &&
-                               r->ar.end <= quota->charge_addr_from)
+                               r->ar.end <= quota->charge_addr_from) {
+                       if (r->ar.end == quota->charge_addr_from ||
+                                       r == damon_last_region(t)) {
+                               quota->charge_target_from = NULL;
+                               quota->charge_addr_from = 0;
+                       }
                        return true;
+               }
 
                if (quota->charge_addr_from && r->ar.start <
                                quota->charge_addr_from) {
Quoted 2 lines

'''

Thank you for the example!

While your approach works, I am curious, why should the cursor be reset every time the function returns false (does not skip)?

In my opinion, a cleaner and more deterministic approach is to reset the cursor only after the target has been fully iterated through. This separates "skip" logic from the "state reset" logic, making the flow easier to reason about.

Here is my proposed minimal change:

'''

mm/damon/core.c +12 -11
@@ -2347,11 +2347,6 @@ static bool damos_skip_charged_region(struct damon_target *t,
        if (quota->charge_target_from) {
                if (t != quota->charge_target_from)
                        return true;
-               if (r == damon_last_region(t)) {
-                       quota->charge_target_from = NULL;
-                       quota->charge_addr_from = 0;
-                       return true;
-               }
                if (quota->charge_addr_from &&
                                r->ar.end <= quota->charge_addr_from)
                        return true;
@@ -2368,8 +2363,6 @@ static bool damos_skip_charged_region(struct damon_target *t,
                        damon_split_region_at(t, r, sz_to_skip);
                        return true;
                }
-               quota->charge_target_from = NULL;
-               quota->charge_addr_from = 0;
        }
        return false;
 }
@@ -2658,18 +2651,26 @@ static void damon_do_apply_schemes(struct damon_ctx *c,
                if (damos_quota_is_full(quota, c->min_region_sz))
                        continue;
 
-               if (damos_skip_charged_region(t, r, s, c->min_region_sz))
-                       continue;
-
                if (s->max_nr_snapshots &&
                                s->max_nr_snapshots <= s->stat.nr_snapshots)
                        continue;
 
+               if (damos_skip_charged_region(t, r, s, c->min_region_sz)) {
+                       if (damon_is_last_region(r, t)) {
+                               quota->charge_target_from = NULL;
+                               quota->charge_addr_from = 0;
+                       }
+                       continue;
+               }
+
                if (damos_valid_target(c, r, s))
                        damos_apply_scheme(c, t, r, s);
 
-               if (damon_is_last_region(r, t))
+               if (damon_is_last_region(r, t)) {
                        s->stat.nr_snapshots++;
+                       quota->charge_target_from = NULL;
+                       quota->charge_addr_from = 0;
+               }
        }
 }

'''

Quoted 7 lines
Quoted 7 lines
Quoted 4 lines

This patch ensures that every target is traversed sequentially and deterministically, even when the quota is set very low. I omitted this benefit in the initial problem description. If you think it is okay, I will add it in the next revision.

What's the problem and benefit? I still don't get it. More clarification would be nice.

My original idea was to ensure that every target would be checked sequentially, which seemed like a fairer approach. However, upon further reflection, I realize this might not offer tangible benefits and could introduce unnecessary complexity.

Since the DAMOS Quota min_score mechanism already ensures that regions truly needing action are prioritized, the current behavior (eventually resetting at the last region and moving on) is functionally sufficient for typical workloads. Therefore, I do not see a strong justification for this change at this stage.

Thank you for pointing this out and pushing me to clarify. In the next revision, I will drop this changes and focus on the minimal fix for Problem 1.

Best regards,
Rui Yan

#1 lore raw

Hi SJ,

Apologies for the noise. I accidentally started a new thread due to missing --in-reply-to flag in my git send-email command.

Original thread: [email protected]

Best regards,
Rui Yan

maintainer
#2 lore raw
Quoted 6 lines · On Thu, 27 Aug 2026 14:02:55 +0800 Liew Rui Yan <[email protected]>…

Hi SJ,

Apologies for the noise. I accidentally started a new thread due to missing --in-reply-to flag in my git send-email command.

Original thread: [email protected]

No worry. Could you please re-post the reply to the original thread?

Thanks,
SJ

[...]