Linux-mm Archive on lore.kernel.org
 help / color / mirror / Atom feed
* [RFC PATCH 0/3] mm/damon: fix overflows in score and ratio calculations
@ 2026-10-27 13:59 DaeMyung Kang
  2026-10-27 13:59 ` [RFC PATCH 1/3] mm/damon/ops-common: avoid overflow in weighted hotness score DaeMyung Kang
                   ` (3 more replies)
  0 siblings, 4 replies; 6+ messages in thread
From: DaeMyung Kang @ 2026-10-27 13:59 UTC (permalink / raw)
  To: SJ Park; +Cc: damon, linux-mm, Andrew Morton, Asier Gutierrez, DaeMyung Kang

Sashiko reported several pre-existing overflows in DAMON score and
ratio calculations [1-5].  This series fixes them with wide
intermediates and an overflow-safe mult_frac() variant.

Patch 1 fixes the weighted hotness score, which overflows with large
user supplied quota weights on any architecture.  It accumulates the
weighted sum in u64, preserving scores for non-overflowing inputs.

Patch 2 adds damon_mult_frac().  It returns the same result as
mult_frac() when that does not overflow, computes the remainder product
in full width when needed, and saturates results that do not fit in
unsigned long.  It fixes the region age and sample ratio conversions
for new monitoring intervals, and the moving sums of access counts and
probe hits.  The helper is introduced with its first users so that each
commit builds without an unused function warning.

Patch 3 uses the helper for the page based memory ratio quota goal
metrics, including the hugepage metric, and for the quota goal score.
The node eligible memory metric counts bytes in phys_addr_t and uses
mul_u64_u64_div_u64() directly to avoid truncation on 32-bit PAE.

The remaining mult_frac() users, including the intervals auto-tuning
score that has an ongoing discussion, are not changed.

I am sending this as RFC to ask whether this approach is preferred for
these overflow issues.

The DAMON KUnit suites pass on UML, i386 QEMU, and x86_64 QEMU with
NUMA enabled (54 tests each).  The new regression cases fail on i386
without the fixes.

[1] https://lore.kernel.org/20260623141527.044D11F000E9@smtp.kernel.org
[2] https://lore.kernel.org/20260708140944.F26821F000E9@smtp.kernel.org
[3] https://lore.kernel.org/20260901133650.EF4F71F000E9@smtp.kernel.org
[4] https://lore.kernel.org/20260626002448.975E21F000E9@smtp.kernel.org
[5] https://lore.kernel.org/20260831184956.1DA321F000E9@smtp.kernel.org

DaeMyung Kang (3):
  mm/damon/ops-common: avoid overflow in weighted hotness score
  mm/damon/core: avoid overflows in monitoring results calculations
  mm/damon/core: avoid overflows in quota goal metrics and scores

 mm/damon/core.c                   |  59 ++++++++++++-----
 mm/damon/ops-common.c             |  10 ++-
 mm/damon/tests/core-kunit.h       | 105 ++++++++++++++++++++++++++++++
 mm/damon/tests/ops-common-kunit.h |  47 +++++++++++++
 4 files changed, 203 insertions(+), 18 deletions(-)
 create mode 100644 mm/damon/tests/ops-common-kunit.h


base-commit: bae9a042b03b27924c6fd5bdc00a20d6a44d6041
-- 
2.43.0



^ permalink raw reply	[flat|nested] 6+ messages in thread

* [RFC PATCH 1/3] mm/damon/ops-common: avoid overflow in weighted hotness score
  2026-10-27 13:59 [RFC PATCH 0/3] mm/damon: fix overflows in score and ratio calculations DaeMyung Kang
