prof_recent.c revision 1.1 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