Skip to contentSkip to conversation

mm/damon: fix zero quota for PSI goals under the temporal tuner

PATCH In review Karl Mehltretter 4 participants 16 messages Sep 15 – Sep 21

Subsystems (2)
lore
Share & tools
lore
#0 lore raw

cover mm/damon: fix zero quota for PSI goals under the temporal tuner

A PSI quota goal without a previous sample is scored as achieved. With the temporal tuner, this sets the quota to zero for the first charge window and the window after each goal commit.

The series is based on next-20260911. It fixes commit a68878f83ae6 ("mm/damon/core: handle uninitialized damos_quota_goal->last_psi_total"), which is in mm-new and tagged for stable. Please carry this fix with it.

Patch 1 keeps the temporal quota active until a PSI delta is available. Patch 2 adds a regression test for the first round, the next measured round, the round after a goal commit, a measured round that reaches the target, and the consist tuner's unmeasured round.

Reproduced with damo 3.4.0 (b41965a) in a QEMU guest running next-20260911 (x86_64, CONFIG_DAMON_SYSFS=y, CONFIG_PSI=y):

  damo start --damos_action stat --damos_quota_interval 120000 \
      --damos_quota_space 1048576 \
      --damos_quota_goal some_mem_psi_us 100000000000 \
      --damos_quota_goal_tuner temporal

then reading quotas/effective_bytes after update_schemes_effective_quotas every 18 seconds. Without patch 1 the effective quota is 0 for the whole first 120 second window and again for the window after "damo tune" with the same parameters. With patch 1 it is 1048576 throughout.

The KUnit test in patch 2 fails without patch 1 and passes with it on x86_64 and i386 (CONFIG_DAMON_KUNIT_TEST=y, built-in KUnit run at boot).

Karl Mehltretter (2):
mm/damon/core: score an unmeasured PSI goal as not achieved for the
temporal tuner
mm/damon/tests/core-kunit: test PSI goal rounds under the temporal
tuner

 mm/damon/core.c             | 14 +++++---
 mm/damon/tests/core-kunit.h | 69 +++++++++++++++++++++++++++++++++++++
 2 files changed, 79 insertions(+), 4 deletions(-)

base-commit: 68142f986ff04b2b70b31db00f719bf690f64a9a

Signature · 1 line
2.53.0
#1 lore raw

PATCH 1/2 mm/damon/core: score an unmeasured PSI goal as not achieved for the temporal tuner

A newly created or committed PSI quota goal has no previous sample. Commit a68878f83ae6 ("mm/damon/core: handle uninitialized damos_quota_goal->last_psi_total") scores the first tuning round as achieved to preserve the consist tuner's quota, since the feedback loop returns its input unchanged at that score.

The temporal tuner sets the quota to zero for an achieved goal. This skips the first charge window and the window after each goal commit. Committing the PSI goal before every tuning round keeps the scheme idle indefinitely.

Score an unmeasured PSI goal as not achieved for the temporal tuner, so a new scheme can run in its first window. Keep the consist tuner's behaviour unchanged.

Fixes a68878f83ae6 ("mm/damon/core: handle uninitialized damos_quota_goal->last_psi_total")
Cc <[email protected]> # 7.1.x
Assisted-by LLM
---
 mm/damon/core.c | 14 ++++++++++----
 1 file changed, 10 insertions(+), 4 deletions(-)