@ 2026-10-27 13:59 ` DaeMyung Kang
  2026-10-27 13:59 ` [RFC PATCH 2/3] mm/damon/core: avoid overflows in monitoring results calculations DaeMyung Kang
                   ` (2 subsequent siblings)
  3 siblings, 0 replies; 6+ messages in thread
From: DaeMyung Kang @ 2026-10-27 13:59 UTC (permalink / raw)
  To: SJ Park; +Cc: damon, linux-mm, Andrew Morton, Asier Gutierrez, DaeMyung Kang

damon_hot_score() combines the frequency and age subscores using the
user supplied DAMOS quota weights.  The weights are unsigned int, and
both the weighted sum and the total weight are calculated in unsigned
int.  This is independent of the architecture word size.

A frequency weight of 50,000,000 makes the weighted sum wrap, so a
region that is accessed in every sample gets a score of 13 instead of
99.  If the two weights add up to exactly 2^32, the total wraps to zero
and the averaging division is skipped.

Accumulate both values in u64 and divide the weighted sum by the full
total weight.  Add a KUnit case covering a large weight, weights summing
above UINT_MAX, and zero weights.

The issue was discovered [1] by Sashiko.

[1] https://lore.kernel.org/20260623141527.044D11F000E9@smtp.kernel.org

Fixes: 198f0f4c58b9 ("mm/damon/vaddr,paddr: support pageout prioritization")
Signed-off-by: DaeMyung Kang <charsyam@gmail.com>
---
 mm/damon/ops-common.c             | 10 +++++--
 mm/damon/tests/ops-common-kunit.h | 47 +++++++++++++++++++++++++++++++
 2 files changed, 54 insertions(+), 3 deletions(-)
 create mode 100644 mm/damon/tests/ops-common-kunit.h

diff --git a/mm/damon/ops-common.c b/mm/damon/ops-common.c
index 2f3bf86a221b..af170b112e08 100644
--- a/mm/damon/ops-common.c
+++ b/mm/damon/ops-common.c
@@ -170,6 +170,7 @@ int damon_hot_score(struct damon_ctx *c, struct damon_region *r,
 	int age_in_log, age_subscore;
 	unsigned int freq_weight = s->quota.weight_nr_accesses;
 	unsigned int age_weight = s->quota.weight_age;
+	u64 total_weight, weighted_sum;
 	int hotness;
 
 	freq_subscore = mult_frac(damon_nr_accesses_mvsum(r, c),
@@ -197,9 +198,10 @@ int damon_hot_score(struct damon_ctx *c, struct damon_region *r,
 	age_subscore = age_in_log * DAMON_MAX_SUBSCORE /
 		DAMON_MAX_AGE_IN_LOG / 2;
 
-	hotness = (freq_weight * freq_subscore + age_weight * age_subscore);
-	if (freq_weight + age_weight)
-		hotness /= freq_weight + age_weight;
+	total_weight = (u64)freq_weight + age_weight;
+	weighted_sum = (u64)freq_weight * freq_subscore +
+			(u64)age_weight * age_subscore;
+	hotness = total_weight ? div64_u64(weighted_sum, total_weight) : 0;
 	/*
 	 * Transform it to fit in [0, DAMOS_MAX_SCORE]
 	 */
@@ -576,3 +578,5 @@ bool damon_ops_filter_match(struct damon_filter *filter, struct folio *folio)
 	}
 	return matched == filter->matching;
 }
+
+#include "tests/ops-common-kunit.h"
diff --git a/mm/damon/tests/ops-common-kunit.h b/mm/damon/tests/ops-common-kunit.h
new file mode 100644
index 000000000000..813bad69cdcd
--- /dev/null
+++ b/mm/damon/tests/ops-common-kunit.h
@@ -0,0 +1,47 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#ifdef CONFIG_DAMON_KUNIT_TEST
+
+#include <kunit/test.h>
+
+static void damon_test_hot_score_large_weight(struct kunit *test)
+{
+	struct damon_ctx ctx = {
+		.attrs = {
+			.sample_interval = 1000,
+			.aggr_interval = 100000,
+		},
+	};
+	struct damon_region region = {
+		.nr_accesses = 100,
+	};
+	struct damos scheme = {
+		.quota = {
+			.weight_nr_accesses = 50000000,
+		},
+	};
+
+	KUNIT_EXPECT_EQ(test, damon_hot_score(&ctx, &region, &scheme), 99);
+
+	scheme.quota.weight_nr_accesses = UINT_MAX;
+	scheme.quota.weight_age = UINT_MAX;
+	KUNIT_EXPECT_EQ(test, damon_hot_score(&ctx, &region, &scheme), 74);
+
+	scheme.quota.weight_nr_accesses = 0;
+	scheme.quota.weight_age = 0;
+	KUNIT_EXPECT_EQ(test, damon_hot_score(&ctx, &region, &scheme), 0);
+}
+
+static struct kunit_case damon_ops_common_test_cases[] = {
+	KUNIT_CASE(damon_test_hot_score_large_weight),
+	{}
+};
+
+static struct kunit_suite damon_ops_common_test_suite = {
+	.name = "damon-ops-common",
+	.test_cases = damon_ops_common_test_cases,
+};
+
+kunit_test_suite(damon_ops_common_test_suite);
+
+#endif /* CONFIG_DAMON_KUNIT_TEST */
-- 
2.43.0



^ permalink raw reply related	[flat|nested] 6+ messages in thread

* [RFC PATCH 2/3] mm/damon/core: avoid overflows in monitoring results calculations
  2026-10-27 13:59 [RFC PATCH 0/3] mm/damon: fix overflows in score and ratio calculations DaeMyung Kang
  2026-10-27 13:59 ` [RFC PATCH 1/3] mm/damon/ops-common: avoid overflow in weighted hotness score DaeMyung Kang
@ 2026-10-27 13:59 ` DaeMyung Kang
  2026-10-27 14:18   ` Gutierrez Asier
  2026-10-27 13:59 ` [RFC PATCH 3/3] mm/damon/core: avoid overflows in quota goal metrics and scores DaeMyung Kang
  2026-10-27 14:23 ` [RFC PATCH 0/3] mm/damon: fix overflows in score and ratio calculations SJ Park
  3 siblings, 1 reply; 6+ messages in thread
From: DaeMyung Kang @ 2026-10-27 13:59 UTC (permalink / raw)
  To: SJ Park; +Cc: damon, linux-mm, Andrew Morton, Asier Gutierrez, DaeMyung Kang

When monitoring intervals change, DAMON converts region ages and sample
counts for the new intervals.  The conversions overflow before division:

- damon_age_for_new_attrs() multiplies the age by the old aggregation
  interval in unsigned long.  On 32-bit kernels, the default 100 ms
  interval makes this overflow after about 72 minutes.
- The sample ratio conversions multiply unsigned int values.  They can
  overflow on every architecture when an aggregation has more than
  429,496 samples, such as a 1 ms sample interval and a 430 second
  aggregation interval.

The moving sums also use mult_frac() for the fraction of an aggregation
window that remains and for the previous window's contribution.  On
32-bit kernels, both can overflow with long aggregation intervals.  For
example, 500,000 remaining samples of a 600,000-sample window are
8,333 bp, but the first calculation returns 1,175 bp.

Add damon_mult_frac(), which preserves mult_frac() results when its
intermediates fit, calculates larger remainder products in full width,
and saturates results that exceed unsigned long.  Use it for the
conversions and the moving sums.  Saturate the unsigned int conversion
results at UINT_MAX.

Add KUnit coverage for the helper, the conversions, and the moving sums.

The conversion issues were discovered [1] by Sashiko.

[1] https://lore.kernel.org/20260708140944.F26821F000E9@smtp.kernel.org

Fixes: 2f5bef5a590b ("mm/damon/core: update monitoring results for new monitoring attributes")
Fixes: 9f3786d8602f ("mm/damon/core: introduce damon_nr_accesses_mvsum()")
Fixes: 2ec26dd341d1 ("mm/damon/core: introduce damon_probe_hits_mvsum()")
Signed-off-by: DaeMyung Kang <charsyam@gmail.com>
---
 mm/damon/core.c             | 41 +++++++++++++++++---
 mm/damon/tests/core-kunit.h | 74 +++++++++++++++++++++++++++++++++++++
 2 files changed, 109 insertions(+), 6 deletions(-)

