/* SPDX-License-Identifier: GPL-2.0 */ /* * Data Access Monitor Unit Tests */ #ifdef CONFIG_DAMON_KUNIT_TEST #ifndef _DAMON_CORE_TEST_H #define _DAMON_CORE_TEST_H #include static void damon_test_regions(struct kunit *test) { struct damon_region *r; struct damon_target *t; r = damon_new_region(1, 2); if (!r) kunit_skip(test, "region alloc fail"); KUNIT_EXPECT_EQ(test, 1ul, r->ar.start); KUNIT_EXPECT_EQ(test, 2ul, r->ar.end); KUNIT_EXPECT_EQ(test, 0u, r->nr_accesses); t = damon_new_target(); if (!t) { damon_free_region(r); kunit_skip(test, "target alloc fail"); } KUNIT_EXPECT_EQ(test, 0u, damon_nr_regions(t)); damon_add_region(r, t); KUNIT_EXPECT_EQ(test, 1u, damon_nr_regions(t)); damon_destroy_region(r, t); KUNIT_EXPECT_EQ(test, 0u, damon_nr_regions(t)); damon_free_target(t); } static unsigned int nr_damon_targets(struct damon_ctx *ctx) { struct damon_target *t; unsigned int nr_targets = 0; damon_for_each_target(t, ctx) nr_targets++; return nr_targets; } static void damon_test_target(struct kunit *test) { struct damon_ctx *c = damon_new_ctx(); struct damon_target *t; if (!c) kunit_skip(test, "ctx alloc fail"); t = damon_new_target(); if (!t) { damon_destroy_ctx(c); kunit_skip(test, "target alloc fail"); } KUNIT_EXPECT_EQ(test, 0u, nr_damon_targets(c)); damon_add_target(c, t); KUNIT_EXPECT_EQ(test, 1u, nr_damon_targets(c)); damon_destroy_target(t, c); KUNIT_EXPECT_EQ(test, 0u, nr_damon_targets(c)); damon_destroy_ctx(c); } /* * Test kdamond_reset_aggregated() * * DAMON checks access to each region and aggregates this information as the * access frequency of each region. In detail, it increases '->nr_accesses' of * regions that an access has confirmed. 'kdamond_reset_aggregated()' flushes * the aggregated information ('->nr_accesses' of each regions) to the result * buffer. As a result of the flushing, the '->nr_accesses' of regions are * initialized to zero. */ static void damon_test_aggregate(struct kunit *test) { struct damon_ctx *ctx = damon_new_ctx(); unsigned long saddr[][3] = {{10, 20, 30}, {5, 42, 49}, {13, 33, 55} }; unsigned long eaddr[][3] = {{15, 27, 40}, {31, 45, 55}, {23, 44, 66} }; unsigned long accesses[][3] = {{42, 95, 84}, {10, 20, 30}, {0, 1, 2} }; struct damon_target *t; struct damon_region *r; int it, ir; if (!ctx) kunit_skip(test, "ctx alloc fail"); for (it = 0; it < 3; it++) { t = damon_new_target(); if (!t) { damon_destroy_ctx(ctx); kunit_skip(test, "target alloc fail"); } damon_add_target(ctx, t); } it = 0; damon_for_each_target(t, ctx) { for (ir = 0; ir < 3; ir++) { r = damon_new_region(saddr[it][ir], eaddr[it][ir]); if (!r) { damon_destroy_ctx(ctx); kunit_skip(test, "region alloc fail"); } r->nr_accesses = accesses[it][ir]; damon_add_region(r, t); } it++; } kdamond_reset_aggregated(ctx); it = 0; damon_for_each_target(t, ctx) { ir = 0; /* '->nr_accesses' should be zeroed */ damon_for_each_region(r, t) { KUNIT_EXPECT_EQ(test, 0u, r->nr_accesses); ir++; } /* regions should be preserved */ KUNIT_EXPECT_EQ(test, 3, ir); it++; } /* targets also should be preserved */ KUNIT_EXPECT_EQ(test, 3, it); damon_destroy_ctx(ctx); } static void damon_test_split_at(struct kunit *test) { struct damon_target *t; struct damon_region *r, *r_new; t = damon_new_target(); if (!t) kunit_skip(test, "target alloc fail"); r = damon_new_region(0, 100); if (!r) { damon_free_target(t); kunit_skip(test, "region alloc fail"); } r->nr_accesses = 42; r->last_nr_accesses = 15; r->probe_hits[0] = 7; r->last_probe_hits[0] = 3; r->age = 10; damon_add_region(r, t); damon_split_region_at(t, r, 25); KUNIT_EXPECT_EQ(test, damon_nr_regions(t), 2); if (damon_nr_regions(t) != 2) goto out; KUNIT_EXPECT_EQ(test, r->ar.start, 0ul); KUNIT_EXPECT_EQ(test, r->ar.end, 25ul); r_new = damon_next_region(r); KUNIT_EXPECT_EQ(test, r_new->ar.start, 25ul); KUNIT_EXPECT_EQ(test, r_new->ar.end, 100ul); KUNIT_EXPECT_EQ(test, r->nr_accesses, r_new->nr_accesses); KUNIT_EXPECT_EQ(test, r->last_nr_accesses, r_new->last_nr_accesses); KUNIT_EXPECT_EQ(test, r->probe_hits[0], r_new->probe_hits[0]); KUNIT_EXPECT_EQ(test, r->last_probe_hits[0], r_new->last_probe_hits[0]); KUNIT_EXPECT_EQ(test, r->age, r_new->age); out: damon_free_target(t); } static void damon_test_merge_two(struct kunit *test) { struct damon_target *t; struct damon_region *r, *r2, *r3; int i; t = damon_new_target(); if (!t) kunit_skip(test, "target alloc fail"); r = damon_new_region(0, 100); if (!r) { damon_free_target(t); kunit_skip(test, "region alloc fail"); } r->nr_accesses = 10; r->probe_hits[0] = 6; r->age = 9; damon_add_region(r, t); r2 = damon_new_region(100, 300); if (!r2) { damon_free_target(t); kunit_skip(test, "second region alloc fail"); } r2->nr_accesses = 20; r2->probe_hits[0] = 14; r2->age = 21; damon_add_region(r2, t); damon_merge_two_regions(t, r, r2); KUNIT_EXPECT_EQ(test, r->ar.start, 0ul); KUNIT_EXPECT_EQ(test, r->ar.end, 300ul); KUNIT_EXPECT_EQ(test, r->nr_accesses, 16u); KUNIT_EXPECT_EQ(test, r->probe_hits[0], 11); KUNIT_EXPECT_EQ(test, r->age, 17u); i = 0; damon_for_each_region(r3, t) { KUNIT_EXPECT_PTR_EQ(test, r, r3); i++; } KUNIT_EXPECT_EQ(test, i, 1); damon_free_target(t); } static struct damon_region *__nth_region_of(struct damon_target *t, int idx) { struct damon_region *r; unsigned int i = 0; damon_for_each_region(r, t) { if (i++ == idx) return r; } return NULL; } static void damon_test_merge_regions_of(struct kunit *test) { struct damon_ctx *ctx; struct damon_target *t; struct damon_region *r; unsigned long sa[] = {0, 100, 114, 122, 130, 156, 170, 184, 230}; unsigned long ea[] = {100, 112, 122, 130, 156, 170, 184, 230, 10170}; unsigned int nrs[] = {0, 0, 10, 10, 20, 30, 1, 2, 5}; unsigned long saddrs[] = {0, 114, 130, 156, 170, 230}; unsigned long eaddrs[] = {112, 130, 156, 170, 230, 10170}; int i; ctx = damon_new_ctx(); if (!ctx) kunit_skip(test, "ctx alloc fail"); t = damon_new_target(); if (!t) { damon_destroy_ctx(ctx); kunit_skip(test, "target alloc fail"); } for (i = 0; i < ARRAY_SIZE(sa); i++) { r = damon_new_region(sa[i], ea[i]); if (!r) { damon_free_target(t); damon_destroy_ctx(ctx); kunit_skip(test, "region alloc fail"); } r->nr_accesses = nrs[i]; damon_add_region(r, t); } damon_merge_regions_of(t, 9, 9999, ctx, true); /* 0-112, 114-130, 130-156, 156-170, 170-230, 230-10170 */ KUNIT_EXPECT_EQ(test, damon_nr_regions(t), 6u); if (damon_nr_regions(t) != 6) goto out; for (i = 0; i < 6; i++) { r = __nth_region_of(t, i); KUNIT_EXPECT_EQ(test, r->ar.start, saddrs[i]); KUNIT_EXPECT_EQ(test, r->ar.end, eaddrs[i]); } out: damon_free_target(t); damon_destroy_ctx(ctx); } static void damon_test_split_regions_of(struct kunit *test) { struct damon_ctx *c; struct damon_target *t; struct damon_region *r; unsigned long sa[] = {0, 300, 500}; unsigned long ea[] = {220, 400, 700}; int i; c = damon_new_ctx(); if (!c) kunit_skip(test, "ctx alloc fail"); t = damon_new_target(); if (!t) { damon_destroy_ctx(c); kunit_skip(test, "target alloc fail"); } r = damon_new_region(0, 22); if (!r) { damon_free_target(t); damon_destroy_ctx(c); kunit_skip(test, "region alloc fail"); } damon_add_region(r, t); damon_split_regions_of(c, t, 2, 1); KUNIT_EXPECT_LE(test, damon_nr_regions(t), 2u); damon_free_target(t); t = damon_new_target(); if (!t) { damon_destroy_ctx(c); kunit_skip(test, "second target alloc fail"); } r = damon_new_region(0, 220); if (!r) { damon_free_target(t); damon_destroy_ctx(c); kunit_skip(test, "second region alloc fail"); } damon_add_region(r, t); damon_split_regions_of(c, t, 4, 1); KUNIT_EXPECT_LE(test, damon_nr_regions(t), 4u); damon_free_target(t); t = damon_new_target(); if (!t) { damon_destroy_ctx(c); kunit_skip(test, "third target alloc fail"); } for (i = 0; i < ARRAY_SIZE(sa); i++) { r = damon_new_region(sa[i], ea[i]); if (!r) { damon_free_target(t); damon_destroy_ctx(c); kunit_skip(test, "region alloc fail"); } damon_add_region(r, t); } damon_split_regions_of(c, t, 4, 5); KUNIT_EXPECT_LE(test, damon_nr_regions(t), 12u); damon_for_each_region(r, t) KUNIT_EXPECT_GE(test, damon_sz_region(r) % 5ul, 0ul); damon_free_target(t); damon_destroy_ctx(c); } /* * When the total region count is already above max_nr_regions / 2, * kdamond_split_regions() must keep refining the resolution by splitting a * fraction of the regions (making progress), without exceeding * max_nr_regions. */ static void damon_test_split_above_half_progresses(struct kunit *test) { struct damon_ctx *c; struct damon_target *t; struct damon_region *r; unsigned long start; unsigned int nr_before, nr_after, i; const unsigned int nr_init = 760; const unsigned long region_sz = 100; c = damon_new_ctx(); if (!c) kunit_skip(test, "ctx alloc fail"); /* Keep the split arithmetic independent of the page size */ c->min_region_sz = 1; c->attrs.min_nr_regions = 10; c->attrs.max_nr_regions = 1500; t = damon_new_target(); if (!t) { damon_destroy_ctx(c); kunit_skip(test, "target alloc fail"); } for (i = 0; i < nr_init; i++) { start = i * region_sz; r = damon_new_region(start, start + region_sz); if (!r) { damon_free_target(t); damon_destroy_ctx(c); kunit_skip(test, "region alloc fail"); } r->nr_accesses = (i & 1) ? 0 : 100; r->age = 5; damon_add_region(r, t); } damon_add_target(c, t); nr_before = damon_nr_regions(t); /* Above max_nr_regions / 2, so the blanket-split path is skipped */ KUNIT_EXPECT_GT(test, (unsigned long)nr_before, c->attrs.max_nr_regions / 2); kdamond_split_regions(c); nr_after = damon_nr_regions(t); /* Still made progress ... */ KUNIT_EXPECT_GT(test, nr_after, nr_before); /* ... but did not overshoot the configured maximum */ KUNIT_EXPECT_LE(test, (unsigned long)nr_after, c->attrs.max_nr_regions); damon_destroy_ctx(c); } static void damon_test_ops_registration(struct kunit *test) { struct damon_ctx *c = damon_new_ctx(); struct damon_operations ops = {.id = DAMON_OPS_VADDR}, bak; bool need_cleanup = false; if (!c) kunit_skip(test, "ctx alloc fail"); /* DAMON_OPS_VADDR is registered only if CONFIG_DAMON_VADDR is set */ if (!damon_is_registered_ops(DAMON_OPS_VADDR)) { bak.id = DAMON_OPS_VADDR; KUNIT_EXPECT_EQ(test, damon_register_ops(&bak), 0); need_cleanup = true; } /* DAMON_OPS_VADDR is ensured to be registered */ KUNIT_EXPECT_EQ(test, damon_select_ops(c, DAMON_OPS_VADDR), 0); /* Double-registration is prohibited */ KUNIT_EXPECT_EQ(test, damon_register_ops(&ops), -EINVAL); /* Unknown ops id cannot be registered */ KUNIT_EXPECT_EQ(test, damon_select_ops(c, NR_DAMON_OPS), -EINVAL); /* Registration should success after unregistration */ scoped_guard(mutex, &damon_ops_lock) { bak = damon_registered_ops[DAMON_OPS_VADDR]; damon_registered_ops[DAMON_OPS_VADDR] = (struct damon_operations){}; } ops.id = DAMON_OPS_VADDR; KUNIT_EXPECT_EQ(test, damon_register_ops(&ops), 0); scoped_guard(mutex, &damon_ops_lock) damon_registered_ops[DAMON_OPS_VADDR] = bak; /* Check double-registration failure again */ KUNIT_EXPECT_EQ(test, damon_register_ops(&ops), -EINVAL); damon_destroy_ctx(c); if (need_cleanup) { scoped_guard(mutex, &damon_ops_lock) damon_registered_ops[DAMON_OPS_VADDR] = (struct damon_operations){}; } } static void damon_test_set_regions_for(struct kunit *test, struct damon_addr_range *old_ranges, int sz_old_ranges, struct damon_addr_range *new_ranges, int sz_new_ranges, unsigned long min_region_sz, struct damon_addr_range *expect_ranges, int sz_expect_ranges, int expect_err) { struct damon_target *t; struct damon_region *r; int i, err; t = damon_new_target(); if (!t) kunit_skip(test, "target alloc fail"); for (i = 0; i < sz_old_ranges; i++) { r = damon_new_region(old_ranges[i].start, old_ranges[i].end); if (!r) { damon_destroy_target(t, NULL); kunit_skip(test, "%d-th r alloc fail\n", i); } damon_add_region(r, t); } err = damon_set_regions(t, new_ranges, sz_new_ranges, min_region_sz); KUNIT_EXPECT_EQ(test, err, expect_err); KUNIT_EXPECT_EQ(test, damon_nr_regions(t), sz_expect_ranges); if (damon_nr_regions(t) != sz_expect_ranges) { damon_destroy_target(t, NULL); return; } i = 0; damon_for_each_region(r, t) { KUNIT_EXPECT_EQ(test, r->ar.start, expect_ranges[i].start); KUNIT_EXPECT_EQ(test, r->ar.end, expect_ranges[i++].end); } damon_destroy_target(t, NULL); } static void damon_test_set_regions(struct kunit *test) { /* Initial build up on empty target. */ damon_test_set_regions_for(test, (struct damon_addr_range[]){}, 0, (struct damon_addr_range[]){ {.start = 5, .end = 15}, {.start = 15, .end = 25}, }, 2, 1, (struct damon_addr_range[]){ {.start = 5, .end = 15}, {.start = 15, .end = 25}, }, 2, 0); /* Un-intersecting regions should be removed. */ damon_test_set_regions_for(test, (struct damon_addr_range[]){ {.start = 4, .end = 16}, {.start = 24, .end = 32}, }, 2, (struct damon_addr_range[]){ {.start = 18, .end = 23}, }, 1, 1, (struct damon_addr_range[]){ {.start = 18, .end = 23}, }, 1, 0); /* * Holes should be filled up with new regions. * * old: [4, 16) [24, 32) * new: [8, 28) * expect: [8, 16)[16,24),[24, 28) */ damon_test_set_regions_for(test, (struct damon_addr_range[]){ {.start = 4, .end = 16}, {.start = 24, .end = 32}, }, 2, (struct damon_addr_range[]){ {.start = 8, .end = 28}, }, 1, 1, (struct damon_addr_range[]){ {.start = 8, .end = 16}, {.start = 16, .end = 24}, {.start = 24, .end = 28}, }, 3, 0); /* * New regions should be able to be appended. * * old: [0, 4)[4, 17) * new: [0, 15) [25, 40) * expect: [0, 4)[4, 15) [25, 40) */ damon_test_set_regions_for(test, (struct damon_addr_range[]){ {.start = 0, .end = 4}, {.start = 4, .end = 17}, }, 2, (struct damon_addr_range[]){ {.start = 0, .end = 15}, {.start = 25, .end = 40}, }, 2, 1, (struct damon_addr_range[]){ {.start = 0, .end = 4}, {.start = 4, .end = 15}, {.start = 25, .end = 40}, }, 3, 0); /* * New regions should be able to be inserted. * * old: [0, 4) [42, 52) * new: [0, 15) [25, 40) [44, 50) * expect: [0, 15) [25, 40) [44, 50) */ damon_test_set_regions_for(test, (struct damon_addr_range[]){ {.start = 0, .end = 4}, {.start = 42, .end = 52}, }, 2, (struct damon_addr_range[]){ {.start = 0, .end = 15}, {.start = 25, .end = 40}, {.start = 44, .end = 50}, }, 3, 1, (struct damon_addr_range[]){ {.start = 0, .end = 15}, {.start = 25, .end = 40}, {.start = 44, .end = 50}, }, 3, 0); /* Zero size regions should return -EINVAL. */ damon_test_set_regions_for(test, (struct damon_addr_range[]){}, 0, (struct damon_addr_range[]){ {.start = 42, .end = 42}, }, 1, 1, (struct damon_addr_range[]){}, 0, -EINVAL); /* Negative size regions should return -EINVAL. */ damon_test_set_regions_for(test, (struct damon_addr_range[]){}, 0, (struct damon_addr_range[]){ {.start = 42, .end = 21}, }, 1, 1, (struct damon_addr_range[]){}, 0, -EINVAL); /* * Regions resulting in same region after alignment should return * -EINVAL. */ damon_test_set_regions_for(test, (struct damon_addr_range[]){}, 0, (struct damon_addr_range[]){ {.start = 10, .end = 20}, {.start = 20, .end = 30}, }, 2, 4096, (struct damon_addr_range[]){}, 0, -EINVAL); } static void damon_test_nr_samples_per_aggr(struct kunit *test) { struct damon_attrs attrs; /* Zero sample interval is treated as one. */ attrs = (struct damon_attrs){ .sample_interval = 0, .aggr_interval = 5000, }; KUNIT_EXPECT_EQ(test, damon_nr_samples_per_aggr(&attrs), 5000); /* Zero sample and aggregation intervals cover the zero-result fallback. */ attrs = (struct damon_attrs){ .sample_interval = 0, .aggr_interval = 0, }; KUNIT_EXPECT_EQ(test, damon_nr_samples_per_aggr(&attrs), 1); /* * Too large aggregation interval on 64 bit system doesn't cause * overflow */ if (ULONG_MAX > UINT_MAX) { attrs = (struct damon_attrs){ .sample_interval = 1, .aggr_interval = (unsigned long)UINT_MAX + 1, }; KUNIT_EXPECT_EQ(test, damon_nr_samples_per_aggr(&attrs), UINT_MAX); } } static void damon_test_update_monitoring_result(struct kunit *test) { struct damon_attrs old_attrs = { .sample_interval = 10, .aggr_interval = 1000,}; struct damon_attrs new_attrs; struct damon_region *r = damon_new_region(3, 7); struct damon_ctx *ctx; if (!r) kunit_skip(test, "region alloc fail"); ctx = damon_new_ctx(); if (!ctx) { damon_free_region(r); kunit_skip(test, "ctx alloc fail"); } r->nr_accesses = 15; r->age = 20; new_attrs = (struct damon_attrs){ .sample_interval = 100, .aggr_interval = 10000,}; damon_update_monitoring_result(r, &old_attrs, &new_attrs, false, ctx); KUNIT_EXPECT_EQ(test, r->nr_accesses, 15); KUNIT_EXPECT_EQ(test, r->age, 2); new_attrs = (struct damon_attrs){ .sample_interval = 1, .aggr_interval = 1000}; damon_update_monitoring_result(r, &old_attrs, &new_attrs, false, ctx); KUNIT_EXPECT_EQ(test, r->nr_accesses, 150); KUNIT_EXPECT_EQ(test, r->age, 2); new_attrs = (struct damon_attrs){ .sample_interval = 1, .aggr_interval = 100}; damon_update_monitoring_result(r, &old_attrs, &new_attrs, false, ctx); KUNIT_EXPECT_EQ(test, r->nr_accesses, 150); KUNIT_EXPECT_EQ(test, r->age, 20); damon_free_region(r); damon_destroy_ctx(ctx); } static void damon_test_set_attrs(struct kunit *test) { struct damon_ctx *c = damon_new_ctx(); struct damon_attrs valid_attrs = { .min_nr_regions = 10, .max_nr_regions = 1000, .sample_interval = 5000, .aggr_interval = 100000,}; struct damon_attrs invalid_attrs; if (!c) kunit_skip(test, "ctx alloc fail"); KUNIT_EXPECT_EQ(test, damon_set_attrs(c, &valid_attrs), 0); invalid_attrs = valid_attrs; invalid_attrs.min_nr_regions = 1; KUNIT_EXPECT_EQ(test, damon_set_attrs(c, &invalid_attrs), -EINVAL); invalid_attrs = valid_attrs; invalid_attrs.max_nr_regions = 9; KUNIT_EXPECT_EQ(test, damon_set_attrs(c, &invalid_attrs), -EINVAL); invalid_attrs = valid_attrs; invalid_attrs.aggr_interval = 4999; KUNIT_EXPECT_EQ(test, damon_set_attrs(c, &invalid_attrs), -EINVAL); damon_destroy_ctx(c); } static void damon_test_mvsum(struct kunit *test) { unsigned long input_expects[] = { /* current value, last value, remaining window (bp) */ 0, 49, 10000, 49, /* 0 + 49 * 1 */ 3, 10, 7000, 10, /* 3 + 10 * 0.7 */ 3, 10, 5000, 8, /* 3 + 10 * 0.5 */ 32, 100, 1000, 42, /* 32 + 100 * 0.1 */ 42, 49, 0, 42, /* 42 + 49 * 0 */ }; int i; for (i = 0; i < ARRAY_SIZE(input_expects); i += 4) { unsigned long current_nr = input_expects[i]; unsigned long last_nr = input_expects[i + 1]; unsigned long left_window_bp = input_expects[i + 2]; unsigned long expect = input_expects[i + 3]; KUNIT_EXPECT_EQ(test, damon_mvsum(current_nr, last_nr, left_window_bp), expect); } } /* * Test damon_nr_accesses_mvsum(), which wraps damon_mvsum() with the * monitoring intervals of the context. With a sample interval of 1 and an * aggregation interval of 10, an aggregation window is 10 sample intervals * long. Each row below specifies the passed sample intervals, the next * aggregation time in sample intervals, the current and last nr_accesses of a * region, and the expected return value. */ static void damon_test_nr_accesses_mvsum(struct kunit *test) { unsigned long input_expects[] = { /* passed, next_aggr, nr_accesses, last_nr_accesses, expect */ 0, 10, 5, 3, 3, /* full window left, unreset */ 0, 10, 0, 7, 7, /* full window left, reset */ 5, 10, 3, 10, 8, /* half window left */ 8, 10, 3, 10, 5, /* 20% window left */ 10, 10, 42, 49, 42, /* no window left */ }; struct damon_ctx *c = damon_new_ctx(); struct damon_region *r; int i; if (!c) kunit_skip(test, "ctx alloc fail"); r = damon_new_region(0, 4096); if (!r) { damon_destroy_ctx(c); kunit_skip(test, "region alloc fail"); } c->attrs.sample_interval = 1; c->attrs.aggr_interval = 10; for (i = 0; i < ARRAY_SIZE(input_expects); i += 5) { c->passed_sample_intervals = input_expects[i]; c->next_aggregation_sis = input_expects[i + 1]; r->nr_accesses = input_expects[i + 2]; r->last_nr_accesses = input_expects[i + 3]; KUNIT_EXPECT_EQ(test, (unsigned int)input_expects[i + 4], damon_nr_accesses_mvsum(r, c)); } damon_free_region(r); damon_destroy_ctx(c); } static void damos_test_new_filter(struct kunit *test) { struct damos_filter *filter; filter = damos_new_filter(DAMOS_FILTER_TYPE_ANON, true, false); if (!filter) kunit_skip(test, "filter alloc fail"); KUNIT_EXPECT_EQ(test, filter->type, DAMOS_FILTER_TYPE_ANON); KUNIT_EXPECT_EQ(test, filter->matching, true); KUNIT_EXPECT_PTR_EQ(test, filter->list.prev, &filter->list); KUNIT_EXPECT_PTR_EQ(test, filter->list.next, &filter->list); damos_destroy_filter(filter); } static void damos_test_new_scheme_keeps_src_quota(struct kunit *test) { struct damos_access_pattern pattern = {}; struct damon_target target = {}; struct damos_quota quota = { .sz = SZ_64K, .esz = 123, .esz_bp = 456, .total_charged_sz = 789, .total_charged_ns = 1011, .charged_sz = 12, .charged_from = 13, .charge_target_from = &target, .charge_addr_from = 14, }; struct damos_watermarks wmarks = {}; struct damos *s; s = damon_new_scheme(&pattern, DAMOS_STAT, 0, "a, &wmarks, NUMA_NO_NODE); if (!s) kunit_skip(test, "scheme alloc fail"); KUNIT_EXPECT_EQ(test, s->quota.sz, (unsigned long)SZ_64K); KUNIT_EXPECT_EQ(test, s->quota.esz, 0ul); KUNIT_EXPECT_EQ(test, s->quota.esz_bp, 0ul); KUNIT_EXPECT_EQ(test, s->quota.total_charged_sz, 0ul); KUNIT_EXPECT_EQ(test, s->quota.total_charged_ns, 0ul); KUNIT_EXPECT_EQ(test, s->quota.charged_sz, 0ul); KUNIT_EXPECT_EQ(test, s->quota.charged_from, 0ul); KUNIT_EXPECT_PTR_EQ(test, s->quota.charge_target_from, NULL); KUNIT_EXPECT_EQ(test, s->quota.charge_addr_from, 0ul); KUNIT_EXPECT_EQ(test, quota.esz, 123ul); KUNIT_EXPECT_EQ(test, quota.esz_bp, 456ul); KUNIT_EXPECT_EQ(test, quota.total_charged_sz, 789ul); KUNIT_EXPECT_EQ(test, quota.total_charged_ns, 1011ul); KUNIT_EXPECT_EQ(test, quota.charged_sz, 12ul); KUNIT_EXPECT_EQ(test, quota.charged_from, 13ul); KUNIT_EXPECT_PTR_EQ(test, quota.charge_target_from, &target); KUNIT_EXPECT_EQ(test, quota.charge_addr_from, 14ul); damon_destroy_scheme(s); } static void damos_test_commit_quota_goal_for(struct kunit *test, struct damos_quota_goal *dst, struct damos_quota_goal *src) { damos_commit_quota_goal(dst, src); KUNIT_EXPECT_EQ(test, dst->metric, src->metric); KUNIT_EXPECT_EQ(test, dst->complement, src->complement); KUNIT_EXPECT_EQ(test, dst->target_value, src->target_value); if (src->metric == DAMOS_QUOTA_USER_INPUT) KUNIT_EXPECT_EQ(test, dst->current_value, src->current_value); switch (dst->metric) { case DAMOS_QUOTA_SOME_MEM_PSI_US: KUNIT_EXPECT_EQ(test, dst->last_psi_total, U64_MAX); break; case DAMOS_QUOTA_NODE_MEM_USED_BP: case DAMOS_QUOTA_NODE_MEM_FREE_BP: KUNIT_EXPECT_EQ(test, dst->nid, src->nid); break; case DAMOS_QUOTA_NODE_MEMCG_USED_BP: case DAMOS_QUOTA_NODE_MEMCG_FREE_BP: KUNIT_EXPECT_EQ(test, dst->nid, src->nid); KUNIT_EXPECT_EQ(test, dst->memcg_id, src->memcg_id); break; case DAMOS_QUOTA_NODE_ELIGIBLE_MEM_BP: KUNIT_EXPECT_EQ(test, dst->nid, src->nid); break; default: break; } } static void damos_test_commit_quota_goal(struct kunit *test) { struct damos_quota_goal dst = { .metric = DAMOS_QUOTA_SOME_MEM_PSI_US, .target_value = 1000, .current_value = 123, .last_psi_total = 456, }; damos_test_commit_quota_goal_for(test, &dst, &(struct damos_quota_goal) { .metric = DAMOS_QUOTA_SOME_MEM_PSI_US, .target_value = 234, .current_value = 345, .last_psi_total = 567, }); damos_test_commit_quota_goal_for(test, &dst, &(struct damos_quota_goal){ .metric = DAMOS_QUOTA_USER_INPUT, .complement = true, .target_value = 789, .current_value = 12}); damos_test_commit_quota_goal_for(test, &dst, &(struct damos_quota_goal){ .metric = DAMOS_QUOTA_NODE_MEM_FREE_BP, .target_value = 345, .current_value = 678, .nid = 9, }); damos_test_commit_quota_goal_for(test, &dst, &(struct damos_quota_goal){ .metric = DAMOS_QUOTA_NODE_MEM_USED_BP, .target_value = 12, .current_value = 345, .nid = 6, }); damos_test_commit_quota_goal_for(test, &dst, &(struct damos_quota_goal){ .metric = DAMOS_QUOTA_NODE_MEMCG_USED_BP, .target_value = 456, .current_value = 567, .nid = 6, .memcg_id = 7, }); damos_test_commit_quota_goal_for(test, &dst, &(struct damos_quota_goal){ .metric = DAMOS_QUOTA_NODE_MEMCG_FREE_BP, .target_value = 890, .current_value = 901, .nid = 10, .memcg_id = 1, }); damos_test_commit_quota_goal_for(test, &dst, &(struct damos_quota_goal) { .metric = DAMOS_QUOTA_SOME_MEM_PSI_US, .target_value = 234, .current_value = 345, .last_psi_total = 567, }); damos_test_commit_quota_goal_for(test, &dst, &(struct damos_quota_goal){ .metric = DAMOS_QUOTA_NODE_ELIGIBLE_MEM_BP, .target_value = 12, .current_value = 345, .nid = 6, }); } static void damos_test_set_psi_current_val(struct kunit *test) { struct damos s = { .quota.goal_tuner = DAMOS_QUOTA_GOAL_TUNER_CONSIST, }; struct damos_quota_goal goal = { .metric = DAMOS_QUOTA_SOME_MEM_PSI_US, .target_value = 100, .last_psi_total = U64_MAX, }; /* uninitialized last_psi_total keeps the consist tuner quota */ damos_set_psi_current_val(1000, &goal, &s); KUNIT_EXPECT_EQ(test, goal.current_value, 100ul); KUNIT_EXPECT_EQ(test, goal.last_psi_total, 1000ull); /* initialized last_psi_total gives the delta */ damos_set_psi_current_val(1030, &goal, &s); KUNIT_EXPECT_EQ(test, goal.current_value, 30ul); KUNIT_EXPECT_EQ(test, goal.last_psi_total, 1030ull); /* temporal tuner keeps a zero quota */ s.quota.goal_tuner = DAMOS_QUOTA_GOAL_TUNER_TEMPORAL; s.quota.esz = 0; goal.last_psi_total = U64_MAX; damos_set_psi_current_val(2000, &goal, &s); KUNIT_EXPECT_EQ(test, goal.current_value, 100ul); KUNIT_EXPECT_EQ(test, goal.last_psi_total, 2000ull); /* temporal tuner keeps a non-zero quota */ s.quota.esz = SZ_64K; goal.last_psi_total = U64_MAX; damos_set_psi_current_val(3000, &goal, &s); KUNIT_EXPECT_EQ(test, goal.current_value, 0ul); KUNIT_EXPECT_EQ(test, goal.last_psi_total, 3000ull); /* temporal tuner uses the measured PSI delta */ damos_set_psi_current_val(3250, &goal, &s); KUNIT_EXPECT_EQ(test, goal.current_value, 250ul); KUNIT_EXPECT_EQ(test, goal.last_psi_total, 3250ull); } 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) { struct damos_quota dst, src; struct damos_quota_goal *goal, *next; bool skip = true; int i; int nr_dst = 0, nr_src = 0; INIT_LIST_HEAD(&dst.goals); INIT_LIST_HEAD(&src.goals); for (i = 0; i < nr_dst_goals; i++) { /* * When nr_src_goals is smaller than dst_goals, * damos_commit_quota_goals() will kfree() the dst goals. * Make it kfree()-able. */ goal = damos_new_quota_goal(dst_goals[i].metric, dst_goals[i].complement, dst_goals[i].target_value); if (!goal) goto out; damos_add_quota_goal(&dst, goal); } skip = false; for (i = 0; i < nr_src_goals; i++) damos_add_quota_goal(&src, &src_goals[i]); damos_commit_quota_goals(&dst, &src); damos_for_each_quota_goal(goal, &dst) nr_dst++; damos_for_each_quota_goal(goal, &src) nr_src++; KUNIT_EXPECT_EQ(test, nr_dst, nr_src); if (nr_dst != nr_src) goto out; i = 0; damos_for_each_quota_goal(goal, (&dst)) { KUNIT_EXPECT_EQ(test, goal->metric, src_goals[i].metric); KUNIT_EXPECT_EQ(test, goal->target_value, src_goals[i++].target_value); } KUNIT_EXPECT_EQ(test, i, nr_src_goals); out: damos_for_each_quota_goal_safe(goal, next, (&dst)) damos_destroy_quota_goal(goal); if (skip) kunit_skip(test, "goal alloc fail"); } static void damos_test_commit_quota_goals(struct kunit *test) { damos_test_commit_quota_goals_for(test, (struct damos_quota_goal[]){}, 0, (struct damos_quota_goal[]){ { .metric = DAMOS_QUOTA_USER_INPUT, .target_value = 123, }, }, 1); damos_test_commit_quota_goals_for(test, (struct damos_quota_goal[]){ { .metric = DAMOS_QUOTA_USER_INPUT, .target_value = 234, }, }, 1, (struct damos_quota_goal[]){ { .metric = DAMOS_QUOTA_USER_INPUT, .target_value = 345, }, }, 1); damos_test_commit_quota_goals_for(test, (struct damos_quota_goal[]){ { .metric = DAMOS_QUOTA_USER_INPUT, .target_value = 456, }, }, 1, (struct damos_quota_goal[]){}, 0); } static void damos_test_commit_quota(struct kunit *test) { struct damos_quota dst = { .reset_interval = 1, .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, }; struct damos_quota src = { .reset_interval = 7, .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, }; INIT_LIST_HEAD(&dst.goals); INIT_LIST_HEAD(&src.goals); damos_commit_quota(&dst, &src); KUNIT_EXPECT_EQ(test, dst.reset_interval, src.reset_interval); 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); } static int damos_test_help_dests_setup(struct damos_migrate_dests *dests, unsigned int *node_id_arr, unsigned int *weight_arr, size_t nr_dests) { size_t i; dests->node_id_arr = kmalloc_objs(*dests->node_id_arr, nr_dests); if (!dests->node_id_arr) return -ENOMEM; dests->weight_arr = kmalloc_objs(*dests->weight_arr, nr_dests); if (!dests->weight_arr) { kfree(dests->node_id_arr); dests->node_id_arr = NULL; return -ENOMEM; } for (i = 0; i < nr_dests; i++) { dests->node_id_arr[i] = node_id_arr[i]; dests->weight_arr[i] = weight_arr[i]; } dests->nr_dests = nr_dests; return 0; } static void damos_test_help_dests_free(struct damos_migrate_dests *dests) { kfree(dests->node_id_arr); kfree(dests->weight_arr); } static void damos_test_commit_dests_for(struct kunit *test, unsigned int *dst_node_id_arr, unsigned int *dst_weight_arr, size_t dst_nr_dests, unsigned int *src_node_id_arr, unsigned int *src_weight_arr, size_t src_nr_dests) { struct damos_migrate_dests dst = {}, src = {}; int i, err; bool skip = true; err = damos_test_help_dests_setup(&dst, dst_node_id_arr, dst_weight_arr, dst_nr_dests); if (err) kunit_skip(test, "dests setup fail"); err = damos_test_help_dests_setup(&src, src_node_id_arr, src_weight_arr, src_nr_dests); if (err) { damos_test_help_dests_free(&dst); kunit_skip(test, "src setup fail"); } err = damos_commit_dests(&dst, &src); if (err) goto out; skip = false; KUNIT_EXPECT_EQ(test, dst.nr_dests, src_nr_dests); if (dst.nr_dests != src_nr_dests) goto out; for (i = 0; i < dst.nr_dests; i++) { KUNIT_EXPECT_EQ(test, dst.node_id_arr[i], src_node_id_arr[i]); KUNIT_EXPECT_EQ(test, dst.weight_arr[i], src_weight_arr[i]); } out: damos_test_help_dests_free(&dst); damos_test_help_dests_free(&src); if (skip) kunit_skip(test, "skip"); } static void damos_test_commit_dests(struct kunit *test) { damos_test_commit_dests_for(test, (unsigned int[]){1, 2, 3}, (unsigned int[]){2, 3, 4}, 3, (unsigned int[]){4, 5, 6}, (unsigned int[]){5, 6, 7}, 3); damos_test_commit_dests_for(test, (unsigned int[]){1, 2}, (unsigned int[]){2, 3}, 2, (unsigned int[]){4, 5, 6}, (unsigned int[]){5, 6, 7}, 3); damos_test_commit_dests_for(test, NULL, NULL, 0, (unsigned int[]){4, 5, 6}, (unsigned int[]){5, 6, 7}, 3); damos_test_commit_dests_for(test, (unsigned int[]){1, 2, 3}, (unsigned int[]){2, 3, 4}, 3, (unsigned int[]){4, 5}, (unsigned int[]){5, 6}, 2); damos_test_commit_dests_for(test, (unsigned int[]){1, 2, 3}, (unsigned int[]){2, 3, 4}, 3, NULL, NULL, 0); } static void damos_test_commit_filter_for(struct kunit *test, struct damos_filter *dst, struct damos_filter *src, bool expect_fail) { int err; err = damos_commit_filter(dst, src); KUNIT_EXPECT_EQ(test, err != 0, expect_fail); if (expect_fail) return; KUNIT_EXPECT_EQ(test, dst->type, src->type); KUNIT_EXPECT_EQ(test, dst->matching, src->matching); KUNIT_EXPECT_EQ(test, dst->allow, src->allow); switch (src->type) { case DAMOS_FILTER_TYPE_MEMCG: KUNIT_EXPECT_EQ(test, dst->memcg_id, src->memcg_id); break; case DAMOS_FILTER_TYPE_ADDR: KUNIT_EXPECT_EQ(test, dst->addr_range.start, src->addr_range.start); KUNIT_EXPECT_EQ(test, dst->addr_range.end, src->addr_range.end); break; case DAMOS_FILTER_TYPE_TARGET: KUNIT_EXPECT_EQ(test, dst->target_idx, src->target_idx); break; case DAMOS_FILTER_TYPE_HUGEPAGE_SIZE: KUNIT_EXPECT_EQ(test, dst->sz_range.min, src->sz_range.min); KUNIT_EXPECT_EQ(test, dst->sz_range.max, src->sz_range.max); break; case DAMOS_FILTER_TYPE_PROBE_HITS_WSUM: KUNIT_EXPECT_EQ(test, dst->range_min, src->range_min); KUNIT_EXPECT_EQ(test, dst->range_max, src->range_max); break; default: break; } } static void damos_test_commit_filter(struct kunit *test) { struct damos_filter dst = { .type = DAMOS_FILTER_TYPE_ACTIVE, .matching = false, .allow = false, }; damos_test_commit_filter_for(test, &dst, &(struct damos_filter){ .type = DAMOS_FILTER_TYPE_ANON, .matching = true, .allow = true, }, false); damos_test_commit_filter_for(test, &dst, &(struct damos_filter){ .type = DAMOS_FILTER_TYPE_MEMCG, .matching = false, .allow = false, .memcg_id = 123, }, false); damos_test_commit_filter_for(test, &dst, &(struct damos_filter){ .type = DAMOS_FILTER_TYPE_YOUNG, .matching = true, .allow = true, }, false); damos_test_commit_filter_for(test, &dst, &(struct damos_filter){ .type = DAMOS_FILTER_TYPE_HUGEPAGE_SIZE, .matching = false, .allow = false, .sz_range = {.min = 234, .max = 345}, }, false); damos_test_commit_filter_for(test, &dst, &(struct damos_filter){ .type = DAMOS_FILTER_TYPE_HUGEPAGE_SIZE, .matching = false, .allow = false, .sz_range = {.min = 456, .max = 123}, }, true); damos_test_commit_filter_for(test, &dst, &(struct damos_filter){ .type = DAMOS_FILTER_TYPE_UNMAPPED, .matching = true, .allow = true, }, false); damos_test_commit_filter_for(test, &dst, &(struct damos_filter){ .type = DAMOS_FILTER_TYPE_ADDR, .matching = false, .allow = false, .addr_range = {.start = 456, .end = 567}, }, false); damos_test_commit_filter_for(test, &dst, &(struct damos_filter){ .type = DAMOS_FILTER_TYPE_ADDR, .matching = false, .allow = false, .addr_range = {.start = 567, .end = 456}, }, true); damos_test_commit_filter_for(test, &dst, &(struct damos_filter){ .type = DAMOS_FILTER_TYPE_TARGET, .matching = true, .allow = true, .target_idx = 6, }, false); damos_test_commit_filter_for(test, &dst, &(struct damos_filter){ .type = DAMOS_FILTER_TYPE_PROBE_HITS_WSUM, .matching = false, .allow = true, .range_min = 12, .range_max = 34, }, false); damos_test_commit_filter_for(test, &dst, &(struct damos_filter){ .type = DAMOS_FILTER_TYPE_PROBE_HITS_WSUM, .matching = false, .allow = true, .range_min = 34, .range_max = 12, }, true); } static void damos_test_help_initailize_scheme(struct damos *scheme) { INIT_LIST_HEAD(&scheme->quota.goals); INIT_LIST_HEAD(&scheme->core_filters); INIT_LIST_HEAD(&scheme->ops_filters); } static void damos_test_commit_for(struct kunit *test, struct damos *dst, struct damos *src) { int err; damos_test_help_initailize_scheme(dst); damos_test_help_initailize_scheme(src); err = damos_commit(dst, src); if (err) kunit_skip(test, "damos_commit fail"); KUNIT_EXPECT_EQ(test, dst->pattern.min_sz_region, src->pattern.min_sz_region); KUNIT_EXPECT_EQ(test, dst->pattern.max_sz_region, src->pattern.max_sz_region); KUNIT_EXPECT_EQ(test, dst->pattern.min_nr_accesses, src->pattern.min_nr_accesses); KUNIT_EXPECT_EQ(test, dst->pattern.max_nr_accesses, src->pattern.max_nr_accesses); KUNIT_EXPECT_EQ(test, dst->pattern.min_age_region, src->pattern.min_age_region); KUNIT_EXPECT_EQ(test, dst->pattern.max_age_region, src->pattern.max_age_region); KUNIT_EXPECT_EQ(test, dst->action, src->action); KUNIT_EXPECT_EQ(test, dst->apply_interval_us, src->apply_interval_us); KUNIT_EXPECT_EQ(test, dst->wmarks.metric, src->wmarks.metric); KUNIT_EXPECT_EQ(test, dst->wmarks.interval, src->wmarks.interval); KUNIT_EXPECT_EQ(test, dst->wmarks.high, src->wmarks.high); KUNIT_EXPECT_EQ(test, dst->wmarks.mid, src->wmarks.mid); KUNIT_EXPECT_EQ(test, dst->wmarks.low, src->wmarks.low); switch (src->action) { case DAMOS_MIGRATE_COLD: case DAMOS_MIGRATE_HOT: KUNIT_EXPECT_EQ(test, dst->target_nid, src->target_nid); break; default: break; } } static void damos_test_commit_pageout(struct kunit *test) { damos_test_commit_for(test, &(struct damos){ .pattern = (struct damos_access_pattern){ 1, 2, 3, 4, 5, 6}, .action = DAMOS_PAGEOUT, .apply_interval_us = 1000000, .wmarks = (struct damos_watermarks){ DAMOS_WMARK_FREE_MEM_RATE, 900, 100, 50}, }, &(struct damos){ .pattern = (struct damos_access_pattern){ 2, 3, 4, 5, 6, 7}, .action = DAMOS_PAGEOUT, .apply_interval_us = 2000000, .wmarks = (struct damos_watermarks){ DAMOS_WMARK_FREE_MEM_RATE, 800, 50, 30}, }); } static void damos_test_commit_migrate_hot(struct kunit *test) { damos_test_commit_for(test, &(struct damos){ .pattern = (struct damos_access_pattern){ 1, 2, 3, 4, 5, 6}, .action = DAMOS_PAGEOUT, .apply_interval_us = 1000000, .wmarks = (struct damos_watermarks){ DAMOS_WMARK_FREE_MEM_RATE, 900, 100, 50}, }, &(struct damos){ .pattern = (struct damos_access_pattern){ 2, 3, 4, 5, 6, 7}, .action = DAMOS_MIGRATE_HOT, .apply_interval_us = 2000000, .target_nid = 5, }); } static struct damon_target *damon_test_help_setup_target( unsigned long region_start_end[][2], int nr_regions) { struct damon_target *t; struct damon_region *r; int i; t = damon_new_target(); if (!t) return NULL; for (i = 0; i < nr_regions; i++) { r = damon_new_region(region_start_end[i][0], region_start_end[i][1]); if (!r) { damon_free_target(t); return NULL; } damon_add_region(r, t); } return t; } static void damon_test_commit_target_regions_for(struct kunit *test, unsigned long dst_start_end[][2], int nr_dst_regions, unsigned long src_start_end[][2], int nr_src_regions, unsigned long expect_start_end[][2], int nr_expect_regions) { struct damon_target *dst_target, *src_target; struct damon_region *r; int i; dst_target = damon_test_help_setup_target(dst_start_end, nr_dst_regions); if (!dst_target) kunit_skip(test, "dst target setup fail"); src_target = damon_test_help_setup_target(src_start_end, nr_src_regions); if (!src_target) { damon_free_target(dst_target); kunit_skip(test, "src target setup fail"); } damon_commit_target_regions(dst_target, src_target, 1); KUNIT_EXPECT_EQ(test, damon_nr_regions(dst_target), nr_expect_regions); if (damon_nr_regions(dst_target) != nr_expect_regions) goto out; i = 0; damon_for_each_region(r, dst_target) { KUNIT_EXPECT_EQ(test, r->ar.start, expect_start_end[i][0]); KUNIT_EXPECT_EQ(test, r->ar.end, expect_start_end[i][1]); i++; } out: damon_free_target(dst_target); damon_free_target(src_target); } static void damon_test_commit_target_regions(struct kunit *test) { damon_test_commit_target_regions_for(test, (unsigned long[][2]) {{3, 8}, {8, 10}}, 2, (unsigned long[][2]) {{4, 6}}, 1, (unsigned long[][2]) {{4, 6}}, 1); damon_test_commit_target_regions_for(test, (unsigned long[][2]) {{3, 8}, {8, 10}}, 2, (unsigned long[][2]) {}, 0, (unsigned long[][2]) {{3, 8}, {8, 10}}, 2); } static void damon_test_commit_filter_for(struct kunit *test, struct damon_filter *dst, struct damon_filter *src) { damon_commit_filter(dst, src); KUNIT_EXPECT_EQ(test, dst->type, src->type); KUNIT_EXPECT_EQ(test, dst->matching, src->matching); KUNIT_EXPECT_EQ(test, dst->allow, src->allow); switch (src->type) { case DAMON_FILTER_TYPE_MEMCG: KUNIT_EXPECT_EQ(test, dst->memcg_id, src->memcg_id); break; default: break; } } static void damon_test_commit_filter(struct kunit *test) { struct damon_filter dst = { .type = DAMON_FILTER_TYPE_ANON, .matching = false, .allow = false, }; damon_test_commit_filter_for(test, &dst, &(struct damon_filter){ .type = DAMON_FILTER_TYPE_ANON, .matching = true, .allow = true, }); damon_test_commit_filter_for(test, &dst, &(struct damon_filter){ .type = DAMON_FILTER_TYPE_MEMCG, .matching = false, .allow = false, .memcg_id = 123, }); } static struct damon_ctx *damon_test_help_setup_probes(unsigned int weights[], int nr_weights) { struct damon_ctx *ctx; struct damon_probe *probe; int i; ctx = damon_new_ctx(); if (!ctx) return NULL; for (i = 0; i < nr_weights; i++) { probe = damon_new_probe(); if (!probe) { damon_destroy_ctx(ctx); return NULL; } probe->weight = weights[i]; damon_add_probe(ctx, probe); } return ctx; } static void damon_test_commit_probes_for(struct kunit *test, unsigned int dst_weights[], int nr_dst_probes, unsigned int src_weights[], int nr_src_probes) { struct damon_ctx *dst, *src; int err; struct damon_probe *dst_probe, *src_probe; dst = damon_test_help_setup_probes(dst_weights, nr_dst_probes); if (!dst) kunit_skip(test, "dst alloc fail"); src = damon_test_help_setup_probes(src_weights, nr_src_probes); if (!src) { damon_destroy_ctx(dst); kunit_skip(test, "src alloc fail"); } err = damon_commit_probes(dst, src); KUNIT_EXPECT_EQ(test, err, 0); if (err) goto out; nr_dst_probes = 0; damon_for_each_probe(dst_probe, dst) nr_dst_probes++; nr_src_probes = 0; damon_for_each_probe(src_probe, src) nr_src_probes++; KUNIT_EXPECT_EQ(test, nr_dst_probes, nr_src_probes); if (nr_dst_probes != nr_src_probes) goto out; nr_dst_probes = 0; damon_for_each_probe(dst_probe, dst) { src_probe = damon_nth_probe(nr_dst_probes, src); KUNIT_EXPECT_EQ(test, src_probe->weight, dst_probe->weight); nr_dst_probes++; } out: damon_destroy_ctx(dst); damon_destroy_ctx(src); } static void damon_test_commit_probes(struct kunit *test) { damon_test_commit_probes_for(test, (unsigned int[]){}, 0, (unsigned int[]){}, 0); damon_test_commit_probes_for(test, (unsigned int[]){}, 0, (unsigned int[]){1}, 1); damon_test_commit_probes_for(test, (unsigned int[]){}, 0, (unsigned int[]){1, 2}, 2); damon_test_commit_probes_for(test, (unsigned int[]){1}, 1, (unsigned int[]){2}, 1); damon_test_commit_probes_for(test, (unsigned int[]){1}, 1, (unsigned int[]){2, 3}, 2); damon_test_commit_probes_for(test, (unsigned int[]){2, 3}, 2, (unsigned int[]){1}, 1); damon_test_commit_probes_for(test, (unsigned int[]){2, 3}, 2, (unsigned int[]){}, 0); damon_test_commit_probes_for(test, (unsigned int[]){2}, 1, (unsigned int[]){}, 0); } static void damon_test_commit_ctx(struct kunit *test) { struct damon_ctx *src, *dst; src = damon_new_ctx(); if (!src) kunit_skip(test, "src alloc fail"); dst = damon_new_ctx(); if (!dst) { damon_destroy_ctx(src); kunit_skip(test, "dst alloc fail"); } /* Only power of two min_region_sz is allowed. */ src->min_region_sz = 4096; KUNIT_EXPECT_EQ(test, damon_commit_ctx(dst, src), 0); src->min_region_sz = 4095; KUNIT_EXPECT_EQ(test, damon_commit_ctx(dst, src), -EINVAL); src->min_region_sz = 4096; src->pause = true; KUNIT_EXPECT_EQ(test, damon_commit_ctx(dst, src), 0); KUNIT_EXPECT_TRUE(test, dst->pause); damon_destroy_ctx(src); damon_destroy_ctx(dst); } static void damon_test_commit_ctx_keeps_quota_for(struct kunit *test, unsigned long min_region_sz, int expected_err) { struct damos_access_pattern pattern = {}; struct damos_quota quota = {.sz = SZ_64K}; struct damos_watermarks wmarks = {}; struct damon_ctx *src, *dst; struct damon_target *target; struct damos *s; dst = damon_new_ctx(); if (!dst) kunit_skip(test, "dst alloc fail"); target = damon_new_target(); if (!target) { damon_destroy_ctx(dst); kunit_skip(test, "target alloc fail"); } damon_add_target(dst, target); s = damon_new_scheme(&pattern, DAMOS_STAT, 0, "a, &wmarks, NUMA_NO_NODE); if (!s) { damon_destroy_ctx(dst); kunit_skip(test, "scheme alloc fail"); } damon_add_scheme(dst, s); /* Copy the parameters before populating dst's runtime quota state. */ src = damon_new_test_ctx(dst); if (!src) { damon_destroy_ctx(dst); kunit_skip(test, "src alloc fail"); } src->min_region_sz = min_region_sz; s->quota.esz = 123; s->quota.esz_bp = 456; s->quota.total_charged_sz = 789; s->quota.total_charged_ns = 1011; s->quota.charged_sz = 12; s->quota.charged_from = 13; s->quota.charge_target_from = target; s->quota.charge_addr_from = 14; KUNIT_EXPECT_EQ(test, damon_commit_ctx(dst, src), expected_err); KUNIT_EXPECT_EQ(test, s->quota.esz, 123ul); KUNIT_EXPECT_EQ(test, s->quota.esz_bp, 456ul); KUNIT_EXPECT_EQ(test, s->quota.total_charged_sz, 789ul); KUNIT_EXPECT_EQ(test, s->quota.total_charged_ns, 1011ul); KUNIT_EXPECT_EQ(test, s->quota.charged_sz, 12ul); KUNIT_EXPECT_EQ(test, s->quota.charged_from, 13ul); KUNIT_EXPECT_PTR_EQ(test, s->quota.charge_target_from, target); KUNIT_EXPECT_EQ(test, s->quota.charge_addr_from, 14ul); damon_destroy_ctx(src); damon_destroy_ctx(dst); } static void damon_test_commit_ctx_keeps_quota(struct kunit *test) { /* Only power of two min_region_sz is allowed. */ damon_test_commit_ctx_keeps_quota_for(test, 4096, 0); damon_test_commit_ctx_keeps_quota_for(test, 4095, -EINVAL); } static void damon_test_valid_probe_params(struct kunit *test) { struct damon_ctx *ctx; struct damon_probe *probe, *probe2; ctx = damon_new_ctx(); if (!ctx) kunit_skip(test, "ctx alloc fail"); probe = damon_new_probe(); if (!probe) { damon_destroy_ctx(ctx); kunit_skip(test, "probe alloc fail"); } damon_add_probe(ctx, probe); /* Parameters are validated only if any probe weight is set. */ ctx->attrs.sample_interval = 1; ctx->attrs.aggr_interval = 1000000; KUNIT_EXPECT_TRUE(test, damon_valid_probe_params(ctx)); /* Up to U8_MAX samples per aggregation interval are allowed. */ probe->weight = 100; ctx->attrs.aggr_interval = 255; KUNIT_EXPECT_TRUE(test, damon_valid_probe_params(ctx)); /* More samples could overflow the probe_hits counters. */ ctx->attrs.aggr_interval = 256; KUNIT_EXPECT_FALSE(test, damon_valid_probe_params(ctx)); /* The largest weight whose weighted hit count fits in unsigned int. */ ctx->attrs.aggr_interval = 255; probe->weight = UINT_MAX / 255; KUNIT_EXPECT_TRUE(test, damon_valid_probe_params(ctx)); /* Any larger weight could overflow its weighted hit count. */ probe->weight = UINT_MAX / 255 + 1; KUNIT_EXPECT_FALSE(test, damon_valid_probe_params(ctx)); /* With one sample per aggregation, even the largest weight fits. */ ctx->attrs.aggr_interval = 1; probe->weight = UINT_MAX; KUNIT_EXPECT_TRUE(test, damon_valid_probe_params(ctx)); /* The sum of all probes' weighted hit counts could also overflow. */ probe2 = damon_new_probe(); if (!probe2) { damon_destroy_ctx(ctx); kunit_skip(test, "probe2 alloc fail"); } probe2->weight = 1; damon_add_probe(ctx, probe2); KUNIT_EXPECT_FALSE(test, damon_valid_probe_params(ctx)); damon_destroy_ctx(ctx); } static void damos_test_filter_out(struct kunit *test) { struct damon_target *t; struct damon_region *r, *r2; struct damos_filter *f; int err; f = damos_new_filter(DAMOS_FILTER_TYPE_ADDR, true, false); if (!f) kunit_skip(test, "filter alloc fail"); f->addr_range = (struct damon_addr_range){.start = 2, .end = 6}; t = damon_new_target(); if (!t) { damos_destroy_filter(f); kunit_skip(test, "target alloc fail"); } r = damon_new_region(3, 5); if (!r) { damos_destroy_filter(f); damon_free_target(t); kunit_skip(test, "region alloc fail"); } damon_add_region(r, t); /* region in the range */ KUNIT_EXPECT_TRUE(test, damos_filter_match(NULL, t, r, f, 1, &err)); KUNIT_EXPECT_EQ(test, damon_nr_regions(t), 1); /* region before the range */ r->ar.start = 1; r->ar.end = 2; KUNIT_EXPECT_FALSE(test, damos_filter_match(NULL, t, r, f, 1, &err)); KUNIT_EXPECT_EQ(test, damon_nr_regions(t), 1); /* region after the range */ r->ar.start = 6; r->ar.end = 8; KUNIT_EXPECT_FALSE(test, damos_filter_match(NULL, t, r, f, 1, &err)); KUNIT_EXPECT_EQ(test, damon_nr_regions(t), 1); /* region started before the range */ r->ar.start = 1; r->ar.end = 4; KUNIT_EXPECT_FALSE(test, damos_filter_match(NULL, t, r, f, 1, &err)); /* filter should have split the region */ KUNIT_EXPECT_EQ(test, r->ar.start, 1); KUNIT_EXPECT_EQ(test, r->ar.end, 2); KUNIT_EXPECT_EQ(test, damon_nr_regions(t), 2); if (damon_nr_regions(t) != 2) goto out; r2 = damon_next_region(r); KUNIT_EXPECT_EQ(test, r2->ar.start, 2); KUNIT_EXPECT_EQ(test, r2->ar.end, 4); damon_destroy_region(r2, t); /* region started in the range */ r->ar.start = 2; r->ar.end = 8; KUNIT_EXPECT_TRUE(test, damos_filter_match(NULL, t, r, f, 1, &err)); /* filter should have split the region */ KUNIT_EXPECT_EQ(test, r->ar.start, 2); KUNIT_EXPECT_EQ(test, r->ar.end, 6); KUNIT_EXPECT_EQ(test, damon_nr_regions(t), 2); if (damon_nr_regions(t) != 2) goto out; r2 = damon_next_region(r); KUNIT_EXPECT_EQ(test, r2->ar.start, 6); KUNIT_EXPECT_EQ(test, r2->ar.end, 8); damon_destroy_region(r2, t); out: damon_free_target(t); damos_free_filter(f); } static unsigned long damos_test_apply_scheme_stub(struct damon_ctx *c, struct damon_target *t, struct damon_region *r, struct damos *s, unsigned long *sz_filter_passed) { return 0; } static void damos_test_apply_scheme_filtered_sz(struct kunit *test) { struct damos_access_pattern pattern = { .min_sz_region = 0, .max_sz_region = ULONG_MAX, .min_nr_accesses = 0, .max_nr_accesses = UINT_MAX, .min_age_region = 0, .max_age_region = UINT_MAX, }; unsigned long min_sz = DAMON_MIN_REGION_SZ; struct damos_watermarks wmarks = {}; struct damos_quota quota = {}; struct damon_ctx *c; struct damon_target *t; struct damon_region *r; struct damos_filter *f; struct damos *s; c = damon_new_ctx(); if (!c) kunit_skip(test, "ctx alloc fail"); c->ops.apply_scheme = damos_test_apply_scheme_stub; s = damon_new_scheme(&pattern, DAMOS_STAT, 0, "a, &wmarks, NUMA_NO_NODE); if (!s) { damon_destroy_ctx(c); kunit_skip(test, "scheme alloc fail"); } damon_add_scheme(c, s); t = damon_new_target(); if (!t) { damon_destroy_ctx(c); kunit_skip(test, "target alloc fail"); } damon_add_target(c, t); f = damos_new_filter(DAMOS_FILTER_TYPE_ADDR, true, false); if (!f) { damon_destroy_ctx(c); kunit_skip(test, "filter alloc fail"); } f->addr_range = (struct damon_addr_range){ .start = 2 * min_sz, .end = 8 * min_sz }; damos_add_filter(s, f); damos_set_filters_default_reject(s); /* reject filter, region starting before the range */ r = damon_new_region(0, 4 * min_sz); if (!r) { damon_destroy_ctx(c); kunit_skip(test, "region alloc fail"); } damon_add_region(r, t); damos_apply_scheme(c, t, r, s); KUNIT_EXPECT_EQ(test, damon_nr_regions(t), 2); KUNIT_EXPECT_EQ(test, r->ar.end, 2 * min_sz); KUNIT_EXPECT_EQ(test, s->stat.sz_tried, 2 * min_sz); if (damon_nr_regions(t) != 2) goto out; damon_destroy_region(damon_next_region(r), t); damon_destroy_region(r, t); s->stat = (struct damos_stat){}; /* allow filter, region starting inside the range */ f->allow = true; damos_set_filters_default_reject(s); r = damon_new_region(2 * min_sz, 10 * min_sz); if (!r) { damon_destroy_ctx(c); kunit_skip(test, "region alloc fail"); } damon_add_region(r, t); damos_apply_scheme(c, t, r, s); KUNIT_EXPECT_EQ(test, damon_nr_regions(t), 2); KUNIT_EXPECT_EQ(test, r->ar.end, 8 * min_sz); KUNIT_EXPECT_EQ(test, s->stat.sz_tried, 6 * min_sz); out: damon_destroy_ctx(c); } static void damos_test_apply_scheme_filter_sz_unchanged(struct kunit *test) { struct damos_access_pattern pattern = { .min_sz_region = 0, .max_sz_region = ULONG_MAX, .min_nr_accesses = 0, .max_nr_accesses = UINT_MAX, .min_age_region = 0, .max_age_region = UINT_MAX, }; unsigned long min_sz = DAMON_MIN_REGION_SZ; struct damos_watermarks wmarks = {}; struct damos_quota quota = {}; struct damos_filter *f, *f2; struct damon_ctx *c; struct damon_target *t; struct damon_region *r; struct damos *s; c = damon_new_ctx(); if (!c) kunit_skip(test, "ctx alloc fail"); c->ops.apply_scheme = damos_test_apply_scheme_stub; s = damon_new_scheme(&pattern, DAMOS_STAT, 0, "a, &wmarks, NUMA_NO_NODE); if (!s) { damon_destroy_ctx(c); kunit_skip(test, "scheme alloc fail"); } damon_add_scheme(c, s); t = damon_new_target(); if (!t) { damon_destroy_ctx(c); kunit_skip(test, "target alloc fail"); } damon_add_target(c, t); f = damos_new_filter(DAMOS_FILTER_TYPE_ADDR, true, false); if (!f) { damon_destroy_ctx(c); kunit_skip(test, "filter alloc fail"); } f->addr_range = (struct damon_addr_range){ .start = 2 * min_sz, .end = 8 * min_sz}; damos_add_filter(s, f); damos_set_filters_default_reject(s); /* wholly inside a reject range: not counted at all */ r = damon_new_region(4 * min_sz, 6 * min_sz); if (!r) { damon_destroy_ctx(c); kunit_skip(test, "region alloc fail"); } damon_add_region(r, t); damos_apply_scheme(c, t, r, s); KUNIT_EXPECT_EQ(test, s->stat.nr_tried, 0); KUNIT_EXPECT_EQ(test, s->stat.sz_tried, 0); KUNIT_EXPECT_EQ(test, damon_nr_regions(t), 1); /* wholly inside an allow range: counted whole, not split */ f->allow = true; damos_set_filters_default_reject(s); damos_apply_scheme(c, t, r, s); KUNIT_EXPECT_EQ(test, s->stat.sz_tried, 2 * min_sz); KUNIT_EXPECT_EQ(test, damon_nr_regions(t), 1); /* two filters, each trimming: the size left after both is counted */ damon_destroy_region(r, t); s->stat = (struct damos_stat){}; f->allow = false; f->addr_range = (struct damon_addr_range){ .start = 4 * min_sz, .end = 12 * min_sz}; f2 = damos_new_filter(DAMOS_FILTER_TYPE_ADDR, true, false); if (!f2) { damon_destroy_ctx(c); kunit_skip(test, "filter alloc fail"); } f2->addr_range = (struct damon_addr_range){ .start = 2 * min_sz, .end = 3 * min_sz}; damos_add_filter(s, f2); damos_set_filters_default_reject(s); r = damon_new_region(0, 8 * min_sz); if (!r) { damon_destroy_ctx(c); kunit_skip(test, "region alloc fail"); } damon_add_region(r, t); damos_apply_scheme(c, t, r, s); KUNIT_EXPECT_EQ(test, damon_nr_regions(t), 3); KUNIT_EXPECT_EQ(test, r->ar.end, 2 * min_sz); KUNIT_EXPECT_EQ(test, s->stat.sz_tried, 2 * min_sz); /* a core filter that never splits: counted whole */ damos_destroy_filter(f2); damos_destroy_filter(f); f = damos_new_filter(DAMOS_FILTER_TYPE_TARGET, true, true); if (!f) { damon_destroy_ctx(c); kunit_skip(test, "filter alloc fail"); } f->target_idx = 0; damos_add_filter(s, f); damos_set_filters_default_reject(s); s->stat = (struct damos_stat){}; damos_apply_scheme(c, t, r, s); KUNIT_EXPECT_EQ(test, damon_nr_regions(t), 3); KUNIT_EXPECT_EQ(test, r->ar.end, 2 * min_sz); KUNIT_EXPECT_EQ(test, s->stat.sz_tried, 2 * min_sz); damon_destroy_ctx(c); } static void damos_test_apply_scheme_quota_sz(struct kunit *test) { struct damos_access_pattern pattern = { .min_sz_region = 0, .max_sz_region = ULONG_MAX, .min_nr_accesses = 0, .max_nr_accesses = UINT_MAX, .min_age_region = 0, .max_age_region = UINT_MAX, }; unsigned long min_sz = DAMON_MIN_REGION_SZ; struct damos_watermarks wmarks = {}; struct damos_quota quota = {}; struct damon_region *r, *next; struct damon_ctx *c; struct damon_target *t; struct damos *s; c = damon_new_ctx(); if (!c) kunit_skip(test, "ctx alloc fail"); s = damon_new_scheme(&pattern, DAMOS_STAT, 0, "a, &wmarks, NUMA_NO_NODE); if (!s) { damon_destroy_ctx(c); kunit_skip(test, "scheme alloc fail"); } damon_add_scheme(c, s); damos_set_filters_default_reject(s); t = damon_new_target(); if (!t) { damon_destroy_ctx(c); kunit_skip(test, "target alloc fail"); } damon_add_target(c, t); /* no apply_scheme operation: the whole region is counted */ r = damon_new_region(0, 4 * min_sz); if (!r) { damon_destroy_ctx(c); kunit_skip(test, "region alloc fail"); } damon_add_region(r, t); damos_apply_scheme(c, t, r, s); KUNIT_EXPECT_EQ(test, s->stat.sz_tried, 4 * min_sz); KUNIT_EXPECT_EQ(test, damon_nr_regions(t), 1); /* no filter and no quota: the whole region is counted */ c->ops.apply_scheme = damos_test_apply_scheme_stub; s->stat = (struct damos_stat){}; damos_apply_scheme(c, t, r, s); KUNIT_EXPECT_EQ(test, s->stat.sz_tried, 4 * min_sz); KUNIT_EXPECT_EQ(test, damon_nr_regions(t), 1); /* the quota trims the region: the trimmed size is counted */ damon_for_each_region_safe(r, next, t) damon_destroy_region(r, t); s->stat = (struct damos_stat){}; s->quota.esz = 2 * min_sz; s->quota.charged_sz = 0; r = damon_new_region(0, 8 * min_sz); if (!r) { damon_destroy_ctx(c); kunit_skip(test, "region alloc fail"); } damon_add_region(r, t); damos_apply_scheme(c, t, r, s); KUNIT_EXPECT_EQ(test, r->ar.end, 2 * min_sz); KUNIT_EXPECT_EQ(test, s->stat.sz_tried, 2 * min_sz); /* quota remainder below one region: tried, but nothing counted */ damon_for_each_region_safe(r, next, t) damon_destroy_region(r, t); s->stat = (struct damos_stat){}; s->quota.esz = 1; s->quota.charged_sz = 0; r = damon_new_region(0, 4 * min_sz); if (!r) { damon_destroy_ctx(c); kunit_skip(test, "region alloc fail"); } damon_add_region(r, t); damos_apply_scheme(c, t, r, s); KUNIT_EXPECT_EQ(test, s->stat.nr_tried, 1); KUNIT_EXPECT_EQ(test, s->stat.sz_tried, 0); KUNIT_EXPECT_EQ(test, r->ar.end, 4 * min_sz); damon_destroy_ctx(c); } static void damon_test_feed_loop_next_input(struct kunit *test) { unsigned long last_input = 900000, current_score = 200; /* * If current score is lower than the goal, which is always 10,000 * (read the comment on damon_feed_loop_next_input()'s comment), next * input should be higher than the last input. */ KUNIT_EXPECT_GT(test, damon_feed_loop_next_input(last_input, current_score), last_input); /* * If current score is higher than the goal, next input should be lower * than the last input. */ current_score = 250000000; KUNIT_EXPECT_LT(test, damon_feed_loop_next_input(last_input, current_score), last_input); /* * The next input depends on the distance between the current score and * the goal */ KUNIT_EXPECT_GT(test, damon_feed_loop_next_input(last_input, 200), damon_feed_loop_next_input(last_input, 2000)); } static void damon_test_set_filters_default_reject(struct kunit *test) { struct damos scheme; struct damos_filter *target_filter, *anon_filter; INIT_LIST_HEAD(&scheme.core_filters); INIT_LIST_HEAD(&scheme.ops_filters); damos_set_filters_default_reject(&scheme); /* * No filter is installed. Allow by default on both core and ops layer * filtering stages, since there are no filters at all. */ KUNIT_EXPECT_EQ(test, scheme.core_filters_default_reject, false); KUNIT_EXPECT_EQ(test, scheme.ops_filters_default_reject, false); target_filter = damos_new_filter(DAMOS_FILTER_TYPE_TARGET, true, true); if (!target_filter) kunit_skip(test, "filter alloc fail"); damos_add_filter(&scheme, target_filter); damos_set_filters_default_reject(&scheme); /* * A core-handled allow-filter is installed. * Reject by default on core layer filtering stage due to the last * core-layer-filter's behavior. * Allow by default on ops layer filtering stage due to the absence of * ops layer filters. */ KUNIT_EXPECT_EQ(test, scheme.core_filters_default_reject, true); KUNIT_EXPECT_EQ(test, scheme.ops_filters_default_reject, false); target_filter->allow = false; damos_set_filters_default_reject(&scheme); /* * A core-handled reject-filter is installed. * Allow by default on core layer filtering stage due to the last * core-layer-filter's behavior. * Allow by default on ops layer filtering stage due to the absence of * ops layer filters. */ KUNIT_EXPECT_EQ(test, scheme.core_filters_default_reject, false); KUNIT_EXPECT_EQ(test, scheme.ops_filters_default_reject, false); anon_filter = damos_new_filter(DAMOS_FILTER_TYPE_ANON, true, true); if (!anon_filter) { damos_free_filter(target_filter); kunit_skip(test, "anon_filter alloc fail"); } damos_add_filter(&scheme, anon_filter); damos_set_filters_default_reject(&scheme); /* * A core-handled reject-filter and ops-handled allow-filter are installed. * Allow by default on core layer filtering stage due to the existence * of the ops-handled filter. * Reject by default on ops layer filtering stage due to the last * ops-layer-filter's behavior. */ KUNIT_EXPECT_EQ(test, scheme.core_filters_default_reject, false); KUNIT_EXPECT_EQ(test, scheme.ops_filters_default_reject, true); target_filter->allow = true; damos_set_filters_default_reject(&scheme); /* * A core-handled allow-filter and ops-handled allow-filter are * installed. * Allow by default on core layer filtering stage due to the existence * of the ops-handled filter. * Reject by default on ops layer filtering stage due to the last * ops-layer-filter's behavior. */ KUNIT_EXPECT_EQ(test, scheme.core_filters_default_reject, false); KUNIT_EXPECT_EQ(test, scheme.ops_filters_default_reject, true); damos_free_filter(anon_filter); damos_free_filter(target_filter); } static void damon_test_apply_min_nr_regions_for(struct kunit *test, unsigned long sz_regions, unsigned long min_region_sz, unsigned long min_nr_regions, unsigned long max_region_sz_expect, unsigned long nr_regions_expect) { struct damon_ctx *ctx; struct damon_target *t; struct damon_region *r; unsigned long max_region_size; ctx = damon_new_ctx(); if (!ctx) kunit_skip(test, "ctx alloc fail\n"); t = damon_new_target(); if (!t) { damon_destroy_ctx(ctx); kunit_skip(test, "target alloc fail\n"); } damon_add_target(ctx, t); r = damon_new_region(0, sz_regions); if (!r) { damon_destroy_ctx(ctx); kunit_skip(test, "region alloc fail\n"); } damon_add_region(r, t); ctx->min_region_sz = min_region_sz; ctx->attrs.min_nr_regions = min_nr_regions; max_region_size = damon_apply_min_nr_regions(ctx); KUNIT_EXPECT_EQ(test, max_region_size, max_region_sz_expect); KUNIT_EXPECT_EQ(test, damon_nr_regions(t), nr_regions_expect); damon_destroy_ctx(ctx); } static void damon_test_apply_min_nr_regions(struct kunit *test) { /* common, expected setup */ damon_test_apply_min_nr_regions_for(test, 10, 1, 10, 1, 10); /* no zero size limit */ damon_test_apply_min_nr_regions_for(test, 10, 1, 15, 1, 10); /* max size should be aligned by min_region_sz */ damon_test_apply_min_nr_regions_for(test, 10, 2, 2, 6, 2); /* * when min_nr_regions and min_region_sz conflicts, min_region_sz wins. */ damon_test_apply_min_nr_regions_for(test, 10, 2, 10, 2, 5); } static void damon_test_is_last_region(struct kunit *test) { struct damon_region *r; struct damon_target *t; int i; t = damon_new_target(); if (!t) kunit_skip(test, "target alloc fail\n"); for (i = 0; i < 4; i++) { r = damon_new_region(i * 2, (i + 1) * 2); if (!r) { damon_free_target(t); kunit_skip(test, "region alloc %d fail\n", i); } damon_add_region(r, t); KUNIT_EXPECT_TRUE(test, damon_is_last_region(r, t)); } damon_free_target(t); } /* * Verify that damos_walk() rejects new requests when * walk_control_obsolete is set. * * This tests the invariant introduced by: * commit 33c3f6c2b48c ("mm/damon/core: fix damos_walk() vs kdamond_fn() exit race") */ static void damon_test_walk_control_obsolete(struct kunit *test) { struct damon_ctx *ctx; struct damos_walk_control control = {}; int ret; ctx = damon_new_ctx(); if (!ctx) kunit_skip(test, "ctx alloc fail"); /* Simulate shutdown phase */ ctx->walk_control_obsolete = true; ret = damos_walk(ctx, &control); KUNIT_EXPECT_EQ(test, ret, -ECANCELED); damon_destroy_ctx(ctx); } static void damon_test_rand(struct kunit *test) { struct damon_ctx ctx; int i; prandom_seed_state(&ctx.rnd_state, get_random_u64()); for (i = 0; i < 10000; i++) { unsigned long rnd = damon_rand(&ctx, 0, 10); KUNIT_EXPECT_GE(test, rnd, 0); KUNIT_EXPECT_LE(test, rnd, 9); } } static void damos_test_esz_goal_temporal(struct kunit *test) { struct damos_access_pattern pattern = {}; struct damos_watermarks wmarks = {}; struct damos_quota quota = { .goal_tuner = DAMOS_QUOTA_GOAL_TUNER_TEMPORAL, }; struct damos_quota_goal *goal; struct damon_ctx *ctx; struct damos *s; ctx = damon_new_ctx(); KUNIT_ASSERT_NOT_NULL(test, ctx); s = damon_new_scheme(&pattern, DAMOS_STAT, 0, "a, &wmarks, NUMA_NO_NODE); if (!s) { damon_destroy_ctx(ctx); kunit_skip(test, "scheme alloc fail"); } damon_add_scheme(ctx, s); goal = damos_new_quota_goal(DAMOS_QUOTA_USER_INPUT, false, 10000); if (!goal) { damon_destroy_ctx(ctx); kunit_skip(test, "quota goal alloc fail"); } goal->current_value = 0; damos_add_quota_goal(&s->quota, goal); /* The largest size quota the basis-point conversion can hold. */ s->quota.sz = ULONG_MAX / 10000; damos_set_effective_quota(ctx, s); KUNIT_EXPECT_EQ(test, s->quota.esz, ULONG_MAX / 10000); /* Any larger one saturates instead of wrapping. */ s->quota.sz = ULONG_MAX / 10000 + 1; damos_set_effective_quota(ctx, s); KUNIT_EXPECT_EQ(test, s->quota.esz, ULONG_MAX / 10000); s->quota.sz = ULONG_MAX; damos_set_effective_quota(ctx, s); KUNIT_EXPECT_EQ(test, s->quota.esz, ULONG_MAX / 10000); damon_destroy_ctx(ctx); } static void damon_test_last_region_quota_reset(struct kunit *test) { struct damos s = {0}; struct damon_target *t; struct damon_region *r1, *r2; unsigned long min_region_sz = 1; bool skipped; t = damon_new_target(); if (!t) kunit_skip(test, "target alloc fail"); r1 = damon_new_region(0, 100); if (!r1) { damon_free_target(t); kunit_skip(test, "region 1 alloc fail"); } damon_add_region(r1, t); r2 = damon_new_region(100, 200); if (!r2) { damon_free_target(t); kunit_skip(test, "region 2 alloc fail"); } damon_add_region(r2, t); s.quota.charge_target_from = t; s.quota.charge_addr_from = r1->ar.end; skipped = damos_skip_charged_region(t, r2, &s, min_region_sz); /* 'r2' is not processed, it should not skip */ KUNIT_EXPECT_EQ(test, skipped, false); /* 'r2' is last region, it should reset charge_{target,addr}_from */ KUNIT_EXPECT_PTR_EQ(test, s.quota.charge_target_from, NULL); KUNIT_EXPECT_EQ(test, s.quota.charge_addr_from, 0); damon_free_target(t); } static struct kunit_case damon_test_cases[] = { KUNIT_CASE(damon_test_target), KUNIT_CASE(damon_test_regions), KUNIT_CASE(damon_test_aggregate), KUNIT_CASE(damon_test_split_at), KUNIT_CASE(damon_test_merge_two), KUNIT_CASE(damon_test_merge_regions_of), KUNIT_CASE(damon_test_split_regions_of), KUNIT_CASE(damon_test_split_above_half_progresses), KUNIT_CASE(damon_test_ops_registration), 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_set_attrs), KUNIT_CASE(damon_test_mvsum), KUNIT_CASE(damon_test_nr_accesses_mvsum), KUNIT_CASE(damos_test_new_filter), KUNIT_CASE(damos_test_new_scheme_keeps_src_quota), KUNIT_CASE(damos_test_commit_quota_goal), KUNIT_CASE(damos_test_set_psi_current_val), KUNIT_CASE(damos_test_commit_quota_goals), KUNIT_CASE(damos_test_commit_quota), KUNIT_CASE(damos_test_commit_dests), KUNIT_CASE(damos_test_commit_filter), KUNIT_CASE(damos_test_commit_pageout), KUNIT_CASE(damos_test_commit_migrate_hot), KUNIT_CASE(damon_test_commit_target_regions), KUNIT_CASE(damon_test_commit_filter), KUNIT_CASE(damon_test_commit_probes), KUNIT_CASE(damon_test_commit_ctx), KUNIT_CASE(damon_test_commit_ctx_keeps_quota), KUNIT_CASE(damon_test_valid_probe_params), KUNIT_CASE(damos_test_filter_out), KUNIT_CASE(damos_test_apply_scheme_filtered_sz), KUNIT_CASE(damos_test_apply_scheme_filter_sz_unchanged), KUNIT_CASE(damos_test_apply_scheme_quota_sz), KUNIT_CASE(damon_test_feed_loop_next_input), KUNIT_CASE(damon_test_set_filters_default_reject), KUNIT_CASE(damon_test_apply_min_nr_regions), KUNIT_CASE(damon_test_is_last_region), KUNIT_CASE(damon_test_walk_control_obsolete), KUNIT_CASE(damon_test_rand), KUNIT_CASE(damos_test_esz_goal_temporal), KUNIT_CASE(damon_test_last_region_quota_reset), {}, }; static struct kunit_suite damon_test_suite = { .name = "damon", .test_cases = damon_test_cases, }; kunit_test_suite(damon_test_suite); #endif /* _DAMON_CORE_TEST_H */ #endif /* CONFIG_DAMON_KUNIT_TEST */