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