diff --git a/mm/damon/core.c b/mm/damon/core.c
index 73b571b429cb..5cd4007cc370 100644
--- a/mm/damon/core.c
+++ b/mm/damon/core.c
@@ -281,6 +281,32 @@ static bool damon_has_probe_weights(struct damon_ctx *c)
 	return false;
 }
 
+/*
+ * damon_mult_frac() - calculate @x * @n / @d without overflowing.
+ *
+ * Same as mult_frac(), but the intermediate product of the remainder does not
+ * overflow, and a result that does not fit in unsigned long is saturated to
+ * ULONG_MAX.  Returns the same value as mult_frac() when mult_frac() does not
+ * overflow.  @d must not be zero.
+ */
+static unsigned long damon_mult_frac(unsigned long x, unsigned long n,
+		unsigned long d)
+{
+	unsigned long q = x / d, r = x % d;
+	unsigned long frac;
+
+	if (n && q > ULONG_MAX / n)
+		return ULONG_MAX;
+	if (!n || r <= ULONG_MAX / n)
+		frac = r * n / d;
+	else
+		/* r < d, so the quotient is smaller than n */
+		frac = mul_u64_u64_div_u64(r, n, d);
+	if (frac > ULONG_MAX - q * n)
+		return ULONG_MAX;
+	return q * n + frac;
+}
+
 /*
  * damon_mvsum() - Returns pseudo moving sum value for a time window.
  * @current_nr:		The value of the current aggregation window.
@@ -305,7 +331,7 @@ static bool damon_has_probe_weights(struct damon_ctx *c)
 static unsigned long damon_mvsum(unsigned long current_nr,
 		unsigned long last_nr, unsigned long left_window_bp)
 {
-	return current_nr + mult_frac(last_nr, left_window_bp, 10000);
+	return current_nr + damon_mult_frac(last_nr, left_window_bp, 10000);
 }
 
 /**
@@ -337,7 +363,7 @@ unsigned int damon_nr_accesses_mvsum(struct damon_region *r,
 	else
 		left_window = ctx->next_aggregation_sis -
 			ctx->passed_sample_intervals;
-	left_window_bp = mult_frac(left_window, 10000, window_len);
+	left_window_bp = damon_mult_frac(left_window, 10000, window_len);
 
 	if (left_window_bp == 10000)
 		return r->last_nr_accesses;
@@ -361,7 +387,7 @@ unsigned char damon_probe_hits_mvsum(int probe_idx, struct damon_region *r,
 	else
 		left_window = ctx->next_aggregation_sis -
 			ctx->passed_sample_intervals;
-	left_window_bp = mult_frac(left_window, 10000, window_len);
+	left_window_bp = damon_mult_frac(left_window, 10000, window_len);
 
 	if (left_window_bp == 10000)
 		return r->last_probe_hits[probe_idx];
@@ -990,21 +1016,24 @@ static bool damon_attrs_equals(const struct damon_attrs *attrs1,
 static unsigned int damon_age_for_new_attrs(unsigned int age,
 		struct damon_attrs *old_attrs, struct damon_attrs *new_attrs)
 {
-	return age * old_attrs->aggr_interval / new_attrs->aggr_interval;
+	return min(damon_mult_frac(age, old_attrs->aggr_interval,
+				new_attrs->aggr_interval), UINT_MAX);
 }
 
 /* convert sample ratio in bp (per 10,000) to count */
 static unsigned int damon_sample_bp_to_count(
 		unsigned int bp, struct damon_attrs *attrs)
 {
-	return bp * damon_nr_samples_per_aggr(attrs) / 10000;
+	return min(damon_mult_frac(bp, damon_nr_samples_per_aggr(attrs),
+				10000), UINT_MAX);
 }
 
 /* convert sample count to ratio in bp (per 10,000) */
 static unsigned int damon_sample_count_to_bp(
 		unsigned int count, struct damon_attrs *attrs)
 {
-	return mult_frac(count, 10000, damon_nr_samples_per_aggr(attrs));
+	return min(damon_mult_frac(count, 10000,
+				damon_nr_samples_per_aggr(attrs)), UINT_MAX);
 }
 
 static unsigned int damon_nr_samples_for_new_attrs(unsigned int nr,
diff --git a/mm/damon/tests/core-kunit.h b/mm/damon/tests/core-kunit.h
index 43e2f950f8ff..e71e60d59400 100644
--- a/mm/damon/tests/core-kunit.h
+++ b/mm/damon/tests/core-kunit.h
@@ -698,6 +698,64 @@ static void damon_test_update_monitoring_result(struct kunit *test)
 	damon_destroy_ctx(ctx);
 }
 
