xref: /netbsd-src/external/bsd/jemalloc/dist/test/unit/prof_recent.c (revision 7bdf38e5b7a28439665f2fdeff81e36913eef7dd)
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