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