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