xref: /netbsd-src/external/bsd/jemalloc/dist/test/unit/sec.c (revision f8cf1a9151c7af1cb0bd8b09c13c66bca599c027)
1 #include "test/jemalloc_test.h"
2 
3 #include "jemalloc/internal/sec.h"
4 
5 typedef struct pai_test_allocator_s pai_test_allocator_t;
6 struct pai_test_allocator_s {
7 	pai_t pai;
8 	bool alloc_fail;
9 	size_t alloc_count;
10 	size_t alloc_batch_count;
11 	size_t dalloc_count;
12 	size_t dalloc_batch_count;
13 	/*
14 	 * We use a simple bump allocator as the implementation.  This isn't
15 	 * *really* correct, since we may allow expansion into a subsequent
16 	 * allocation, but it's not like the SEC is really examining the
17 	 * pointers it gets back; this is mostly just helpful for debugging.
18 	 */
19 	uintptr_t next_ptr;
20 	size_t expand_count;
21 	bool expand_return_value;
22 	size_t shrink_count;
23 	bool shrink_return_value;
24 };
25 
26 static void
27 test_sec_init(sec_t *sec, pai_t *fallback, size_t nshards, size_t max_alloc,
28     size_t max_bytes) {
29 	sec_opts_t opts;
30 	opts.nshards = 1;
31 	opts.max_alloc = max_alloc;
32 	opts.max_bytes = max_bytes;
33 	/*
34 	 * Just choose reasonable defaults for these; most tests don't care so
35 	 * long as they're something reasonable.
36 	 */
37 	opts.bytes_after_flush = max_bytes / 2;
38 	opts.batch_fill_extra = 4;
39 
40 	/*
41 	 * We end up leaking this base, but that's fine; this test is
42 	 * short-running, and SECs are arena-scoped in reality.
43 	 */
44 	base_t *base = base_new(TSDN_NULL, /* ind */ 123,
45 	    &ehooks_default_extent_hooks, /* metadata_use_hooks */ true);
46 
47 	bool err = sec_init(TSDN_NULL, sec, base, fallback, &opts);
48 	assert_false(err, "Unexpected initialization failure");
49 	assert_u_ge(sec->npsizes, 0, "Zero size classes allowed for caching");
50 }
51 
52 static inline edata_t *
53 pai_test_allocator_alloc(tsdn_t *tsdn, pai_t *self, size_t size,
54     size_t alignment, bool zero, bool guarded, bool frequent_reuse,
55     bool *deferred_work_generated) {
56 	assert(!guarded);
57 	pai_test_allocator_t *ta = (pai_test_allocator_t *)self;
58 	if (ta->alloc_fail) {
59 		return NULL;
60 	}
61 	edata_t *edata = malloc(sizeof(edata_t));
62 	assert_ptr_not_null(edata, "");
63 	ta->next_ptr += alignment - 1;
64 	edata_init(edata, /* arena_ind */ 0,
65 	    (void *)(ta->next_ptr & ~(alignment - 1)), size,
66 	    /* slab */ false,
67 	    /* szind */ 0, /* sn */ 1, extent_state_active, /* zero */ zero,
68 	    /* comitted */ true, /* ranged */ false, EXTENT_NOT_HEAD);
69 	ta->next_ptr += size;
70 	ta->alloc_count++;
71 	return edata;
72 }
73 
74 static inline size_t
75 pai_test_allocator_alloc_batch(tsdn_t *tsdn, pai_t *self, size_t size,
76     size_t nallocs, edata_list_active_t *results,
77     bool *deferred_work_generated) {
78 	pai_test_allocator_t *ta = (pai_test_allocator_t *)self;
79 	if (ta->alloc_fail) {
80 		return 0;
81 	}
82 	for (size_t i = 0; i < nallocs; i++) {
83 		edata_t *edata = malloc(sizeof(edata_t));
84 		assert_ptr_not_null(edata, "");
85 		edata_init(edata, /* arena_ind */ 0,
86 		    (void *)ta->next_ptr, size,
87 		    /* slab */ false, /* szind */ 0, /* sn */ 1,
88 		    extent_state_active, /* zero */ false, /* comitted */ true,
89 		    /* ranged */ false, EXTENT_NOT_HEAD);
90 		ta->next_ptr += size;
91 		ta->alloc_batch_count++;
92 		edata_list_active_append(results, edata);
93 	}
94 	return nallocs;
95 }
96 
97 static bool
98 pai_test_allocator_expand(tsdn_t *tsdn, pai_t *self, edata_t *edata,
99     size_t old_size, size_t new_size, bool zero,
100     bool *deferred_work_generated) {
101 	pai_test_allocator_t *ta = (pai_test_allocator_t *)self;
102 	ta->expand_count++;
103 	return ta->expand_return_value;
104 }
105 
106 static bool
107 pai_test_allocator_shrink(tsdn_t *tsdn, pai_t *self, edata_t *edata,
108     size_t old_size, size_t new_size, bool *deferred_work_generated) {
109 	pai_test_allocator_t *ta = (pai_test_allocator_t *)self;
110 	ta->shrink_count++;
111 	return ta->shrink_return_value;
112 }
113 
114 static void
115 pai_test_allocator_dalloc(tsdn_t *tsdn, pai_t *self, edata_t *edata,
116     bool *deferred_work_generated) {
117 	pai_test_allocator_t *ta = (pai_test_allocator_t *)self;
118 	ta->dalloc_count++;
119 	free(edata);
120 }
121 
122 static void
123 pai_test_allocator_dalloc_batch(tsdn_t *tsdn, pai_t *self,
124     edata_list_active_t *list, bool *deferred_work_generated) {
125 	pai_test_allocator_t *ta = (pai_test_allocator_t *)self;
126 
127 	edata_t *edata;
128 	while ((edata = edata_list_active_first(list)) != NULL) {
129 		edata_list_active_remove(list, edata);
130 		ta->dalloc_batch_count++;
131 		free(edata);
132 	}
133 }
134 
135 static inline void
136 pai_test_allocator_init(pai_test_allocator_t *ta) {
137 	ta->alloc_fail = false;
138 	ta->alloc_count = 0;
139 	ta->alloc_batch_count = 0;
140 	ta->dalloc_count = 0;
141 	ta->dalloc_batch_count = 0;
142 	/* Just don't start the edata at 0. */
143 	ta->next_ptr = 10 * PAGE;
144 	ta->expand_count = 0;
145 	ta->expand_return_value = false;
146 	ta->shrink_count = 0;
147 	ta->shrink_return_value = false;
148 	ta->pai.alloc = &pai_test_allocator_alloc;
149 	ta->pai.alloc_batch = &pai_test_allocator_alloc_batch;
150 	ta->pai.expand = &pai_test_allocator_expand;
151 	ta->pai.shrink = &pai_test_allocator_shrink;
152 	ta->pai.dalloc = &pai_test_allocator_dalloc;
153 	ta->pai.dalloc_batch = &pai_test_allocator_dalloc_batch;
154 }
155 
156 TEST_BEGIN(test_reuse) {
157 	pai_test_allocator_t ta;
158 	pai_test_allocator_init(&ta);
159 	sec_t sec;
160 	/*
161 	 * We can't use the "real" tsd, since we malloc within the test
162 	 * allocator hooks; we'd get lock inversion crashes.  Eventually, we
163 	 * should have a way to mock tsds, but for now just don't do any
164 	 * lock-order checking.
165 	 */
166 	tsdn_t *tsdn = TSDN_NULL;
167 	/*
168 	 * 11 allocs apiece of 1-PAGE and 2-PAGE objects means that we should be
169 	 * able to get to 33 pages in the cache before triggering a flush.  We
170 	 * set the flush liimt to twice this amount, to avoid accidentally
171 	 * triggering a flush caused by the batch-allocation down the cache fill
172 	 * pathway disrupting ordering.
173 	 */
174 	enum { NALLOCS = 11 };
175 	edata_t *one_page[NALLOCS];
176 	edata_t *two_page[NALLOCS];
177 	bool deferred_work_generated = false;
178 	test_sec_init(&sec, &ta.pai, /* nshards */ 1, /* max_alloc */ 2 * PAGE,
179 	    /* max_bytes */ 2 * (NALLOCS * PAGE + NALLOCS * 2 * PAGE));
180 	for (int i = 0; i < NALLOCS; i++) {
181 		one_page[i] = pai_alloc(tsdn, &sec.pai, PAGE, PAGE,
182 		    /* zero */ false, /* guarded */ false, /* frequent_reuse */
183 		    false, &deferred_work_generated);
184 		expect_ptr_not_null(one_page[i], "Unexpected alloc failure");
185 		two_page[i] = pai_alloc(tsdn, &sec.pai, 2 * PAGE, PAGE,
186 		    /* zero */ false, /* guarded */ false, /* frequent_reuse */
187 		    false, &deferred_work_generated);
188 		expect_ptr_not_null(one_page[i], "Unexpected alloc failure");
189 	}
190 	expect_zu_eq(0, ta.alloc_count, "Should be using batch allocs");
191 	size_t max_allocs = ta.alloc_count + ta.alloc_batch_count;
192 	expect_zu_le(2 * NALLOCS, max_allocs,
193 	    "Incorrect number of allocations");
194 	expect_zu_eq(0, ta.dalloc_count,
195 	    "Incorrect number of allocations");
196 	/*
197 	 * Free in a different order than we allocated, to make sure free-list
198 	 * separation works correctly.
199 	 */
200 	for (int i = NALLOCS - 1; i >= 0; i--) {
201 		pai_dalloc(tsdn, &sec.pai, one_page[i],
202 		    &deferred_work_generated);
203 	}
204 	for (int i = NALLOCS - 1; i >= 0; i--) {
205 		pai_dalloc(tsdn, &sec.pai, two_page[i],
206 		    &deferred_work_generated);
207 	}
208 	expect_zu_eq(max_allocs, ta.alloc_count + ta.alloc_batch_count,
209 	    "Incorrect number of allocations");
210 	expect_zu_eq(0, ta.dalloc_count,
211 	    "Incorrect number of allocations");
212 	/*
213 	 * Check that the n'th most recent deallocated extent is returned for
214 	 * the n'th alloc request of a given size.
215 	 */
216 	for (int i = 0; i < NALLOCS; i++) {
217 		edata_t *alloc1 = pai_alloc(tsdn, &sec.pai, PAGE, PAGE,
218 		    /* zero */ false, /* guarded */ false, /* frequent_reuse */
219 		    false, &deferred_work_generated);
220 		edata_t *alloc2 = pai_alloc(tsdn, &sec.pai, 2 * PAGE, PAGE,
221 		    /* zero */ false, /* guarded */ false, /* frequent_reuse */
222 		    false, &deferred_work_generated);
223 		expect_ptr_eq(one_page[i], alloc1,
224 		    "Got unexpected allocation");
225 		expect_ptr_eq(two_page[i], alloc2,
226 		    "Got unexpected allocation");
227 	}
228 	expect_zu_eq(max_allocs, ta.alloc_count + ta.alloc_batch_count,
229 	    "Incorrect number of allocations");
230 	expect_zu_eq(0, ta.dalloc_count,
231 	    "Incorrect number of allocations");
232 }
233 TEST_END
234 
235 
236 TEST_BEGIN(test_auto_flush) {
237 	pai_test_allocator_t ta;
238 	pai_test_allocator_init(&ta);
239 	sec_t sec;
240 	/* See the note above -- we can't use the real tsd. */
241 	tsdn_t *tsdn = TSDN_NULL;
242 	/*
243 	 * 10-allocs apiece of 1-PAGE and 2-PAGE objects means that we should be
244 	 * able to get to 30 pages in the cache before triggering a flush.  The
245 	 * choice of NALLOCS here is chosen to match the batch allocation
246 	 * default (4 extra + 1 == 5; so 10 allocations leaves the cache exactly
247 	 * empty, even in the presence of batch allocation on fill).
248 	 * Eventually, once our allocation batching strategies become smarter,
249 	 * this should change.
250 	 */
251 	enum { NALLOCS = 10 };
252 	edata_t *extra_alloc;
253 	edata_t *allocs[NALLOCS];
254 	bool deferred_work_generated = false;
255 	test_sec_init(&sec, &ta.pai, /* nshards */ 1, /* max_alloc */ PAGE,
256 	    /* max_bytes */ NALLOCS * PAGE);
257 	for (int i = 0; i < NALLOCS; i++) {
258 		allocs[i] = pai_alloc(tsdn, &sec.pai, PAGE, PAGE,
259 		    /* zero */ false, /* guarded */ false, /* frequent_reuse */
260 		    false, &deferred_work_generated);
261 		expect_ptr_not_null(allocs[i], "Unexpected alloc failure");
262 	}
263 	extra_alloc = pai_alloc(tsdn, &sec.pai, PAGE, PAGE, /* zero */ false,
264 	    /* guarded */ false, /* frequent_reuse */ false,
265 	    &deferred_work_generated);
266 	expect_ptr_not_null(extra_alloc, "Unexpected alloc failure");
267 	size_t max_allocs = ta.alloc_count + ta.alloc_batch_count;
268 	expect_zu_le(NALLOCS + 1, max_allocs,
269 	    "Incorrect number of allocations");
270 	expect_zu_eq(0, ta.dalloc_count,
271 	    "Incorrect number of allocations");
272 	/* Free until the SEC is full, but should not have flushed yet. */
273 	for (int i = 0; i < NALLOCS; i++) {
274 		pai_dalloc(tsdn, &sec.pai, allocs[i], &deferred_work_generated);
275 	}
276 	expect_zu_le(NALLOCS + 1, max_allocs,
277 	    "Incorrect number of allocations");
278 	expect_zu_eq(0, ta.dalloc_count,
279 	    "Incorrect number of allocations");
280 	/*
281 	 * Free the extra allocation; this should trigger a flush.  The internal
282 	 * flushing logic is allowed to get complicated; for now, we rely on our
283 	 * whitebox knowledge of the fact that the SEC flushes bins in their
284 	 * entirety when it decides to do so, and it has only one bin active
285 	 * right now.
286 	 */
287 	pai_dalloc(tsdn, &sec.pai, extra_alloc, &deferred_work_generated);
288 	expect_zu_eq(max_allocs, ta.alloc_count + ta.alloc_batch_count,
289 	    "Incorrect number of allocations");
290 	expect_zu_eq(0, ta.dalloc_count,
291 	    "Incorrect number of (non-batch) deallocations");
292 	expect_zu_eq(NALLOCS + 1, ta.dalloc_batch_count,
293 	    "Incorrect number of batch deallocations");
294 }
295 TEST_END
296 
297 /*
298  * A disable and a flush are *almost* equivalent; the only difference is what
299  * happens afterwards; disabling disallows all future caching as well.
300  */
301 static void
302 do_disable_flush_test(bool is_disable) {
303 	pai_test_allocator_t ta;
304 	pai_test_allocator_init(&ta);
305 	sec_t sec;
306 	/* See the note above -- we can't use the real tsd. */
307 	tsdn_t *tsdn = TSDN_NULL;
308 
309 	enum { NALLOCS = 11 };
310 	edata_t *allocs[NALLOCS];
311 	bool deferred_work_generated = false;
312 	test_sec_init(&sec, &ta.pai, /* nshards */ 1, /* max_alloc */ PAGE,
313 	    /* max_bytes */ NALLOCS * PAGE);
314 	for (int i = 0; i < NALLOCS; i++) {
315 		allocs[i] = pai_alloc(tsdn, &sec.pai, PAGE, PAGE,
316 		    /* zero */ false, /* guarded */ false, /* frequent_reuse */
317 		    false, &deferred_work_generated);
318 		expect_ptr_not_null(allocs[i], "Unexpected alloc failure");
319 	}
320 	/* Free all but the last aloc. */
321 	for (int i = 0; i < NALLOCS - 1; i++) {
322 		pai_dalloc(tsdn, &sec.pai, allocs[i], &deferred_work_generated);
323 	}
324 	size_t max_allocs = ta.alloc_count + ta.alloc_batch_count;
325 
326 	expect_zu_le(NALLOCS, max_allocs, "Incorrect number of allocations");
327 	expect_zu_eq(0, ta.dalloc_count,
328 	    "Incorrect number of allocations");
329 
330 	if (is_disable) {
331 		sec_disable(tsdn, &sec);
332 	} else {
333 		sec_flush(tsdn, &sec);
334 	}
335 
336 	expect_zu_eq(max_allocs, ta.alloc_count + ta.alloc_batch_count,
337 	    "Incorrect number of allocations");
338 	expect_zu_eq(0, ta.dalloc_count,
339 	    "Incorrect number of (non-batch) deallocations");
340 	expect_zu_le(NALLOCS - 1, ta.dalloc_batch_count,
341 	    "Incorrect number of batch deallocations");
342 	size_t old_dalloc_batch_count = ta.dalloc_batch_count;
343 
344 	/*
345 	 * If we free into a disabled SEC, it should forward to the fallback.
346 	 * Otherwise, the SEC should accept the allocation.
347 	 */
348 	pai_dalloc(tsdn, &sec.pai, allocs[NALLOCS - 1],
349 	    &deferred_work_generated);
350 
351 	expect_zu_eq(max_allocs, ta.alloc_count + ta.alloc_batch_count,
352 	    "Incorrect number of allocations");
353 	expect_zu_eq(is_disable ? 1 : 0, ta.dalloc_count,
354 	    "Incorrect number of (non-batch) deallocations");
355 	expect_zu_eq(old_dalloc_batch_count, ta.dalloc_batch_count,
356 	    "Incorrect number of batch deallocations");
357 }
358 
359 TEST_BEGIN(test_disable) {
360 	do_disable_flush_test(/* is_disable */ true);
361 }
362 TEST_END
363 
364 TEST_BEGIN(test_flush) {
365 	do_disable_flush_test(/* is_disable */ false);
366 }
367 TEST_END
368 
369 TEST_BEGIN(test_max_alloc_respected) {
370 	pai_test_allocator_t ta;
371 	pai_test_allocator_init(&ta);
372 	sec_t sec;
373 	/* See the note above -- we can't use the real tsd. */
374 	tsdn_t *tsdn = TSDN_NULL;
375 
376 	size_t max_alloc = 2 * PAGE;
377 	size_t attempted_alloc = 3 * PAGE;
378 
379 	bool deferred_work_generated = false;
380 
381 	test_sec_init(&sec, &ta.pai, /* nshards */ 1, max_alloc,
382 	    /* max_bytes */ 1000 * PAGE);
383 
384 	for (size_t i = 0; i < 100; i++) {
385 		expect_zu_eq(i, ta.alloc_count,
386 		    "Incorrect number of allocations");
387 		expect_zu_eq(i, ta.dalloc_count,
388 		    "Incorrect number of deallocations");
389 		edata_t *edata = pai_alloc(tsdn, &sec.pai, attempted_alloc,
390 		    PAGE, /* zero */ false, /* guarded */ false,
391 		    /* frequent_reuse */ false, &deferred_work_generated);
392 		expect_ptr_not_null(edata, "Unexpected alloc failure");
393 		expect_zu_eq(i + 1, ta.alloc_count,
394 		    "Incorrect number of allocations");
395 		expect_zu_eq(i, ta.dalloc_count,
396 		    "Incorrect number of deallocations");
397 		pai_dalloc(tsdn, &sec.pai, edata, &deferred_work_generated);
398 	}
399 }
400 TEST_END
401 
402 TEST_BEGIN(test_expand_shrink_delegate) {
403 	/*
404 	 * Expand and shrink shouldn't affect sec state; they should just
405 	 * delegate to the fallback PAI.
406 	 */
407 	pai_test_allocator_t ta;
408 	pai_test_allocator_init(&ta);
409 	sec_t sec;
410 	/* See the note above -- we can't use the real tsd. */
411 	tsdn_t *tsdn = TSDN_NULL;
412 
413 	bool deferred_work_generated = false;
414 
415 	test_sec_init(&sec, &ta.pai, /* nshards */ 1, /* max_alloc */ 10 * PAGE,
416 	    /* max_bytes */ 1000 * PAGE);
417 	edata_t *edata = pai_alloc(tsdn, &sec.pai, PAGE, PAGE,
418 	    /* zero */ false, /* guarded */ false, /* frequent_reuse */ false,
419 	    &deferred_work_generated);
420 	expect_ptr_not_null(edata, "Unexpected alloc failure");
421 
422 	bool err = pai_expand(tsdn, &sec.pai, edata, PAGE, 4 * PAGE,
423 	    /* zero */ false, &deferred_work_generated);
424 	expect_false(err, "Unexpected expand failure");
425 	expect_zu_eq(1, ta.expand_count, "");
426 	ta.expand_return_value = true;
427 	err = pai_expand(tsdn, &sec.pai, edata, 4 * PAGE, 3 * PAGE,
428 	    /* zero */ false, &deferred_work_generated);
429 	expect_true(err, "Unexpected expand success");
430 	expect_zu_eq(2, ta.expand_count, "");
431 
432 	err = pai_shrink(tsdn, &sec.pai, edata, 4 * PAGE, 2 * PAGE,
433 	    &deferred_work_generated);
434 	expect_false(err, "Unexpected shrink failure");
435 	expect_zu_eq(1, ta.shrink_count, "");
436 	ta.shrink_return_value = true;
437 	err = pai_shrink(tsdn, &sec.pai, edata, 2 * PAGE, PAGE,
438 	    &deferred_work_generated);
439 	expect_true(err, "Unexpected shrink success");
440 	expect_zu_eq(2, ta.shrink_count, "");
441 }
442 TEST_END
443 
444 TEST_BEGIN(test_nshards_0) {
445 	pai_test_allocator_t ta;
446 	pai_test_allocator_init(&ta);
447 	sec_t sec;
448 	/* See the note above -- we can't use the real tsd. */
449 	tsdn_t *tsdn = TSDN_NULL;
450 	base_t *base = base_new(TSDN_NULL, /* ind */ 123,
451 	    &ehooks_default_extent_hooks, /* metadata_use_hooks */ true);
452 
453 	sec_opts_t opts = SEC_OPTS_DEFAULT;
454 	opts.nshards = 0;
455 	sec_init(TSDN_NULL, &sec, base, &ta.pai, &opts);
456 
457 	bool deferred_work_generated = false;
458 	edata_t *edata = pai_alloc(tsdn, &sec.pai, PAGE, PAGE,
459 	    /* zero */ false, /* guarded */ false, /* frequent_reuse */ false,
460 	    &deferred_work_generated);
461 	pai_dalloc(tsdn, &sec.pai, edata, &deferred_work_generated);
462 
463 	/* Both operations should have gone directly to the fallback. */
464 	expect_zu_eq(1, ta.alloc_count, "");
465 	expect_zu_eq(1, ta.dalloc_count, "");
466 }
467 TEST_END
468 
469 static void
470 expect_stats_pages(tsdn_t *tsdn, sec_t *sec, size_t npages) {
471 	sec_stats_t stats;
472 	/*
473 	 * Check that the stats merging accumulates rather than overwrites by
474 	 * putting some (made up) data there to begin with.
475 	 */
476 	stats.bytes = 123;
477 	sec_stats_merge(tsdn, sec, &stats);
478 	assert_zu_le(npages * PAGE + 123, stats.bytes, "");
479 }
480 
481 TEST_BEGIN(test_stats_simple) {
482 	pai_test_allocator_t ta;
483 	pai_test_allocator_init(&ta);
484 	sec_t sec;
485 
486 	/* See the note above -- we can't use the real tsd. */
487 	tsdn_t *tsdn = TSDN_NULL;
488 
489 	enum {
490 		NITERS = 100,
491 		FLUSH_PAGES = 20,
492 	};
493 
494 	bool deferred_work_generated = false;
495 
496 	test_sec_init(&sec, &ta.pai, /* nshards */ 1, /* max_alloc */ PAGE,
497 	    /* max_bytes */ FLUSH_PAGES * PAGE);
498 
499 	edata_t *allocs[FLUSH_PAGES];
500 	for (size_t i = 0; i < FLUSH_PAGES; i++) {
501 		allocs[i] = pai_alloc(tsdn, &sec.pai, PAGE, PAGE,
502 		    /* zero */ false, /* guarded */ false, /* frequent_reuse */
503 		    false, &deferred_work_generated);
504 		expect_stats_pages(tsdn, &sec, 0);
505 	}
506 
507 	/* Increase and decrease, without flushing. */
508 	for (size_t i = 0; i < NITERS; i++) {
509 		for (size_t j = 0; j < FLUSH_PAGES / 2; j++) {
510 			pai_dalloc(tsdn, &sec.pai, allocs[j],
511 			    &deferred_work_generated);
512 			expect_stats_pages(tsdn, &sec, j + 1);
513 		}
514 		for (size_t j = 0; j < FLUSH_PAGES / 2; j++) {
515 			allocs[j] = pai_alloc(tsdn, &sec.pai, PAGE, PAGE,
516 			    /* zero */ false, /* guarded */ false,
517 			    /* frequent_reuse */ false,
518 			    &deferred_work_generated);
519 			expect_stats_pages(tsdn, &sec, FLUSH_PAGES / 2 - j - 1);
520 		}
521 	}
522 }
523 TEST_END
524 
525 TEST_BEGIN(test_stats_auto_flush) {
526 	pai_test_allocator_t ta;
527 	pai_test_allocator_init(&ta);
528 	sec_t sec;
529 
530 	/* See the note above -- we can't use the real tsd. */
531 	tsdn_t *tsdn = TSDN_NULL;
532 
533 	enum {
534 		FLUSH_PAGES = 10,
535 	};
536 
537 	test_sec_init(&sec, &ta.pai, /* nshards */ 1, /* max_alloc */ PAGE,
538 	    /* max_bytes */ FLUSH_PAGES * PAGE);
539 
540 	edata_t *extra_alloc0;
541 	edata_t *extra_alloc1;
542 	edata_t *allocs[2 * FLUSH_PAGES];
543 
544 	bool deferred_work_generated = false;
545 
546 	extra_alloc0 = pai_alloc(tsdn, &sec.pai, PAGE, PAGE, /* zero */ false,
547 	    /* guarded */ false, /* frequent_reuse */ false,
548 	    &deferred_work_generated);
549 	extra_alloc1 = pai_alloc(tsdn, &sec.pai, PAGE, PAGE, /* zero */ false,
550 	    /* guarded */ false, /* frequent_reuse */ false,
551 	    &deferred_work_generated);
552 
553 	for (size_t i = 0; i < 2 * FLUSH_PAGES; i++) {
554 		allocs[i] = pai_alloc(tsdn, &sec.pai, PAGE, PAGE,
555 		    /* zero */ false, /* guarded */ false, /* frequent_reuse */
556 		    false, &deferred_work_generated);
557 	}
558 
559 	for (size_t i = 0; i < FLUSH_PAGES; i++) {
560 		pai_dalloc(tsdn, &sec.pai, allocs[i], &deferred_work_generated);
561 	}
562 	pai_dalloc(tsdn, &sec.pai, extra_alloc0, &deferred_work_generated);
563 
564 	/* Flush the remaining pages; stats should still work. */
565 	for (size_t i = 0; i < FLUSH_PAGES; i++) {
566 		pai_dalloc(tsdn, &sec.pai, allocs[FLUSH_PAGES + i],
567 		    &deferred_work_generated);
568 	}
569 
570 	pai_dalloc(tsdn, &sec.pai, extra_alloc1, &deferred_work_generated);
571 
572 	expect_stats_pages(tsdn, &sec, ta.alloc_count + ta.alloc_batch_count
573 	    - ta.dalloc_count - ta.dalloc_batch_count);
574 }
575 TEST_END
576 
577 TEST_BEGIN(test_stats_manual_flush) {
578 	pai_test_allocator_t ta;
579 	pai_test_allocator_init(&ta);
580 	sec_t sec;
581 
582 	/* See the note above -- we can't use the real tsd. */
583 	tsdn_t *tsdn = TSDN_NULL;
584 
585 	enum {
586 		FLUSH_PAGES = 10,
587 	};
588 
589 	test_sec_init(&sec, &ta.pai, /* nshards */ 1, /* max_alloc */ PAGE,
590 	    /* max_bytes */ FLUSH_PAGES * PAGE);
591 
592 	bool deferred_work_generated = false;
593 	edata_t *allocs[FLUSH_PAGES];
594 	for (size_t i = 0; i < FLUSH_PAGES; i++) {
595 		allocs[i] = pai_alloc(tsdn, &sec.pai, PAGE, PAGE,
596 		    /* zero */ false, /* guarded */ false, /* frequent_reuse */
597 		    false, &deferred_work_generated);
598 		expect_stats_pages(tsdn, &sec, 0);
599 	}
600 
601 	/* Dalloc the first half of the allocations. */
602 	for (size_t i = 0; i < FLUSH_PAGES / 2; i++) {
603 		pai_dalloc(tsdn, &sec.pai, allocs[i], &deferred_work_generated);
604 		expect_stats_pages(tsdn, &sec, i + 1);
605 	}
606 
607 	sec_flush(tsdn, &sec);
608 	expect_stats_pages(tsdn, &sec, 0);
609 
610 	/* Flush the remaining pages. */
611 	for (size_t i = 0; i < FLUSH_PAGES / 2; i++) {
612 		pai_dalloc(tsdn, &sec.pai, allocs[FLUSH_PAGES / 2 + i],
613 		    &deferred_work_generated);
614 		expect_stats_pages(tsdn, &sec, i + 1);
615 	}
616 	sec_disable(tsdn, &sec);
617 	expect_stats_pages(tsdn, &sec, 0);
618 }
619 TEST_END
620 
621 int
622 main(void) {
623 	return test(
624 	    test_reuse,
625 	    test_auto_flush,
626 	    test_disable,
627 	    test_flush,
628 	    test_max_alloc_respected,
629 	    test_expand_shrink_delegate,
630 	    test_nshards_0,
631 	    test_stats_simple,
632 	    test_stats_auto_flush,
633 	    test_stats_manual_flush);
634 }
635