mm/damon/core.c +10 -4
index 06cf2ab7e97d..e6d87fd3992e 100644
@@ -3161,12 +3161,18 @@ static void damos_set_quota_goal_current_value(struct damon_ctx *c,
 		break;
 	case DAMOS_QUOTA_SOME_MEM_PSI_US:
 		now_psi_total = damos_get_some_mem_psi_total();
-		/* uninitialized last_psi_total; make no effect this round */
-		if (goal->last_psi_total == U64_MAX)
-			goal->current_value = goal->target_value;
-		else
+		if (goal->last_psi_total == U64_MAX) {
+			/* uninitialized last_psi_total; make no effect this round */
+			if (s->quota.goal_tuner ==
+					DAMOS_QUOTA_GOAL_TUNER_TEMPORAL)
+				/* an achieved score would zero the temporal quota */
+				goal->current_value = 0;
+			else
+				goal->current_value = goal->target_value;
+		} else {
 			goal->current_value = now_psi_total -
 				goal->last_psi_total;
+		}
 		goal->last_psi_total = now_psi_total;
 		break;
 	case DAMOS_QUOTA_NODE_MEM_USED_BP:
Signature · 1 line
2.53.0
#2 lore raw

Re: [PATCH 1/2] mm/damon/core: score an unmeasured PSI goal as not achieved for the temporal tuner

Sashiko has reviewed this patch and found no issues. It looks great!
Quoted 11 lines

A newly created or committed PSI quota goal has no previous sample. Commit a68878f83ae6 ("mm/damon/core: handle uninitialized damos_quota_goal->last_psi_total") scores the first tuning round as achieved to preserve the consist tuner's quota, since the feedback loop returns its input unchanged at that score.

The temporal tuner sets the quota to zero for an achieved goal. This skips the first charge window and the window after each goal commit. Committing the PSI goal before every tuning round keeps the scheme idle indefinitely.

Quoted 8 lines

Score an unmeasured PSI goal as not achieved for the temporal tuner, so a new scheme can run in its first window. Keep the consist tuner's behaviour unchanged.

Fixes a68878f83ae6 ("mm/damon/core: handle uninitialized damos_quota_goal->last_psi_total")
Cc <[email protected]> # 7.1.x
Assisted-by LLM

Sashiko has reviewed this patch and found no issues. It looks great!

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=1
maintainer
#3 lore raw

Re: [PATCH 1/2] mm/damon/core: score an unmeasured PSI goal as not achieved for the temporal tuner

In reply to Karl Mehltretter

Hello Karl,

Quoted 6 lines · On Tue, 15 Sep 2026 08:09:36 +0200 Karl Mehltretter <[email protected]>…

A newly created or committed PSI quota goal has no previous sample. Commit a68878f83ae6 ("mm/damon/core: handle uninitialized damos_quota_goal->last_psi_total") scores the first tuning round as achieved to preserve the consist tuner's quota, since the feedback loop returns its input unchanged at that score.

Quoted 8 lines

The temporal tuner sets the quota to zero for an achieved goal. This skips the first charge window and the window after each goal commit. Committing the PSI goal before every tuning round keeps the scheme idle indefinitely.

Score an unmeasured PSI goal as not achieved for the temporal tuner, so a new scheme can run in its first window. Keep the consist tuner's behaviour unchanged.

Good catch, thank you.

Quoted 20 lines
Fixes a68878f83ae6 ("mm/damon/core: handle uninitialized damos_quota_goal->last_psi_total")
Cc <[email protected]> # 7.1.x
Assisted-by LLM
---
 mm/damon/core.c | 14 ++++++++++----
 1 file changed, 10 insertions(+), 4 deletions(-)
mm/damon/core.c +0 -3
index 06cf2ab7e97d..e6d87fd3992e 100644
@@ -3161,12 +3161,18 @@ static void damos_set_quota_goal_current_value(struct damon_ctx *c,
 		break;
 	case DAMOS_QUOTA_SOME_MEM_PSI_US:
 		now_psi_total = damos_get_some_mem_psi_total();
-		/* uninitialized last_psi_total; make no effect this round */
-		if (goal->last_psi_total == U64_MAX)
-			goal->current_value = goal->target_value;

mm/damon/core.c static void damos_set_quota_goal_current_value(struct damon_ctx *c, · line 3172

Quoted 10 lines
mm/damon/core.c +9 -1
-		else
+		if (goal->last_psi_total == U64_MAX) {
+			/* uninitialized last_psi_total; make no effect this round */
+			if (s->quota.goal_tuner ==
+					DAMOS_QUOTA_GOAL_TUNER_TEMPORAL)
+				/* an achieved score would zero the temporal quota */
+				goal->current_value = 0;
+			else
+				goal->current_value = goal->target_value;
+		} else {

The intention is to make no effect this round. Setting current_value to zero to temporal tuner means it will now have highest quota it could have. If the effective quota before this was zero, it gets an effect.

Ideally, we should somehow remember what was the last esz and keep it. I have no good idea for doing that with minimum change. This is a corner case in my opinion (correct me if I'm wrong) so I want to keep the change as simple as possible.

If there is not easy way to do that, I think just keeping the behavior but making it explicitly explained might be better.

What do you think, Karl?

Thanks,
SJ

[...]

#4 lore raw

Re: [PATCH 1/2] mm/damon/core: score an unmeasured PSI goal as not achieved for the temporal tuner

Quoted 5 lines · SJ Park

The intention is to make no effect this round. Setting current_value to zero to temporal tuner means it will now have highest quota it could have. If the effective quota before this was zero, it gets an effect.

Quoted 8 lines

Ideally, we should somehow remember what was the last esz and keep it. I have no good idea for doing that with minimum change. This is a corner case in my opinion (correct me if I'm wrong) so I want to keep the change as simple as possible.

If there is not easy way to do that, I think just keeping the behavior but making it explicitly explained might be better.

Hello SJ,

I believe I have a version that addresses your concern without adding too much complexity.

In short, it leaves the tuners as they are and instead keeps last_psi_total across a commit of an existing PSI goal, as the code did before a68878f83ae6. The U64_MAX round then happens only once per new goal.

That also covers anything that commits before every tuning round, like the user_input feedback path or "damo tune", which currently never gets a measured round on either tuner.

I will test that and send it as v2.

Thanks,
Karl

maintainer
#5 lore raw

Re: [PATCH 1/2] mm/damon/core: score an unmeasured PSI goal as not achieved for the temporal tuner

Quoted 11 lines · On Tue, 15 Sep 2026 23:19:27 +0200 Karl Mehltretter <[email protected]>…
Quoted 10 lines · SJ Park

The intention is to make no effect this round. Setting current_value to zero to temporal tuner means it will now have highest quota it could have. If the effective quota before this was zero, it gets an effect.

Ideally, we should somehow remember what was the last esz and keep it. I have no good idea for doing that with minimum change. This is a corner case in my opinion (correct me if I'm wrong) so I want to keep the change as simple as possible.

Quoted 8 lines
Quoted 3 lines

If there is not easy way to do that, I think just keeping the behavior but making it explicitly explained might be better.

Hello SJ,

I believe I have a version that addresses your concern without adding too much complexity.

Thank you for keep pursuing on improving DAMON, Karl!

Quoted 5 lines

In short, it leaves the tuners as they are and instead keeps last_psi_total across a commit of an existing PSI goal, as the code did before a68878f83ae6. The U64_MAX round then happens only once per new goal.

I actually considered this option when working on commit a68878f83ae6. I had two following concerns though. First, if the user commits multiple times before the next quota reset interval, the next tuning round will work with pressure times that cumulated for longer than the quota reset interval. This is a quite rare corner case, but I didn't feel that comfortable. Second, the behavior for new commit and update commit is different.

So I'd still prefer to just making no effect this round. And my commit was failed at doing that, because it didn't aware of temporal tuner. Maybe we could show whether the goal was achieved or not, using esz and let the tuner show same achieveness? What about something like below?

'''

mm/damon/core.c +20 -6
@@ -3187,6 +3187,25 @@ static inline u64 damos_get_some_mem_psi_total(void)
 
 #endif /* CONFIG_PSI */
 
+static void damos_set_psi_current_val(u64 now_psi_total, struct
+               damos_quota_goal *goal, struct damos *s)
+{
+       if (goal->last_psi_total != U64_MAX) {
+               goal->current_value = now_psi_total - goal->last_psi_total;
+               return;
+       }
+       /* Uninitialized last_psi_total; make no effect this round */
+       if (s->quota.goal_tuner == DAMOS_QUOTA_GOAL_TUNER_CONSIST) {
+               goal->current_value = goal->target_value;
+               return;
+       }
+       /* Let temporal tuner show goal achieveness same to the last round */
+       if (!s->quota.esz)
+               goal->current_value = goal->target_value;
+       else
+               goal->current_value = 0;
+}
+
 #ifdef CONFIG_NUMA
 static bool invalid_mem_node(int nid)
 {
@@ -3439,12 +3458,7 @@ static void damos_set_quota_goal_current_value(struct damon_ctx *c,
                break;
        case DAMOS_QUOTA_SOME_MEM_PSI_US:
                now_psi_total = damos_get_some_mem_psi_total();
-               /* uninitialized last_psi_total; make no effect this round */
-               if (goal->last_psi_total == U64_MAX)
-                       goal->current_value = goal->target_value;
-               else
-                       goal->current_value = now_psi_total -
-                               goal->last_psi_total;
+               damos_set_psi_current_val(now_psi_total, goal, s);
                goal->last_psi_total = now_psi_total;
                break;
        case DAMOS_QUOTA_NODE_MEM_USED_BP:

'''

The code could further cleaned and optimized, but hopefully that will give you my rough idea.

What do you think?

Thanks,
SJ

[...]

#6 lore raw

Re: [PATCH 1/2] mm/damon/core: score an unmeasured PSI goal as not achieved for the temporal tuner

Quoted 5 lines · SJ Park

So I'd still prefer to just making no effect this round. And my commit was failed at doing that, because it didn't aware of temporal tuner. Maybe we could show whether the goal was achieved or not, using esz and let the tuner show same achieveness? What about something like below?

Thanks for explaining the two concerns. I am still new to the DAMON code, so I may be missing something, but your suggestion looks good to me and it is simpler than what I had. I have prepared a v2 this way and will wait for your comments before I send it.

One note on the first concern: as far as I can see last_psi_total is updated in every tuning round, so commits in between would not make the measured time longer than the reset interval. But I am fine with your approach either way.

I would also move the last_psi_total update into the new helper, so that the helper is the only place that touches the field and the unit test can check it.

While testing this with damo I found a second problem. A full "damo tune" still gave a zero quota for one window, on the unpatched kernel too. damon_new_scheme() runs damos_quota_init() on the quota of the caller, and since b90408ef1163 damon_commit_ctx() passes the quota of each running scheme to it for the test context. So every full commit zeroes esz, esz_bp and the charge state of the running schemes. Please correct me if I read this wrong. I can send a separate patch for that.

In a quick QEMU test with your suggestion and that change, the quota stays at 1 MiB over "damo tune" and "damo tune --quota_goals_only".

Thanks,
Karl

maintainer
#7 lore raw

Re: [PATCH 1/2] mm/damon/core: score an unmeasured PSI goal as not achieved for the temporal tuner

Quoted 11 lines · On Sat, 19 Sep 2026 07:44:11 +0200 Karl Mehltretter <[email protected]>…
Quoted 5 lines · SJ Park

So I'd still prefer to just making no effect this round. And my commit was failed at doing that, because it didn't aware of temporal tuner. Maybe we could show whether the goal was achieved or not, using esz and let the tuner show same achieveness? What about something like below?

Thanks for explaining the two concerns. I am still new to the DAMON code, so I may be missing something, but your suggestion looks good to me and it is simpler than what I had. I have prepared a v2 this way and will wait for your comments before I send it.

Thank you for accepting my humble suggestion, please feel free to send it when you are ready!

Quoted 5 lines

One note on the first concern: as far as I can see last_psi_total is updated in every tuning round, so commits in between would not make the measured time longer than the reset interval. But I am fine with your approach either way.

last_psi_total is updated in every tuning round, as you mentioned. But the tuning round comes if both the scheme apply interval and the quota reset interval are passed. damos_adjust_quota() has the quota reset interval check. kdamond_apply_schemes() has the apply interval check before calling damos_adjust_quota().

'''
static void kdamond_apply_schemes(struct damon_ctx *c) {
[...]

        damon_for_each_scheme(s, c) {
                if (time_before(c->passed_sample_intervals, s->next_apply_sis))
                        continue;
[...]
                damos_adjust_quota(c, s);

'''

'''
static void damos_adjust_quota(struct damon_ctx *c, struct damos *s) {
[...]

        /* New charge window starts */
        if (!time_in_range_open(jiffies, quota->charged_from,
                                quota->charged_from +
                                msecs_to_jiffies(quota->reset_interval))) {
[...]
                damos_set_effective_quota(c, s);

'''

And the apply interval (or, next_apply_sis) is extended for each commit, in damon_set_attrs(). Hence, the problem can still happen.

Quoted 4 lines

I would also move the last_psi_total update into the new helper, so that the helper is the only place that touches the field and the unit test can check it.

Sounds good!

Quoted 9 lines

While testing this with damo I found a second problem. A full "damo tune" still gave a zero quota for one window, on the unpatched kernel too. damon_new_scheme() runs damos_quota_init() on the quota of the caller, and since b90408ef1163 damon_commit_ctx() passes the quota of each running scheme to it for the test context. So every full commit zeroes esz, esz_bp and the charge state of the running schemes. Please correct me if I read this wrong. I can send a separate patch for that.

I don't really understand the problem. Could you please further elaborate?

Quoted 3 lines

In a quick QEMU test with your suggestion and that change, the quota stays at 1 MiB over "damo tune" and "damo tune --quota_goals_only".

Sounds good. Looking forward to your next patch!

Thanks,
SJ

[...]

#8 lore raw

Re: [PATCH 1/2] mm/damon/core: score an unmeasured PSI goal as not achieved for the temporal tuner

Quoted 3 lines · SJ Park

And the apply interval (or, next_apply_sis) is extended for each commit, in damon_set_attrs(). Hence, the problem can still happen.

Thank you for explaining the apply interval. So the PSI measurement period can exceed the quota reset interval.

Quoted 6 lines
Quoted 3 lines

full commit zeroes esz, esz_bp and the charge state of the running schemes. Please correct me if I read this wrong. I can send a separate patch for that.

I don't really understand the problem. Could you please further elaborate?

I posted this as a separate patch series with KUnit tests:

https://lore.kernel.org/r/[email protected]/

Thanks,
Karl

#9 lore raw

PATCH 2/2 mm/damon/tests/core-kunit: test PSI goal rounds under the temporal tuner

In reply to Karl Mehltretter

Exercise a PSI quota goal under the temporal tuner on its first round, its next measured round, and after a goal commit. Use ULONG_MAX as the target so the goal is not reached during the test. Then check that a measured round which does reach the target sets the quota to zero, and that the consist tuner keeps its quota over an unmeasured round.

Without the previous patch, the first and the post-commit checks see a zero quota.

Assisted-by LLM
---
 mm/damon/tests/core-kunit.h | 69 +++++++++++++++++++++++++++++++++++++
 1 file changed, 69 insertions(+)
mm/damon/tests/core-kunit.h +69 -0
index c01e6a75cadc..2b9b24ce7dad 100644
@@ -889,6 +889,74 @@ static void damos_test_commit_quota_goal(struct kunit *test)
 			});
 }
 
+/*
+ * Unmeasured PSI goals must not disable the temporal quota.
+ * Keep sz * 10000 within a 32-bit unsigned long.
+ */
+static void damos_test_set_effective_quota_temporal_psi(struct kunit *test)
+{
+	struct damon_ctx *c = damon_new_ctx();
+	struct damos_access_pattern pattern = {};
+	struct damos_quota quota = {
+		.sz = SZ_64K,
+		.goal_tuner = DAMOS_QUOTA_GOAL_TUNER_TEMPORAL,
+	};
+	struct damos_watermarks wmarks = {};
+	struct damos_quota_goal src = {
+		.metric = DAMOS_QUOTA_SOME_MEM_PSI_US,
+		.target_value = ULONG_MAX,
+	};
+	struct damos_quota_goal *goal;
+	struct damos *s;
+
+	if (!c)
+		kunit_skip(test, "ctx alloc fail");
+	s = damon_new_scheme(&pattern, DAMOS_STAT, 0, &quota, &wmarks,
+			NUMA_NO_NODE);
+	if (!s) {
+		damon_destroy_ctx(c);
+		kunit_skip(test, "scheme alloc fail");
+	}
+	damon_add_scheme(c, s);
+	goal = damos_new_quota_goal(DAMOS_QUOTA_SOME_MEM_PSI_US, ULONG_MAX);
+	if (!goal) {
+		damon_destroy_ctx(c);
+		kunit_skip(test, "goal alloc fail");
+	}
+	damos_add_quota_goal(&s->quota, goal);
+
+	/* fresh goal, first tuning round */
+	damos_set_effective_quota(c, s);
+	KUNIT_EXPECT_EQ(test, s->quota.esz, (unsigned long)SZ_64K);
+
+	/* second round: last_psi_total is initialised now */
+	damos_set_effective_quota(c, s);
+	KUNIT_EXPECT_EQ(test, s->quota.esz, (unsigned long)SZ_64K);
+
+	/* commit a PSI goal onto the initialised PSI goal */
+	damos_commit_quota_goal(goal, &src);
+	damos_set_effective_quota(c, s);
+	KUNIT_EXPECT_EQ(test, s->quota.esz, (unsigned long)SZ_64K);
+
+	/* a measured round that reaches the target disables the quota */
+	goal->target_value = 10;
+	goal->last_psi_total = damos_get_some_mem_psi_total() - 10;
+	/* U64_MAX marks an unmeasured goal, keep the sample away from it */
+	if (goal->last_psi_total == U64_MAX)
+		goal->last_psi_total--;
+	damos_set_effective_quota(c, s);
+	KUNIT_EXPECT_EQ(test, s->quota.esz, 0ul);
+
+	/* the consist tuner keeps its quota over an unmeasured round */
+	s->quota.goal_tuner = DAMOS_QUOTA_GOAL_TUNER_CONSIST;
+	s->quota.esz_bp = SZ_32K * 10000;
+	damos_commit_quota_goal(goal, &src);
+	damos_set_effective_quota(c, s);
+	KUNIT_EXPECT_EQ(test, s->quota.esz, (unsigned long)SZ_32K);
+
+	damon_destroy_ctx(c);
+}
+
 static void damos_test_commit_quota_goals_for(struct kunit *test,
 		struct damos_quota_goal *dst_goals, int nr_dst_goals,
 		struct damos_quota_goal *src_goals, int nr_src_goals)
@@ -1887,6 +1955,7 @@ static struct kunit_case damon_test_cases[] = {
 	KUNIT_CASE(damos_test_new_filter),
 	KUNIT_CASE(damos_test_commit_quota_goal),
 	KUNIT_CASE(damos_test_commit_quota_goals),
+	KUNIT_CASE(damos_test_set_effective_quota_temporal_psi),
 	KUNIT_CASE(damos_test_commit_quota),
 	KUNIT_CASE(damos_test_commit_dests),
 	KUNIT_CASE(damos_test_commit_filter),
Signature · 1 line
2.53.0
#10 lore raw

Re: [PATCH 2/2] mm/damon/tests/core-kunit: test PSI goal rounds under the temporal tuner

Sashiko has reviewed this patch and found no issues. It looks great!
Quoted 7 lines

Exercise a PSI quota goal under the temporal tuner on its first round, its next measured round, and after a goal commit. Use ULONG_MAX as the target so the goal is not reached during the test. Then check that a measured round which does reach the target sets the quota to zero, and that the consist tuner keeps its quota over an unmeasured round.

Quoted 5 lines

Without the previous patch, the first and the post-commit checks see a zero quota.

Assisted-by LLM

Sashiko has reviewed this patch and found no issues. It looks great!

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=2
#11 lore raw

Re: [PATCH 2/2] mm/damon/tests/core-kunit: test PSI goal rounds under the temporal tuner

In reply to Karl Mehltretter
Quoted 56 lines · Karl Mehltretter

Exercise a PSI quota goal under the temporal tuner on its first round, its next measured round, and after a goal commit. Use ULONG_MAX as the target so the goal is not reached during the test. Then check that a measured round which does reach the target sets the quota to zero, and that the consist tuner keeps its quota over an unmeasured round.

Without the previous patch, the first and the post-commit checks see a zero quota.

Assisted-by LLM
---
 mm/damon/tests/core-kunit.h | 69 +++++++++++++++++++++++++++++++++++++
 1 file changed, 69 insertions(+)
mm/damon/tests/core-kunit.h +31 -0
index c01e6a75cadc..2b9b24ce7dad 100644
@@ -889,6 +889,74 @@ static void damos_test_commit_quota_goal(struct kunit *test)
                        });
 }
 
+/*
+ * Unmeasured PSI goals must not disable the temporal quota.
+ * Keep sz * 10000 within a 32-bit unsigned long.
+ */
+static void damos_test_set_effective_quota_temporal_psi(struct kunit *test)
+{
+       struct damon_ctx *c = damon_new_ctx();
+       struct damos_access_pattern pattern = {};
+       struct damos_quota quota = {
+               .sz = SZ_64K,
+               .goal_tuner = DAMOS_QUOTA_GOAL_TUNER_TEMPORAL,
+       };
+       struct damos_watermarks wmarks = {};
+       struct damos_quota_goal src = {
+               .metric = DAMOS_QUOTA_SOME_MEM_PSI_US,
+               .target_value = ULONG_MAX,
+       };
+       struct damos_quota_goal *goal;
+       struct damos *s;
+
+       if (!c)
+               kunit_skip(test, "ctx alloc fail");
+       s = damon_new_scheme(&pattern, DAMOS_STAT, 0, &quota, &wmarks,
+                       NUMA_NO_NODE);
+       if (!s) {
+               damon_destroy_ctx(c);
+               kunit_skip(test, "scheme alloc fail");
+       }
+       damon_add_scheme(c, s);
+       goal = damos_new_quota_goal(DAMOS_QUOTA_SOME_MEM_PSI_US, ULONG_MAX);
+       if (!goal) {

mm/damon/tests/core-kunit.h static void damos_test_commit_quota_goal(struct kunit *test) · line 932

Quoted 10 lines
mm/damon/tests/core-kunit.h +10 -0
+               damon_destroy_ctx(c);
+               kunit_skip(test, "goal alloc fail");
+       }
+       damos_add_quota_goal(&s->quota, goal);
+
+       /* fresh goal, first tuning round */
+       damos_set_effective_quota(c, s);
+       KUNIT_EXPECT_EQ(test, s->quota.esz, (unsigned long)SZ_64K);
+
+       /* second round: last_psi_total is initialised now */

Could we also check goal->last_psi_total here?

The first tuning round should consume the U64_MAX sentinel and establish the measured state.

Testing this explicitly would lock in that state transition.

Thanks,
Kunwu

Quoted 41 lines
mm/damon/tests/core-kunit.h +28 -1
+       damos_set_effective_quota(c, s);
+       KUNIT_EXPECT_EQ(test, s->quota.esz, (unsigned long)SZ_64K);
+
+       /* commit a PSI goal onto the initialised PSI goal */
+       damos_commit_quota_goal(goal, &src);
+       damos_set_effective_quota(c, s);
+       KUNIT_EXPECT_EQ(test, s->quota.esz, (unsigned long)SZ_64K);
+
+       /* a measured round that reaches the target disables the quota */
+       goal->target_value = 10;
+       goal->last_psi_total = damos_get_some_mem_psi_total() - 10;
+       /* U64_MAX marks an unmeasured goal, keep the sample away from it */
+       if (goal->last_psi_total == U64_MAX)
+               goal->last_psi_total--;
+       damos_set_effective_quota(c, s);
+       KUNIT_EXPECT_EQ(test, s->quota.esz, 0ul);
+
+       /* the consist tuner keeps its quota over an unmeasured round */
+       s->quota.goal_tuner = DAMOS_QUOTA_GOAL_TUNER_CONSIST;
+       s->quota.esz_bp = SZ_32K * 10000;
+       damos_commit_quota_goal(goal, &src);
+       damos_set_effective_quota(c, s);
+       KUNIT_EXPECT_EQ(test, s->quota.esz, (unsigned long)SZ_32K);
+
+       damon_destroy_ctx(c);
+}
+
 static void damos_test_commit_quota_goals_for(struct kunit *test,
                struct damos_quota_goal *dst_goals, int nr_dst_goals,
                struct damos_quota_goal *src_goals, int nr_src_goals)
@@ -1887,6 +1955,7 @@ static struct kunit_case damon_test_cases[] = {
        KUNIT_CASE(damos_test_new_filter),
        KUNIT_CASE(damos_test_commit_quota_goal),
        KUNIT_CASE(damos_test_commit_quota_goals),
+       KUNIT_CASE(damos_test_set_effective_quota_temporal_psi),
        KUNIT_CASE(damos_test_commit_quota),
        KUNIT_CASE(damos_test_commit_dests),
        KUNIT_CASE(damos_test_commit_filter),
--

2.53.0

#12 lore raw

Re: [PATCH 2/2] mm/damon/tests/core-kunit: test PSI goal rounds under the temporal tuner

Quoted 7 lines · KunWu Chan

Could we also check goal->last_psi_total here?

The first tuning round should consume the U64_MAX sentinel and establish the measured state.

Testing this explicitly would lock in that state transition.

Thanks for the review, good idea! The test only inferred the state from the effective quota, a direct check of last_psi_total is clearer and stricter.

It also fits the v2 fix. I plan to keep last_psi_total across a commit of an existing PSI goal instead of touching the tuners, so that value is what the test should assert.

For the v2 test I plan to check last_psi_total at each transition:
not U64_MAX after the first round, still the kept sample after a commit of the same metric, U64_MAX again after a commit that changes the metric and back. The quota checks stay to cover the tuners.

Karl

maintainer
#13 lore raw

Re: [PATCH 2/2] mm/damon/tests/core-kunit: test PSI goal rounds under the temporal tuner

In reply to Karl Mehltretter
Quoted 9 lines · On Tue, 15 Sep 2026 08:09:37 +0200 Karl Mehltretter <[email protected]>…

Exercise a PSI quota goal under the temporal tuner on its first round, its next measured round, and after a goal commit. Use ULONG_MAX as the target so the goal is not reached during the test. Then check that a measured round which does reach the target sets the quota to zero, and that the consist tuner keeps its quota over an unmeasured round.

Without the previous patch, the first and the post-commit checks see a zero quota.

Thank you for adding this test.

Quoted 12 lines
Assisted-by LLM
---
 mm/damon/tests/core-kunit.h | 69 +++++++++++++++++++++++++++++++++++++
 1 file changed, 69 insertions(+)
mm/damon/tests/core-kunit.h +0 -0
index c01e6a75cadc..2b9b24ce7dad 100644

mm/damon/tests/core-kunit.h static void damos_test_commit_quota_goal(struct kunit *test) · line 897

Quoted 9 lines
mm/damon/tests/core-kunit.h +6 -0
@@ -889,6 +889,74 @@ static void damos_test_commit_quota_goal(struct kunit *test)
 			});
 }
 
