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