xref: /freebsd/contrib/jemalloc/src/stats.c (revision 47606b869eb149ebb7135d6594c3b9b9f05b9aed)
1 #define JEMALLOC_STATS_C_
2 #include "jemalloc/internal/jemalloc_preamble.h"
3 #include "jemalloc/internal/jemalloc_internal_includes.h"
4 
5 #include "jemalloc/internal/assert.h"
6 #include "jemalloc/internal/ctl.h"
7 #include "jemalloc/internal/mutex.h"
8 #include "jemalloc/internal/mutex_prof.h"
9 
10 const char *global_mutex_names[mutex_prof_num_global_mutexes] = {
11 #define OP(mtx) #mtx,
12 	MUTEX_PROF_GLOBAL_MUTEXES
13 #undef OP
14 };
15 
16 const char *arena_mutex_names[mutex_prof_num_arena_mutexes] = {
17 #define OP(mtx) #mtx,
18 	MUTEX_PROF_ARENA_MUTEXES
19 #undef OP
20 };
21 
22 #define CTL_GET(n, v, t) do {						\
23 	size_t sz = sizeof(t);						\
24 	xmallctl(n, (void *)v, &sz, NULL, 0);				\
25 } while (0)
26 
27 #define CTL_M2_GET(n, i, v, t) do {					\
28 	size_t mib[CTL_MAX_DEPTH];					\
29 	size_t miblen = sizeof(mib) / sizeof(size_t);			\
30 	size_t sz = sizeof(t);						\
31 	xmallctlnametomib(n, mib, &miblen);				\
32 	mib[2] = (i);							\
33 	xmallctlbymib(mib, miblen, (void *)v, &sz, NULL, 0);		\
34 } while (0)
35 
36 #define CTL_M2_M4_GET(n, i, j, v, t) do {				\
37 	size_t mib[CTL_MAX_DEPTH];					\
38 	size_t miblen = sizeof(mib) / sizeof(size_t);			\
39 	size_t sz = sizeof(t);						\
40 	xmallctlnametomib(n, mib, &miblen);				\
41 	mib[2] = (i);							\
42 	mib[4] = (j);							\
43 	xmallctlbymib(mib, miblen, (void *)v, &sz, NULL, 0);		\
44 } while (0)
45 
46 /******************************************************************************/
47 /* Data. */
48 
49 bool opt_stats_print = false;
50 char opt_stats_print_opts[stats_print_tot_num_options+1] = "";
51 
52 /******************************************************************************/
53 
54 /* Calculate x.yyy and output a string (takes a fixed sized char array). */
55 static bool
56 get_rate_str(uint64_t dividend, uint64_t divisor, char str[6]) {
57 	if (divisor == 0 || dividend > divisor) {
58 		/* The rate is not supposed to be greater than 1. */
59 		return true;
60 	}
61 	if (dividend > 0) {
62 		assert(UINT64_MAX / dividend >= 1000);
63 	}
64 
65 	unsigned n = (unsigned)((dividend * 1000) / divisor);
66 	if (n < 10) {
67 		malloc_snprintf(str, 6, "0.00%u", n);
68 	} else if (n < 100) {
69 		malloc_snprintf(str, 6, "0.0%u", n);
70 	} else if (n < 1000) {
71 		malloc_snprintf(str, 6, "0.%u", n);
72 	} else {
73 		malloc_snprintf(str, 6, "1");
74 	}
75 
76 	return false;
77 }
78 
79 #define MUTEX_CTL_STR_MAX_LENGTH 128
80 static void
81 gen_mutex_ctl_str(char *str, size_t buf_len, const char *prefix,
82     const char *mutex, const char *counter) {
83 	malloc_snprintf(str, buf_len, "stats.%s.%s.%s", prefix, mutex, counter);
84 }
85 
86 static void
87 read_arena_bin_mutex_stats(unsigned arena_ind, unsigned bin_ind,
88     uint64_t results[mutex_prof_num_counters]) {
89 	char cmd[MUTEX_CTL_STR_MAX_LENGTH];
90 #define OP(c, t)							\
91     gen_mutex_ctl_str(cmd, MUTEX_CTL_STR_MAX_LENGTH,			\
92         "arenas.0.bins.0","mutex", #c);					\
93     CTL_M2_M4_GET(cmd, arena_ind, bin_ind,				\
94         (t *)&results[mutex_counter_##c], t);
95 MUTEX_PROF_COUNTERS
96 #undef OP
97 }
98 
99 static void
100 mutex_stats_output_json(void (*write_cb)(void *, const char *), void *cbopaque,
101     const char *name, uint64_t stats[mutex_prof_num_counters],
102     const char *json_indent, bool last) {
103 	malloc_cprintf(write_cb, cbopaque, "%s\"%s\": {\n", json_indent, name);
104 
105 	mutex_prof_counter_ind_t k = 0;
106 	char *fmt_str[2] = {"%s\t\"%s\": %"FMTu32"%s\n",
107 	    "%s\t\"%s\": %"FMTu64"%s\n"};
108 #define OP(c, t)							\
109 	malloc_cprintf(write_cb, cbopaque,				\
110 	    fmt_str[sizeof(t) / sizeof(uint32_t) - 1], 			\
111 	    json_indent, #c, (t)stats[mutex_counter_##c],		\
112 	    (++k == mutex_prof_num_counters) ? "" : ",");
113 MUTEX_PROF_COUNTERS
114 #undef OP
115 	malloc_cprintf(write_cb, cbopaque, "%s}%s\n", json_indent,
116 	    last ? "" : ",");
117 }
118 
119 static void
120 stats_arena_bins_print(void (*write_cb)(void *, const char *), void *cbopaque,
121     bool json, bool large, bool mutex, unsigned i) {
122 	size_t page;
123 	bool in_gap, in_gap_prev;
124 	unsigned nbins, j;
125 
126 	CTL_GET("arenas.page", &page, size_t);
127 
128 	CTL_GET("arenas.nbins", &nbins, unsigned);
129 	if (json) {
130 		malloc_cprintf(write_cb, cbopaque,
131 		    "\t\t\t\t\"bins\": [\n");
132 	} else {
133 		char *mutex_counters = "   n_lock_ops    n_waiting"
134 		    "   n_spin_acq  total_wait_ns  max_wait_ns\n";
135 		malloc_cprintf(write_cb, cbopaque,
136 		    "bins:           size ind    allocated      nmalloc"
137 		    "      ndalloc    nrequests      curregs     curslabs regs"
138 		    " pgs  util       nfills     nflushes     newslabs"
139 		    "      reslabs%s", mutex ? mutex_counters : "\n");
140 	}
141 	for (j = 0, in_gap = false; j < nbins; j++) {
142 		uint64_t nslabs;
143 		size_t reg_size, slab_size, curregs;
144 		size_t curslabs;
145 		uint32_t nregs;
146 		uint64_t nmalloc, ndalloc, nrequests, nfills, nflushes;
147 		uint64_t nreslabs;
148 
149 		CTL_M2_M4_GET("stats.arenas.0.bins.0.nslabs", i, j, &nslabs,
150 		    uint64_t);
151 		in_gap_prev = in_gap;
152 		in_gap = (nslabs == 0);
153 
154 		if (!json && in_gap_prev && !in_gap) {
155 			malloc_cprintf(write_cb, cbopaque,
156 			    "                     ---\n");
157 		}
158 
159 		CTL_M2_GET("arenas.bin.0.size", j, &reg_size, size_t);
160 		CTL_M2_GET("arenas.bin.0.nregs", j, &nregs, uint32_t);
161 		CTL_M2_GET("arenas.bin.0.slab_size", j, &slab_size, size_t);
162 
163 		CTL_M2_M4_GET("stats.arenas.0.bins.0.nmalloc", i, j, &nmalloc,
164 		    uint64_t);
165 		CTL_M2_M4_GET("stats.arenas.0.bins.0.ndalloc", i, j, &ndalloc,
166 		    uint64_t);
167 		CTL_M2_M4_GET("stats.arenas.0.bins.0.curregs", i, j, &curregs,
168 		    size_t);
169 		CTL_M2_M4_GET("stats.arenas.0.bins.0.nrequests", i, j,
170 		    &nrequests, uint64_t);
171 		CTL_M2_M4_GET("stats.arenas.0.bins.0.nfills", i, j, &nfills,
172 		    uint64_t);
173 		CTL_M2_M4_GET("stats.arenas.0.bins.0.nflushes", i, j, &nflushes,
174 		    uint64_t);
175 		CTL_M2_M4_GET("stats.arenas.0.bins.0.nreslabs", i, j, &nreslabs,
176 		    uint64_t);
177 		CTL_M2_M4_GET("stats.arenas.0.bins.0.curslabs", i, j, &curslabs,
178 		    size_t);
179 
180 		if (json) {
181 			malloc_cprintf(write_cb, cbopaque,
182 			    "\t\t\t\t\t{\n"
183 			    "\t\t\t\t\t\t\"nmalloc\": %"FMTu64",\n"
184 			    "\t\t\t\t\t\t\"ndalloc\": %"FMTu64",\n"
185 			    "\t\t\t\t\t\t\"curregs\": %zu,\n"
186 			    "\t\t\t\t\t\t\"nrequests\": %"FMTu64",\n"
187 			    "\t\t\t\t\t\t\"nfills\": %"FMTu64",\n"
188 			    "\t\t\t\t\t\t\"nflushes\": %"FMTu64",\n"
189 			    "\t\t\t\t\t\t\"nreslabs\": %"FMTu64",\n"
190 			    "\t\t\t\t\t\t\"curslabs\": %zu%s\n",
191 			    nmalloc, ndalloc, curregs, nrequests, nfills,
192 			    nflushes, nreslabs, curslabs, mutex ? "," : "");
193 			if (mutex) {
194 				uint64_t mutex_stats[mutex_prof_num_counters];
195 				read_arena_bin_mutex_stats(i, j, mutex_stats);
196 				mutex_stats_output_json(write_cb, cbopaque,
197 				    "mutex", mutex_stats, "\t\t\t\t\t\t", true);
198 			}
199 			malloc_cprintf(write_cb, cbopaque,
200 			    "\t\t\t\t\t}%s\n",
201 			    (j + 1 < nbins) ? "," : "");
202 		} else if (!in_gap) {
203 			size_t availregs = nregs * curslabs;
204 			char util[6];
205 			if (get_rate_str((uint64_t)curregs, (uint64_t)availregs,
206 			    util)) {
207 				if (availregs == 0) {
208 					malloc_snprintf(util, sizeof(util),
209 					    "1");
210 				} else if (curregs > availregs) {
211 					/*
212 					 * Race detected: the counters were read
213 					 * in separate mallctl calls and
214 					 * concurrent operations happened in
215 					 * between. In this case no meaningful
216 					 * utilization can be computed.
217 					 */
218 					malloc_snprintf(util, sizeof(util),
219 					    " race");
220 				} else {
221 					not_reached();
222 				}
223 			}
224 			uint64_t mutex_stats[mutex_prof_num_counters];
225 			if (mutex) {
226 				read_arena_bin_mutex_stats(i, j, mutex_stats);
227 			}
228 
229 			malloc_cprintf(write_cb, cbopaque, "%20zu %3u %12zu %12"
230 			    FMTu64" %12"FMTu64" %12"FMTu64" %12zu %12zu %4u"
231 			    " %3zu %-5s %12"FMTu64" %12"FMTu64" %12"FMTu64
232 			    " %12"FMTu64, reg_size, j, curregs * reg_size,
233 			    nmalloc, ndalloc, nrequests, curregs, curslabs,
234 			    nregs, slab_size / page, util, nfills, nflushes,
235 			    nslabs, nreslabs);
236 
237 			/* Output less info for bin mutexes to save space. */
238 			if (mutex) {
239 				malloc_cprintf(write_cb, cbopaque,
240 				    " %12"FMTu64" %12"FMTu64" %12"FMTu64
241 				    " %14"FMTu64" %12"FMTu64"\n",
242 				    mutex_stats[mutex_counter_num_ops],
243 				    mutex_stats[mutex_counter_num_wait],
244 				    mutex_stats[mutex_counter_num_spin_acq],
245 				    mutex_stats[mutex_counter_total_wait_time],
246 				    mutex_stats[mutex_counter_max_wait_time]);
247 			} else {
248 				malloc_cprintf(write_cb, cbopaque, "\n");
249 			}
250 		}
251 	}
252 	if (json) {
253 		malloc_cprintf(write_cb, cbopaque,
254 		    "\t\t\t\t]%s\n", large ? "," : "");
255 	} else {
256 		if (in_gap) {
257 			malloc_cprintf(write_cb, cbopaque,
258 			    "                     ---\n");
259 		}
260 	}
261 }
262 
263 static void
264 stats_arena_lextents_print(void (*write_cb)(void *, const char *),
265     void *cbopaque, bool json, unsigned i) {
266 	unsigned nbins, nlextents, j;
267 	bool in_gap, in_gap_prev;
268 
269 	CTL_GET("arenas.nbins", &nbins, unsigned);
270 	CTL_GET("arenas.nlextents", &nlextents, unsigned);
271 	if (json) {
272 		malloc_cprintf(write_cb, cbopaque,
273 		    "\t\t\t\t\"lextents\": [\n");
274 	} else {
275 		malloc_cprintf(write_cb, cbopaque,
276 		    "large:          size ind    allocated      nmalloc"
277 		    "      ndalloc    nrequests  curlextents\n");
278 	}
279 	for (j = 0, in_gap = false; j < nlextents; j++) {
280 		uint64_t nmalloc, ndalloc, nrequests;
281 		size_t lextent_size, curlextents;
282 
283 		CTL_M2_M4_GET("stats.arenas.0.lextents.0.nmalloc", i, j,
284 		    &nmalloc, uint64_t);
285 		CTL_M2_M4_GET("stats.arenas.0.lextents.0.ndalloc", i, j,
286 		    &ndalloc, uint64_t);
287 		CTL_M2_M4_GET("stats.arenas.0.lextents.0.nrequests", i, j,
288 		    &nrequests, uint64_t);
289 		in_gap_prev = in_gap;
290 		in_gap = (nrequests == 0);
291 
292 		if (!json && in_gap_prev && !in_gap) {
293 			malloc_cprintf(write_cb, cbopaque,
294 			    "                     ---\n");
295 		}
296 
297 		CTL_M2_GET("arenas.lextent.0.size", j, &lextent_size, size_t);
298 		CTL_M2_M4_GET("stats.arenas.0.lextents.0.curlextents", i, j,
299 		    &curlextents, size_t);
300 		if (json) {
301 			malloc_cprintf(write_cb, cbopaque,
302 			    "\t\t\t\t\t{\n"
303 			    "\t\t\t\t\t\t\"curlextents\": %zu\n"
304 			    "\t\t\t\t\t}%s\n",
305 			    curlextents,
306 			    (j + 1 < nlextents) ? "," : "");
307 		} else if (!in_gap) {
308 			malloc_cprintf(write_cb, cbopaque,
309 			    "%20zu %3u %12zu %12"FMTu64" %12"FMTu64
310 			    " %12"FMTu64" %12zu\n",
311 			    lextent_size, nbins + j,
312 			    curlextents * lextent_size, nmalloc, ndalloc,
313 			    nrequests, curlextents);
314 		}
315 	}
316 	if (json) {
317 		malloc_cprintf(write_cb, cbopaque,
318 		    "\t\t\t\t]\n");
319 	} else {
320 		if (in_gap) {
321 			malloc_cprintf(write_cb, cbopaque,
322 			    "                     ---\n");
323 		}
324 	}
325 }
326 
327 static void
328 read_arena_mutex_stats(unsigned arena_ind,
329     uint64_t results[mutex_prof_num_arena_mutexes][mutex_prof_num_counters]) {
330 	char cmd[MUTEX_CTL_STR_MAX_LENGTH];
331 
332 	mutex_prof_arena_ind_t i;
333 	for (i = 0; i < mutex_prof_num_arena_mutexes; i++) {
334 #define OP(c, t)							\
335 		gen_mutex_ctl_str(cmd, MUTEX_CTL_STR_MAX_LENGTH,	\
336 		    "arenas.0.mutexes",	arena_mutex_names[i], #c);	\
337 		CTL_M2_GET(cmd, arena_ind,				\
338 		    (t *)&results[i][mutex_counter_##c], t);
339 MUTEX_PROF_COUNTERS
340 #undef OP
341 	}
342 }
343 
344 static void
345 mutex_stats_output(void (*write_cb)(void *, const char *), void *cbopaque,
346     const char *name, uint64_t stats[mutex_prof_num_counters],
347     bool first_mutex) {
348 	if (first_mutex) {
349 		/* Print title. */
350 		malloc_cprintf(write_cb, cbopaque,
351 		    "                           n_lock_ops       n_waiting"
352 		    "      n_spin_acq  n_owner_switch   total_wait_ns"
353 		    "     max_wait_ns  max_n_thds\n");
354 	}
355 
356 	malloc_cprintf(write_cb, cbopaque, "%s", name);
357 	malloc_cprintf(write_cb, cbopaque, ":%*c",
358 	    (int)(20 - strlen(name)), ' ');
359 
360 	char *fmt_str[2] = {"%12"FMTu32, "%16"FMTu64};
361 #define OP(c, t)							\
362 	malloc_cprintf(write_cb, cbopaque,				\
363 	    fmt_str[sizeof(t) / sizeof(uint32_t) - 1],			\
364 	    (t)stats[mutex_counter_##c]);
365 MUTEX_PROF_COUNTERS
366 #undef OP
367 	malloc_cprintf(write_cb, cbopaque, "\n");
368 }
369 
370 static void
371 stats_arena_mutexes_print(void (*write_cb)(void *, const char *),
372     void *cbopaque, bool json, bool json_end, unsigned arena_ind) {
373 	uint64_t mutex_stats[mutex_prof_num_arena_mutexes][mutex_prof_num_counters];
374 	read_arena_mutex_stats(arena_ind, mutex_stats);
375 
376 	/* Output mutex stats. */
377 	if (json) {
378 		malloc_cprintf(write_cb, cbopaque, "\t\t\t\t\"mutexes\": {\n");
379 		mutex_prof_arena_ind_t i, last_mutex;
380 		last_mutex = mutex_prof_num_arena_mutexes - 1;
381 		for (i = 0; i < mutex_prof_num_arena_mutexes; i++) {
382 			mutex_stats_output_json(write_cb, cbopaque,
383 			    arena_mutex_names[i], mutex_stats[i],
384 			    "\t\t\t\t\t", (i == last_mutex));
385 		}
386 		malloc_cprintf(write_cb, cbopaque, "\t\t\t\t}%s\n",
387 		    json_end ? "" : ",");
388 	} else {
389 		mutex_prof_arena_ind_t i;
390 		for (i = 0; i < mutex_prof_num_arena_mutexes; i++) {
391 			mutex_stats_output(write_cb, cbopaque,
392 			    arena_mutex_names[i], mutex_stats[i], i == 0);
393 		}
394 	}
395 }
396 
397 static void
398 stats_arena_print(void (*write_cb)(void *, const char *), void *cbopaque,
399     bool json, unsigned i, bool bins, bool large, bool mutex) {
400 	unsigned nthreads;
401 	const char *dss;
402 	ssize_t dirty_decay_ms, muzzy_decay_ms;
403 	size_t page, pactive, pdirty, pmuzzy, mapped, retained;
404 	size_t base, internal, resident;
405 	uint64_t dirty_npurge, dirty_nmadvise, dirty_purged;
406 	uint64_t muzzy_npurge, muzzy_nmadvise, muzzy_purged;
407 	size_t small_allocated;
408 	uint64_t small_nmalloc, small_ndalloc, small_nrequests;
409 	size_t large_allocated;
410 	uint64_t large_nmalloc, large_ndalloc, large_nrequests;
411 	size_t tcache_bytes;
412 	uint64_t uptime;
413 
414 	CTL_GET("arenas.page", &page, size_t);
415 
416 	CTL_M2_GET("stats.arenas.0.nthreads", i, &nthreads, unsigned);
417 	if (json) {
418 		malloc_cprintf(write_cb, cbopaque,
419 		    "\t\t\t\t\"nthreads\": %u,\n", nthreads);
420 	} else {
421 		malloc_cprintf(write_cb, cbopaque,
422 		    "assigned threads: %u\n", nthreads);
423 	}
424 
425 	CTL_M2_GET("stats.arenas.0.uptime", i, &uptime, uint64_t);
426 	if (json) {
427 		malloc_cprintf(write_cb, cbopaque,
428 		    "\t\t\t\t\"uptime_ns\": %"FMTu64",\n", uptime);
429 	} else {
430 		malloc_cprintf(write_cb, cbopaque,
431 		    "uptime: %"FMTu64"\n", uptime);
432 	}
433 
434 	CTL_M2_GET("stats.arenas.0.dss", i, &dss, const char *);
435 	if (json) {
436 		malloc_cprintf(write_cb, cbopaque,
437 		    "\t\t\t\t\"dss\": \"%s\",\n", dss);
438 	} else {
439 		malloc_cprintf(write_cb, cbopaque,
440 		    "dss allocation precedence: %s\n", dss);
441 	}
442 
443 	CTL_M2_GET("stats.arenas.0.dirty_decay_ms", i, &dirty_decay_ms,
444 	    ssize_t);
445 	CTL_M2_GET("stats.arenas.0.muzzy_decay_ms", i, &muzzy_decay_ms,
446 	    ssize_t);
447 	CTL_M2_GET("stats.arenas.0.pactive", i, &pactive, size_t);
448 	CTL_M2_GET("stats.arenas.0.pdirty", i, &pdirty, size_t);
449 	CTL_M2_GET("stats.arenas.0.pmuzzy", i, &pmuzzy, size_t);
450 	CTL_M2_GET("stats.arenas.0.dirty_npurge", i, &dirty_npurge, uint64_t);
451 	CTL_M2_GET("stats.arenas.0.dirty_nmadvise", i, &dirty_nmadvise,
452 	    uint64_t);
453 	CTL_M2_GET("stats.arenas.0.dirty_purged", i, &dirty_purged, uint64_t);
454 	CTL_M2_GET("stats.arenas.0.muzzy_npurge", i, &muzzy_npurge, uint64_t);
455 	CTL_M2_GET("stats.arenas.0.muzzy_nmadvise", i, &muzzy_nmadvise,
456 	    uint64_t);
457 	CTL_M2_GET("stats.arenas.0.muzzy_purged", i, &muzzy_purged, uint64_t);
458 	if (json) {
459 		malloc_cprintf(write_cb, cbopaque,
460 		    "\t\t\t\t\"dirty_decay_ms\": %zd,\n", dirty_decay_ms);
461 		malloc_cprintf(write_cb, cbopaque,
462 		    "\t\t\t\t\"muzzy_decay_ms\": %zd,\n", muzzy_decay_ms);
463 		malloc_cprintf(write_cb, cbopaque,
464 		    "\t\t\t\t\"pactive\": %zu,\n", pactive);
465 		malloc_cprintf(write_cb, cbopaque,
466 		    "\t\t\t\t\"pdirty\": %zu,\n", pdirty);
467 		malloc_cprintf(write_cb, cbopaque,
468 		    "\t\t\t\t\"pmuzzy\": %zu,\n", pmuzzy);
469 		malloc_cprintf(write_cb, cbopaque,
470 		    "\t\t\t\t\"dirty_npurge\": %"FMTu64",\n", dirty_npurge);
471 		malloc_cprintf(write_cb, cbopaque,
472 		    "\t\t\t\t\"dirty_nmadvise\": %"FMTu64",\n", dirty_nmadvise);
473 		malloc_cprintf(write_cb, cbopaque,
474 		    "\t\t\t\t\"dirty_purged\": %"FMTu64",\n", dirty_purged);
475 		malloc_cprintf(write_cb, cbopaque,
476 		    "\t\t\t\t\"muzzy_npurge\": %"FMTu64",\n", muzzy_npurge);
477 		malloc_cprintf(write_cb, cbopaque,
478 		    "\t\t\t\t\"muzzy_nmadvise\": %"FMTu64",\n", muzzy_nmadvise);
479 		malloc_cprintf(write_cb, cbopaque,
480 		    "\t\t\t\t\"muzzy_purged\": %"FMTu64",\n", muzzy_purged);
481 	} else {
482 		malloc_cprintf(write_cb, cbopaque,
483 		    "decaying:  time       npages       sweeps     madvises"
484 		    "       purged\n");
485 		if (dirty_decay_ms >= 0) {
486 			malloc_cprintf(write_cb, cbopaque,
487 			    "   dirty: %5zd %12zu %12"FMTu64" %12"FMTu64" %12"
488 			    FMTu64"\n", dirty_decay_ms, pdirty, dirty_npurge,
489 			    dirty_nmadvise, dirty_purged);
490 		} else {
491 			malloc_cprintf(write_cb, cbopaque,
492 			    "   dirty:   N/A %12zu %12"FMTu64" %12"FMTu64" %12"
493 			    FMTu64"\n", pdirty, dirty_npurge, dirty_nmadvise,
494 			    dirty_purged);
495 		}
496 		if (muzzy_decay_ms >= 0) {
497 			malloc_cprintf(write_cb, cbopaque,
498 			    "   muzzy: %5zd %12zu %12"FMTu64" %12"FMTu64" %12"
499 			    FMTu64"\n", muzzy_decay_ms, pmuzzy, muzzy_npurge,
500 			    muzzy_nmadvise, muzzy_purged);
501 		} else {
502 			malloc_cprintf(write_cb, cbopaque,
503 			    "   muzzy:   N/A %12zu %12"FMTu64" %12"FMTu64" %12"
504 			    FMTu64"\n", pmuzzy, muzzy_npurge, muzzy_nmadvise,
505 			    muzzy_purged);
506 		}
507 	}
508 
509 	CTL_M2_GET("stats.arenas.0.small.allocated", i, &small_allocated,
510 	    size_t);
511 	CTL_M2_GET("stats.arenas.0.small.nmalloc", i, &small_nmalloc, uint64_t);
512 	CTL_M2_GET("stats.arenas.0.small.ndalloc", i, &small_ndalloc, uint64_t);
513 	CTL_M2_GET("stats.arenas.0.small.nrequests", i, &small_nrequests,
514 	    uint64_t);
515 	if (json) {
516 		malloc_cprintf(write_cb, cbopaque,
517 		    "\t\t\t\t\"small\": {\n");
518 
519 		malloc_cprintf(write_cb, cbopaque,
520 		    "\t\t\t\t\t\"allocated\": %zu,\n", small_allocated);
521 		malloc_cprintf(write_cb, cbopaque,
522 		    "\t\t\t\t\t\"nmalloc\": %"FMTu64",\n", small_nmalloc);
523 		malloc_cprintf(write_cb, cbopaque,
524 		    "\t\t\t\t\t\"ndalloc\": %"FMTu64",\n", small_ndalloc);
525 		malloc_cprintf(write_cb, cbopaque,
526 		    "\t\t\t\t\t\"nrequests\": %"FMTu64"\n", small_nrequests);
527 
528 		malloc_cprintf(write_cb, cbopaque,
529 		    "\t\t\t\t},\n");
530 	} else {
531 		malloc_cprintf(write_cb, cbopaque,
532 		    "                            allocated      nmalloc"
533 		    "      ndalloc    nrequests\n");
534 		malloc_cprintf(write_cb, cbopaque,
535 		    "small:                   %12zu %12"FMTu64" %12"FMTu64
536 		    " %12"FMTu64"\n",
537 		    small_allocated, small_nmalloc, small_ndalloc,
538 		    small_nrequests);
539 	}
540 
541 	CTL_M2_GET("stats.arenas.0.large.allocated", i, &large_allocated,
542 	    size_t);
543 	CTL_M2_GET("stats.arenas.0.large.nmalloc", i, &large_nmalloc, uint64_t);
544 	CTL_M2_GET("stats.arenas.0.large.ndalloc", i, &large_ndalloc, uint64_t);
545 	CTL_M2_GET("stats.arenas.0.large.nrequests", i, &large_nrequests,
546 	    uint64_t);
547 	if (json) {
548 		malloc_cprintf(write_cb, cbopaque,
549 		    "\t\t\t\t\"large\": {\n");
550 
551 		malloc_cprintf(write_cb, cbopaque,
552 		    "\t\t\t\t\t\"allocated\": %zu,\n", large_allocated);
553 		malloc_cprintf(write_cb, cbopaque,
554 		    "\t\t\t\t\t\"nmalloc\": %"FMTu64",\n", large_nmalloc);
555 		malloc_cprintf(write_cb, cbopaque,
556 		    "\t\t\t\t\t\"ndalloc\": %"FMTu64",\n", large_ndalloc);
557 		malloc_cprintf(write_cb, cbopaque,
558 		    "\t\t\t\t\t\"nrequests\": %"FMTu64"\n", large_nrequests);
559 
560 		malloc_cprintf(write_cb, cbopaque,
561 		    "\t\t\t\t},\n");
562 	} else {
563 		malloc_cprintf(write_cb, cbopaque,
564 		    "large:                   %12zu %12"FMTu64" %12"FMTu64
565 		    " %12"FMTu64"\n",
566 		    large_allocated, large_nmalloc, large_ndalloc,
567 		    large_nrequests);
568 		malloc_cprintf(write_cb, cbopaque,
569 		    "total:                   %12zu %12"FMTu64" %12"FMTu64
570 		    " %12"FMTu64"\n",
571 		    small_allocated + large_allocated, small_nmalloc +
572 		    large_nmalloc, small_ndalloc + large_ndalloc,
573 		    small_nrequests + large_nrequests);
574 	}
575 	if (!json) {
576 		malloc_cprintf(write_cb, cbopaque,
577 		    "active:                  %12zu\n", pactive * page);
578 	}
579 
580 	CTL_M2_GET("stats.arenas.0.mapped", i, &mapped, size_t);
581 	if (json) {
582 		malloc_cprintf(write_cb, cbopaque,
583 		    "\t\t\t\t\"mapped\": %zu,\n", mapped);
584 	} else {
585 		malloc_cprintf(write_cb, cbopaque,
586 		    "mapped:                  %12zu\n", mapped);
587 	}
588 
589 	CTL_M2_GET("stats.arenas.0.retained", i, &retained, size_t);
590 	if (json) {
591 		malloc_cprintf(write_cb, cbopaque,
592 		    "\t\t\t\t\"retained\": %zu,\n", retained);
593 	} else {
594 		malloc_cprintf(write_cb, cbopaque,
595 		    "retained:                %12zu\n", retained);
596 	}
597 
598 	CTL_M2_GET("stats.arenas.0.base", i, &base, size_t);
599 	if (json) {
600 		malloc_cprintf(write_cb, cbopaque,
601 		    "\t\t\t\t\"base\": %zu,\n", base);
602 	} else {
603 		malloc_cprintf(write_cb, cbopaque,
604 		    "base:                    %12zu\n", base);
605 	}
606 
607 	CTL_M2_GET("stats.arenas.0.internal", i, &internal, size_t);
608 	if (json) {
609 		malloc_cprintf(write_cb, cbopaque,
610 		    "\t\t\t\t\"internal\": %zu,\n", internal);
611 	} else {
612 		malloc_cprintf(write_cb, cbopaque,
613 		    "internal:                %12zu\n", internal);
614 	}
615 
616 	CTL_M2_GET("stats.arenas.0.tcache_bytes", i, &tcache_bytes, size_t);
617 	if (json) {
618 		malloc_cprintf(write_cb, cbopaque,
619 		    "\t\t\t\t\"tcache\": %zu,\n", tcache_bytes);
620 	} else {
621 		malloc_cprintf(write_cb, cbopaque,
622 		    "tcache:                  %12zu\n", tcache_bytes);
623 	}
624 
625 	CTL_M2_GET("stats.arenas.0.resident", i, &resident, size_t);
626 	if (json) {
627 		malloc_cprintf(write_cb, cbopaque,
628 		    "\t\t\t\t\"resident\": %zu%s\n", resident,
629 		    (bins || large || mutex) ? "," : "");
630 	} else {
631 		malloc_cprintf(write_cb, cbopaque,
632 		    "resident:                %12zu\n", resident);
633 	}
634 
635 	if (mutex) {
636 		stats_arena_mutexes_print(write_cb, cbopaque, json,
637 		    !(bins || large), i);
638 	}
639 	if (bins) {
640 		stats_arena_bins_print(write_cb, cbopaque, json, large, mutex,
641 		    i);
642 	}
643 	if (large) {
644 		stats_arena_lextents_print(write_cb, cbopaque, json, i);
645 	}
646 }
647 
648 static void
649 stats_general_print(void (*write_cb)(void *, const char *), void *cbopaque,
650     bool json, bool more) {
651 	const char *cpv;
652 	bool bv;
653 	unsigned uv;
654 	uint32_t u32v;
655 	uint64_t u64v;
656 	ssize_t ssv;
657 	size_t sv, bsz, usz, ssz, sssz, cpsz;
658 
659 	bsz = sizeof(bool);
660 	usz = sizeof(unsigned);
661 	ssz = sizeof(size_t);
662 	sssz = sizeof(ssize_t);
663 	cpsz = sizeof(const char *);
664 
665 	CTL_GET("version", &cpv, const char *);
666 	if (json) {
667 		malloc_cprintf(write_cb, cbopaque,
668 		"\t\t\"version\": \"%s\",\n", cpv);
669 	} else {
670 		malloc_cprintf(write_cb, cbopaque, "Version: %s\n", cpv);
671 	}
672 
673 	/* config. */
674 #define CONFIG_WRITE_BOOL_JSON(n, c)					\
675 	if (json) {							\
676 		CTL_GET("config."#n, &bv, bool);			\
677 		malloc_cprintf(write_cb, cbopaque,			\
678 		    "\t\t\t\""#n"\": %s%s\n", bv ? "true" : "false",	\
679 		    (c));						\
680 	}
681 
682 	if (json) {
683 		malloc_cprintf(write_cb, cbopaque,
684 		    "\t\t\"config\": {\n");
685 	}
686 
687 	CONFIG_WRITE_BOOL_JSON(cache_oblivious, ",")
688 
689 	CTL_GET("config.debug", &bv, bool);
690 	if (json) {
691 		malloc_cprintf(write_cb, cbopaque,
692 		    "\t\t\t\"debug\": %s,\n", bv ? "true" : "false");
693 	} else {
694 		malloc_cprintf(write_cb, cbopaque, "Assertions %s\n",
695 		    bv ? "enabled" : "disabled");
696 	}
697 
698 	CONFIG_WRITE_BOOL_JSON(fill, ",")
699 	CONFIG_WRITE_BOOL_JSON(lazy_lock, ",")
700 
701 	if (json) {
702 		malloc_cprintf(write_cb, cbopaque,
703 		    "\t\t\t\"malloc_conf\": \"%s\",\n",
704 		    config_malloc_conf);
705 	} else {
706 		malloc_cprintf(write_cb, cbopaque,
707 		    "config.malloc_conf: \"%s\"\n", config_malloc_conf);
708 	}
709 
710 	CONFIG_WRITE_BOOL_JSON(prof, ",")
711 	CONFIG_WRITE_BOOL_JSON(prof_libgcc, ",")
712 	CONFIG_WRITE_BOOL_JSON(prof_libunwind, ",")
713 	CONFIG_WRITE_BOOL_JSON(stats, ",")
714 	CONFIG_WRITE_BOOL_JSON(thp, ",")
715 	CONFIG_WRITE_BOOL_JSON(utrace, ",")
716 	CONFIG_WRITE_BOOL_JSON(xmalloc, "")
717 
718 	if (json) {
719 		malloc_cprintf(write_cb, cbopaque,
720 		    "\t\t},\n");
721 	}
722 #undef CONFIG_WRITE_BOOL_JSON
723 
724 	/* opt. */
725 #define OPT_WRITE_BOOL(n, c)						\
726 	if (je_mallctl("opt."#n, (void *)&bv, &bsz, NULL, 0) == 0) {	\
727 		if (json) {						\
728 			malloc_cprintf(write_cb, cbopaque,		\
729 			    "\t\t\t\""#n"\": %s%s\n", bv ? "true" :	\
730 			    "false", (c));				\
731 		} else {						\
732 			malloc_cprintf(write_cb, cbopaque,		\
733 			    "  opt."#n": %s\n", bv ? "true" : "false");	\
734 		}							\
735 	}
736 #define OPT_WRITE_BOOL_MUTABLE(n, m, c) {				\
737 	bool bv2;							\
738 	if (je_mallctl("opt."#n, (void *)&bv, &bsz, NULL, 0) == 0 &&	\
739 	    je_mallctl(#m, (void *)&bv2, &bsz, NULL, 0) == 0) {		\
740 		if (json) {						\
741 			malloc_cprintf(write_cb, cbopaque,		\
742 			    "\t\t\t\""#n"\": %s%s\n", bv ? "true" :	\
743 			    "false", (c));				\
744 		} else {						\
745 			malloc_cprintf(write_cb, cbopaque,		\
746 			    "  opt."#n": %s ("#m": %s)\n", bv ? "true"	\
747 			    : "false", bv2 ? "true" : "false");		\
748 		}							\
749 	}								\
750 }
751 #define OPT_WRITE_UNSIGNED(n, c)					\
752 	if (je_mallctl("opt."#n, (void *)&uv, &usz, NULL, 0) == 0) {	\
753 		if (json) {						\
754 			malloc_cprintf(write_cb, cbopaque,		\
755 			    "\t\t\t\""#n"\": %u%s\n", uv, (c));		\
756 		} else {						\
757 			malloc_cprintf(write_cb, cbopaque,		\
758 			"  opt."#n": %u\n", uv);			\
759 		}							\
760 	}
761 #define OPT_WRITE_SSIZE_T(n, c)						\
762 	if (je_mallctl("opt."#n, (void *)&ssv, &sssz, NULL, 0) == 0) {	\
763 		if (json) {						\
764 			malloc_cprintf(write_cb, cbopaque,		\
765 			    "\t\t\t\""#n"\": %zd%s\n", ssv, (c));	\
766 		} else {						\
767 			malloc_cprintf(write_cb, cbopaque,		\
768 			    "  opt."#n": %zd\n", ssv);			\
769 		}							\
770 	}
771 #define OPT_WRITE_SSIZE_T_MUTABLE(n, m, c) {				\
772 	ssize_t ssv2;							\
773 	if (je_mallctl("opt."#n, (void *)&ssv, &sssz, NULL, 0) == 0 &&	\
774 	    je_mallctl(#m, (void *)&ssv2, &sssz, NULL, 0) == 0) {	\
775 		if (json) {						\
776 			malloc_cprintf(write_cb, cbopaque,		\
777 			    "\t\t\t\""#n"\": %zd%s\n", ssv, (c));	\
778 		} else {						\
779 			malloc_cprintf(write_cb, cbopaque,		\
780 			    "  opt."#n": %zd ("#m": %zd)\n",		\
781 			    ssv, ssv2);					\
782 		}							\
783 	}								\
784 }
785 #define OPT_WRITE_CHAR_P(n, c)						\
786 	if (je_mallctl("opt."#n, (void *)&cpv, &cpsz, NULL, 0) == 0) {	\
787 		if (json) {						\
788 			malloc_cprintf(write_cb, cbopaque,		\
789 			    "\t\t\t\""#n"\": \"%s\"%s\n", cpv, (c));	\
790 		} else {						\
791 			malloc_cprintf(write_cb, cbopaque,		\
792 			    "  opt."#n": \"%s\"\n", cpv);		\
793 		}							\
794 	}
795 
796 	if (json) {
797 		malloc_cprintf(write_cb, cbopaque,
798 		    "\t\t\"opt\": {\n");
799 	} else {
800 		malloc_cprintf(write_cb, cbopaque,
801 		    "Run-time option settings:\n");
802 	}
803 	OPT_WRITE_BOOL(abort, ",")
804 	OPT_WRITE_BOOL(abort_conf, ",")
805 	OPT_WRITE_BOOL(retain, ",")
806 	OPT_WRITE_CHAR_P(dss, ",")
807 	OPT_WRITE_UNSIGNED(narenas, ",")
808 	OPT_WRITE_CHAR_P(percpu_arena, ",")
809 	OPT_WRITE_BOOL_MUTABLE(background_thread, background_thread, ",")
810 	OPT_WRITE_SSIZE_T_MUTABLE(dirty_decay_ms, arenas.dirty_decay_ms, ",")
811 	OPT_WRITE_SSIZE_T_MUTABLE(muzzy_decay_ms, arenas.muzzy_decay_ms, ",")
812 	OPT_WRITE_CHAR_P(junk, ",")
813 	OPT_WRITE_BOOL(zero, ",")
814 	OPT_WRITE_BOOL(utrace, ",")
815 	OPT_WRITE_BOOL(xmalloc, ",")
816 	OPT_WRITE_BOOL(tcache, ",")
817 	OPT_WRITE_SSIZE_T(lg_tcache_max, ",")
818 	OPT_WRITE_BOOL(prof, ",")
819 	OPT_WRITE_CHAR_P(prof_prefix, ",")
820 	OPT_WRITE_BOOL_MUTABLE(prof_active, prof.active, ",")
821 	OPT_WRITE_BOOL_MUTABLE(prof_thread_active_init, prof.thread_active_init,
822 	    ",")
823 	OPT_WRITE_SSIZE_T_MUTABLE(lg_prof_sample, prof.lg_sample, ",")
824 	OPT_WRITE_BOOL(prof_accum, ",")
825 	OPT_WRITE_SSIZE_T(lg_prof_interval, ",")
826 	OPT_WRITE_BOOL(prof_gdump, ",")
827 	OPT_WRITE_BOOL(prof_final, ",")
828 	OPT_WRITE_BOOL(prof_leak, ",")
829 	OPT_WRITE_BOOL(stats_print, ",")
830 	if (json || opt_stats_print) {
831 		/*
832 		 * stats_print_opts is always emitted for JSON, so as long as it
833 		 * comes last it's safe to unconditionally omit the comma here
834 		 * (rather than having to conditionally omit it elsewhere
835 		 * depending on configuration).
836 		 */
837 		OPT_WRITE_CHAR_P(stats_print_opts, "")
838 	}
839 	if (json) {
840 		malloc_cprintf(write_cb, cbopaque,
841 		    "\t\t},\n");
842 	}
843 
844 #undef OPT_WRITE_BOOL
845 #undef OPT_WRITE_BOOL_MUTABLE
846 #undef OPT_WRITE_SSIZE_T
847 #undef OPT_WRITE_CHAR_P
848 
849 	/* arenas. */
850 	if (json) {
851 		malloc_cprintf(write_cb, cbopaque,
852 		    "\t\t\"arenas\": {\n");
853 	}
854 
855 	CTL_GET("arenas.narenas", &uv, unsigned);
856 	if (json) {
857 		malloc_cprintf(write_cb, cbopaque,
858 		    "\t\t\t\"narenas\": %u,\n", uv);
859 	} else {
860 		malloc_cprintf(write_cb, cbopaque, "Arenas: %u\n", uv);
861 	}
862 
863 	if (json) {
864 		CTL_GET("arenas.dirty_decay_ms", &ssv, ssize_t);
865 		malloc_cprintf(write_cb, cbopaque,
866 		    "\t\t\t\"dirty_decay_ms\": %zd,\n", ssv);
867 
868 		CTL_GET("arenas.muzzy_decay_ms", &ssv, ssize_t);
869 		malloc_cprintf(write_cb, cbopaque,
870 		    "\t\t\t\"muzzy_decay_ms\": %zd,\n", ssv);
871 	}
872 
873 	CTL_GET("arenas.quantum", &sv, size_t);
874 	if (json) {
875 		malloc_cprintf(write_cb, cbopaque,
876 		    "\t\t\t\"quantum\": %zu,\n", sv);
877 	} else {
878 		malloc_cprintf(write_cb, cbopaque, "Quantum size: %zu\n", sv);
879 	}
880 
881 	CTL_GET("arenas.page", &sv, size_t);
882 	if (json) {
883 		malloc_cprintf(write_cb, cbopaque,
884 		    "\t\t\t\"page\": %zu,\n", sv);
885 	} else {
886 		malloc_cprintf(write_cb, cbopaque, "Page size: %zu\n", sv);
887 	}
888 
889 	if (je_mallctl("arenas.tcache_max", (void *)&sv, &ssz, NULL, 0) == 0) {
890 		if (json) {
891 			malloc_cprintf(write_cb, cbopaque,
892 			    "\t\t\t\"tcache_max\": %zu,\n", sv);
893 		} else {
894 			malloc_cprintf(write_cb, cbopaque,
895 			    "Maximum thread-cached size class: %zu\n", sv);
896 		}
897 	}
898 
899 	if (json) {
900 		unsigned nbins, nlextents, i;
901 
902 		CTL_GET("arenas.nbins", &nbins, unsigned);
903 		malloc_cprintf(write_cb, cbopaque,
904 		    "\t\t\t\"nbins\": %u,\n", nbins);
905 
906 		CTL_GET("arenas.nhbins", &uv, unsigned);
907 		malloc_cprintf(write_cb, cbopaque, "\t\t\t\"nhbins\": %u,\n",
908 		    uv);
909 
910 		malloc_cprintf(write_cb, cbopaque,
911 		    "\t\t\t\"bin\": [\n");
912 		for (i = 0; i < nbins; i++) {
913 			malloc_cprintf(write_cb, cbopaque,
914 			    "\t\t\t\t{\n");
915 
916 			CTL_M2_GET("arenas.bin.0.size", i, &sv, size_t);
917 			malloc_cprintf(write_cb, cbopaque,
918 			    "\t\t\t\t\t\"size\": %zu,\n", sv);
919 
920 			CTL_M2_GET("arenas.bin.0.nregs", i, &u32v, uint32_t);
921 			malloc_cprintf(write_cb, cbopaque,
922 			    "\t\t\t\t\t\"nregs\": %"FMTu32",\n", u32v);
923 
924 			CTL_M2_GET("arenas.bin.0.slab_size", i, &sv, size_t);
925 			malloc_cprintf(write_cb, cbopaque,
926 			    "\t\t\t\t\t\"slab_size\": %zu\n", sv);
927 
928 			malloc_cprintf(write_cb, cbopaque,
929 			    "\t\t\t\t}%s\n", (i + 1 < nbins) ? "," : "");
930 		}
931 		malloc_cprintf(write_cb, cbopaque,
932 		    "\t\t\t],\n");
933 
934 		CTL_GET("arenas.nlextents", &nlextents, unsigned);
935 		malloc_cprintf(write_cb, cbopaque,
936 		    "\t\t\t\"nlextents\": %u,\n", nlextents);
937 
938 		malloc_cprintf(write_cb, cbopaque,
939 		    "\t\t\t\"lextent\": [\n");
940 		for (i = 0; i < nlextents; i++) {
941 			malloc_cprintf(write_cb, cbopaque,
942 			    "\t\t\t\t{\n");
943 
944 			CTL_M2_GET("arenas.lextent.0.size", i, &sv, size_t);
945 			malloc_cprintf(write_cb, cbopaque,
946 			    "\t\t\t\t\t\"size\": %zu\n", sv);
947 
948 			malloc_cprintf(write_cb, cbopaque,
949 			    "\t\t\t\t}%s\n", (i + 1 < nlextents) ? "," : "");
950 		}
951 		malloc_cprintf(write_cb, cbopaque,
952 		    "\t\t\t]\n");
953 
954 		malloc_cprintf(write_cb, cbopaque,
955 		    "\t\t}%s\n", (config_prof || more) ? "," : "");
956 	}
957 
958 	/* prof. */
959 	if (config_prof && json) {
960 		malloc_cprintf(write_cb, cbopaque,
961 		    "\t\t\"prof\": {\n");
962 
963 		CTL_GET("prof.thread_active_init", &bv, bool);
964 		malloc_cprintf(write_cb, cbopaque,
965 		    "\t\t\t\"thread_active_init\": %s,\n", bv ? "true" :
966 		    "false");
967 
968 		CTL_GET("prof.active", &bv, bool);
969 		malloc_cprintf(write_cb, cbopaque,
970 		    "\t\t\t\"active\": %s,\n", bv ? "true" : "false");
971 
972 		CTL_GET("prof.gdump", &bv, bool);
973 		malloc_cprintf(write_cb, cbopaque,
974 		    "\t\t\t\"gdump\": %s,\n", bv ? "true" : "false");
975 
976 		CTL_GET("prof.interval", &u64v, uint64_t);
977 		malloc_cprintf(write_cb, cbopaque,
978 		    "\t\t\t\"interval\": %"FMTu64",\n", u64v);
979 
980 		CTL_GET("prof.lg_sample", &ssv, ssize_t);
981 		malloc_cprintf(write_cb, cbopaque,
982 		    "\t\t\t\"lg_sample\": %zd\n", ssv);
983 
984 		malloc_cprintf(write_cb, cbopaque,
985 		    "\t\t}%s\n", more ? "," : "");
986 	}
987 }
988 
989 static void
990 read_global_mutex_stats(
991     uint64_t results[mutex_prof_num_global_mutexes][mutex_prof_num_counters]) {
992 	char cmd[MUTEX_CTL_STR_MAX_LENGTH];
993 
994 	mutex_prof_global_ind_t i;
995 	for (i = 0; i < mutex_prof_num_global_mutexes; i++) {
996 #define OP(c, t)							\
997 		gen_mutex_ctl_str(cmd, MUTEX_CTL_STR_MAX_LENGTH,	\
998 		    "mutexes", global_mutex_names[i], #c);		\
999 		CTL_GET(cmd, (t *)&results[i][mutex_counter_##c], t);
1000 MUTEX_PROF_COUNTERS
1001 #undef OP
1002 	}
1003 }
1004 
1005 static void
1006 stats_print_helper(void (*write_cb)(void *, const char *), void *cbopaque,
1007     bool json, bool merged, bool destroyed, bool unmerged, bool bins,
1008     bool large, bool mutex) {
1009 	size_t allocated, active, metadata, resident, mapped, retained;
1010 	size_t num_background_threads;
1011 	uint64_t background_thread_num_runs, background_thread_run_interval;
1012 
1013 	CTL_GET("stats.allocated", &allocated, size_t);
1014 	CTL_GET("stats.active", &active, size_t);
1015 	CTL_GET("stats.metadata", &metadata, size_t);
1016 	CTL_GET("stats.resident", &resident, size_t);
1017 	CTL_GET("stats.mapped", &mapped, size_t);
1018 	CTL_GET("stats.retained", &retained, size_t);
1019 
1020 	uint64_t mutex_stats[mutex_prof_num_global_mutexes][mutex_prof_num_counters];
1021 	if (mutex) {
1022 		read_global_mutex_stats(mutex_stats);
1023 	}
1024 
1025 	if (have_background_thread) {
1026 		CTL_GET("stats.background_thread.num_threads",
1027 		    &num_background_threads, size_t);
1028 		CTL_GET("stats.background_thread.num_runs",
1029 		    &background_thread_num_runs, uint64_t);
1030 		CTL_GET("stats.background_thread.run_interval",
1031 		    &background_thread_run_interval, uint64_t);
1032 	} else {
1033 		num_background_threads = 0;
1034 		background_thread_num_runs = 0;
1035 		background_thread_run_interval = 0;
1036 	}
1037 
1038 	if (json) {
1039 		malloc_cprintf(write_cb, cbopaque,
1040 		    "\t\t\"stats\": {\n");
1041 
1042 		malloc_cprintf(write_cb, cbopaque,
1043 		    "\t\t\t\"allocated\": %zu,\n", allocated);
1044 		malloc_cprintf(write_cb, cbopaque,
1045 		    "\t\t\t\"active\": %zu,\n", active);
1046 		malloc_cprintf(write_cb, cbopaque,
1047 		    "\t\t\t\"metadata\": %zu,\n", metadata);
1048 		malloc_cprintf(write_cb, cbopaque,
1049 		    "\t\t\t\"resident\": %zu,\n", resident);
1050 		malloc_cprintf(write_cb, cbopaque,
1051 		    "\t\t\t\"mapped\": %zu,\n", mapped);
1052 		malloc_cprintf(write_cb, cbopaque,
1053 		    "\t\t\t\"retained\": %zu,\n", retained);
1054 
1055 		malloc_cprintf(write_cb, cbopaque,
1056 		    "\t\t\t\"background_thread\": {\n");
1057 		malloc_cprintf(write_cb, cbopaque,
1058 		    "\t\t\t\t\"num_threads\": %zu,\n", num_background_threads);
1059 		malloc_cprintf(write_cb, cbopaque,
1060 		    "\t\t\t\t\"num_runs\": %"FMTu64",\n",
1061 		    background_thread_num_runs);
1062 		malloc_cprintf(write_cb, cbopaque,
1063 		    "\t\t\t\t\"run_interval\": %"FMTu64"\n",
1064 		    background_thread_run_interval);
1065 		malloc_cprintf(write_cb, cbopaque, "\t\t\t}%s\n",
1066 		    mutex ? "," : "");
1067 
1068 		if (mutex) {
1069 			malloc_cprintf(write_cb, cbopaque,
1070 			    "\t\t\t\"mutexes\": {\n");
1071 			mutex_prof_global_ind_t i;
1072 			for (i = 0; i < mutex_prof_num_global_mutexes; i++) {
1073 				mutex_stats_output_json(write_cb, cbopaque,
1074 				    global_mutex_names[i], mutex_stats[i],
1075 				    "\t\t\t\t",
1076 				    i == mutex_prof_num_global_mutexes - 1);
1077 			}
1078 			malloc_cprintf(write_cb, cbopaque, "\t\t\t}\n");
1079 		}
1080 		malloc_cprintf(write_cb, cbopaque,
1081 		    "\t\t}%s\n", (merged || unmerged || destroyed) ? "," : "");
1082 	} else {
1083 		malloc_cprintf(write_cb, cbopaque,
1084 		    "Allocated: %zu, active: %zu, metadata: %zu,"
1085 		    " resident: %zu, mapped: %zu, retained: %zu\n",
1086 		    allocated, active, metadata, resident, mapped, retained);
1087 
1088 		if (have_background_thread && num_background_threads > 0) {
1089 			malloc_cprintf(write_cb, cbopaque,
1090 			    "Background threads: %zu, num_runs: %"FMTu64", "
1091 			    "run_interval: %"FMTu64" ns\n",
1092 			    num_background_threads,
1093 			    background_thread_num_runs,
1094 			    background_thread_run_interval);
1095 		}
1096 		if (mutex) {
1097 			mutex_prof_global_ind_t i;
1098 			for (i = 0; i < mutex_prof_num_global_mutexes; i++) {
1099 				mutex_stats_output(write_cb, cbopaque,
1100 				    global_mutex_names[i], mutex_stats[i],
1101 				    i == 0);
1102 			}
1103 		}
1104 	}
1105 
1106 	if (merged || destroyed || unmerged) {
1107 		unsigned narenas;
1108 
1109 		if (json) {
1110 			malloc_cprintf(write_cb, cbopaque,
1111 			    "\t\t\"stats.arenas\": {\n");
1112 		}
1113 
1114 		CTL_GET("arenas.narenas", &narenas, unsigned);
1115 		{
1116 			size_t mib[3];
1117 			size_t miblen = sizeof(mib) / sizeof(size_t);
1118 			size_t sz;
1119 			VARIABLE_ARRAY(bool, initialized, narenas);
1120 			bool destroyed_initialized;
1121 			unsigned i, j, ninitialized;
1122 
1123 			xmallctlnametomib("arena.0.initialized", mib, &miblen);
1124 			for (i = ninitialized = 0; i < narenas; i++) {
1125 				mib[1] = i;
1126 				sz = sizeof(bool);
1127 				xmallctlbymib(mib, miblen, &initialized[i], &sz,
1128 				    NULL, 0);
1129 				if (initialized[i]) {
1130 					ninitialized++;
1131 				}
1132 			}
1133 			mib[1] = MALLCTL_ARENAS_DESTROYED;
1134 			sz = sizeof(bool);
1135 			xmallctlbymib(mib, miblen, &destroyed_initialized, &sz,
1136 			    NULL, 0);
1137 
1138 			/* Merged stats. */
1139 			if (merged && (ninitialized > 1 || !unmerged)) {
1140 				/* Print merged arena stats. */
1141 				if (json) {
1142 					malloc_cprintf(write_cb, cbopaque,
1143 					    "\t\t\t\"merged\": {\n");
1144 				} else {
1145 					malloc_cprintf(write_cb, cbopaque,
1146 					    "\nMerged arenas stats:\n");
1147 				}
1148 				stats_arena_print(write_cb, cbopaque, json,
1149 				    MALLCTL_ARENAS_ALL, bins, large, mutex);
1150 				if (json) {
1151 					malloc_cprintf(write_cb, cbopaque,
1152 					    "\t\t\t}%s\n",
1153 					    ((destroyed_initialized &&
1154 					    destroyed) || unmerged) ?  "," :
1155 					    "");
1156 				}
1157 			}
1158 
1159 			/* Destroyed stats. */
1160 			if (destroyed_initialized && destroyed) {
1161 				/* Print destroyed arena stats. */
1162 				if (json) {
1163 					malloc_cprintf(write_cb, cbopaque,
1164 					    "\t\t\t\"destroyed\": {\n");
1165 				} else {
1166 					malloc_cprintf(write_cb, cbopaque,
1167 					    "\nDestroyed arenas stats:\n");
1168 				}
1169 				stats_arena_print(write_cb, cbopaque, json,
1170 				    MALLCTL_ARENAS_DESTROYED, bins, large,
1171 				    mutex);
1172 				if (json) {
1173 					malloc_cprintf(write_cb, cbopaque,
1174 					    "\t\t\t}%s\n", unmerged ?  "," :
1175 					    "");
1176 				}
1177 			}
1178 
1179 			/* Unmerged stats. */
1180 			if (unmerged) {
1181 				for (i = j = 0; i < narenas; i++) {
1182 					if (initialized[i]) {
1183 						if (json) {
1184 							j++;
1185 							malloc_cprintf(write_cb,
1186 							    cbopaque,
1187 							    "\t\t\t\"%u\": {\n",
1188 							    i);
1189 						} else {
1190 							malloc_cprintf(write_cb,
1191 							    cbopaque,
1192 							    "\narenas[%u]:\n",
1193 							    i);
1194 						}
1195 						stats_arena_print(write_cb,
1196 						    cbopaque, json, i, bins,
1197 						    large, mutex);
1198 						if (json) {
1199 							malloc_cprintf(write_cb,
1200 							    cbopaque,
1201 							    "\t\t\t}%s\n", (j <
1202 							    ninitialized) ? ","
1203 							    : "");
1204 						}
1205 					}
1206 				}
1207 			}
1208 		}
1209 
1210 		if (json) {
1211 			malloc_cprintf(write_cb, cbopaque,
1212 			    "\t\t}\n");
1213 		}
1214 	}
1215 }
1216 
1217 void
1218 stats_print(void (*write_cb)(void *, const char *), void *cbopaque,
1219     const char *opts) {
1220 	int err;
1221 	uint64_t epoch;
1222 	size_t u64sz;
1223 #define OPTION(o, v, d, s) bool v = d;
1224 	STATS_PRINT_OPTIONS
1225 #undef OPTION
1226 
1227 	/*
1228 	 * Refresh stats, in case mallctl() was called by the application.
1229 	 *
1230 	 * Check for OOM here, since refreshing the ctl cache can trigger
1231 	 * allocation.  In practice, none of the subsequent mallctl()-related
1232 	 * calls in this function will cause OOM if this one succeeds.
1233 	 * */
1234 	epoch = 1;
1235 	u64sz = sizeof(uint64_t);
1236 	err = je_mallctl("epoch", (void *)&epoch, &u64sz, (void *)&epoch,
1237 	    sizeof(uint64_t));
1238 	if (err != 0) {
1239 		if (err == EAGAIN) {
1240 			malloc_write("<jemalloc>: Memory allocation failure in "
1241 			    "mallctl(\"epoch\", ...)\n");
1242 			return;
1243 		}
1244 		malloc_write("<jemalloc>: Failure in mallctl(\"epoch\", "
1245 		    "...)\n");
1246 		abort();
1247 	}
1248 
1249 	if (opts != NULL) {
1250 		for (unsigned i = 0; opts[i] != '\0'; i++) {
1251 			switch (opts[i]) {
1252 #define OPTION(o, v, d, s) case o: v = s; break;
1253 				STATS_PRINT_OPTIONS
1254 #undef OPTION
1255 			default:;
1256 			}
1257 		}
1258 	}
1259 
1260 	if (json) {
1261 		malloc_cprintf(write_cb, cbopaque,
1262 		    "{\n"
1263 		    "\t\"jemalloc\": {\n");
1264 	} else {
1265 		malloc_cprintf(write_cb, cbopaque,
1266 		    "___ Begin jemalloc statistics ___\n");
1267 	}
1268 
1269 	if (general) {
1270 		stats_general_print(write_cb, cbopaque, json, config_stats);
1271 	}
1272 	if (config_stats) {
1273 		stats_print_helper(write_cb, cbopaque, json, merged, destroyed,
1274 		    unmerged, bins, large, mutex);
1275 	}
1276 
1277 	if (json) {
1278 		malloc_cprintf(write_cb, cbopaque,
1279 		    "\t}\n"
1280 		    "}\n");
1281 	} else {
1282 		malloc_cprintf(write_cb, cbopaque,
1283 		    "--- End jemalloc statistics ---\n");
1284 	}
1285 }
1286