1 1.1 christos #include "test/jemalloc_test.h" 2 1.1 christos 3 1.1 christos #include "jemalloc/internal/prof_recent.h" 4 1.1 christos 5 1.1 christos /* As specified in the shell script */ 6 1.1 christos #define OPT_ALLOC_MAX 3 7 1.1 christos 8 1.1 christos /* Invariant before and after every test (when config_prof is on) */ 9 1.1 christos static void 10 1.1 christos confirm_prof_setup() { 11 1.1 christos /* Options */ 12 1.1 christos assert_true(opt_prof, "opt_prof not on"); 13 1.1 christos assert_true(opt_prof_active, "opt_prof_active not on"); 14 1.1 christos assert_zd_eq(opt_prof_recent_alloc_max, OPT_ALLOC_MAX, 15 1.1 christos "opt_prof_recent_alloc_max not set correctly"); 16 1.1 christos 17 1.1 christos /* Dynamics */ 18 1.1 christos assert_true(prof_active_state, "prof_active not on"); 19 1.1 christos assert_zd_eq(prof_recent_alloc_max_ctl_read(), OPT_ALLOC_MAX, 20 1.1 christos "prof_recent_alloc_max not set correctly"); 21 1.1 christos } 22 1.1 christos 23 1.1 christos TEST_BEGIN(test_confirm_setup) { 24 1.1 christos test_skip_if(!config_prof); 25 1.1 christos confirm_prof_setup(); 26 1.1 christos } 27 1.1 christos TEST_END 28 1.1 christos 29 1.1 christos TEST_BEGIN(test_prof_recent_off) { 30 1.1 christos test_skip_if(config_prof); 31 1.1 christos 32 1.1 christos const ssize_t past_ref = 0, future_ref = 0; 33 1.1 christos const size_t len_ref = sizeof(ssize_t); 34 1.1 christos 35 1.1 christos ssize_t past = past_ref, future = future_ref; 36 1.1 christos size_t len = len_ref; 37 1.1 christos 38 1.1 christos #define ASSERT_SHOULD_FAIL(opt, a, b, c, d) do { \ 39 1.1 christos assert_d_eq(mallctl("experimental.prof_recent." opt, a, b, c, \ 40 1.1 christos d), ENOENT, "Should return ENOENT when config_prof is off");\ 41 1.1 christos assert_zd_eq(past, past_ref, "output was touched"); \ 42 1.1 christos assert_zu_eq(len, len_ref, "output length was touched"); \ 43 1.1 christos assert_zd_eq(future, future_ref, "input was touched"); \ 44 1.1 christos } while (0) 45 1.1 christos 46 1.1 christos ASSERT_SHOULD_FAIL("alloc_max", NULL, NULL, NULL, 0); 47 1.1 christos ASSERT_SHOULD_FAIL("alloc_max", &past, &len, NULL, 0); 48 1.1 christos ASSERT_SHOULD_FAIL("alloc_max", NULL, NULL, &future, len); 49 1.1 christos ASSERT_SHOULD_FAIL("alloc_max", &past, &len, &future, len); 50 1.1 christos 51 1.1 christos #undef ASSERT_SHOULD_FAIL 52 1.1 christos } 53 1.1 christos TEST_END 54 1.1 christos 55 1.1 christos TEST_BEGIN(test_prof_recent_on) { 56 1.1 christos test_skip_if(!config_prof); 57 1.1 christos 58 1.1 christos ssize_t past, future; 59 1.1 christos size_t len = sizeof(ssize_t); 60 1.1 christos 61 1.1 christos confirm_prof_setup(); 62 1.1 christos 63 1.1 christos assert_d_eq(mallctl("experimental.prof_recent.alloc_max", 64 1.1 christos NULL, NULL, NULL, 0), 0, "no-op mallctl should be allowed"); 65 1.1 christos confirm_prof_setup(); 66 1.1 christos 67 1.1 christos assert_d_eq(mallctl("experimental.prof_recent.alloc_max", 68 1.1 christos &past, &len, NULL, 0), 0, "Read error"); 69 1.1 christos expect_zd_eq(past, OPT_ALLOC_MAX, "Wrong read result"); 70 1.1 christos future = OPT_ALLOC_MAX + 1; 71 1.1 christos assert_d_eq(mallctl("experimental.prof_recent.alloc_max", 72 1.1 christos NULL, NULL, &future, len), 0, "Write error"); 73 1.1 christos future = -1; 74 1.1 christos assert_d_eq(mallctl("experimental.prof_recent.alloc_max", 75 1.1 christos &past, &len, &future, len), 0, "Read/write error"); 76 1.1 christos expect_zd_eq(past, OPT_ALLOC_MAX + 1, "Wrong read result"); 77 1.1 christos future = -2; 78 1.1 christos assert_d_eq(mallctl("experimental.prof_recent.alloc_max", 79 1.1 christos &past, &len, &future, len), EINVAL, 80 1.1 christos "Invalid write should return EINVAL"); 81 1.1 christos expect_zd_eq(past, OPT_ALLOC_MAX + 1, 82 1.1 christos "Output should not be touched given invalid write"); 83 1.1 christos future = OPT_ALLOC_MAX; 84 1.1 christos assert_d_eq(mallctl("experimental.prof_recent.alloc_max", 85 1.1 christos &past, &len, &future, len), 0, "Read/write error"); 86 1.1 christos expect_zd_eq(past, -1, "Wrong read result"); 87 1.1 christos future = OPT_ALLOC_MAX + 2; 88 1.1 christos assert_d_eq(mallctl("experimental.prof_recent.alloc_max", 89 1.1 christos &past, &len, &future, len * 2), EINVAL, 90 1.1 christos "Invalid write should return EINVAL"); 91 1.1 christos expect_zd_eq(past, -1, 92 1.1 christos "Output should not be touched given invalid write"); 93 1.1 christos 94 1.1 christos confirm_prof_setup(); 95 1.1 christos } 96 1.1 christos TEST_END 97 1.1 christos 98 1.1 christos /* Reproducible sequence of request sizes */ 99 1.1 christos #define NTH_REQ_SIZE(n) ((n) * 97 + 101) 100 1.1 christos 101 1.1 christos static void 102 1.1 christos confirm_malloc(void *p) { 103 1.1 christos assert_ptr_not_null(p, "malloc failed unexpectedly"); 104 1.1 christos edata_t *e = emap_edata_lookup(TSDN_NULL, &arena_emap_global, p); 105 1.1 christos assert_ptr_not_null(e, "NULL edata for living pointer"); 106 1.1 christos prof_recent_t *n = edata_prof_recent_alloc_get_no_lock_test(e); 107 1.1 christos assert_ptr_not_null(n, "Record in edata should not be NULL"); 108 1.1 christos expect_ptr_not_null(n->alloc_tctx, 109 1.1 christos "alloc_tctx in record should not be NULL"); 110 1.1 christos expect_ptr_eq(e, prof_recent_alloc_edata_get_no_lock_test(n), 111 1.1 christos "edata pointer in record is not correct"); 112 1.1 christos expect_ptr_null(n->dalloc_tctx, "dalloc_tctx in record should be NULL"); 113 1.1 christos } 114 1.1 christos 115 1.1 christos static void 116 1.1 christos confirm_record_size(prof_recent_t *n, unsigned kth) { 117 1.1 christos expect_zu_eq(n->size, NTH_REQ_SIZE(kth), 118 1.1 christos "Recorded allocation size is wrong"); 119 1.1 christos } 120 1.1 christos 121 1.1 christos static void 122 1.1 christos confirm_record_living(prof_recent_t *n) { 123 1.1 christos expect_ptr_not_null(n->alloc_tctx, 124 1.1 christos "alloc_tctx in record should not be NULL"); 125 1.1 christos edata_t *edata = prof_recent_alloc_edata_get_no_lock_test(n); 126 1.1 christos assert_ptr_not_null(edata, 127 1.1 christos "Recorded edata should not be NULL for living pointer"); 128 1.1 christos expect_ptr_eq(n, edata_prof_recent_alloc_get_no_lock_test(edata), 129 1.1 christos "Record in edata is not correct"); 130 1.1 christos expect_ptr_null(n->dalloc_tctx, "dalloc_tctx in record should be NULL"); 131 1.1 christos } 132 1.1 christos 133 1.1 christos static void 134 1.1 christos confirm_record_released(prof_recent_t *n) { 135 1.1 christos expect_ptr_not_null(n->alloc_tctx, 136 1.1 christos "alloc_tctx in record should not be NULL"); 137 1.1 christos expect_ptr_null(prof_recent_alloc_edata_get_no_lock_test(n), 138 1.1 christos "Recorded edata should be NULL for released pointer"); 139 1.1 christos expect_ptr_not_null(n->dalloc_tctx, 140 1.1 christos "dalloc_tctx in record should not be NULL for released pointer"); 141 1.1 christos } 142 1.1 christos 143 1.1 christos TEST_BEGIN(test_prof_recent_alloc) { 144 1.1 christos test_skip_if(!config_prof); 145 1.1 christos 146 1.1 christos bool b; 147 1.1 christos unsigned i, c; 148 1.1 christos size_t req_size; 149 1.1 christos void *p; 150 1.1 christos prof_recent_t *n; 151 1.1 christos ssize_t future; 152 1.1 christos 153 1.1 christos confirm_prof_setup(); 154 1.1 christos 155 1.1 christos /* 156 1.1 christos * First batch of 2 * OPT_ALLOC_MAX allocations. After the 157 1.1 christos * (OPT_ALLOC_MAX - 1)'th allocation the recorded allocations should 158 1.1 christos * always be the last OPT_ALLOC_MAX allocations coming from here. 159 1.1 christos */ 160 1.1 christos for (i = 0; i < 2 * OPT_ALLOC_MAX; ++i) { 161 1.1 christos req_size = NTH_REQ_SIZE(i); 162 1.1 christos p = malloc(req_size); 163 1.1 christos confirm_malloc(p); 164 1.1 christos if (i < OPT_ALLOC_MAX - 1) { 165 1.1 christos assert_false(ql_empty(&prof_recent_alloc_list), 166 1.1 christos "Empty recent allocation"); 167 1.1 christos free(p); 168 1.1 christos /* 169 1.1 christos * The recorded allocations may still include some 170 1.1 christos * other allocations before the test run started, 171 1.1 christos * so keep allocating without checking anything. 172 1.1 christos */ 173 1.1 christos continue; 174 1.1 christos } 175 1.1 christos c = 0; 176 1.1 christos ql_foreach(n, &prof_recent_alloc_list, link) { 177 1.1 christos ++c; 178 1.1 christos confirm_record_size(n, i + c - OPT_ALLOC_MAX); 179 1.1 christos if (c == OPT_ALLOC_MAX) { 180 1.1 christos confirm_record_living(n); 181 1.1 christos } else { 182 1.1 christos confirm_record_released(n); 183 1.1 christos } 184 1.1 christos } 185 1.1 christos assert_u_eq(c, OPT_ALLOC_MAX, 186 1.1 christos "Incorrect total number of allocations"); 187 1.1 christos free(p); 188 1.1 christos } 189 1.1 christos 190 1.1 christos confirm_prof_setup(); 191 1.1 christos 192 1.1 christos b = false; 193 1.1 christos assert_d_eq(mallctl("prof.active", NULL, NULL, &b, sizeof(bool)), 0, 194 1.1 christos "mallctl for turning off prof_active failed"); 195 1.1 christos 196 1.1 christos /* 197 1.1 christos * Second batch of OPT_ALLOC_MAX allocations. Since prof_active is 198 1.1 christos * turned off, this batch shouldn't be recorded. 199 1.1 christos */ 200 1.1 christos for (; i < 3 * OPT_ALLOC_MAX; ++i) { 201 1.1 christos req_size = NTH_REQ_SIZE(i); 202 1.1 christos p = malloc(req_size); 203 1.1 christos assert_ptr_not_null(p, "malloc failed unexpectedly"); 204 1.1 christos c = 0; 205 1.1 christos ql_foreach(n, &prof_recent_alloc_list, link) { 206 1.1 christos confirm_record_size(n, c + OPT_ALLOC_MAX); 207 1.1 christos confirm_record_released(n); 208 1.1 christos ++c; 209 1.1 christos } 210 1.1 christos assert_u_eq(c, OPT_ALLOC_MAX, 211 1.1 christos "Incorrect total number of allocations"); 212 1.1 christos free(p); 213 1.1 christos } 214 1.1 christos 215 1.1 christos b = true; 216 1.1 christos assert_d_eq(mallctl("prof.active", NULL, NULL, &b, sizeof(bool)), 0, 217 1.1 christos "mallctl for turning on prof_active failed"); 218 1.1 christos 219 1.1 christos confirm_prof_setup(); 220 1.1 christos 221 1.1 christos /* 222 1.1 christos * Third batch of OPT_ALLOC_MAX allocations. Since prof_active is 223 1.1 christos * turned back on, they should be recorded, and in the list of recorded 224 1.1 christos * allocations they should follow the first batch rather than the 225 1.1 christos * second batch. 226 1.1 christos */ 227 1.1 christos for (; i < 4 * OPT_ALLOC_MAX; ++i) { 228 1.1 christos req_size = NTH_REQ_SIZE(i); 229 1.1 christos p = malloc(req_size); 230 1.1 christos confirm_malloc(p); 231 1.1 christos c = 0; 232 1.1 christos ql_foreach(n, &prof_recent_alloc_list, link) { 233 1.1 christos ++c; 234 1.1 christos confirm_record_size(n, 235 1.1 christos /* Is the allocation from the third batch? */ 236 1.1 christos i + c - OPT_ALLOC_MAX >= 3 * OPT_ALLOC_MAX ? 237 1.1 christos /* If yes, then it's just recorded. */ 238 1.1 christos i + c - OPT_ALLOC_MAX : 239 1.1 christos /* 240 1.1 christos * Otherwise, it should come from the first batch 241 1.1 christos * instead of the second batch. 242 1.1 christos */ 243 1.1 christos i + c - 2 * OPT_ALLOC_MAX); 244 1.1 christos if (c == OPT_ALLOC_MAX) { 245 1.1 christos confirm_record_living(n); 246 1.1 christos } else { 247 1.1 christos confirm_record_released(n); 248 1.1 christos } 249 1.1 christos } 250 1.1 christos assert_u_eq(c, OPT_ALLOC_MAX, 251 1.1 christos "Incorrect total number of allocations"); 252 1.1 christos free(p); 253 1.1 christos } 254 1.1 christos 255 1.1 christos /* Increasing the limit shouldn't alter the list of records. */ 256 1.1 christos future = OPT_ALLOC_MAX + 1; 257 1.1 christos assert_d_eq(mallctl("experimental.prof_recent.alloc_max", 258 1.1 christos NULL, NULL, &future, sizeof(ssize_t)), 0, "Write error"); 259 1.1 christos c = 0; 260 1.1 christos ql_foreach(n, &prof_recent_alloc_list, link) { 261 1.1 christos confirm_record_size(n, c + 3 * OPT_ALLOC_MAX); 262 1.1 christos confirm_record_released(n); 263 1.1 christos ++c; 264 1.1 christos } 265 1.1 christos assert_u_eq(c, OPT_ALLOC_MAX, 266 1.1 christos "Incorrect total number of allocations"); 267 1.1 christos 268 1.1 christos /* 269 1.1 christos * Decreasing the limit shouldn't alter the list of records as long as 270 1.1 christos * the new limit is still no less than the length of the list. 271 1.1 christos */ 272 1.1 christos future = OPT_ALLOC_MAX; 273 1.1 christos assert_d_eq(mallctl("experimental.prof_recent.alloc_max", 274 1.1 christos NULL, NULL, &future, sizeof(ssize_t)), 0, "Write error"); 275 1.1 christos c = 0; 276 1.1 christos ql_foreach(n, &prof_recent_alloc_list, link) { 277 1.1 christos confirm_record_size(n, c + 3 * OPT_ALLOC_MAX); 278 1.1 christos confirm_record_released(n); 279 1.1 christos ++c; 280 1.1 christos } 281 1.1 christos assert_u_eq(c, OPT_ALLOC_MAX, 282 1.1 christos "Incorrect total number of allocations"); 283 1.1 christos 284 1.1 christos /* 285 1.1 christos * Decreasing the limit should shorten the list of records if the new 286 1.1 christos * limit is less than the length of the list. 287 1.1 christos */ 288 1.1 christos future = OPT_ALLOC_MAX - 1; 289 1.1 christos assert_d_eq(mallctl("experimental.prof_recent.alloc_max", 290 1.1 christos NULL, NULL, &future, sizeof(ssize_t)), 0, "Write error"); 291 1.1 christos c = 0; 292 1.1 christos ql_foreach(n, &prof_recent_alloc_list, link) { 293 1.1 christos ++c; 294 1.1 christos confirm_record_size(n, c + 3 * OPT_ALLOC_MAX); 295 1.1 christos confirm_record_released(n); 296 1.1 christos } 297 1.1 christos assert_u_eq(c, OPT_ALLOC_MAX - 1, 298 1.1 christos "Incorrect total number of allocations"); 299 1.1 christos 300 1.1 christos /* Setting to unlimited shouldn't alter the list of records. */ 301 1.1 christos future = -1; 302 1.1 christos assert_d_eq(mallctl("experimental.prof_recent.alloc_max", 303 1.1 christos NULL, NULL, &future, sizeof(ssize_t)), 0, "Write error"); 304 1.1 christos c = 0; 305 1.1 christos ql_foreach(n, &prof_recent_alloc_list, link) { 306 1.1 christos ++c; 307 1.1 christos confirm_record_size(n, c + 3 * OPT_ALLOC_MAX); 308 1.1 christos confirm_record_released(n); 309 1.1 christos } 310 1.1 christos assert_u_eq(c, OPT_ALLOC_MAX - 1, 311 1.1 christos "Incorrect total number of allocations"); 312 1.1 christos 313 1.1 christos /* Downshift to only one record. */ 314 1.1 christos future = 1; 315 1.1 christos assert_d_eq(mallctl("experimental.prof_recent.alloc_max", 316 1.1 christos NULL, NULL, &future, sizeof(ssize_t)), 0, "Write error"); 317 1.1 christos assert_false(ql_empty(&prof_recent_alloc_list), "Recent list is empty"); 318 1.1 christos n = ql_first(&prof_recent_alloc_list); 319 1.1 christos confirm_record_size(n, 4 * OPT_ALLOC_MAX - 1); 320 1.1 christos confirm_record_released(n); 321 1.1 christos n = ql_next(&prof_recent_alloc_list, n, link); 322 1.1 christos assert_ptr_null(n, "Recent list should only contain one record"); 323 1.1 christos 324 1.1 christos /* Completely turn off. */ 325 1.1 christos future = 0; 326 1.1 christos assert_d_eq(mallctl("experimental.prof_recent.alloc_max", 327 1.1 christos NULL, NULL, &future, sizeof(ssize_t)), 0, "Write error"); 328 1.1 christos assert_true(ql_empty(&prof_recent_alloc_list), 329 1.1 christos "Recent list should be empty"); 330 1.1 christos 331 1.1 christos /* Restore the settings. */ 332 1.1 christos future = OPT_ALLOC_MAX; 333 1.1 christos assert_d_eq(mallctl("experimental.prof_recent.alloc_max", 334 1.1 christos NULL, NULL, &future, sizeof(ssize_t)), 0, "Write error"); 335 1.1 christos assert_true(ql_empty(&prof_recent_alloc_list), 336 1.1 christos "Recent list should be empty"); 337 1.1 christos 338 1.1 christos confirm_prof_setup(); 339 1.1 christos } 340 1.1 christos TEST_END 341 1.1 christos 342 1.1 christos #undef NTH_REQ_SIZE 343 1.1 christos 344 1.1 christos #define DUMP_OUT_SIZE 4096 345 1.1 christos static char dump_out[DUMP_OUT_SIZE]; 346 1.1 christos static size_t dump_out_len = 0; 347 1.1 christos 348 1.1 christos static void 349 1.1 christos test_dump_write_cb(void *not_used, const char *str) { 350 1.1 christos size_t len = strlen(str); 351 1.1 christos assert(dump_out_len + len < DUMP_OUT_SIZE); 352 1.1 christos memcpy(dump_out + dump_out_len, str, len + 1); 353 1.1 christos dump_out_len += len; 354 1.1 christos } 355 1.1 christos 356 1.1 christos static void 357 1.1 christos call_dump() { 358 1.1 christos static void *in[2] = {test_dump_write_cb, NULL}; 359 1.1 christos dump_out_len = 0; 360 1.1 christos assert_d_eq(mallctl("experimental.prof_recent.alloc_dump", 361 1.1 christos NULL, NULL, in, sizeof(in)), 0, "Dump mallctl raised error"); 362 1.1 christos } 363 1.1 christos 364 1.1 christos typedef struct { 365 1.1 christos size_t size; 366 1.1 christos size_t usize; 367 1.1 christos bool released; 368 1.1 christos } confirm_record_t; 369 1.1 christos 370 1.1 christos #define DUMP_ERROR "Dump output is wrong" 371 1.1 christos 372 1.1 christos static void 373 1.1 christos confirm_record(const char *template, const confirm_record_t *records, 374 1.1 christos const size_t n_records) { 375 1.1 christos static const char *types[2] = {"alloc", "dalloc"}; 376 1.1 christos static char buf[64]; 377 1.1 christos 378 1.1 christos /* 379 1.1 christos * The template string would be in the form of: 380 1.1 christos * "{...,\"recent_alloc\":[]}", 381 1.1 christos * and dump_out would be in the form of: 382 1.1 christos * "{...,\"recent_alloc\":[...]}". 383 1.1 christos * Using "- 2" serves to cut right before the ending "]}". 384 1.1 christos */ 385 1.1 christos assert_d_eq(memcmp(dump_out, template, strlen(template) - 2), 0, 386 1.1 christos DUMP_ERROR); 387 1.1 christos assert_d_eq(memcmp(dump_out + strlen(dump_out) - 2, 388 1.1 christos template + strlen(template) - 2, 2), 0, DUMP_ERROR); 389 1.1 christos 390 1.1 christos const char *start = dump_out + strlen(template) - 2; 391 1.1 christos const char *end = dump_out + strlen(dump_out) - 2; 392 1.1 christos const confirm_record_t *record; 393 1.1 christos for (record = records; record < records + n_records; ++record) { 394 1.1 christos 395 1.1 christos #define ASSERT_CHAR(c) do { \ 396 1.1 christos assert_true(start < end, DUMP_ERROR); \ 397 1.1 christos assert_c_eq(*start++, c, DUMP_ERROR); \ 398 1.1 christos } while (0) 399 1.1 christos 400 1.1 christos #define ASSERT_STR(s) do { \ 401 1.1 christos const size_t len = strlen(s); \ 402 1.1 christos assert_true(start + len <= end, DUMP_ERROR); \ 403 1.1 christos assert_d_eq(memcmp(start, s, len), 0, DUMP_ERROR); \ 404 1.1 christos start += len; \ 405 1.1 christos } while (0) 406 1.1 christos 407 1.1 christos #define ASSERT_FORMATTED_STR(s, ...) do { \ 408 1.1 christos malloc_snprintf(buf, sizeof(buf), s, __VA_ARGS__); \ 409 1.1 christos ASSERT_STR(buf); \ 410 1.1 christos } while (0) 411 1.1 christos 412 1.1 christos if (record != records) { 413 1.1 christos ASSERT_CHAR(','); 414 1.1 christos } 415 1.1 christos 416 1.1 christos ASSERT_CHAR('{'); 417 1.1 christos 418 1.1 christos ASSERT_STR("\"size\""); 419 1.1 christos ASSERT_CHAR(':'); 420 1.1 christos ASSERT_FORMATTED_STR("%zu", record->size); 421 1.1 christos ASSERT_CHAR(','); 422 1.1 christos 423 1.1 christos ASSERT_STR("\"usize\""); 424 1.1 christos ASSERT_CHAR(':'); 425 1.1 christos ASSERT_FORMATTED_STR("%zu", record->usize); 426 1.1 christos ASSERT_CHAR(','); 427 1.1 christos 428 1.1 christos ASSERT_STR("\"released\""); 429 1.1 christos ASSERT_CHAR(':'); 430 1.1 christos ASSERT_STR(record->released ? "true" : "false"); 431 1.1 christos ASSERT_CHAR(','); 432 1.1 christos 433 1.1 christos const char **type = types; 434 1.1 christos while (true) { 435 1.1 christos ASSERT_FORMATTED_STR("\"%s_thread_uid\"", *type); 436 1.1 christos ASSERT_CHAR(':'); 437 1.1 christos while (isdigit(*start)) { 438 1.1 christos ++start; 439 1.1 christos } 440 1.1 christos ASSERT_CHAR(','); 441 1.1 christos 442 1.1 christos if (opt_prof_sys_thread_name) { 443 1.1 christos ASSERT_FORMATTED_STR("\"%s_thread_name\"", 444 1.1 christos *type); 445 1.1 christos ASSERT_CHAR(':'); 446 1.1 christos ASSERT_CHAR('"'); 447 1.1 christos while (*start != '"') { 448 1.1 christos ++start; 449 1.1 christos } 450 1.1 christos ASSERT_CHAR('"'); 451 1.1 christos ASSERT_CHAR(','); 452 1.1 christos } 453 1.1 christos 454 1.1 christos ASSERT_FORMATTED_STR("\"%s_time\"", *type); 455 1.1 christos ASSERT_CHAR(':'); 456 1.1 christos while (isdigit(*start)) { 457 1.1 christos ++start; 458 1.1 christos } 459 1.1 christos ASSERT_CHAR(','); 460 1.1 christos 461 1.1 christos ASSERT_FORMATTED_STR("\"%s_trace\"", *type); 462 1.1 christos ASSERT_CHAR(':'); 463 1.1 christos ASSERT_CHAR('['); 464 1.1 christos while (isdigit(*start) || *start == 'x' || 465 1.1 christos (*start >= 'a' && *start <= 'f') || 466 1.1 christos *start == '\"' || *start == ',') { 467 1.1 christos ++start; 468 1.1 christos } 469 1.1 christos ASSERT_CHAR(']'); 470 1.1 christos 471 1.1 christos if (strcmp(*type, "dalloc") == 0) { 472 1.1 christos break; 473 1.1 christos } 474 1.1 christos 475 1.1 christos assert(strcmp(*type, "alloc") == 0); 476 1.1 christos if (!record->released) { 477 1.1 christos break; 478 1.1 christos } 479 1.1 christos 480 1.1 christos ASSERT_CHAR(','); 481 1.1 christos ++type; 482 1.1 christos } 483 1.1 christos 484 1.1 christos ASSERT_CHAR('}'); 485 1.1 christos 486 1.1 christos #undef ASSERT_FORMATTED_STR 487 1.1 christos #undef ASSERT_STR 488 1.1 christos #undef ASSERT_CHAR 489 1.1 christos 490 1.1 christos } 491 1.1 christos assert_ptr_eq(record, records + n_records, DUMP_ERROR); 492 1.1 christos assert_ptr_eq(start, end, DUMP_ERROR); 493 1.1 christos } 494 1.1 christos 495 1.1 christos TEST_BEGIN(test_prof_recent_alloc_dump) { 496 1.1 christos test_skip_if(!config_prof); 497 1.1 christos 498 1.1 christos confirm_prof_setup(); 499 1.1 christos 500 1.1 christos ssize_t future; 501 1.1 christos void *p, *q; 502 1.1 christos confirm_record_t records[2]; 503 1.1 christos 504 1.1 christos assert_zu_eq(lg_prof_sample, (size_t)0, 505 1.1 christos "lg_prof_sample not set correctly"); 506 1.1 christos 507 1.1 christos future = 0; 508 1.1 christos assert_d_eq(mallctl("experimental.prof_recent.alloc_max", 509 1.1 christos NULL, NULL, &future, sizeof(ssize_t)), 0, "Write error"); 510 1.1 christos call_dump(); 511 1.1 christos expect_str_eq(dump_out, "{\"sample_interval\":1," 512 1.1 christos "\"recent_alloc_max\":0,\"recent_alloc\":[]}", DUMP_ERROR); 513 1.1 christos 514 1.1 christos future = 2; 515 1.1 christos assert_d_eq(mallctl("experimental.prof_recent.alloc_max", 516 1.1 christos NULL, NULL, &future, sizeof(ssize_t)), 0, "Write error"); 517 1.1 christos call_dump(); 518 1.1 christos const char *template = "{\"sample_interval\":1," 519 1.1 christos "\"recent_alloc_max\":2,\"recent_alloc\":[]}"; 520 1.1 christos expect_str_eq(dump_out, template, DUMP_ERROR); 521 1.1 christos 522 1.1 christos p = malloc(7); 523 1.1 christos call_dump(); 524 1.1 christos records[0].size = 7; 525 1.1 christos records[0].usize = sz_s2u(7); 526 1.1 christos records[0].released = false; 527 1.1 christos confirm_record(template, records, 1); 528 1.1 christos 529 1.1 christos q = mallocx(17, MALLOCX_ALIGN(128)); 530 1.1 christos call_dump(); 531 1.1 christos records[1].size = 17; 532 1.1 christos records[1].usize = sz_sa2u(17, 128); 533 1.1 christos records[1].released = false; 534 1.1 christos confirm_record(template, records, 2); 535 1.1 christos 536 1.1 christos free(q); 537 1.1 christos call_dump(); 538 1.1 christos records[1].released = true; 539 1.1 christos confirm_record(template, records, 2); 540 1.1 christos 541 1.1 christos free(p); 542 1.1 christos call_dump(); 543 1.1 christos records[0].released = true; 544 1.1 christos confirm_record(template, records, 2); 545 1.1 christos 546 1.1 christos future = OPT_ALLOC_MAX; 547 1.1 christos assert_d_eq(mallctl("experimental.prof_recent.alloc_max", 548 1.1 christos NULL, NULL, &future, sizeof(ssize_t)), 0, "Write error"); 549 1.1 christos confirm_prof_setup(); 550 1.1 christos } 551 1.1 christos TEST_END 552 1.1 christos 553 1.1 christos #undef DUMP_ERROR 554 1.1 christos #undef DUMP_OUT_SIZE 555 1.1 christos 556 1.1 christos #define N_THREADS 8 557 1.1 christos #define N_PTRS 512 558 1.1 christos #define N_CTLS 8 559 1.1 christos #define N_ITERS 2048 560 1.1 christos #define STRESS_ALLOC_MAX 4096 561 1.1 christos 562 1.1 christos typedef struct { 563 1.1 christos thd_t thd; 564 1.1 christos size_t id; 565 1.1 christos void *ptrs[N_PTRS]; 566 1.1 christos size_t count; 567 1.1 christos } thd_data_t; 568 1.1 christos 569 1.1 christos static thd_data_t thd_data[N_THREADS]; 570 1.1 christos static ssize_t test_max; 571 1.1 christos 572 1.1 christos static void 573 1.1 christos test_write_cb(void *cbopaque, const char *str) { 574 1.1 christos sleep_ns(1000 * 1000); 575 1.1 christos } 576 1.1 christos 577 1.1 christos static void * 578 1.1 christos f_thread(void *arg) { 579 1.1 christos const size_t thd_id = *(size_t *)arg; 580 1.1 christos thd_data_t *data_p = thd_data + thd_id; 581 1.1 christos assert(data_p->id == thd_id); 582 1.1 christos data_p->count = 0; 583 1.1 christos uint64_t rand = (uint64_t)thd_id; 584 1.1 christos tsd_t *tsd = tsd_fetch(); 585 1.1 christos assert(test_max > 1); 586 1.1 christos ssize_t last_max = -1; 587 1.1 christos for (int i = 0; i < N_ITERS; i++) { 588 1.1 christos rand = prng_range_u64(&rand, N_PTRS + N_CTLS * 5); 589 1.1 christos assert(data_p->count <= N_PTRS); 590 1.1 christos if (rand < data_p->count) { 591 1.1 christos assert(data_p->count > 0); 592 1.1 christos if (rand != data_p->count - 1) { 593 1.1 christos assert(data_p->count > 1); 594 1.1 christos void *temp = data_p->ptrs[rand]; 595 1.1 christos data_p->ptrs[rand] = 596 1.1 christos data_p->ptrs[data_p->count - 1]; 597 1.1 christos data_p->ptrs[data_p->count - 1] = temp; 598 1.1 christos } 599 1.1 christos free(data_p->ptrs[--data_p->count]); 600 1.1 christos } else if (rand < N_PTRS) { 601 1.1 christos assert(data_p->count < N_PTRS); 602 1.1 christos data_p->ptrs[data_p->count++] = malloc(1); 603 1.1 christos } else if (rand % 5 == 0) { 604 1.1 christos prof_recent_alloc_dump(tsd, test_write_cb, NULL); 605 1.1 christos } else if (rand % 5 == 1) { 606 1.1 christos last_max = prof_recent_alloc_max_ctl_read(); 607 1.1 christos } else if (rand % 5 == 2) { 608 1.1 christos last_max = 609 1.1 christos prof_recent_alloc_max_ctl_write(tsd, test_max * 2); 610 1.1 christos } else if (rand % 5 == 3) { 611 1.1 christos last_max = 612 1.1 christos prof_recent_alloc_max_ctl_write(tsd, test_max); 613 1.1 christos } else { 614 1.1 christos assert(rand % 5 == 4); 615 1.1 christos last_max = 616 1.1 christos prof_recent_alloc_max_ctl_write(tsd, test_max / 2); 617 1.1 christos } 618 1.1 christos assert_zd_ge(last_max, -1, "Illegal last-N max"); 619 1.1 christos } 620 1.1 christos 621 1.1 christos while (data_p->count > 0) { 622 1.1 christos free(data_p->ptrs[--data_p->count]); 623 1.1 christos } 624 1.1 christos 625 1.1 christos return NULL; 626 1.1 christos } 627 1.1 christos 628 1.1 christos TEST_BEGIN(test_prof_recent_stress) { 629 1.1 christos test_skip_if(!config_prof); 630 1.1 christos 631 1.1 christos confirm_prof_setup(); 632 1.1 christos 633 1.1 christos test_max = OPT_ALLOC_MAX; 634 1.1 christos for (size_t i = 0; i < N_THREADS; i++) { 635 1.1 christos thd_data_t *data_p = thd_data + i; 636 1.1 christos data_p->id = i; 637 1.1 christos thd_create(&data_p->thd, &f_thread, &data_p->id); 638 1.1 christos } 639 1.1 christos for (size_t i = 0; i < N_THREADS; i++) { 640 1.1 christos thd_data_t *data_p = thd_data + i; 641 1.1 christos thd_join(data_p->thd, NULL); 642 1.1 christos } 643 1.1 christos 644 1.1 christos test_max = STRESS_ALLOC_MAX; 645 1.1 christos assert_d_eq(mallctl("experimental.prof_recent.alloc_max", 646 1.1 christos NULL, NULL, &test_max, sizeof(ssize_t)), 0, "Write error"); 647 1.1 christos for (size_t i = 0; i < N_THREADS; i++) { 648 1.1 christos thd_data_t *data_p = thd_data + i; 649 1.1 christos data_p->id = i; 650 1.1 christos thd_create(&data_p->thd, &f_thread, &data_p->id); 651 1.1 christos } 652 1.1 christos for (size_t i = 0; i < N_THREADS; i++) { 653 1.1 christos thd_data_t *data_p = thd_data + i; 654 1.1 christos thd_join(data_p->thd, NULL); 655 1.1 christos } 656 1.1 christos 657 1.1 christos test_max = OPT_ALLOC_MAX; 658 1.1 christos assert_d_eq(mallctl("experimental.prof_recent.alloc_max", 659 1.1 christos NULL, NULL, &test_max, sizeof(ssize_t)), 0, "Write error"); 660 1.1 christos confirm_prof_setup(); 661 1.1 christos } 662 1.1 christos TEST_END 663 1.1 christos 664 1.1 christos #undef STRESS_ALLOC_MAX 665 1.1 christos #undef N_ITERS 666 1.1 christos #undef N_PTRS 667 1.1 christos #undef N_THREADS 668 1.1 christos 669 1.1 christos int 670 1.1 christos main(void) { 671 1.1 christos return test( 672 1.1 christos test_confirm_setup, 673 1.1 christos test_prof_recent_off, 674 1.1 christos test_prof_recent_on, 675 1.1 christos test_prof_recent_alloc, 676 1.1 christos test_prof_recent_alloc_dump, 677 1.1 christos test_prof_recent_stress); 678 1.1 christos } 679