Home | History | Annotate | Line # | Download | only in unit
      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