xref: /netbsd-src/external/bsd/jemalloc/dist/test/unit/decay.c (revision 7bdf38e5b7a28439665f2fdeff81e36913eef7dd)
1a0698ed9Schristos #include "test/jemalloc_test.h"
2a0698ed9Schristos 
3*7bdf38e5Schristos #include "jemalloc/internal/decay.h"
4a0698ed9Schristos 
5*7bdf38e5Schristos TEST_BEGIN(test_decay_init) {
6*7bdf38e5Schristos 	decay_t decay;
7*7bdf38e5Schristos 	memset(&decay, 0, sizeof(decay));
8a0698ed9Schristos 
9*7bdf38e5Schristos 	nstime_t curtime;
10*7bdf38e5Schristos 	nstime_init(&curtime, 0);
11a0698ed9Schristos 
12*7bdf38e5Schristos 	ssize_t decay_ms = 1000;
13*7bdf38e5Schristos 	assert_true(decay_ms_valid(decay_ms), "");
14*7bdf38e5Schristos 
15*7bdf38e5Schristos 	expect_false(decay_init(&decay, &curtime, decay_ms),
16*7bdf38e5Schristos 	    "Failed to initialize decay");
17*7bdf38e5Schristos 	expect_zd_eq(decay_ms_read(&decay), decay_ms,
18*7bdf38e5Schristos 	    "Decay_ms was initialized incorrectly");
19*7bdf38e5Schristos 	expect_u64_ne(decay_epoch_duration_ns(&decay), 0,
20*7bdf38e5Schristos 	    "Epoch duration was initialized incorrectly");
21a0698ed9Schristos }
22*7bdf38e5Schristos TEST_END
23a0698ed9Schristos 
24*7bdf38e5Schristos TEST_BEGIN(test_decay_ms_valid) {
25*7bdf38e5Schristos 	expect_false(decay_ms_valid(-7),
26*7bdf38e5Schristos 	    "Misclassified negative decay as valid");
27*7bdf38e5Schristos 	expect_true(decay_ms_valid(-1),
28*7bdf38e5Schristos 	    "Misclassified -1 (never decay) as invalid decay");
29*7bdf38e5Schristos 	expect_true(decay_ms_valid(8943),
30*7bdf38e5Schristos 	    "Misclassified valid decay");
31*7bdf38e5Schristos 	if (SSIZE_MAX > NSTIME_SEC_MAX) {
32*7bdf38e5Schristos 		expect_false(
33*7bdf38e5Schristos 		    decay_ms_valid((ssize_t)(NSTIME_SEC_MAX * KQU(1000) + 39)),
34*7bdf38e5Schristos 		    "Misclassified too large decay");
35a0698ed9Schristos 	}
36a0698ed9Schristos }
37a0698ed9Schristos TEST_END
38a0698ed9Schristos 
39*7bdf38e5Schristos TEST_BEGIN(test_decay_npages_purge_in) {
40*7bdf38e5Schristos 	decay_t decay;
41*7bdf38e5Schristos 	memset(&decay, 0, sizeof(decay));
42*7bdf38e5Schristos 
43*7bdf38e5Schristos 	nstime_t curtime;
44*7bdf38e5Schristos 	nstime_init(&curtime, 0);
45*7bdf38e5Schristos 
46*7bdf38e5Schristos 	uint64_t decay_ms = 1000;
47*7bdf38e5Schristos 	nstime_t decay_nstime;
48*7bdf38e5Schristos 	nstime_init(&decay_nstime, decay_ms * 1000 * 1000);
49*7bdf38e5Schristos 	expect_false(decay_init(&decay, &curtime, (ssize_t)decay_ms),
50*7bdf38e5Schristos 	    "Failed to initialize decay");
51*7bdf38e5Schristos 
52*7bdf38e5Schristos 	size_t new_pages = 100;
53*7bdf38e5Schristos 
54*7bdf38e5Schristos 	nstime_t time;
55*7bdf38e5Schristos 	nstime_copy(&time, &decay_nstime);
56*7bdf38e5Schristos 	expect_u64_eq(decay_npages_purge_in(&decay, &time, new_pages),
57*7bdf38e5Schristos 	    new_pages, "Not all pages are expected to decay in decay_ms");
58a0698ed9Schristos 
59a0698ed9Schristos 	nstime_init(&time, 0);
60*7bdf38e5Schristos 	expect_u64_eq(decay_npages_purge_in(&decay, &time, new_pages), 0,
61*7bdf38e5Schristos 	    "More than zero pages are expected to instantly decay");
62a0698ed9Schristos 
63*7bdf38e5Schristos 	nstime_copy(&time, &decay_nstime);
64*7bdf38e5Schristos 	nstime_idivide(&time, 2);
65*7bdf38e5Schristos 	expect_u64_eq(decay_npages_purge_in(&decay, &time, new_pages),
66*7bdf38e5Schristos 	    new_pages / 2, "Not half of pages decay in half the decay period");
67*7bdf38e5Schristos }
68*7bdf38e5Schristos TEST_END
69a0698ed9Schristos 
70*7bdf38e5Schristos TEST_BEGIN(test_decay_maybe_advance_epoch) {
71*7bdf38e5Schristos 	decay_t decay;
72*7bdf38e5Schristos 	memset(&decay, 0, sizeof(decay));
73*7bdf38e5Schristos 
74*7bdf38e5Schristos 	nstime_t curtime;
75*7bdf38e5Schristos 	nstime_init(&curtime, 0);
76*7bdf38e5Schristos 
77*7bdf38e5Schristos 	uint64_t decay_ms = 1000;
78*7bdf38e5Schristos 
79*7bdf38e5Schristos 	bool err = decay_init(&decay, &curtime, (ssize_t)decay_ms);
80*7bdf38e5Schristos 	expect_false(err, "");
81*7bdf38e5Schristos 
82*7bdf38e5Schristos 	bool advanced;
83*7bdf38e5Schristos 	advanced = decay_maybe_advance_epoch(&decay, &curtime, 0);
84*7bdf38e5Schristos 	expect_false(advanced, "Epoch advanced while time didn't");
85*7bdf38e5Schristos 
86*7bdf38e5Schristos 	nstime_t interval;
87*7bdf38e5Schristos 	nstime_init(&interval, decay_epoch_duration_ns(&decay));
88*7bdf38e5Schristos 
89*7bdf38e5Schristos 	nstime_add(&curtime, &interval);
90*7bdf38e5Schristos 	advanced = decay_maybe_advance_epoch(&decay, &curtime, 0);
91*7bdf38e5Schristos 	expect_false(advanced, "Epoch advanced after first interval");
92*7bdf38e5Schristos 
93*7bdf38e5Schristos 	nstime_add(&curtime, &interval);
94*7bdf38e5Schristos 	advanced = decay_maybe_advance_epoch(&decay, &curtime, 0);
95*7bdf38e5Schristos 	expect_true(advanced, "Epoch didn't advance after two intervals");
96*7bdf38e5Schristos }
97*7bdf38e5Schristos TEST_END
98*7bdf38e5Schristos 
99*7bdf38e5Schristos TEST_BEGIN(test_decay_empty) {
100*7bdf38e5Schristos 	/* If we never have any decaying pages, npages_limit should be 0. */
101*7bdf38e5Schristos 	decay_t decay;
102*7bdf38e5Schristos 	memset(&decay, 0, sizeof(decay));
103*7bdf38e5Schristos 
104*7bdf38e5Schristos 	nstime_t curtime;
105*7bdf38e5Schristos 	nstime_init(&curtime, 0);
106*7bdf38e5Schristos 
107*7bdf38e5Schristos 	uint64_t decay_ms = 1000;
108*7bdf38e5Schristos 	uint64_t decay_ns = decay_ms * 1000 * 1000;
109*7bdf38e5Schristos 
110*7bdf38e5Schristos 	bool err = decay_init(&decay, &curtime, (ssize_t)decay_ms);
111*7bdf38e5Schristos 	assert_false(err, "");
112*7bdf38e5Schristos 
113*7bdf38e5Schristos 	uint64_t time_between_calls = decay_epoch_duration_ns(&decay) / 5;
114*7bdf38e5Schristos 	int nepochs = 0;
115*7bdf38e5Schristos 	for (uint64_t i = 0; i < decay_ns / time_between_calls * 10; i++) {
116*7bdf38e5Schristos 		size_t dirty_pages = 0;
117*7bdf38e5Schristos 		nstime_init(&curtime, i * time_between_calls);
118*7bdf38e5Schristos 		bool epoch_advanced = decay_maybe_advance_epoch(&decay,
119*7bdf38e5Schristos 		    &curtime, dirty_pages);
120*7bdf38e5Schristos 		if (epoch_advanced) {
121*7bdf38e5Schristos 			nepochs++;
122*7bdf38e5Schristos 			expect_zu_eq(decay_npages_limit_get(&decay), 0,
123*7bdf38e5Schristos 			    "Unexpectedly increased npages_limit");
124*7bdf38e5Schristos 		}
125*7bdf38e5Schristos 	}
126*7bdf38e5Schristos 	expect_d_gt(nepochs, 0, "Epochs never advanced");
127*7bdf38e5Schristos }
128*7bdf38e5Schristos TEST_END
129a0698ed9Schristos 
130a0698ed9Schristos /*
131*7bdf38e5Schristos  * Verify that npages_limit correctly decays as the time goes.
132*7bdf38e5Schristos  *
133*7bdf38e5Schristos  * During first 'nepoch_init' epochs, add new dirty pages.
134*7bdf38e5Schristos  * After that, let them decay and verify npages_limit decreases.
135*7bdf38e5Schristos  * Then proceed with another 'nepoch_init' epochs and check that
136*7bdf38e5Schristos  * all dirty pages are flushed out of backlog, bringing npages_limit
137*7bdf38e5Schristos  * down to zero.
138a0698ed9Schristos  */
139*7bdf38e5Schristos TEST_BEGIN(test_decay) {
140*7bdf38e5Schristos 	const uint64_t nepoch_init = 10;
141a0698ed9Schristos 
142*7bdf38e5Schristos 	decay_t decay;
143*7bdf38e5Schristos 	memset(&decay, 0, sizeof(decay));
144a0698ed9Schristos 
145*7bdf38e5Schristos 	nstime_t curtime;
146*7bdf38e5Schristos 	nstime_init(&curtime, 0);
147*7bdf38e5Schristos 
148*7bdf38e5Schristos 	uint64_t decay_ms = 1000;
149*7bdf38e5Schristos 	uint64_t decay_ns = decay_ms * 1000 * 1000;
150*7bdf38e5Schristos 
151*7bdf38e5Schristos 	bool err = decay_init(&decay, &curtime, (ssize_t)decay_ms);
152*7bdf38e5Schristos 	assert_false(err, "");
153*7bdf38e5Schristos 
154*7bdf38e5Schristos 	expect_zu_eq(decay_npages_limit_get(&decay), 0,
155*7bdf38e5Schristos 	    "Empty decay returned nonzero npages_limit");
156*7bdf38e5Schristos 
157*7bdf38e5Schristos 	nstime_t epochtime;
158*7bdf38e5Schristos 	nstime_init(&epochtime, decay_epoch_duration_ns(&decay));
159*7bdf38e5Schristos 
160*7bdf38e5Schristos 	const size_t dirty_pages_per_epoch = 1000;
161*7bdf38e5Schristos 	size_t dirty_pages = 0;
162*7bdf38e5Schristos 	uint64_t epoch_ns = decay_epoch_duration_ns(&decay);
163*7bdf38e5Schristos 	bool epoch_advanced = false;
164*7bdf38e5Schristos 
165*7bdf38e5Schristos 	/* Populate backlog with some dirty pages */
166*7bdf38e5Schristos 	for (uint64_t i = 0; i < nepoch_init; i++) {
167*7bdf38e5Schristos 		nstime_add(&curtime, &epochtime);
168*7bdf38e5Schristos 		dirty_pages += dirty_pages_per_epoch;
169*7bdf38e5Schristos 		epoch_advanced |= decay_maybe_advance_epoch(&decay, &curtime,
170*7bdf38e5Schristos 		    dirty_pages);
171a0698ed9Schristos 	}
172*7bdf38e5Schristos 	expect_true(epoch_advanced, "Epoch never advanced");
173*7bdf38e5Schristos 
174*7bdf38e5Schristos 	size_t npages_limit = decay_npages_limit_get(&decay);
175*7bdf38e5Schristos 	expect_zu_gt(npages_limit, 0, "npages_limit is incorrectly equal "
176*7bdf38e5Schristos 	    "to zero after dirty pages have been added");
177*7bdf38e5Schristos 
178*7bdf38e5Schristos 	/* Keep dirty pages unchanged and verify that npages_limit decreases */
179*7bdf38e5Schristos 	for (uint64_t i = nepoch_init; i * epoch_ns < decay_ns; ++i) {
180*7bdf38e5Schristos 		nstime_add(&curtime, &epochtime);
181*7bdf38e5Schristos 		epoch_advanced = decay_maybe_advance_epoch(&decay, &curtime,
182*7bdf38e5Schristos 				    dirty_pages);
183*7bdf38e5Schristos 		if (epoch_advanced) {
184*7bdf38e5Schristos 			size_t npages_limit_new = decay_npages_limit_get(&decay);
185*7bdf38e5Schristos 			expect_zu_lt(npages_limit_new, npages_limit,
186*7bdf38e5Schristos 			    "napges_limit failed to decay");
187*7bdf38e5Schristos 
188*7bdf38e5Schristos 			npages_limit = npages_limit_new;
189*7bdf38e5Schristos 		}
190a0698ed9Schristos 	}
191a0698ed9Schristos 
192*7bdf38e5Schristos 	expect_zu_gt(npages_limit, 0, "npages_limit decayed to zero earlier "
193*7bdf38e5Schristos 	    "than decay_ms since last dirty page was added");
194a0698ed9Schristos 
195*7bdf38e5Schristos 	/* Completely push all dirty pages out of the backlog */
196*7bdf38e5Schristos 	epoch_advanced = false;
197*7bdf38e5Schristos 	for (uint64_t i = 0; i < nepoch_init; i++) {
198*7bdf38e5Schristos 		nstime_add(&curtime, &epochtime);
199*7bdf38e5Schristos 		epoch_advanced |= decay_maybe_advance_epoch(&decay, &curtime,
200*7bdf38e5Schristos 		    dirty_pages);
201a0698ed9Schristos 	}
202*7bdf38e5Schristos 	expect_true(epoch_advanced, "Epoch never advanced");
203a0698ed9Schristos 
204*7bdf38e5Schristos 	npages_limit = decay_npages_limit_get(&decay);
205*7bdf38e5Schristos 	expect_zu_eq(npages_limit, 0, "npages_limit didn't decay to 0 after "
206*7bdf38e5Schristos 	    "decay_ms since last bump in dirty pages");
207a0698ed9Schristos }
208a0698ed9Schristos TEST_END
209a0698ed9Schristos 
210*7bdf38e5Schristos TEST_BEGIN(test_decay_ns_until_purge) {
211*7bdf38e5Schristos 	const uint64_t nepoch_init = 10;
212a0698ed9Schristos 
213*7bdf38e5Schristos 	decay_t decay;
214*7bdf38e5Schristos 	memset(&decay, 0, sizeof(decay));
215a0698ed9Schristos 
216*7bdf38e5Schristos 	nstime_t curtime;
217*7bdf38e5Schristos 	nstime_init(&curtime, 0);
218a0698ed9Schristos 
219*7bdf38e5Schristos 	uint64_t decay_ms = 1000;
220*7bdf38e5Schristos 	uint64_t decay_ns = decay_ms * 1000 * 1000;
221a0698ed9Schristos 
222*7bdf38e5Schristos 	bool err = decay_init(&decay, &curtime, (ssize_t)decay_ms);
223*7bdf38e5Schristos 	assert_false(err, "");
224a0698ed9Schristos 
225*7bdf38e5Schristos 	nstime_t epochtime;
226*7bdf38e5Schristos 	nstime_init(&epochtime, decay_epoch_duration_ns(&decay));
227*7bdf38e5Schristos 
228*7bdf38e5Schristos 	uint64_t ns_until_purge_empty = decay_ns_until_purge(&decay, 0, 0);
229*7bdf38e5Schristos 	expect_u64_eq(ns_until_purge_empty, DECAY_UNBOUNDED_TIME_TO_PURGE,
230*7bdf38e5Schristos 	    "Failed to return unbounded wait time for zero threshold");
231*7bdf38e5Schristos 
232*7bdf38e5Schristos 	const size_t dirty_pages_per_epoch = 1000;
233*7bdf38e5Schristos 	size_t dirty_pages = 0;
234*7bdf38e5Schristos 	bool epoch_advanced = false;
235*7bdf38e5Schristos 	for (uint64_t i = 0; i < nepoch_init; i++) {
236*7bdf38e5Schristos 		nstime_add(&curtime, &epochtime);
237*7bdf38e5Schristos 		dirty_pages += dirty_pages_per_epoch;
238*7bdf38e5Schristos 		epoch_advanced |= decay_maybe_advance_epoch(&decay, &curtime,
239*7bdf38e5Schristos 		    dirty_pages);
240a0698ed9Schristos 	}
241*7bdf38e5Schristos 	expect_true(epoch_advanced, "Epoch never advanced");
242a0698ed9Schristos 
243*7bdf38e5Schristos 	uint64_t ns_until_purge_all = decay_ns_until_purge(&decay,
244*7bdf38e5Schristos 	    dirty_pages, dirty_pages);
245*7bdf38e5Schristos 	expect_u64_ge(ns_until_purge_all, decay_ns,
246*7bdf38e5Schristos 	    "Incorrectly calculated time to purge all pages");
247a0698ed9Schristos 
248*7bdf38e5Schristos 	uint64_t ns_until_purge_none = decay_ns_until_purge(&decay,
249*7bdf38e5Schristos 	    dirty_pages, 0);
250*7bdf38e5Schristos 	expect_u64_eq(ns_until_purge_none, decay_epoch_duration_ns(&decay) * 2,
251*7bdf38e5Schristos 	    "Incorrectly calculated time to purge 0 pages");
252a0698ed9Schristos 
253*7bdf38e5Schristos 	uint64_t npages_threshold = dirty_pages / 2;
254*7bdf38e5Schristos 	uint64_t ns_until_purge_half = decay_ns_until_purge(&decay,
255*7bdf38e5Schristos 	    dirty_pages, npages_threshold);
256a0698ed9Schristos 
257*7bdf38e5Schristos 	nstime_t waittime;
258*7bdf38e5Schristos 	nstime_init(&waittime, ns_until_purge_half);
259*7bdf38e5Schristos 	nstime_add(&curtime, &waittime);
260a0698ed9Schristos 
261*7bdf38e5Schristos 	decay_maybe_advance_epoch(&decay, &curtime, dirty_pages);
262*7bdf38e5Schristos 	size_t npages_limit = decay_npages_limit_get(&decay);
263*7bdf38e5Schristos 	expect_zu_lt(npages_limit, dirty_pages,
264*7bdf38e5Schristos 	    "npages_limit failed to decrease after waiting");
265*7bdf38e5Schristos 	size_t expected = dirty_pages - npages_limit;
266*7bdf38e5Schristos 	int deviation = abs((int)expected - (int)(npages_threshold));
267*7bdf38e5Schristos 	expect_d_lt(deviation, (int)(npages_threshold / 2),
268*7bdf38e5Schristos 	    "After waiting, number of pages is out of the expected interval "
269*7bdf38e5Schristos 	    "[0.5 * npages_threshold .. 1.5 * npages_threshold]");
270a0698ed9Schristos }
271a0698ed9Schristos TEST_END
272a0698ed9Schristos 
273a0698ed9Schristos int
274a0698ed9Schristos main(void) {
275a0698ed9Schristos 	return test(
276*7bdf38e5Schristos 	    test_decay_init,
277*7bdf38e5Schristos 	    test_decay_ms_valid,
278*7bdf38e5Schristos 	    test_decay_npages_purge_in,
279*7bdf38e5Schristos 	    test_decay_maybe_advance_epoch,
280*7bdf38e5Schristos 	    test_decay_empty,
281*7bdf38e5Schristos 	    test_decay,
282*7bdf38e5Schristos 	    test_decay_ns_until_purge);
283a0698ed9Schristos }
284