+static void damon_test_mult_frac(struct kunit *test)
+{
+	KUNIT_EXPECT_EQ(test, damon_mult_frac(7, 3, 2), 10ul);
+	KUNIT_EXPECT_EQ(test, damon_mult_frac(ULONG_MAX, 0, 1), 0ul);
+	/* the remainder product overflows on 32-bit */
+	KUNIT_EXPECT_EQ(test, damon_mult_frac(524288, 10000, 786432), 6666ul);
+	KUNIT_EXPECT_EQ(test, damon_mult_frac(500000, 10000, 1000000), 5000ul);
+	/* the remainder product overflows even on 64-bit */
+	KUNIT_EXPECT_EQ(test, damon_mult_frac(ULONG_MAX - 1, ULONG_MAX,
+				ULONG_MAX), ULONG_MAX - 1);
+	/* the quotient product overflows */
+	KUNIT_EXPECT_EQ(test, damon_mult_frac(ULONG_MAX, 10000, 1), ULONG_MAX);
+	/* the largest representable result, and one beyond it */
+	KUNIT_EXPECT_EQ(test, damon_mult_frac(ULONG_MAX - ULONG_MAX / 10000,
+				10000, 9999), ULONG_MAX);
+	KUNIT_EXPECT_EQ(test, damon_mult_frac(ULONG_MAX - ULONG_MAX / 10000 + 1,
+				10000, 9999), ULONG_MAX);
+}
+
+static void damon_test_age_for_new_attrs_large_value(struct kunit *test)
+{
+	struct damon_attrs old_attrs = {.aggr_interval = 100000};
+	struct damon_attrs new_attrs = {.aggr_interval = 50000};
+	unsigned int age;
+
+	age = damon_age_for_new_attrs(50000, &old_attrs, &new_attrs);
+	KUNIT_EXPECT_EQ(test, age, 100000u);
+
+	new_attrs.aggr_interval = 1;
+	age = damon_age_for_new_attrs(UINT_MAX, &old_attrs, &new_attrs);
+	KUNIT_EXPECT_EQ(test, age, UINT_MAX);
+
+	old_attrs.aggr_interval = ULONG_MAX;
+	age = damon_age_for_new_attrs(UINT_MAX, &old_attrs, &new_attrs);
+	KUNIT_EXPECT_EQ(test, age, UINT_MAX);
+}
+
+static void damon_test_sample_ratio_large_value(struct kunit *test)
+{
+	struct damon_attrs attrs = {
+		.sample_interval = 1000,
+		.aggr_interval = 600000000,
+	};
+
+	KUNIT_EXPECT_EQ(test, damon_sample_bp_to_count(10000, &attrs),
+			600000u);
+	KUNIT_EXPECT_EQ(test, damon_sample_count_to_bp(600000, &attrs),
+			10000u);
+	KUNIT_EXPECT_EQ(test, damon_sample_count_to_bp(500000, &attrs),
+			8333u);
+	KUNIT_EXPECT_EQ(test, damon_sample_bp_to_count(UINT_MAX, &attrs),
+			UINT_MAX);
+
+	attrs.aggr_interval = attrs.sample_interval;
+	KUNIT_EXPECT_EQ(test, damon_sample_count_to_bp(UINT_MAX, &attrs),
+			UINT_MAX);
+}
+
 static void damon_test_set_attrs(struct kunit *test)
 {
 	struct damon_ctx *c = damon_new_ctx();
@@ -735,6 +793,7 @@ static void damon_test_mvsum(struct kunit *test)
 		3, 10, 5000, 8,		/* 3 + 10 * 0.5 */
 		32, 100, 1000, 42,	/* 32 + 100 * 0.1 */
 		42, 49, 0, 42,		/* 42 + 49 * 0 */
+		0, 600000, 8333, 499980, /* 32-bit remainder product overflow */
 	};
 
 	int i;
@@ -794,6 +853,18 @@ static void damon_test_nr_accesses_mvsum(struct kunit *test)
 				damon_nr_accesses_mvsum(r, c));
 	}
 
+	c->attrs.sample_interval = 1000;
+	c->attrs.aggr_interval = 600000000;
+	c->passed_sample_intervals = 100000;
+	c->next_aggregation_sis = 600000;
+	r->nr_accesses = 100000;
+	r->last_nr_accesses = 600000;
+	KUNIT_EXPECT_EQ(test, damon_nr_accesses_mvsum(r, c), 599980u);
+
+	r->probe_hits[0] = 0;
+	r->last_probe_hits[0] = 100;
+	KUNIT_EXPECT_EQ(test, damon_probe_hits_mvsum(0, r, c), 83);
+
 	damon_free_region(r);
 	damon_destroy_ctx(c);
 }