+/*
+ * Unmeasured PSI goals must not disable the temporal quota.
+ * Keep sz * 10000 within a 32-bit unsigned long.
+ */
+static void damos_test_set_effective_quota_temporal_psi(struct kunit *test)
+{
	[...]

mm/damon/tests/core-kunit.h static void damos_test_commit_quota_goal(struct kunit *test) · line 934

Quoted 7 lines
mm/damon/tests/core-kunit.h +7 -0
+	/* fresh goal, first tuning round */
+	damos_set_effective_quota(c, s);
+	KUNIT_EXPECT_EQ(test, s->quota.esz, (unsigned long)SZ_64K);
+
+	/* second round: last_psi_total is initialised now */
+	damos_set_effective_quota(c, s);
+	KUNIT_EXPECT_EQ(test, s->quota.esz, (unsigned long)SZ_64K);

So, this test depends on the memory pressure level of the testing system? I concern if that could make the test result not very reliable. Maybe we could split out the corner case handling logic to explicitly receive the current memory PSI total time. For example, like what I suggested in the reply to the first patch of this series. With it, we could build a kunit test for only the target logic, without depending on the test system's status.

Thanks,
SJ

[...]

#14 lore raw

Re: [PATCH 2/2] mm/damon/tests/core-kunit: test PSI goal rounds under the temporal tuner

Quoted 7 lines · SJ Park

So, this test depends on the memory pressure level of the testing system? I concern if that could make the test result not very reliable. Maybe we could split out the corner case handling logic to explicitly receive the current memory PSI total time. For example, like what I suggested in the reply to the first patch of this series. With it, we could build a kunit test for only the target logic, without depending on the test system's status.

Yes, the second round reads the real PSI total. I used ULONG_MAX as the target to make it unreachable, but I agree that a test which does not read system state is better.

For v2 I can test only the helper from your reply to patch 1, with explicit PSI totals. It would cover both tuners, zero and non-zero esz for the temporal one, and the last_psi_total value after each call as Kunwu asked.

Thanks,
Karl

maintainer
#15 lore raw

Re: [PATCH 2/2] mm/damon/tests/core-kunit: test PSI goal rounds under the temporal tuner

Quoted 9 lines · On Sat, 19 Sep 2026 07:47:11 +0200 Karl Mehltretter <[email protected]>…
Quoted 7 lines · SJ Park

So, this test depends on the memory pressure level of the testing system? I concern if that could make the test result not very reliable. Maybe we could split out the corner case handling logic to explicitly receive the current memory PSI total time. For example, like what I suggested in the reply to the first patch of this series. With it, we could build a kunit test for only the target logic, without depending on the test system's status.

Quoted 8 lines

Yes, the second round reads the real PSI total. I used ULONG_MAX as the target to make it unreachable, but I agree that a test which does not read system state is better.

For v2 I can test only the helper from your reply to patch 1, with explicit PSI totals. It would cover both tuners, zero and non-zero esz for the temporal one, and the last_psi_total value after each call as Kunwu asked.

Sounds good, looking forward to your v2!

Thanks,
SJ

[...]