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