@@ -2494,6 +2565,9 @@ static struct kunit_case damon_test_cases[] = {
 	KUNIT_CASE(damon_test_set_regions),
 	KUNIT_CASE(damon_test_nr_samples_per_aggr),
 	KUNIT_CASE(damon_test_update_monitoring_result),
+	KUNIT_CASE(damon_test_mult_frac),
+	KUNIT_CASE(damon_test_age_for_new_attrs_large_value),
+	KUNIT_CASE(damon_test_sample_ratio_large_value),
 	KUNIT_CASE(damon_test_set_attrs),
 	KUNIT_CASE(damon_test_mvsum),
 	KUNIT_CASE(damon_test_nr_accesses_mvsum),
-- 
2.43.0



^ permalink raw reply related	[flat|nested] 6+ messages in thread

* [RFC PATCH 3/3] mm/damon/core: avoid overflows in quota goal metrics and scores
  2026-10-27 13:59 [RFC PATCH 0/3] mm/damon: fix overflows in score and ratio calculations DaeMyung Kang
  2026-10-27 13:59 ` [RFC PATCH 1/3] mm/damon/ops-common: avoid overflow in weighted hotness score DaeMyung Kang
  2026-10-27 13:59 ` [RFC PATCH 2/3] mm/damon/core: avoid overflows in monitoring results calculations DaeMyung Kang
@ 2026-10-27 13:59 ` DaeMyung Kang
  2026-10-27 14:23 ` [RFC PATCH 0/3] mm/damon: fix overflows in score and ratio calculations SJ Park
  3 siblings, 0 replies; 6+ messages in thread
From: DaeMyung Kang @ 2026-10-27 13:59 UTC (permalink / raw)
  To: SJ Park; +Cc: damon, linux-mm, Andrew Morton, Asier Gutierrez, DaeMyung Kang

The node memory, node memcg memory, LRU [in]active memory, and hugepage
memory quota goal metrics, as well as damos_quota_score(), use
mult_frac() to scale a value by 10000.  mult_frac() multiplies the
remainder by 10000 in unsigned long, which overflows on 32-bit kernels
even when the result fits:

- For the page based metrics, this happens once the numerator exceeds
  about 429,496 pages, or 1.6 GiB with 4 KiB pages.  For example, 2 GiB
  used out of 3 GiB returns 1205 bp instead of 6666 bp.  The LRU
  [in]active and hugepage metrics do not depend on NUMA.

- For quota goal scores, any current value above about 429,496 can
  overflow.  For example, a DAMOS_QUOTA_SOME_MEM_PSI_US goal with a
  target of one second and a current value of half a second returns 705
  instead of 5000.  On any architecture, mult_frac() also silently wraps
  scores that do not fit in unsigned long.

Use damon_mult_frac() for those calculations.

The node eligible memory metric counts bytes in phys_addr_t, so on
32-bit kernels it overflows once the eligible memory exceeds about
420 KiB, and it also truncates the byte counts to unsigned long with
PAE.  Use mul_u64_u64_div_u64() on the phys_addr_t values for it.  The
node eligible memory never exceeds the total, so the result fits.

Add a KUnit case for the quota goal scores.  The helper's KUnit cases
also cover the large page ratio used by the metrics.

The issues were discovered [1,2,3] by Sashiko.

[1] https://lore.kernel.org/20260901133650.EF4F71F000E9@smtp.kernel.org
[2] https://lore.kernel.org/20260626002448.975E21F000E9@smtp.kernel.org
[3] https://lore.kernel.org/20260831184956.1DA321F000E9@smtp.kernel.org

Fixes: 0e1c773b501f ("mm/damon/core: introduce damos quota goal metrics for memory node utilization")
Fixes: b74a120bcf50 ("mm/damon/core: implement DAMOS_QUOTA_NODE_MEMCG_USED_BP")
Fixes: 4835e2871321 ("mm/damon/core: introduce [in]active memory ratio damos quota goal metric")
Fixes: f465dc71aba0 ("mm/damon: introduce DAMOS_QUOTA_HUGEPAGE auto tuning")
Fixes: 9138e27a3bc3 ("mm/damon: add node_eligible_mem_bp goal metric")
Fixes: 06ba5b309ed8 ("mm/damon/core: let goal specified with only target and current values")
Signed-off-by: DaeMyung Kang <charsyam@gmail.com>
---
 mm/damon/core.c             | 18 +++++++++---------
 mm/damon/tests/core-kunit.h | 31 +++++++++++++++++++++++++++++++
 2 files changed, 40 insertions(+), 9 deletions(-)

diff --git a/mm/damon/core.c b/mm/damon/core.c
index 5cd4007cc370..cc4e010e400d 100644
--- a/mm/damon/core.c
+++ b/mm/damon/core.c
@@ -3056,7 +3056,7 @@ static __kernel_ulong_t damos_get_node_mem_bp(
 		numerator = i.totalram - i.freeram;
 	else	/* DAMOS_QUOTA_NODE_MEM_FREE_BP */
 		numerator = i.freeram;
-	return mult_frac(numerator, 10000, i.totalram);
+	return damon_mult_frac(numerator, 10000, i.totalram);
 }
 
 static unsigned long damos_get_node_memcg_used_bp(
@@ -3102,7 +3102,7 @@ static unsigned long damos_get_node_memcg_used_bp(
 		numerator = used_pages;
 	else	/* DAMOS_QUOTA_NODE_MEMCG_FREE_BP */
 		numerator = i.totalram - used_pages;
-	return mult_frac(numerator, 10000, i.totalram);
+	return damon_mult_frac(numerator, 10000, i.totalram);
 }
 
 #ifdef CONFIG_DAMON_PADDR
@@ -3199,11 +3199,11 @@ static unsigned long damos_get_node_eligible_mem_bp(struct damon_ctx *c,
 
 	node_eligible = damos_calc_eligible_bytes(c, s, nid, &total_eligible);
 
-	if (!(unsigned long)total_eligible)
+	if (!total_eligible)
 		return 0;
 
-	return mult_frac((unsigned long)node_eligible, 10000,
-			(unsigned long)total_eligible);
+	/* node_eligible <= total_eligible, so this cannot overflow */
+	return mul_u64_u64_div_u64(node_eligible, 10000, total_eligible);
 }
 #else /* CONFIG_DAMON_PADDR */
 static unsigned long damos_get_node_eligible_mem_bp(struct damon_ctx *c,
@@ -3246,8 +3246,8 @@ static unsigned int damos_get_in_active_mem_bp(bool active_ratio)
 		global_node_page_state(NR_LRU_BASE + LRU_INACTIVE_FILE);
 	total = max(active + inactive, 1);
 	if (active_ratio)
-		return mult_frac(active, 10000, total);
-	return mult_frac(inactive, 10000, total);
+		return damon_mult_frac(active, 10000, total);
+	return damon_mult_frac(inactive, 10000, total);
 }
 
 static unsigned int damos_hugepage_mem_bp(void)
@@ -3263,7 +3263,7 @@ static unsigned int damos_hugepage_mem_bp(void)
 	thp = global_node_page_state(NR_ANON_THPS) +
 				global_node_page_state(NR_SHMEM_THPS) +
 				global_node_page_state(NR_FILE_THPS);
-	return mult_frac(thp, 10000, total_pages - free_pages);
+	return damon_mult_frac(thp, 10000, total_pages - free_pages);
 }
 
 static void damos_set_quota_goal_current_value(struct damon_ctx *c,
@@ -3329,7 +3329,7 @@ static unsigned long damos_quota_score(struct damon_ctx *c, struct damos *s)
 	damos_for_each_quota_goal(goal, quota) {
 		damos_set_quota_goal_current_value(c, s, goal);
 		highest_score = max(highest_score,
-				mult_frac(goal->current_value, 10000,
+				damon_mult_frac(goal->current_value, 10000,
 					goal->target_value));
 	}
 
diff --git a/mm/damon/tests/core-kunit.h b/mm/damon/tests/core-kunit.h
index e71e60d59400..aaf65828a5e1 100644
--- a/mm/damon/tests/core-kunit.h
+++ b/mm/damon/tests/core-kunit.h
@@ -756,6 +756,36 @@ static void damon_test_sample_ratio_large_value(struct kunit *test)
 			UINT_MAX);
 }
 
+static void damos_test_quota_score_large_value(struct kunit *test)
+{
+	struct damos s;
+	struct damos_quota_goal goal = {
+		.metric = DAMOS_QUOTA_USER_INPUT,
+		.current_value = 500000,
+		.target_value = 1000000,
+	};
+
+	memset(&s, 0, sizeof(s));
+	INIT_LIST_HEAD(&s.quota.goals);
+	INIT_LIST_HEAD(&goal.list);
+	damos_add_quota_goal(&s.quota, &goal);
+	KUNIT_EXPECT_EQ(test, damos_quota_score(NULL, &s), 5000ul);
+
+	goal.current_value = 1500001;
+	KUNIT_EXPECT_EQ(test, damos_quota_score(NULL, &s), 15000ul);
+
+	goal.current_value = ULONG_MAX;
+	goal.target_value = 1;
+	KUNIT_EXPECT_EQ(test, damos_quota_score(NULL, &s), ULONG_MAX);
+
+	/* the largest representable score, and one beyond it */
+	goal.current_value = ULONG_MAX - ULONG_MAX / 10000;
+	goal.target_value = 9999;
+	KUNIT_EXPECT_EQ(test, damos_quota_score(NULL, &s), ULONG_MAX);
+	goal.current_value++;
+	KUNIT_EXPECT_EQ(test, damos_quota_score(NULL, &s), ULONG_MAX);
+}
+
 static void damon_test_set_attrs(struct kunit *test)
 {
 	struct damon_ctx *c = damon_new_ctx();
@@ -2568,6 +2598,7 @@ static struct kunit_case damon_test_cases[] = {
 	KUNIT_CASE(damon_test_mult_frac),
 	KUNIT_CASE(damon_test_age_for_new_attrs_large_value),
 	KUNIT_CASE(damon_test_sample_ratio_large_value),
+	KUNIT_CASE(damos_test_quota_score_large_value),
 	KUNIT_CASE(damon_test_set_attrs),
 	KUNIT_CASE(damon_test_mvsum),
 	KUNIT_CASE(damon_test_nr_accesses_mvsum),
-- 
2.43.0



^ permalink raw reply related	[flat|nested] 6+ messages in thread

* Re: [RFC PATCH 2/3] mm/damon/core: avoid overflows in monitoring results calculations
  2026-10-27 13:59 ` [RFC PATCH 2/3] mm/damon/core: avoid overflows in monitoring results calculations DaeMyung Kang
@ 2026-10-27 14:18   ` Gutierrez Asier
  0 siblings, 0 replies; 6+ messages in thread
From: Gutierrez Asier @ 2026-10-27 14:18 UTC (permalink / raw)
  To: DaeMyung Kang, SJ Park; +Cc: damon, linux-mm, Andrew Morton

Hi DaeMyung,

Thanks for the patch.

On 10/27/2026 4:59 PM, DaeMyung Kang wrote:
> When monitoring intervals change, DAMON converts region ages and sample
> counts for the new intervals.  The conversions overflow before division:
> 
> - damon_age_for_new_attrs() multiplies the age by the old aggregation
>   interval in unsigned long.  On 32-bit kernels, the default 100 ms
>   interval makes this overflow after about 72 minutes.
> - The sample ratio conversions multiply unsigned int values.  They can
>   overflow on every architecture when an aggregation has more than
>   429,496 samples, such as a 1 ms sample interval and a 430 second
>   aggregation interval.
> 
> The moving sums also use mult_frac() for the fraction of an aggregation
> window that remains and for the previous window's contribution.  On
> 32-bit kernels, both can overflow with long aggregation intervals.  For
> example, 500,000 remaining samples of a 600,000-sample window are
> 8,333 bp, but the first calculation returns 1,175 bp.
> 
> Add damon_mult_frac(), which preserves mult_frac() results when its
> intermediates fit, calculates larger remainder products in full width,
> and saturates results that exceed unsigned long.  Use it for the
> conversions and the moving sums.  Saturate the unsigned int conversion
> results at UINT_MAX.
> 
> Add KUnit coverage for the helper, the conversions, and the moving sums.
> 
> The conversion issues were discovered [1] by Sashiko.
> 
> [1] https://lore.kernel.org/20260708140944.F26821F000E9@smtp.kernel.org
> 
> Fixes: 2f5bef5a590b ("mm/damon/core: update monitoring results for new monitoring attributes")
> Fixes: 9f3786d8602f ("mm/damon/core: introduce damon_nr_accesses_mvsum()")
> Fixes: 2ec26dd341d1 ("mm/damon/core: introduce damon_probe_hits_mvsum()")
> Signed-off-by: DaeMyung Kang <charsyam@gmail.com>
> ---
>  mm/damon/core.c             | 41 +++++++++++++++++---
>  mm/damon/tests/core-kunit.h | 74 +++++++++++++++++++++++++++++++++++++
>  2 files changed, 109 insertions(+), 6 deletions(-)
> 
> diff --git a/mm/damon/core.c b/mm/damon/core.c
> index 73b571b429cb..5cd4007cc370 100644
> --- a/mm/damon/core.c
> +++ b/mm/damon/core.c
> @@ -281,6 +281,32 @@ static bool damon_has_probe_weights(struct damon_ctx *c)
>  	return false;
>  }
>  
> +/*
> + * damon_mult_frac() - calculate @x * @n / @d without overflowing.
> + *
> + * Same as mult_frac(), but the intermediate product of the remainder does not
> + * overflow, and a result that does not fit in unsigned long is saturated to
> + * ULONG_MAX.  Returns the same value as mult_frac() when mult_frac() does not
> + * overflow.  @d must not be zero.
> + */
> +static unsigned long damon_mult_frac(unsigned long x, unsigned long n,
> +		unsigned long d)
> +{
> +	unsigned long q = x / d, r = x % d;
> +	unsigned long frac;
> +
> +	if (n && q > ULONG_MAX / n)
> +		return ULONG_MAX;
> +	if (!n || r <= ULONG_MAX / n)
> +		frac = r * n / d;
> +	else
> +		/* r < d, so the quotient is smaller than n */
> +		frac = mul_u64_u64_div_u64(r, n, d);
> +	if (frac > ULONG_MAX - q * n)
> +		return ULONG_MAX;
> +	return q * n + frac;
> +}
To me, this is too complex. We are introducing a bunch of branches,
which will run for all cases, 64 and 32 bit architectures. This adds
overhead to all users.

I don't think there are 32 bit architecture users who are running modern
linux versions with DAMON enabled. I may be wrong, of course.
> +
>  /*
>   * damon_mvsum() - Returns pseudo moving sum value for a time window.
>   * @current_nr:		The value of the current aggregation window.
> @@ -305,7 +331,7 @@ static bool damon_has_probe_weights(struct damon_ctx *c)
>  static unsigned long damon_mvsum(unsigned long current_nr,
>  		unsigned long last_nr, unsigned long left_window_bp)
>  {
> -	return current_nr + mult_frac(last_nr, left_window_bp, 10000);
> +	return current_nr + damon_mult_frac(last_nr, left_window_bp, 10000);
>  }
>  
>  /**
> @@ -337,7 +363,7 @@ unsigned int damon_nr_accesses_mvsum(struct damon_region *r,
>  	else
>  		left_window = ctx->next_aggregation_sis -
>  			ctx->passed_sample_intervals;
> -	left_window_bp = mult_frac(left_window, 10000, window_len);
> +	left_window_bp = damon_mult_frac(left_window, 10000, window_len);
>  
>  	if (left_window_bp == 10000)
>  		return r->last_nr_accesses;
> @@ -361,7 +387,7 @@ unsigned char damon_probe_hits_mvsum(int probe_idx, struct damon_region *r,
>  	else
>  		left_window = ctx->next_aggregation_sis -
>  			ctx->passed_sample_intervals;
> -	left_window_bp = mult_frac(left_window, 10000, window_len);
> +	left_window_bp = damon_mult_frac(left_window, 10000, window_len);
>  
>  	if (left_window_bp == 10000)
>  		return r->last_probe_hits[probe_idx];
> @@ -990,21 +1016,24 @@ static bool damon_attrs_equals(const struct damon_attrs *attrs1,
>  static unsigned int damon_age_for_new_attrs(unsigned int age,
>  		struct damon_attrs *old_attrs, struct damon_attrs *new_attrs)
>  {
> -	return age * old_attrs->aggr_interval / new_attrs->aggr_interval;
> +	return min(damon_mult_frac(age, old_attrs->aggr_interval,
> +				new_attrs->aggr_interval), UINT_MAX);
>  }
>  
>  /* convert sample ratio in bp (per 10,000) to count */
>  static unsigned int damon_sample_bp_to_count(
>  		unsigned int bp, struct damon_attrs *attrs)
>  {
> -	return bp * damon_nr_samples_per_aggr(attrs) / 10000;
> +	return min(damon_mult_frac(bp, damon_nr_samples_per_aggr(attrs),
> +				10000), UINT_MAX);
>  }
>  
>  /* convert sample count to ratio in bp (per 10,000) */
>  static unsigned int damon_sample_count_to_bp(
>  		unsigned int count, struct damon_attrs *attrs)
>  {
> -	return mult_frac(count, 10000, damon_nr_samples_per_aggr(attrs));
> +	return min(damon_mult_frac(count, 10000,
> +				damon_nr_samples_per_aggr(attrs)), UINT_MAX);
>  }
>  
>  static unsigned int damon_nr_samples_for_new_attrs(unsigned int nr,
> diff --git a/mm/damon/tests/core-kunit.h b/mm/damon/tests/core-kunit.h
> index 43e2f950f8ff..e71e60d59400 100644
> --- a/mm/damon/tests/core-kunit.h
> +++ b/mm/damon/tests/core-kunit.h
> @@ -698,6 +698,64 @@ static void damon_test_update_monitoring_result(struct kunit *test)
>  	damon_destroy_ctx(ctx);
>  }
>  
> +static void damon_test_mult_frac(struct kunit *test)
> +{
> +	KUNIT_EXPECT_EQ(test, damon_mult_frac(7, 3, 2), 10ul);
> +	KUNIT_EXPECT_EQ(test, damon_mult_frac(ULONG_MAX, 0, 1), 0ul);
> +	/* the remainder product overflows on 32-bit */
> +	KUNIT_EXPECT_EQ(test, damon_mult_frac(524288, 10000, 786432), 6666ul);
> +	KUNIT_EXPECT_EQ(test, damon_mult_frac(500000, 10000, 1000000), 5000ul);
> +	/* the remainder product overflows even on 64-bit */
> +	KUNIT_EXPECT_EQ(test, damon_mult_frac(ULONG_MAX - 1, ULONG_MAX,
> +				ULONG_MAX), ULONG_MAX - 1);
> +	/* the quotient product overflows */
> +	KUNIT_EXPECT_EQ(test, damon_mult_frac(ULONG_MAX, 10000, 1), ULONG_MAX);
> +	/* the largest representable result, and one beyond it */
> +	KUNIT_EXPECT_EQ(test, damon_mult_frac(ULONG_MAX - ULONG_MAX / 10000,
> +				10000, 9999), ULONG_MAX);
> +	KUNIT_EXPECT_EQ(test, damon_mult_frac(ULONG_MAX - ULONG_MAX / 10000 + 1,
> +				10000, 9999), ULONG_MAX);
> +}
> +
> +static void damon_test_age_for_new_attrs_large_value(struct kunit *test)
> +{
> +	struct damon_attrs old_attrs = {.aggr_interval = 100000};
> +	struct damon_attrs new_attrs = {.aggr_interval = 50000};
> +	unsigned int age;
> +
> +	age = damon_age_for_new_attrs(50000, &old_attrs, &new_attrs);
> +	KUNIT_EXPECT_EQ(test, age, 100000u);
> +
> +	new_attrs.aggr_interval = 1;
> +	age = damon_age_for_new_attrs(UINT_MAX, &old_attrs, &new_attrs);
> +	KUNIT_EXPECT_EQ(test, age, UINT_MAX);
> +
> +	old_attrs.aggr_interval = ULONG_MAX;
> +	age = damon_age_for_new_attrs(UINT_MAX, &old_attrs, &new_attrs);
> +	KUNIT_EXPECT_EQ(test, age, UINT_MAX);
> +}
> +
> +static void damon_test_sample_ratio_large_value(struct kunit *test)
> +{
> +	struct damon_attrs attrs = {
> +		.sample_interval = 1000,
> +		.aggr_interval = 600000000,
> +	};
> +
> +	KUNIT_EXPECT_EQ(test, damon_sample_bp_to_count(10000, &attrs),
> +			600000u);
> +	KUNIT_EXPECT_EQ(test, damon_sample_count_to_bp(600000, &attrs),
> +			10000u);
> +	KUNIT_EXPECT_EQ(test, damon_sample_count_to_bp(500000, &attrs),
> +			8333u);
> +	KUNIT_EXPECT_EQ(test, damon_sample_bp_to_count(UINT_MAX, &attrs),
> +			UINT_MAX);
> +
> +	attrs.aggr_interval = attrs.sample_interval;
> +	KUNIT_EXPECT_EQ(test, damon_sample_count_to_bp(UINT_MAX, &attrs),
> +			UINT_MAX);
> +}
> +
>  static void damon_test_set_attrs(struct kunit *test)
>  {
>  	struct damon_ctx *c = damon_new_ctx();
> @@ -735,6 +793,7 @@ static void damon_test_mvsum(struct kunit *test)
>  		3, 10, 5000, 8,		/* 3 + 10 * 0.5 */
>  		32, 100, 1000, 42,	/* 32 + 100 * 0.1 */
>  		42, 49, 0, 42,		/* 42 + 49 * 0 */
> +		0, 600000, 8333, 499980, /* 32-bit remainder product overflow */
>  	};
>  
>  	int i;
> @@ -794,6 +853,18 @@ static void damon_test_nr_accesses_mvsum(struct kunit *test)
>  				damon_nr_accesses_mvsum(r, c));
>  	}
>  
> +	c->attrs.sample_interval = 1000;
> +	c->attrs.aggr_interval = 600000000;
> +	c->passed_sample_intervals = 100000;
> +	c->next_aggregation_sis = 600000;
> +	r->nr_accesses = 100000;
> +	r->last_nr_accesses = 600000;
> +	KUNIT_EXPECT_EQ(test, damon_nr_accesses_mvsum(r, c), 599980u);
> +
> +	r->probe_hits[0] = 0;
> +	r->last_probe_hits[0] = 100;
> +	KUNIT_EXPECT_EQ(test, damon_probe_hits_mvsum(0, r, c), 83);
> +
>  	damon_free_region(r);
>  	damon_destroy_ctx(c);
>  }
> @@ -2494,6 +2565,9 @@ static struct kunit_case damon_test_cases[] = {
>  	KUNIT_CASE(damon_test_set_regions),
>  	KUNIT_CASE(damon_test_nr_samples_per_aggr),
>  	KUNIT_CASE(damon_test_update_monitoring_result),
> +	KUNIT_CASE(damon_test_mult_frac),
> +	KUNIT_CASE(damon_test_age_for_new_attrs_large_value),
> +	KUNIT_CASE(damon_test_sample_ratio_large_value),
>  	KUNIT_CASE(damon_test_set_attrs),
>  	KUNIT_CASE(damon_test_mvsum),
>  	KUNIT_CASE(damon_test_nr_accesses_mvsum),
Thanks
-- 
Asier Gutierrez
Huawei



^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [RFC PATCH 0/3] mm/damon: fix overflows in score and ratio calculations
  2026-10-27 13:59 [RFC PATCH 0/3] mm/damon: fix overflows in score and ratio calculations DaeMyung Kang
                   ` (2 preceding siblings ...)
  2026-10-27 13:59 ` [RFC PATCH 3/3] mm/damon/core: avoid overflows in quota goal metrics and scores DaeMyung Kang
@ 2026-10-27 14:23 ` SJ Park
  3 siblings, 0 replies; 6+ messages in thread
From: SJ Park @ 2026-10-27 14:23 UTC (permalink / raw)
  To: DaeMyung Kang; +Cc: SJ Park, damon, linux-mm, Andrew Morton, Asier Gutierrez

Hello DaeMyung,


Thank you for this patch series.

However, I prefer reviewing only single patch series per sender at a time.  A
discussion on your other patch series [1] is ongoing.  So I will skip reviewing
this series.  Please don't post other new patch series before the ongoing
discussion is completed.

[1] https://lore.kernel.org/20261027035431.1007584-1-charsyam@gmail.com


Thanks,
SJ

[...]


^ permalink raw reply	[flat|nested] 6+ messages in thread

end of thread, other threads:[~2026-10-27 14:23 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-27 13:59 [RFC PATCH 0/3] mm/damon: fix overflows in score and ratio calculations DaeMyung Kang
2026-10-27 13:59 ` [RFC PATCH 1/3] mm/damon/ops-common: avoid overflow in weighted hotness score DaeMyung Kang
2026-10-27 13:59 ` [RFC PATCH 2/3] mm/damon/core: avoid overflows in monitoring results calculations DaeMyung Kang
2026-10-27 14:18   ` Gutierrez Asier
2026-10-27 13:59 ` [RFC PATCH 3/3] mm/damon/core: avoid overflows in quota goal metrics and scores DaeMyung Kang
2026-10-27 14:23 ` [RFC PATCH 0/3] mm/damon: fix overflows in score and ratio calculations SJ Park

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox