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