xref: /netbsd-src/external/bsd/jemalloc.old/dist/test/unit/decay.c (revision 8e33eff89e26cf71871ead62f0d5063e1313c33a)
1*8e33eff8Schristos #include "test/jemalloc_test.h"
2*8e33eff8Schristos 
3*8e33eff8Schristos #include "jemalloc/internal/ticker.h"
4*8e33eff8Schristos 
5*8e33eff8Schristos static nstime_monotonic_t *nstime_monotonic_orig;
6*8e33eff8Schristos static nstime_update_t *nstime_update_orig;
7*8e33eff8Schristos 
8*8e33eff8Schristos static unsigned nupdates_mock;
9*8e33eff8Schristos static nstime_t time_mock;
10*8e33eff8Schristos static bool monotonic_mock;
11*8e33eff8Schristos 
12*8e33eff8Schristos static bool
13*8e33eff8Schristos check_background_thread_enabled(void) {
14*8e33eff8Schristos 	bool enabled;
15*8e33eff8Schristos 	size_t sz = sizeof(bool);
16*8e33eff8Schristos 	int ret = mallctl("background_thread", (void *)&enabled, &sz, NULL,0);
17*8e33eff8Schristos 	if (ret == ENOENT) {
18*8e33eff8Schristos 		return false;
19*8e33eff8Schristos 	}
20*8e33eff8Schristos 	assert_d_eq(ret, 0, "Unexpected mallctl error");
21*8e33eff8Schristos 	return enabled;
22*8e33eff8Schristos }
23*8e33eff8Schristos 
24*8e33eff8Schristos static bool
25*8e33eff8Schristos nstime_monotonic_mock(void) {
26*8e33eff8Schristos 	return monotonic_mock;
27*8e33eff8Schristos }
28*8e33eff8Schristos 
29*8e33eff8Schristos static bool
30*8e33eff8Schristos nstime_update_mock(nstime_t *time) {
31*8e33eff8Schristos 	nupdates_mock++;
32*8e33eff8Schristos 	if (monotonic_mock) {
33*8e33eff8Schristos 		nstime_copy(time, &time_mock);
34*8e33eff8Schristos 	}
35*8e33eff8Schristos 	return !monotonic_mock;
36*8e33eff8Schristos }
37*8e33eff8Schristos 
38*8e33eff8Schristos static unsigned
39*8e33eff8Schristos do_arena_create(ssize_t dirty_decay_ms, ssize_t muzzy_decay_ms) {
40*8e33eff8Schristos 	unsigned arena_ind;
41*8e33eff8Schristos 	size_t sz = sizeof(unsigned);
42*8e33eff8Schristos 	assert_d_eq(mallctl("arenas.create", (void *)&arena_ind, &sz, NULL, 0),
43*8e33eff8Schristos 	    0, "Unexpected mallctl() failure");
44*8e33eff8Schristos 	size_t mib[3];
45*8e33eff8Schristos 	size_t miblen = sizeof(mib)/sizeof(size_t);
46*8e33eff8Schristos 
47*8e33eff8Schristos 	assert_d_eq(mallctlnametomib("arena.0.dirty_decay_ms", mib, &miblen),
48*8e33eff8Schristos 	    0, "Unexpected mallctlnametomib() failure");
49*8e33eff8Schristos 	mib[1] = (size_t)arena_ind;
50*8e33eff8Schristos 	assert_d_eq(mallctlbymib(mib, miblen, NULL, NULL,
51*8e33eff8Schristos 	    (void *)&dirty_decay_ms, sizeof(dirty_decay_ms)), 0,
52*8e33eff8Schristos 	    "Unexpected mallctlbymib() failure");
53*8e33eff8Schristos 
54*8e33eff8Schristos 	assert_d_eq(mallctlnametomib("arena.0.muzzy_decay_ms", mib, &miblen),
55*8e33eff8Schristos 	    0, "Unexpected mallctlnametomib() failure");
56*8e33eff8Schristos 	mib[1] = (size_t)arena_ind;
57*8e33eff8Schristos 	assert_d_eq(mallctlbymib(mib, miblen, NULL, NULL,
58*8e33eff8Schristos 	    (void *)&muzzy_decay_ms, sizeof(muzzy_decay_ms)), 0,
59*8e33eff8Schristos 	    "Unexpected mallctlbymib() failure");
60*8e33eff8Schristos 
61*8e33eff8Schristos 	return arena_ind;
62*8e33eff8Schristos }
63*8e33eff8Schristos 
64*8e33eff8Schristos static void
65*8e33eff8Schristos do_arena_destroy(unsigned arena_ind) {
66*8e33eff8Schristos 	size_t mib[3];
67*8e33eff8Schristos 	size_t miblen = sizeof(mib)/sizeof(size_t);
68*8e33eff8Schristos 	assert_d_eq(mallctlnametomib("arena.0.destroy", mib, &miblen), 0,
69*8e33eff8Schristos 	    "Unexpected mallctlnametomib() failure");
70*8e33eff8Schristos 	mib[1] = (size_t)arena_ind;
71*8e33eff8Schristos 	assert_d_eq(mallctlbymib(mib, miblen, NULL, NULL, NULL, 0), 0,
72*8e33eff8Schristos 	    "Unexpected mallctlbymib() failure");
73*8e33eff8Schristos }
74*8e33eff8Schristos 
75*8e33eff8Schristos void
76*8e33eff8Schristos do_epoch(void) {
77*8e33eff8Schristos 	uint64_t epoch = 1;
78*8e33eff8Schristos 	assert_d_eq(mallctl("epoch", NULL, NULL, (void *)&epoch, sizeof(epoch)),
79*8e33eff8Schristos 	    0, "Unexpected mallctl() failure");
80*8e33eff8Schristos }
81*8e33eff8Schristos 
82*8e33eff8Schristos void
83*8e33eff8Schristos do_purge(unsigned arena_ind) {
84*8e33eff8Schristos 	size_t mib[3];
85*8e33eff8Schristos 	size_t miblen = sizeof(mib)/sizeof(size_t);
86*8e33eff8Schristos 	assert_d_eq(mallctlnametomib("arena.0.purge", mib, &miblen), 0,
87*8e33eff8Schristos 	    "Unexpected mallctlnametomib() failure");
88*8e33eff8Schristos 	mib[1] = (size_t)arena_ind;
89*8e33eff8Schristos 	assert_d_eq(mallctlbymib(mib, miblen, NULL, NULL, NULL, 0), 0,
90*8e33eff8Schristos 	    "Unexpected mallctlbymib() failure");
91*8e33eff8Schristos }
92*8e33eff8Schristos 
93*8e33eff8Schristos void
94*8e33eff8Schristos do_decay(unsigned arena_ind) {
95*8e33eff8Schristos 	size_t mib[3];
96*8e33eff8Schristos 	size_t miblen = sizeof(mib)/sizeof(size_t);
97*8e33eff8Schristos 	assert_d_eq(mallctlnametomib("arena.0.decay", mib, &miblen), 0,
98*8e33eff8Schristos 	    "Unexpected mallctlnametomib() failure");
99*8e33eff8Schristos 	mib[1] = (size_t)arena_ind;
100*8e33eff8Schristos 	assert_d_eq(mallctlbymib(mib, miblen, NULL, NULL, NULL, 0), 0,
101*8e33eff8Schristos 	    "Unexpected mallctlbymib() failure");
102*8e33eff8Schristos }
103*8e33eff8Schristos 
104*8e33eff8Schristos static uint64_t
105*8e33eff8Schristos get_arena_npurge_impl(const char *mibname, unsigned arena_ind) {
106*8e33eff8Schristos 	size_t mib[4];
107*8e33eff8Schristos 	size_t miblen = sizeof(mib)/sizeof(size_t);
108*8e33eff8Schristos 	assert_d_eq(mallctlnametomib(mibname, mib, &miblen), 0,
109*8e33eff8Schristos 	    "Unexpected mallctlnametomib() failure");
110*8e33eff8Schristos 	mib[2] = (size_t)arena_ind;
111*8e33eff8Schristos 	uint64_t npurge = 0;
112*8e33eff8Schristos 	size_t sz = sizeof(npurge);
113*8e33eff8Schristos 	assert_d_eq(mallctlbymib(mib, miblen, (void *)&npurge, &sz, NULL, 0),
114*8e33eff8Schristos 	    config_stats ? 0 : ENOENT, "Unexpected mallctlbymib() failure");
115*8e33eff8Schristos 	return npurge;
116*8e33eff8Schristos }
117*8e33eff8Schristos 
118*8e33eff8Schristos static uint64_t
119*8e33eff8Schristos get_arena_dirty_npurge(unsigned arena_ind) {
120*8e33eff8Schristos 	do_epoch();
121*8e33eff8Schristos 	return get_arena_npurge_impl("stats.arenas.0.dirty_npurge", arena_ind);
122*8e33eff8Schristos }
123*8e33eff8Schristos 
124*8e33eff8Schristos static uint64_t
125*8e33eff8Schristos get_arena_muzzy_npurge(unsigned arena_ind) {
126*8e33eff8Schristos 	do_epoch();
127*8e33eff8Schristos 	return get_arena_npurge_impl("stats.arenas.0.muzzy_npurge", arena_ind);
128*8e33eff8Schristos }
129*8e33eff8Schristos 
130*8e33eff8Schristos static uint64_t
131*8e33eff8Schristos get_arena_npurge(unsigned arena_ind) {
132*8e33eff8Schristos 	do_epoch();
133*8e33eff8Schristos 	return get_arena_npurge_impl("stats.arenas.0.dirty_npurge", arena_ind) +
134*8e33eff8Schristos 	    get_arena_npurge_impl("stats.arenas.0.muzzy_npurge", arena_ind);
135*8e33eff8Schristos }
136*8e33eff8Schristos 
137*8e33eff8Schristos static size_t
138*8e33eff8Schristos get_arena_pdirty(unsigned arena_ind) {
139*8e33eff8Schristos 	do_epoch();
140*8e33eff8Schristos 	size_t mib[4];
141*8e33eff8Schristos 	size_t miblen = sizeof(mib)/sizeof(size_t);
142*8e33eff8Schristos 	assert_d_eq(mallctlnametomib("stats.arenas.0.pdirty", mib, &miblen), 0,
143*8e33eff8Schristos 	    "Unexpected mallctlnametomib() failure");
144*8e33eff8Schristos 	mib[2] = (size_t)arena_ind;
145*8e33eff8Schristos 	size_t pdirty;
146*8e33eff8Schristos 	size_t sz = sizeof(pdirty);
147*8e33eff8Schristos 	assert_d_eq(mallctlbymib(mib, miblen, (void *)&pdirty, &sz, NULL, 0), 0,
148*8e33eff8Schristos 	    "Unexpected mallctlbymib() failure");
149*8e33eff8Schristos 	return pdirty;
150*8e33eff8Schristos }
151*8e33eff8Schristos 
152*8e33eff8Schristos static size_t
153*8e33eff8Schristos get_arena_pmuzzy(unsigned arena_ind) {
154*8e33eff8Schristos 	do_epoch();
155*8e33eff8Schristos 	size_t mib[4];
156*8e33eff8Schristos 	size_t miblen = sizeof(mib)/sizeof(size_t);
157*8e33eff8Schristos 	assert_d_eq(mallctlnametomib("stats.arenas.0.pmuzzy", mib, &miblen), 0,
158*8e33eff8Schristos 	    "Unexpected mallctlnametomib() failure");
159*8e33eff8Schristos 	mib[2] = (size_t)arena_ind;
160*8e33eff8Schristos 	size_t pmuzzy;
161*8e33eff8Schristos 	size_t sz = sizeof(pmuzzy);
162*8e33eff8Schristos 	assert_d_eq(mallctlbymib(mib, miblen, (void *)&pmuzzy, &sz, NULL, 0), 0,
163*8e33eff8Schristos 	    "Unexpected mallctlbymib() failure");
164*8e33eff8Schristos 	return pmuzzy;
165*8e33eff8Schristos }
166*8e33eff8Schristos 
167*8e33eff8Schristos static void *
168*8e33eff8Schristos do_mallocx(size_t size, int flags) {
169*8e33eff8Schristos 	void *p = mallocx(size, flags);
170*8e33eff8Schristos 	assert_ptr_not_null(p, "Unexpected mallocx() failure");
171*8e33eff8Schristos 	return p;
172*8e33eff8Schristos }
173*8e33eff8Schristos 
174*8e33eff8Schristos static void
175*8e33eff8Schristos generate_dirty(unsigned arena_ind, size_t size) {
176*8e33eff8Schristos 	int flags = MALLOCX_ARENA(arena_ind) | MALLOCX_TCACHE_NONE;
177*8e33eff8Schristos 	void *p = do_mallocx(size, flags);
178*8e33eff8Schristos 	dallocx(p, flags);
179*8e33eff8Schristos }
180*8e33eff8Schristos 
181*8e33eff8Schristos TEST_BEGIN(test_decay_ticks) {
182*8e33eff8Schristos 	test_skip_if(check_background_thread_enabled());
183*8e33eff8Schristos 
184*8e33eff8Schristos 	ticker_t *decay_ticker;
185*8e33eff8Schristos 	unsigned tick0, tick1, arena_ind;
186*8e33eff8Schristos 	size_t sz, large0;
187*8e33eff8Schristos 	void *p;
188*8e33eff8Schristos 
189*8e33eff8Schristos 	sz = sizeof(size_t);
190*8e33eff8Schristos 	assert_d_eq(mallctl("arenas.lextent.0.size", (void *)&large0, &sz, NULL,
191*8e33eff8Schristos 	    0), 0, "Unexpected mallctl failure");
192*8e33eff8Schristos 
193*8e33eff8Schristos 	/* Set up a manually managed arena for test. */
194*8e33eff8Schristos 	arena_ind = do_arena_create(0, 0);
195*8e33eff8Schristos 
196*8e33eff8Schristos 	/* Migrate to the new arena, and get the ticker. */
197*8e33eff8Schristos 	unsigned old_arena_ind;
198*8e33eff8Schristos 	size_t sz_arena_ind = sizeof(old_arena_ind);
199*8e33eff8Schristos 	assert_d_eq(mallctl("thread.arena", (void *)&old_arena_ind,
200*8e33eff8Schristos 	    &sz_arena_ind, (void *)&arena_ind, sizeof(arena_ind)), 0,
201*8e33eff8Schristos 	    "Unexpected mallctl() failure");
202*8e33eff8Schristos 	decay_ticker = decay_ticker_get(tsd_fetch(), arena_ind);
203*8e33eff8Schristos 	assert_ptr_not_null(decay_ticker,
204*8e33eff8Schristos 	    "Unexpected failure getting decay ticker");
205*8e33eff8Schristos 
206*8e33eff8Schristos 	/*
207*8e33eff8Schristos 	 * Test the standard APIs using a large size class, since we can't
208*8e33eff8Schristos 	 * control tcache interactions for small size classes (except by
209*8e33eff8Schristos 	 * completely disabling tcache for the entire test program).
210*8e33eff8Schristos 	 */
211*8e33eff8Schristos 
212*8e33eff8Schristos 	/* malloc(). */
213*8e33eff8Schristos 	tick0 = ticker_read(decay_ticker);
214*8e33eff8Schristos 	p = malloc(large0);
215*8e33eff8Schristos 	assert_ptr_not_null(p, "Unexpected malloc() failure");
216*8e33eff8Schristos 	tick1 = ticker_read(decay_ticker);
217*8e33eff8Schristos 	assert_u32_ne(tick1, tick0, "Expected ticker to tick during malloc()");
218*8e33eff8Schristos 	/* free(). */
219*8e33eff8Schristos 	tick0 = ticker_read(decay_ticker);
220*8e33eff8Schristos 	free(p);
221*8e33eff8Schristos 	tick1 = ticker_read(decay_ticker);
222*8e33eff8Schristos 	assert_u32_ne(tick1, tick0, "Expected ticker to tick during free()");
223*8e33eff8Schristos 
224*8e33eff8Schristos 	/* calloc(). */
225*8e33eff8Schristos 	tick0 = ticker_read(decay_ticker);
226*8e33eff8Schristos 	p = calloc(1, large0);
227*8e33eff8Schristos 	assert_ptr_not_null(p, "Unexpected calloc() failure");
228*8e33eff8Schristos 	tick1 = ticker_read(decay_ticker);
229*8e33eff8Schristos 	assert_u32_ne(tick1, tick0, "Expected ticker to tick during calloc()");
230*8e33eff8Schristos 	free(p);
231*8e33eff8Schristos 
232*8e33eff8Schristos 	/* posix_memalign(). */
233*8e33eff8Schristos 	tick0 = ticker_read(decay_ticker);
234*8e33eff8Schristos 	assert_d_eq(posix_memalign(&p, sizeof(size_t), large0), 0,
235*8e33eff8Schristos 	    "Unexpected posix_memalign() failure");
236*8e33eff8Schristos 	tick1 = ticker_read(decay_ticker);
237*8e33eff8Schristos 	assert_u32_ne(tick1, tick0,
238*8e33eff8Schristos 	    "Expected ticker to tick during posix_memalign()");
239*8e33eff8Schristos 	free(p);
240*8e33eff8Schristos 
241*8e33eff8Schristos 	/* aligned_alloc(). */
242*8e33eff8Schristos 	tick0 = ticker_read(decay_ticker);
243*8e33eff8Schristos 	p = aligned_alloc(sizeof(size_t), large0);
244*8e33eff8Schristos 	assert_ptr_not_null(p, "Unexpected aligned_alloc() failure");
245*8e33eff8Schristos 	tick1 = ticker_read(decay_ticker);
246*8e33eff8Schristos 	assert_u32_ne(tick1, tick0,
247*8e33eff8Schristos 	    "Expected ticker to tick during aligned_alloc()");
248*8e33eff8Schristos 	free(p);
249*8e33eff8Schristos 
250*8e33eff8Schristos 	/* realloc(). */
251*8e33eff8Schristos 	/* Allocate. */
252*8e33eff8Schristos 	tick0 = ticker_read(decay_ticker);
253*8e33eff8Schristos 	p = realloc(NULL, large0);
254*8e33eff8Schristos 	assert_ptr_not_null(p, "Unexpected realloc() failure");
255*8e33eff8Schristos 	tick1 = ticker_read(decay_ticker);
256*8e33eff8Schristos 	assert_u32_ne(tick1, tick0, "Expected ticker to tick during realloc()");
257*8e33eff8Schristos 	/* Reallocate. */
258*8e33eff8Schristos 	tick0 = ticker_read(decay_ticker);
259*8e33eff8Schristos 	p = realloc(p, large0);
260*8e33eff8Schristos 	assert_ptr_not_null(p, "Unexpected realloc() failure");
261*8e33eff8Schristos 	tick1 = ticker_read(decay_ticker);
262*8e33eff8Schristos 	assert_u32_ne(tick1, tick0, "Expected ticker to tick during realloc()");
263*8e33eff8Schristos 	/* Deallocate. */
264*8e33eff8Schristos 	tick0 = ticker_read(decay_ticker);
265*8e33eff8Schristos 	realloc(p, 0);
266*8e33eff8Schristos 	tick1 = ticker_read(decay_ticker);
267*8e33eff8Schristos 	assert_u32_ne(tick1, tick0, "Expected ticker to tick during realloc()");
268*8e33eff8Schristos 
269*8e33eff8Schristos 	/*
270*8e33eff8Schristos 	 * Test the *allocx() APIs using large and small size classes, with
271*8e33eff8Schristos 	 * tcache explicitly disabled.
272*8e33eff8Schristos 	 */
273*8e33eff8Schristos 	{
274*8e33eff8Schristos 		unsigned i;
275*8e33eff8Schristos 		size_t allocx_sizes[2];
276*8e33eff8Schristos 		allocx_sizes[0] = large0;
277*8e33eff8Schristos 		allocx_sizes[1] = 1;
278*8e33eff8Schristos 
279*8e33eff8Schristos 		for (i = 0; i < sizeof(allocx_sizes) / sizeof(size_t); i++) {
280*8e33eff8Schristos 			sz = allocx_sizes[i];
281*8e33eff8Schristos 
282*8e33eff8Schristos 			/* mallocx(). */
283*8e33eff8Schristos 			tick0 = ticker_read(decay_ticker);
284*8e33eff8Schristos 			p = mallocx(sz, MALLOCX_TCACHE_NONE);
285*8e33eff8Schristos 			assert_ptr_not_null(p, "Unexpected mallocx() failure");
286*8e33eff8Schristos 			tick1 = ticker_read(decay_ticker);
287*8e33eff8Schristos 			assert_u32_ne(tick1, tick0,
288*8e33eff8Schristos 			    "Expected ticker to tick during mallocx() (sz=%zu)",
289*8e33eff8Schristos 			    sz);
290*8e33eff8Schristos 			/* rallocx(). */
291*8e33eff8Schristos 			tick0 = ticker_read(decay_ticker);
292*8e33eff8Schristos 			p = rallocx(p, sz, MALLOCX_TCACHE_NONE);
293*8e33eff8Schristos 			assert_ptr_not_null(p, "Unexpected rallocx() failure");
294*8e33eff8Schristos 			tick1 = ticker_read(decay_ticker);
295*8e33eff8Schristos 			assert_u32_ne(tick1, tick0,
296*8e33eff8Schristos 			    "Expected ticker to tick during rallocx() (sz=%zu)",
297*8e33eff8Schristos 			    sz);
298*8e33eff8Schristos 			/* xallocx(). */
299*8e33eff8Schristos 			tick0 = ticker_read(decay_ticker);
300*8e33eff8Schristos 			xallocx(p, sz, 0, MALLOCX_TCACHE_NONE);
301*8e33eff8Schristos 			tick1 = ticker_read(decay_ticker);
302*8e33eff8Schristos 			assert_u32_ne(tick1, tick0,
303*8e33eff8Schristos 			    "Expected ticker to tick during xallocx() (sz=%zu)",
304*8e33eff8Schristos 			    sz);
305*8e33eff8Schristos 			/* dallocx(). */
306*8e33eff8Schristos 			tick0 = ticker_read(decay_ticker);
307*8e33eff8Schristos 			dallocx(p, MALLOCX_TCACHE_NONE);
308*8e33eff8Schristos 			tick1 = ticker_read(decay_ticker);
309*8e33eff8Schristos 			assert_u32_ne(tick1, tick0,
310*8e33eff8Schristos 			    "Expected ticker to tick during dallocx() (sz=%zu)",
311*8e33eff8Schristos 			    sz);
312*8e33eff8Schristos 			/* sdallocx(). */
313*8e33eff8Schristos 			p = mallocx(sz, MALLOCX_TCACHE_NONE);
314*8e33eff8Schristos 			assert_ptr_not_null(p, "Unexpected mallocx() failure");
315*8e33eff8Schristos 			tick0 = ticker_read(decay_ticker);
316*8e33eff8Schristos 			sdallocx(p, sz, MALLOCX_TCACHE_NONE);
317*8e33eff8Schristos 			tick1 = ticker_read(decay_ticker);
318*8e33eff8Schristos 			assert_u32_ne(tick1, tick0,
319*8e33eff8Schristos 			    "Expected ticker to tick during sdallocx() "
320*8e33eff8Schristos 			    "(sz=%zu)", sz);
321*8e33eff8Schristos 		}
322*8e33eff8Schristos 	}
323*8e33eff8Schristos 
324*8e33eff8Schristos 	/*
325*8e33eff8Schristos 	 * Test tcache fill/flush interactions for large and small size classes,
326*8e33eff8Schristos 	 * using an explicit tcache.
327*8e33eff8Schristos 	 */
328*8e33eff8Schristos 	unsigned tcache_ind, i;
329*8e33eff8Schristos 	size_t tcache_sizes[2];
330*8e33eff8Schristos 	tcache_sizes[0] = large0;
331*8e33eff8Schristos 	tcache_sizes[1] = 1;
332*8e33eff8Schristos 
333*8e33eff8Schristos 	size_t tcache_max, sz_tcache_max;
334*8e33eff8Schristos 	sz_tcache_max = sizeof(tcache_max);
335*8e33eff8Schristos 	assert_d_eq(mallctl("arenas.tcache_max", (void *)&tcache_max,
336*8e33eff8Schristos 	    &sz_tcache_max, NULL, 0), 0, "Unexpected mallctl() failure");
337*8e33eff8Schristos 
338*8e33eff8Schristos 	sz = sizeof(unsigned);
339*8e33eff8Schristos 	assert_d_eq(mallctl("tcache.create", (void *)&tcache_ind, &sz,
340*8e33eff8Schristos 	    NULL, 0), 0, "Unexpected mallctl failure");
341*8e33eff8Schristos 
342*8e33eff8Schristos 	for (i = 0; i < sizeof(tcache_sizes) / sizeof(size_t); i++) {
343*8e33eff8Schristos 		sz = tcache_sizes[i];
344*8e33eff8Schristos 
345*8e33eff8Schristos 		/* tcache fill. */
346*8e33eff8Schristos 		tick0 = ticker_read(decay_ticker);
347*8e33eff8Schristos 		p = mallocx(sz, MALLOCX_TCACHE(tcache_ind));
348*8e33eff8Schristos 		assert_ptr_not_null(p, "Unexpected mallocx() failure");
349*8e33eff8Schristos 		tick1 = ticker_read(decay_ticker);
350*8e33eff8Schristos 		assert_u32_ne(tick1, tick0,
351*8e33eff8Schristos 		    "Expected ticker to tick during tcache fill "
352*8e33eff8Schristos 		    "(sz=%zu)", sz);
353*8e33eff8Schristos 		/* tcache flush. */
354*8e33eff8Schristos 		dallocx(p, MALLOCX_TCACHE(tcache_ind));
355*8e33eff8Schristos 		tick0 = ticker_read(decay_ticker);
356*8e33eff8Schristos 		assert_d_eq(mallctl("tcache.flush", NULL, NULL,
357*8e33eff8Schristos 		    (void *)&tcache_ind, sizeof(unsigned)), 0,
358*8e33eff8Schristos 		    "Unexpected mallctl failure");
359*8e33eff8Schristos 		tick1 = ticker_read(decay_ticker);
360*8e33eff8Schristos 
361*8e33eff8Schristos 		/* Will only tick if it's in tcache. */
362*8e33eff8Schristos 		if (sz <= tcache_max) {
363*8e33eff8Schristos 			assert_u32_ne(tick1, tick0,
364*8e33eff8Schristos 			    "Expected ticker to tick during tcache "
365*8e33eff8Schristos 			    "flush (sz=%zu)", sz);
366*8e33eff8Schristos 		} else {
367*8e33eff8Schristos 			assert_u32_eq(tick1, tick0,
368*8e33eff8Schristos 			    "Unexpected ticker tick during tcache "
369*8e33eff8Schristos 			    "flush (sz=%zu)", sz);
370*8e33eff8Schristos 		}
371*8e33eff8Schristos 	}
372*8e33eff8Schristos }
373*8e33eff8Schristos TEST_END
374*8e33eff8Schristos 
375*8e33eff8Schristos static void
376*8e33eff8Schristos decay_ticker_helper(unsigned arena_ind, int flags, bool dirty, ssize_t dt,
377*8e33eff8Schristos     uint64_t dirty_npurge0, uint64_t muzzy_npurge0, bool terminate_asap) {
378*8e33eff8Schristos #define NINTERVALS 101
379*8e33eff8Schristos 	nstime_t time, update_interval, decay_ms, deadline;
380*8e33eff8Schristos 
381*8e33eff8Schristos 	nstime_init(&time, 0);
382*8e33eff8Schristos 	nstime_update(&time);
383*8e33eff8Schristos 
384*8e33eff8Schristos 	nstime_init2(&decay_ms, dt, 0);
385*8e33eff8Schristos 	nstime_copy(&deadline, &time);
386*8e33eff8Schristos 	nstime_add(&deadline, &decay_ms);
387*8e33eff8Schristos 
388*8e33eff8Schristos 	nstime_init2(&update_interval, dt, 0);
389*8e33eff8Schristos 	nstime_idivide(&update_interval, NINTERVALS);
390*8e33eff8Schristos 
391*8e33eff8Schristos 	/*
392*8e33eff8Schristos 	 * Keep q's slab from being deallocated during the looping below.  If a
393*8e33eff8Schristos 	 * cached slab were to repeatedly come and go during looping, it could
394*8e33eff8Schristos 	 * prevent the decay backlog ever becoming empty.
395*8e33eff8Schristos 	 */
396*8e33eff8Schristos 	void *p = do_mallocx(1, flags);
397*8e33eff8Schristos 	uint64_t dirty_npurge1, muzzy_npurge1;
398*8e33eff8Schristos 	do {
399*8e33eff8Schristos 		for (unsigned i = 0; i < DECAY_NTICKS_PER_UPDATE / 2;
400*8e33eff8Schristos 		    i++) {
401*8e33eff8Schristos 			void *q = do_mallocx(1, flags);
402*8e33eff8Schristos 			dallocx(q, flags);
403*8e33eff8Schristos 		}
404*8e33eff8Schristos 		dirty_npurge1 = get_arena_dirty_npurge(arena_ind);
405*8e33eff8Schristos 		muzzy_npurge1 = get_arena_muzzy_npurge(arena_ind);
406*8e33eff8Schristos 
407*8e33eff8Schristos 		nstime_add(&time_mock, &update_interval);
408*8e33eff8Schristos 		nstime_update(&time);
409*8e33eff8Schristos 	} while (nstime_compare(&time, &deadline) <= 0 && ((dirty_npurge1 ==
410*8e33eff8Schristos 	    dirty_npurge0 && muzzy_npurge1 == muzzy_npurge0) ||
411*8e33eff8Schristos 	    !terminate_asap));
412*8e33eff8Schristos 	dallocx(p, flags);
413*8e33eff8Schristos 
414*8e33eff8Schristos 	if (config_stats) {
415*8e33eff8Schristos 		assert_u64_gt(dirty_npurge1 + muzzy_npurge1, dirty_npurge0 +
416*8e33eff8Schristos 		    muzzy_npurge0, "Expected purging to occur");
417*8e33eff8Schristos 	}
418*8e33eff8Schristos #undef NINTERVALS
419*8e33eff8Schristos }
420*8e33eff8Schristos 
421*8e33eff8Schristos TEST_BEGIN(test_decay_ticker) {
422*8e33eff8Schristos 	test_skip_if(check_background_thread_enabled());
423*8e33eff8Schristos #define NPS 2048
424*8e33eff8Schristos 	ssize_t ddt = opt_dirty_decay_ms;
425*8e33eff8Schristos 	ssize_t mdt = opt_muzzy_decay_ms;
426*8e33eff8Schristos 	unsigned arena_ind = do_arena_create(ddt, mdt);
427*8e33eff8Schristos 	int flags = (MALLOCX_ARENA(arena_ind) | MALLOCX_TCACHE_NONE);
428*8e33eff8Schristos 	void *ps[NPS];
429*8e33eff8Schristos 	size_t large;
430*8e33eff8Schristos 
431*8e33eff8Schristos 	/*
432*8e33eff8Schristos 	 * Allocate a bunch of large objects, pause the clock, deallocate every
433*8e33eff8Schristos 	 * other object (to fragment virtual memory), restore the clock, then
434*8e33eff8Schristos 	 * [md]allocx() in a tight loop while advancing time rapidly to verify
435*8e33eff8Schristos 	 * the ticker triggers purging.
436*8e33eff8Schristos 	 */
437*8e33eff8Schristos 
438*8e33eff8Schristos 	size_t tcache_max;
439*8e33eff8Schristos 	size_t sz = sizeof(size_t);
440*8e33eff8Schristos 	assert_d_eq(mallctl("arenas.tcache_max", (void *)&tcache_max, &sz, NULL,
441*8e33eff8Schristos 	    0), 0, "Unexpected mallctl failure");
442*8e33eff8Schristos 	large = nallocx(tcache_max + 1, flags);
443*8e33eff8Schristos 
444*8e33eff8Schristos 	do_purge(arena_ind);
445*8e33eff8Schristos 	uint64_t dirty_npurge0 = get_arena_dirty_npurge(arena_ind);
446*8e33eff8Schristos 	uint64_t muzzy_npurge0 = get_arena_muzzy_npurge(arena_ind);
447*8e33eff8Schristos 
448*8e33eff8Schristos 	for (unsigned i = 0; i < NPS; i++) {
449*8e33eff8Schristos 		ps[i] = do_mallocx(large, flags);
450*8e33eff8Schristos 	}
451*8e33eff8Schristos 
452*8e33eff8Schristos 	nupdates_mock = 0;
453*8e33eff8Schristos 	nstime_init(&time_mock, 0);
454*8e33eff8Schristos 	nstime_update(&time_mock);
455*8e33eff8Schristos 	monotonic_mock = true;
456*8e33eff8Schristos 
457*8e33eff8Schristos 	nstime_monotonic_orig = nstime_monotonic;
458*8e33eff8Schristos 	nstime_update_orig = nstime_update;
459*8e33eff8Schristos 	nstime_monotonic = nstime_monotonic_mock;
460*8e33eff8Schristos 	nstime_update = nstime_update_mock;
461*8e33eff8Schristos 
462*8e33eff8Schristos 	for (unsigned i = 0; i < NPS; i += 2) {
463*8e33eff8Schristos 		dallocx(ps[i], flags);
464*8e33eff8Schristos 		unsigned nupdates0 = nupdates_mock;
465*8e33eff8Schristos 		do_decay(arena_ind);
466*8e33eff8Schristos 		assert_u_gt(nupdates_mock, nupdates0,
467*8e33eff8Schristos 		    "Expected nstime_update() to be called");
468*8e33eff8Schristos 	}
469*8e33eff8Schristos 
470*8e33eff8Schristos 	decay_ticker_helper(arena_ind, flags, true, ddt, dirty_npurge0,
471*8e33eff8Schristos 	    muzzy_npurge0, true);
472*8e33eff8Schristos 	decay_ticker_helper(arena_ind, flags, false, ddt+mdt, dirty_npurge0,
473*8e33eff8Schristos 	    muzzy_npurge0, false);
474*8e33eff8Schristos 
475*8e33eff8Schristos 	do_arena_destroy(arena_ind);
476*8e33eff8Schristos 
477*8e33eff8Schristos 	nstime_monotonic = nstime_monotonic_orig;
478*8e33eff8Schristos 	nstime_update = nstime_update_orig;
479*8e33eff8Schristos #undef NPS
480*8e33eff8Schristos }
481*8e33eff8Schristos TEST_END
482*8e33eff8Schristos 
483*8e33eff8Schristos TEST_BEGIN(test_decay_nonmonotonic) {
484*8e33eff8Schristos 	test_skip_if(check_background_thread_enabled());
485*8e33eff8Schristos #define NPS (SMOOTHSTEP_NSTEPS + 1)
486*8e33eff8Schristos 	int flags = (MALLOCX_ARENA(0) | MALLOCX_TCACHE_NONE);
487*8e33eff8Schristos 	void *ps[NPS];
488*8e33eff8Schristos 	uint64_t npurge0 = 0;
489*8e33eff8Schristos 	uint64_t npurge1 = 0;
490*8e33eff8Schristos 	size_t sz, large0;
491*8e33eff8Schristos 	unsigned i, nupdates0;
492*8e33eff8Schristos 
493*8e33eff8Schristos 	sz = sizeof(size_t);
494*8e33eff8Schristos 	assert_d_eq(mallctl("arenas.lextent.0.size", (void *)&large0, &sz, NULL,
495*8e33eff8Schristos 	    0), 0, "Unexpected mallctl failure");
496*8e33eff8Schristos 
497*8e33eff8Schristos 	assert_d_eq(mallctl("arena.0.purge", NULL, NULL, NULL, 0), 0,
498*8e33eff8Schristos 	    "Unexpected mallctl failure");
499*8e33eff8Schristos 	do_epoch();
500*8e33eff8Schristos 	sz = sizeof(uint64_t);
501*8e33eff8Schristos 	npurge0 = get_arena_npurge(0);
502*8e33eff8Schristos 
503*8e33eff8Schristos 	nupdates_mock = 0;
504*8e33eff8Schristos 	nstime_init(&time_mock, 0);
505*8e33eff8Schristos 	nstime_update(&time_mock);
506*8e33eff8Schristos 	monotonic_mock = false;
507*8e33eff8Schristos 
508*8e33eff8Schristos 	nstime_monotonic_orig = nstime_monotonic;
509*8e33eff8Schristos 	nstime_update_orig = nstime_update;
510*8e33eff8Schristos 	nstime_monotonic = nstime_monotonic_mock;
511*8e33eff8Schristos 	nstime_update = nstime_update_mock;
512*8e33eff8Schristos 
513*8e33eff8Schristos 	for (i = 0; i < NPS; i++) {
514*8e33eff8Schristos 		ps[i] = mallocx(large0, flags);
515*8e33eff8Schristos 		assert_ptr_not_null(ps[i], "Unexpected mallocx() failure");
516*8e33eff8Schristos 	}
517*8e33eff8Schristos 
518*8e33eff8Schristos 	for (i = 0; i < NPS; i++) {
519*8e33eff8Schristos 		dallocx(ps[i], flags);
520*8e33eff8Schristos 		nupdates0 = nupdates_mock;
521*8e33eff8Schristos 		assert_d_eq(mallctl("arena.0.decay", NULL, NULL, NULL, 0), 0,
522*8e33eff8Schristos 		    "Unexpected arena.0.decay failure");
523*8e33eff8Schristos 		assert_u_gt(nupdates_mock, nupdates0,
524*8e33eff8Schristos 		    "Expected nstime_update() to be called");
525*8e33eff8Schristos 	}
526*8e33eff8Schristos 
527*8e33eff8Schristos 	do_epoch();
528*8e33eff8Schristos 	sz = sizeof(uint64_t);
529*8e33eff8Schristos 	npurge1 = get_arena_npurge(0);
530*8e33eff8Schristos 
531*8e33eff8Schristos 	if (config_stats) {
532*8e33eff8Schristos 		assert_u64_eq(npurge0, npurge1, "Unexpected purging occurred");
533*8e33eff8Schristos 	}
534*8e33eff8Schristos 
535*8e33eff8Schristos 	nstime_monotonic = nstime_monotonic_orig;
536*8e33eff8Schristos 	nstime_update = nstime_update_orig;
537*8e33eff8Schristos #undef NPS
538*8e33eff8Schristos }
539*8e33eff8Schristos TEST_END
540*8e33eff8Schristos 
541*8e33eff8Schristos TEST_BEGIN(test_decay_now) {
542*8e33eff8Schristos 	test_skip_if(check_background_thread_enabled());
543*8e33eff8Schristos 
544*8e33eff8Schristos 	unsigned arena_ind = do_arena_create(0, 0);
545*8e33eff8Schristos 	assert_zu_eq(get_arena_pdirty(arena_ind), 0, "Unexpected dirty pages");
546*8e33eff8Schristos 	assert_zu_eq(get_arena_pmuzzy(arena_ind), 0, "Unexpected muzzy pages");
547*8e33eff8Schristos 	size_t sizes[] = {16, PAGE<<2, HUGEPAGE<<2};
548*8e33eff8Schristos 	/* Verify that dirty/muzzy pages never linger after deallocation. */
549*8e33eff8Schristos 	for (unsigned i = 0; i < sizeof(sizes)/sizeof(size_t); i++) {
550*8e33eff8Schristos 		size_t size = sizes[i];
551*8e33eff8Schristos 		generate_dirty(arena_ind, size);
552*8e33eff8Schristos 		assert_zu_eq(get_arena_pdirty(arena_ind), 0,
553*8e33eff8Schristos 		    "Unexpected dirty pages");
554*8e33eff8Schristos 		assert_zu_eq(get_arena_pmuzzy(arena_ind), 0,
555*8e33eff8Schristos 		    "Unexpected muzzy pages");
556*8e33eff8Schristos 	}
557*8e33eff8Schristos 	do_arena_destroy(arena_ind);
558*8e33eff8Schristos }
559*8e33eff8Schristos TEST_END
560*8e33eff8Schristos 
561*8e33eff8Schristos TEST_BEGIN(test_decay_never) {
562*8e33eff8Schristos 	test_skip_if(check_background_thread_enabled());
563*8e33eff8Schristos 
564*8e33eff8Schristos 	unsigned arena_ind = do_arena_create(-1, -1);
565*8e33eff8Schristos 	int flags = MALLOCX_ARENA(arena_ind) | MALLOCX_TCACHE_NONE;
566*8e33eff8Schristos 	assert_zu_eq(get_arena_pdirty(arena_ind), 0, "Unexpected dirty pages");
567*8e33eff8Schristos 	assert_zu_eq(get_arena_pmuzzy(arena_ind), 0, "Unexpected muzzy pages");
568*8e33eff8Schristos 	size_t sizes[] = {16, PAGE<<2, HUGEPAGE<<2};
569*8e33eff8Schristos 	void *ptrs[sizeof(sizes)/sizeof(size_t)];
570*8e33eff8Schristos 	for (unsigned i = 0; i < sizeof(sizes)/sizeof(size_t); i++) {
571*8e33eff8Schristos 		ptrs[i] = do_mallocx(sizes[i], flags);
572*8e33eff8Schristos 	}
573*8e33eff8Schristos 	/* Verify that each deallocation generates additional dirty pages. */
574*8e33eff8Schristos 	size_t pdirty_prev = get_arena_pdirty(arena_ind);
575*8e33eff8Schristos 	size_t pmuzzy_prev = get_arena_pmuzzy(arena_ind);
576*8e33eff8Schristos 	assert_zu_eq(pdirty_prev, 0, "Unexpected dirty pages");
577*8e33eff8Schristos 	assert_zu_eq(pmuzzy_prev, 0, "Unexpected muzzy pages");
578*8e33eff8Schristos 	for (unsigned i = 0; i < sizeof(sizes)/sizeof(size_t); i++) {
579*8e33eff8Schristos 		dallocx(ptrs[i], flags);
580*8e33eff8Schristos 		size_t pdirty = get_arena_pdirty(arena_ind);
581*8e33eff8Schristos 		size_t pmuzzy = get_arena_pmuzzy(arena_ind);
582*8e33eff8Schristos 		assert_zu_gt(pdirty, pdirty_prev,
583*8e33eff8Schristos 		    "Expected dirty pages to increase.");
584*8e33eff8Schristos 		assert_zu_eq(pmuzzy, 0, "Unexpected muzzy pages");
585*8e33eff8Schristos 		pdirty_prev = pdirty;
586*8e33eff8Schristos 	}
587*8e33eff8Schristos 	do_arena_destroy(arena_ind);
588*8e33eff8Schristos }
589*8e33eff8Schristos TEST_END
590*8e33eff8Schristos 
591*8e33eff8Schristos int
592*8e33eff8Schristos main(void) {
593*8e33eff8Schristos 	return test(
594*8e33eff8Schristos 	    test_decay_ticks,
595*8e33eff8Schristos 	    test_decay_ticker,
596*8e33eff8Schristos 	    test_decay_nonmonotonic,
597*8e33eff8Schristos 	    test_decay_now,
598*8e33eff8Schristos 	    test_decay_never);
599*8e33eff8Schristos }
600