#include "jemalloc/internal/jemalloc_preamble.h" #include "jemalloc/internal/arena.h" #include "jemalloc/internal/assert.h" #include "jemalloc/internal/background_thread.h" #include "jemalloc/internal/ctl.h" #include "jemalloc/internal/emitter.h" #include "jemalloc/internal/fxp.h" #include "jemalloc/internal/mutex_prof.h" #include "jemalloc/internal/prof.h" #include "jemalloc/internal/prof_inlines.h" #include "jemalloc/internal/prof_stats.h" #include "jemalloc/internal/stats_internal.h" #include "jemalloc/internal/tcache.h" static const char *const global_mutex_names[mutex_prof_num_global_mutexes] = { #define OP(mtx) #mtx, MUTEX_PROF_GLOBAL_MUTEXES #undef OP }; static const char *const arena_mutex_names[mutex_prof_num_arena_mutexes] = { #define OP(mtx) #mtx, MUTEX_PROF_ARENA_MUTEXES #undef OP }; /******************************************************************************/ /* Data. */ bool opt_stats_print = false; char opt_stats_print_opts[stats_print_tot_num_options + 1] = ""; int64_t opt_stats_interval = STATS_INTERVAL_DEFAULT; char opt_stats_interval_opts[stats_print_tot_num_options + 1] = ""; static counter_accum_t stats_interval_accumulated; /* Per thread batch accum size for stats_interval. */ uint64_t stats_interval_accum_batch; /* * Forward declarations for the two top-level section helpers. The public * stats_print() appears first (just below) and calls these; their definitions * follow it, so the file reads top-down. */ static void stats_general_print(emitter_t *emitter, bool omit_size_class_meta); static void stats_print_runtime_stats(emitter_t *emitter, bool merged, bool destroyed, bool unmerged, bool bins, bool large, bool mutex, bool extents, bool hpa); /******************************************************************************/ /* * malloc_stats_print() output structure (produced by stats_print(), below); * each line names the function that prints it, plus its JSON key where useful. * Quoted names are literal keys below the "jemalloc" root unless shown with a * leading dot. * * ___ Begin jemalloc statistics ___ * { "jemalloc": * general [g] -> stats_general_print * version -> stats_general_version * config -> stats_general_config * system -> stats_general_system * opt.* -> stats_general_opts * prof -> stats_general_prof * arenas metadata -> stats_general_arenas_print ("arenas") * stats (config_stats) -> stats_print_runtime_stats * global totals -> stats_print_globals ("stats") * background_thread -> stats_print_globals * mutexes [x] -> stats_global_mutexes_print * per arena [m]/[d]/[a] -> stats_arena_print ("stats.arenas") * basics -> stats_arena_basics_print * decay -> stats_arena_decay_print * alloc counts -> stats_arena_alloc_print (.small/.large) * memory -> stats_arena_mem_print * pac/sec -> stats_arena_pac_sec_print * mutexes [x] -> stats_arena_mutexes_print (.mutexes) * bins [b] -> stats_arena_bins_print (.bins[j]) * lextents [l] -> stats_arena_lextents_print (.lextents[j]) * extents [e] -> stats_arena_extents_print (.extents[j]) * hpa shard [h] -> stats_arena_hpa_shard_print (.hpa_shard) * } * --- End jemalloc statistics --- * * Most refactored sections read values through mallctl into typed structs * before rendering them through the emitter. This makes their coverage easier * to audit, but does not guarantee parity between formats: format-specific * emission remains, including JSON-only geometry and table-only derived rates * and summary rows. * * Mallctl names identify data sources, not mechanical JSON paths. For example, * stats_gather_arena_alloc reads "stats.arenas.0.small.allocated" (replacing * the MIB arena index at runtime), while the emitter writes it below * jemalloc["stats.arenas"][].small.allocated. In particular, "stats" * and "stats.arenas" are sibling keys, not nested objects. */ void stats_print(write_cb_t *write_cb, void *cbopaque, const char *opts) { int err; uint64_t epoch; size_t u64sz; #define OPTION(o, v, d, s) bool v = d; STATS_PRINT_OPTIONS #undef OPTION /* * Refresh stats, in case mallctl() was called by the application. * * Check for OOM here, since refreshing the ctl cache can trigger * allocation. In practice, none of the subsequent mallctl()-related * calls in this function will cause OOM if this one succeeds. * */ epoch = 1; u64sz = sizeof(uint64_t); err = je_mallctl( "epoch", (void *)&epoch, &u64sz, (void *)&epoch, sizeof(uint64_t)); if (err != 0) { if (err == EAGAIN) { malloc_write( ": Memory allocation failure in " "mallctl(\"epoch\", ...)\n"); return; } malloc_write( ": Failure in mallctl(\"epoch\", " "...)\n"); abort(); } if (opts != NULL) { for (unsigned i = 0; opts[i] != '\0'; i++) { switch (opts[i]) { #define OPTION(o, v, d, s) \ case o: \ v = s; \ break; STATS_PRINT_OPTIONS #undef OPTION default:; } } } emitter_t emitter; emitter_init(&emitter, json ? emitter_output_json_compact : emitter_output_table, write_cb, cbopaque); emitter_begin(&emitter); emitter_table_printf(&emitter, "___ Begin jemalloc statistics ___\n"); emitter_json_object_kv_begin(&emitter, "jemalloc"); if (general) { /* * Private (non-public) omission: the per-size-class geometry * arrays are shown only in JSON; the raw format never did. */ bool omit_size_class_meta = !json; stats_general_print(&emitter, omit_size_class_meta); } if (config_stats) { stats_print_runtime_stats(&emitter, merged, destroyed, unmerged, bins, large, mutex, extents, hpa); } emitter_json_object_end(&emitter); /* Closes the "jemalloc" dict. */ emitter_table_printf(&emitter, "--- End jemalloc statistics ---\n"); emitter_end(&emitter); } /******************************************************************************/ /* Statistics gathering. */ /* * Read stats out of the allocator through mallctl into typed per-section * structs. Keeping the mallctl reads together (and separate from emission) * makes coverage auditable. They are static to src/stats.c; the gather structs * and plumbing macros they use live in stats_internal.h. */ /* mib must be prepared through "stats.arenas..extents" (depth 4). */ static void stats_gather_arena_extent(size_t *mib, unsigned j, stats_arena_extent_t *e) { mib[4] = j; CTL_LEAF(mib, 5, "ndirty", &e->ndirty, size_t); CTL_LEAF(mib, 5, "nmuzzy", &e->nmuzzy, size_t); CTL_LEAF(mib, 5, "nretained", &e->nretained, size_t); CTL_LEAF(mib, 5, "npinned", &e->npinned, size_t); CTL_LEAF(mib, 5, "dirty_bytes", &e->dirty_bytes, size_t); CTL_LEAF(mib, 5, "muzzy_bytes", &e->muzzy_bytes, size_t); CTL_LEAF(mib, 5, "retained_bytes", &e->retained_bytes, size_t); CTL_LEAF(mib, 5, "pinned_bytes", &e->pinned_bytes, size_t); e->ntotal = e->ndirty + e->nmuzzy + e->nretained + e->npinned; e->total_bytes = e->dirty_bytes + e->muzzy_bytes + e->retained_bytes + e->pinned_bytes; } static void stats_gather_arena_mem(unsigned i, stats_arena_mem_t *mem) { CTL_M2_GET("stats.arenas.0.mapped", i, &mem->mapped, size_t); CTL_M2_GET("stats.arenas.0.retained", i, &mem->retained, size_t); CTL_M2_GET("stats.arenas.0.pinned", i, &mem->pinned, size_t); CTL_M2_GET("stats.arenas.0.base", i, &mem->base, size_t); CTL_M2_GET("stats.arenas.0.internal", i, &mem->internal, size_t); CTL_M2_GET("stats.arenas.0.metadata_edata", i, &mem->metadata_edata, size_t); CTL_M2_GET("stats.arenas.0.metadata_rtree", i, &mem->metadata_rtree, size_t); CTL_M2_GET("stats.arenas.0.metadata_thp", i, &mem->metadata_thp, size_t); CTL_M2_GET("stats.arenas.0.tcache_bytes", i, &mem->tcache_bytes, size_t); CTL_M2_GET("stats.arenas.0.tcache_stashed_bytes", i, &mem->tcache_stashed_bytes, size_t); CTL_M2_GET("stats.arenas.0.resident", i, &mem->resident, size_t); CTL_M2_GET("stats.arenas.0.abandoned_vm", i, &mem->abandoned_vm, size_t); CTL_M2_GET("stats.arenas.0.extent_avail", i, &mem->extent_avail, size_t); CTL_GET("arenas.page", &mem->page, size_t); } static void stats_gather_arena_basics(unsigned i, stats_arena_basics_t *b) { CTL_M2_GET("stats.arenas.0.nthreads", i, &b->nthreads, unsigned); CTL_M2_GET("stats.arenas.0.uptime", i, &b->uptime, uint64_t); CTL_M2_GET("stats.arenas.0.dss", i, &b->dss, const char *); /* * The arena..name ctl copies the name into the caller-provided * buffer (b->name), so we hand it a real buffer rather than reading * into a borrowed pointer. The merged/destroyed pseudo-arenas have no * name, and querying them would fail, so guard the read. */ b->has_name = (i != MALLCTL_ARENAS_ALL && i != MALLCTL_ARENAS_DESTROYED); if (b->has_name) { char *namep = b->name; CTL_M1_GET("arena.0.name", i, (void *)&namep, const char *); } } static void stats_gather_arena_decay(unsigned i, stats_arena_decay_t *d) { CTL_M2_GET( "stats.arenas.0.dirty_decay_ms", i, &d->dirty_decay_ms, ssize_t); CTL_M2_GET( "stats.arenas.0.muzzy_decay_ms", i, &d->muzzy_decay_ms, ssize_t); CTL_M2_GET("stats.arenas.0.pactive", i, &d->pactive, size_t); CTL_M2_GET("stats.arenas.0.pdirty", i, &d->pdirty, size_t); CTL_M2_GET("stats.arenas.0.pmuzzy", i, &d->pmuzzy, size_t); CTL_M2_GET("stats.arenas.0.dirty_npurge", i, &d->dirty_npurge, uint64_t); CTL_M2_GET( "stats.arenas.0.dirty_nmadvise", i, &d->dirty_nmadvise, uint64_t); CTL_M2_GET("stats.arenas.0.dirty_purged", i, &d->dirty_purged, uint64_t); CTL_M2_GET("stats.arenas.0.muzzy_npurge", i, &d->muzzy_npurge, uint64_t); CTL_M2_GET( "stats.arenas.0.muzzy_nmadvise", i, &d->muzzy_nmadvise, uint64_t); CTL_M2_GET("stats.arenas.0.muzzy_purged", i, &d->muzzy_purged, uint64_t); } static void stats_gather_arena_alloc(unsigned i, stats_arena_alloc_t *small, stats_arena_alloc_t *large) { CTL_M2_GET( "stats.arenas.0.small.allocated", i, &small->allocated, size_t); CTL_M2_GET("stats.arenas.0.small.nmalloc", i, &small->nmalloc, uint64_t); CTL_M2_GET("stats.arenas.0.small.ndalloc", i, &small->ndalloc, uint64_t); CTL_M2_GET( "stats.arenas.0.small.nrequests", i, &small->nrequests, uint64_t); CTL_M2_GET("stats.arenas.0.small.nfills", i, &small->nfills, uint64_t); CTL_M2_GET("stats.arenas.0.small.nflushes", i, &small->nflushes, uint64_t); CTL_M2_GET( "stats.arenas.0.large.allocated", i, &large->allocated, size_t); CTL_M2_GET("stats.arenas.0.large.nmalloc", i, &large->nmalloc, uint64_t); CTL_M2_GET("stats.arenas.0.large.ndalloc", i, &large->ndalloc, uint64_t); CTL_M2_GET( "stats.arenas.0.large.nrequests", i, &large->nrequests, uint64_t); CTL_M2_GET("stats.arenas.0.large.nfills", i, &large->nfills, uint64_t); CTL_M2_GET("stats.arenas.0.large.nflushes", i, &large->nflushes, uint64_t); } /* * nslabs + prof (which gate the table gap/skip) are read by the caller; this * reads the remaining detail fields. mibs prepared + indexed by the caller. */ static void stats_gather_arena_bin(size_t *stats_mib, size_t *arenas_bin_mib, stats_arena_bin_t *bin) { CTL_LEAF(arenas_bin_mib, 3, "size", &bin->reg_size, size_t); CTL_LEAF(arenas_bin_mib, 3, "nregs", &bin->nregs, uint32_t); CTL_LEAF(arenas_bin_mib, 3, "slab_size", &bin->slab_size, size_t); CTL_LEAF(arenas_bin_mib, 3, "nshards", &bin->nshards, uint32_t); CTL_LEAF(stats_mib, 5, "nmalloc", &bin->nmalloc, uint64_t); CTL_LEAF(stats_mib, 5, "ndalloc", &bin->ndalloc, uint64_t); CTL_LEAF(stats_mib, 5, "curregs", &bin->curregs, size_t); CTL_LEAF(stats_mib, 5, "nrequests", &bin->nrequests, uint64_t); CTL_LEAF(stats_mib, 5, "nfills", &bin->nfills, uint64_t); CTL_LEAF(stats_mib, 5, "nflushes", &bin->nflushes, uint64_t); CTL_LEAF(stats_mib, 5, "nreslabs", &bin->nreslabs, uint64_t); CTL_LEAF(stats_mib, 5, "curslabs", &bin->curslabs, size_t); CTL_LEAF(stats_mib, 5, "nonfull_slabs", &bin->nonfull_slabs, size_t); } static void stats_gather_arena_lextent(size_t *stats_mib, size_t *arenas_lextent_mib, size_t *prof_stats_mib, unsigned j, bool prof_stats_on, stats_arena_lextent_t *lext) { stats_mib[4] = j; arenas_lextent_mib[2] = j; CTL_LEAF(stats_mib, 5, "nmalloc", &lext->nmalloc, uint64_t); CTL_LEAF(stats_mib, 5, "ndalloc", &lext->ndalloc, uint64_t); CTL_LEAF(stats_mib, 5, "nrequests", &lext->nrequests, uint64_t); CTL_LEAF(arenas_lextent_mib, 3, "size", &lext->lextent_size, size_t); CTL_LEAF(stats_mib, 5, "curlextents", &lext->curlextents, size_t); if (prof_stats_on) { prof_stats_mib[3] = j; CTL_LEAF(prof_stats_mib, 4, "live", &lext->prof_live, prof_stats_t); CTL_LEAF(prof_stats_mib, 4, "accum", &lext->prof_accum, prof_stats_t); } } static void stats_gather_arena_hpa_sec(unsigned i, stats_arena_hpa_sec_t *sec) { CTL_M2_GET("stats.arenas.0.hpa_sec_bytes", i, &sec->sec_bytes, size_t); CTL_M2_GET("stats.arenas.0.hpa_sec_hits", i, &sec->sec_hits, size_t); CTL_M2_GET("stats.arenas.0.hpa_sec_misses", i, &sec->sec_misses, size_t); CTL_M2_GET("stats.arenas.0.hpa_sec_dalloc_noflush", i, &sec->sec_dalloc_noflush, size_t); CTL_M2_GET("stats.arenas.0.hpa_sec_dalloc_flush", i, &sec->sec_dalloc_flush, size_t); CTL_M2_GET("stats.arenas.0.hpa_sec_overfills", i, &sec->sec_overfills, size_t); } static void stats_gather_arena_pac_sec(unsigned i, stats_arena_pac_sec_t *sec) { CTL_M2_GET("stats.arenas.0.pac_sec_bytes", i, &sec->sec_bytes, size_t); CTL_M2_GET("stats.arenas.0.pac_sec_hits", i, &sec->sec_hits, size_t); CTL_M2_GET("stats.arenas.0.pac_sec_misses", i, &sec->sec_misses, size_t); CTL_M2_GET("stats.arenas.0.pac_sec_dalloc_noflush", i, &sec->sec_dalloc_noflush, size_t); CTL_M2_GET("stats.arenas.0.pac_sec_dalloc_flush", i, &sec->sec_dalloc_flush, size_t); } static void stats_gather_arena_hpa_counters(unsigned i, stats_arena_hpa_counters_t *c) { CTL_M2_GET( "stats.arenas.0.hpa_shard.npageslabs", i, &c->npageslabs, size_t); CTL_M2_GET("stats.arenas.0.hpa_shard.nactive", i, &c->nactive, size_t); CTL_M2_GET("stats.arenas.0.hpa_shard.ndirty", i, &c->ndirty, size_t); CTL_M2_GET("stats.arenas.0.hpa_shard.slabs.npageslabs_nonhuge", i, &c->npageslabs_nonhuge, size_t); CTL_M2_GET("stats.arenas.0.hpa_shard.slabs.nactive_nonhuge", i, &c->nactive_nonhuge, size_t); CTL_M2_GET("stats.arenas.0.hpa_shard.slabs.ndirty_nonhuge", i, &c->ndirty_nonhuge, size_t); c->nretained_nonhuge = c->npageslabs_nonhuge * HUGEPAGE_PAGES - c->nactive_nonhuge - c->ndirty_nonhuge; CTL_M2_GET("stats.arenas.0.hpa_shard.slabs.npageslabs_huge", i, &c->npageslabs_huge, size_t); CTL_M2_GET("stats.arenas.0.hpa_shard.slabs.nactive_huge", i, &c->nactive_huge, size_t); CTL_M2_GET("stats.arenas.0.hpa_shard.slabs.ndirty_huge", i, &c->ndirty_huge, size_t); CTL_M2_GET("stats.arenas.0.hpa_shard.npurge_passes", i, &c->npurge_passes, uint64_t); CTL_M2_GET("stats.arenas.0.hpa_shard.npurges", i, &c->npurges, uint64_t); CTL_M2_GET( "stats.arenas.0.hpa_shard.nhugifies", i, &c->nhugifies, uint64_t); CTL_M2_GET("stats.arenas.0.hpa_shard.nhugify_failures", i, &c->nhugify_failures, uint64_t); CTL_M2_GET("stats.arenas.0.hpa_shard.ndehugifies", i, &c->ndehugifies, uint64_t); } /* kind is "full_slabs" or "empty_slabs". */ static void stats_gather_arena_hpa_slab(unsigned i, const char *kind, stats_arena_hpa_slab_t *s) { size_t mib[CTL_MAX_DEPTH]; CTL_LEAF_PREPARE(mib, 0, "stats.arenas"); mib[2] = i; CTL_LEAF_PREPARE(mib, 3, "hpa_shard"); CTL_LEAF_PREPARE(mib, 4, kind); CTL_LEAF(mib, 5, "npageslabs_huge", &s->npageslabs_huge, size_t); CTL_LEAF(mib, 5, "nactive_huge", &s->nactive_huge, size_t); CTL_LEAF(mib, 5, "ndirty_huge", &s->ndirty_huge, size_t); CTL_LEAF(mib, 5, "npageslabs_nonhuge", &s->npageslabs_nonhuge, size_t); CTL_LEAF(mib, 5, "nactive_nonhuge", &s->nactive_nonhuge, size_t); CTL_LEAF(mib, 5, "ndirty_nonhuge", &s->ndirty_nonhuge, size_t); s->nretained_nonhuge = s->npageslabs_nonhuge * HUGEPAGE_PAGES - s->nactive_nonhuge - s->ndirty_nonhuge; } /* mib prepared through "stats.arenas..hpa_shard.nonfull_slabs" (depth 5). */ static void stats_gather_arena_hpa_nonfull(size_t *mib, unsigned j, stats_arena_hpa_slab_t *s) { mib[5] = j; CTL_LEAF(mib, 6, "npageslabs_huge", &s->npageslabs_huge, size_t); CTL_LEAF(mib, 6, "nactive_huge", &s->nactive_huge, size_t); CTL_LEAF(mib, 6, "ndirty_huge", &s->ndirty_huge, size_t); CTL_LEAF(mib, 6, "npageslabs_nonhuge", &s->npageslabs_nonhuge, size_t); CTL_LEAF(mib, 6, "nactive_nonhuge", &s->nactive_nonhuge, size_t); CTL_LEAF(mib, 6, "ndirty_nonhuge", &s->ndirty_nonhuge, size_t); s->nretained_nonhuge = s->npageslabs_nonhuge * HUGEPAGE_PAGES - s->nactive_nonhuge - s->ndirty_nonhuge; } static void stats_gather_arena_config(stats_arena_config_t *cfg) { CTL_GET("arenas.narenas", &cfg->narenas, unsigned); CTL_GET("arenas.dirty_decay_ms", &cfg->dirty_decay_ms, ssize_t); CTL_GET("arenas.muzzy_decay_ms", &cfg->muzzy_decay_ms, ssize_t); CTL_GET("arenas.quantum", &cfg->quantum, size_t); CTL_GET("arenas.page", &cfg->page, size_t); CTL_GET("arenas.hugepage", &cfg->hugepage, size_t); size_t sz = sizeof(size_t); cfg->have_tcache_max = (je_mallctl("arenas.tcache_max", (void *)&cfg->tcache_max, &sz, NULL, 0) == 0); CTL_GET("arenas.nbins", &cfg->nbins, unsigned); CTL_GET("arenas.nhbins", &cfg->nhbins, unsigned); CTL_GET("arenas.nlextents", &cfg->nlextents, unsigned); } /* mib must be prepared through "arenas.bin" (depth 2). */ static void stats_gather_arena_bin_meta(size_t *mib, unsigned j, stats_arena_bin_meta_t *bm) { mib[2] = j; CTL_LEAF(mib, 3, "size", &bm->size, size_t); CTL_LEAF(mib, 3, "nregs", &bm->nregs, uint32_t); CTL_LEAF(mib, 3, "slab_size", &bm->slab_size, size_t); CTL_LEAF(mib, 3, "nshards", &bm->nshards, uint32_t); } /* mib must be prepared through "arenas.lextent" (depth 2). */ static void stats_gather_arena_lextent_meta(size_t *mib, unsigned j, stats_arena_lextent_meta_t *lm) { mib[2] = j; CTL_LEAF(mib, 3, "size", &lm->size, size_t); } static void stats_gather_global(stats_global_t *g) { CTL_GET("stats.allocated", &g->allocated, size_t); CTL_GET("stats.active", &g->active, size_t); CTL_GET("stats.metadata", &g->metadata, size_t); CTL_GET("stats.metadata_edata", &g->metadata_edata, size_t); CTL_GET("stats.metadata_rtree", &g->metadata_rtree, size_t); CTL_GET("stats.metadata_thp", &g->metadata_thp, size_t); CTL_GET("stats.resident", &g->resident, size_t); CTL_GET("stats.mapped", &g->mapped, size_t); CTL_GET("stats.retained", &g->retained, size_t); CTL_GET("stats.pinned", &g->pinned, size_t); CTL_GET("stats.zero_reallocs", &g->zero_reallocs, size_t); if (have_background_thread) { CTL_GET("stats.background_thread.num_threads", &g->num_background_threads, size_t); CTL_GET("stats.background_thread.num_runs", &g->background_thread_num_runs, uint64_t); CTL_GET("stats.background_thread.run_interval", &g->background_thread_run_interval, uint64_t); } else { g->num_background_threads = 0; g->background_thread_num_runs = 0; g->background_thread_run_interval = 0; } } /* * Fills initialized[narenas] and *destroyed_initialized; returns the number of * initialized arenas. */ static unsigned stats_gather_arenas_initialized(unsigned narenas, bool *initialized, bool *destroyed_initialized) { size_t mib[3]; size_t miblen = sizeof(mib) / sizeof(size_t); size_t sz; unsigned ninitialized = 0; xmallctlnametomib("arena.0.initialized", mib, &miblen); for (unsigned i = 0; i < narenas; i++) { mib[1] = i; sz = sizeof(bool); xmallctlbymib(mib, miblen, &initialized[i], &sz, NULL, 0); if (initialized[i]) { ninitialized++; } } mib[1] = MALLCTL_ARENAS_DESTROYED; sz = sizeof(bool); xmallctlbymib(mib, miblen, destroyed_initialized, &sz, NULL, 0); return ninitialized; } /******************************************************************************/ static uint64_t rate_per_second(uint64_t value, uint64_t uptime_ns) { uint64_t billion = 1000000000; if (uptime_ns == 0 || value == 0) { return 0; } if (uptime_ns < billion) { return value; } else { uint64_t uptime_s = uptime_ns / billion; return value / uptime_s; } } /* Calculate x.yyy and output a string (takes a fixed sized char array). */ static bool get_rate_str(uint64_t dividend, uint64_t divisor, char str[6]) { if (divisor == 0 || dividend > divisor) { /* The rate is not supposed to be greater than 1. */ return true; } if (dividend > 0) { assert(UINT64_MAX / dividend >= 1000); } unsigned n = (unsigned)((dividend * 1000) / divisor); if (n < 10) { malloc_snprintf(str, 6, "0.00%u", n); } else if (n < 100) { malloc_snprintf(str, 6, "0.0%u", n); } else if (n < 1000) { malloc_snprintf(str, 6, "0.%u", n); } else { malloc_snprintf(str, 6, "1"); } return false; } static void mutex_stats_init_cols(emitter_row_t *row, const char *table_name, emitter_col_t *name, emitter_col_t col_uint64_t[mutex_prof_num_uint64_t_counters], emitter_col_t col_uint32_t[mutex_prof_num_uint32_t_counters]) { mutex_prof_uint64_t_counter_ind_t k_uint64_t = 0; mutex_prof_uint32_t_counter_ind_t k_uint32_t = 0; emitter_col_t *col; if (name != NULL) { emitter_col_init(name, row); name->justify = emitter_justify_left; name->width = 21; name->type = emitter_type_title; name->str_val = table_name; } #define WIDTH_uint32_t 12 #define WIDTH_uint64_t 16 #define OP(counter, counter_type, human, derived, base_counter) \ col = &col_##counter_type[k_##counter_type]; \ ++k_##counter_type; \ emitter_col_init(col, row); \ col->justify = emitter_justify_right; \ col->width = derived ? 8 : WIDTH_##counter_type; \ col->type = emitter_type_title; \ col->str_val = human; MUTEX_PROF_COUNTERS #undef OP #undef WIDTH_uint32_t #undef WIDTH_uint64_t col_uint64_t[mutex_counter_total_wait_time_ps].width = 10; } /* * Read one mutex's counters (leaf "name" under the prepared mib) into the * emitter columns. Shared by the global and per-arena mutex tables, whose rows * differ only in the mib and mutex name they pass. */ static void mutex_stats_read(size_t mib[], size_t miblen, const char *name, emitter_col_t *col_name, emitter_col_t col_uint64_t[mutex_prof_num_uint64_t_counters], emitter_col_t col_uint32_t[mutex_prof_num_uint32_t_counters], uint64_t uptime) { CTL_LEAF_PREPARE(mib, miblen, name); size_t miblen_name = miblen + 1; col_name->str_val = name; emitter_col_t *dst; #define EMITTER_TYPE_uint32_t emitter_type_uint32 #define EMITTER_TYPE_uint64_t emitter_type_uint64 #define OP(counter, counter_type, human, derived, base_counter) \ dst = &col_##counter_type[mutex_counter_##counter]; \ dst->type = EMITTER_TYPE_##counter_type; \ if (!derived) { \ CTL_LEAF(mib, miblen_name, #counter, \ (counter_type *)&dst->bool_val, counter_type); \ } else { \ emitter_col_t *base = \ &col_##counter_type[mutex_counter_##base_counter]; \ dst->counter_type##_val = (counter_type)rate_per_second( \ base->counter_type##_val, uptime); \ } MUTEX_PROF_COUNTERS #undef OP #undef EMITTER_TYPE_uint32_t #undef EMITTER_TYPE_uint64_t } static void mutex_stats_read_arena_bin(size_t mib[], size_t miblen, emitter_col_t col_uint64_t[mutex_prof_num_uint64_t_counters], emitter_col_t col_uint32_t[mutex_prof_num_uint32_t_counters], uint64_t uptime) { CTL_LEAF_PREPARE(mib, miblen, "mutex"); size_t miblen_mutex = miblen + 1; emitter_col_t *dst; #define EMITTER_TYPE_uint32_t emitter_type_uint32 #define EMITTER_TYPE_uint64_t emitter_type_uint64 #define OP(counter, counter_type, human, derived, base_counter) \ dst = &col_##counter_type[mutex_counter_##counter]; \ dst->type = EMITTER_TYPE_##counter_type; \ if (!derived) { \ CTL_LEAF(mib, miblen_mutex, #counter, \ (counter_type *)&dst->bool_val, counter_type); \ } else { \ emitter_col_t *base = \ &col_##counter_type[mutex_counter_##base_counter]; \ dst->counter_type##_val = (counter_type)rate_per_second( \ base->counter_type##_val, uptime); \ } MUTEX_PROF_COUNTERS #undef OP #undef EMITTER_TYPE_uint32_t #undef EMITTER_TYPE_uint64_t } /* "row" can be NULL to avoid emitting in table mode. */ static void mutex_stats_emit(emitter_t *emitter, emitter_row_t *row, emitter_col_t col_uint64_t[mutex_prof_num_uint64_t_counters], emitter_col_t col_uint32_t[mutex_prof_num_uint32_t_counters]) { if (row != NULL) { emitter_table_row(emitter, row); } mutex_prof_uint64_t_counter_ind_t k_uint64_t = 0; mutex_prof_uint32_t_counter_ind_t k_uint32_t = 0; emitter_col_t *col; #define EMITTER_TYPE_uint32_t emitter_type_uint32 #define EMITTER_TYPE_uint64_t emitter_type_uint64 #define OP(counter, type, human, derived, base_counter) \ if (!derived) { \ col = &col_##type[k_##type]; \ emitter_json_kv(emitter, #counter, EMITTER_TYPE_##type, \ (const void *)&col->bool_val); \ } \ ++k_##type; MUTEX_PROF_COUNTERS; #undef OP #undef EMITTER_TYPE_uint32_t #undef EMITTER_TYPE_uint64_t } /* * Set a per-size-class row's "size" column. Above the slow-growth threshold, * display the bucket bounds as "(prev_size,size]". This notation does not * imply that every integer in the interval is a valid usable size. Smaller * classes are displayed as a single size. buf must outlive row emission. * (This is table labeling only; the ordered JSON arrays imply the bounds.) */ static void stats_size_col_set(emitter_col_t *col, size_t size, size_t prev_size, char *buf, size_t bufsz) { if (size > USIZE_GROW_SLOW_THRESHOLD) { malloc_snprintf(buf, bufsz, "(%zu,%zu]", prev_size, size); col->type = emitter_type_title; col->str_val = buf; } else { col->type = emitter_type_size; col->size_val = size; } } enum { STATS_COL_FLAG_NONE = 0, STATS_COL_FLAG_PROF = 1U << 0, }; static unsigned stats_col_active_flags(bool prof_stats_on) { return prof_stats_on ? STATS_COL_FLAG_PROF : STATS_COL_FLAG_NONE; } typedef struct { const stats_arena_bin_t *bin; unsigned ind; size_t page; uint64_t uptime; const char *util; } stats_arena_bin_emit_row_t; #define BIN_COL_GET(name, value_member, field) \ static void \ stats_bin_col_get_##name(const void *vrow, emitter_col_t *col) { \ const stats_arena_bin_emit_row_t *row = vrow; \ col->value_member = row->bin->field; \ } #define BIN_COL_GET_RATE(name, field) \ static void \ stats_bin_col_get_##name(const void *vrow, emitter_col_t *col) { \ const stats_arena_bin_emit_row_t *row = vrow; \ col->uint64_val = rate_per_second( \ row->bin->field, row->uptime); \ } BIN_COL_GET(size, size_val, reg_size) BIN_COL_GET(nmalloc, uint64_val, nmalloc) BIN_COL_GET(ndalloc, uint64_val, ndalloc) BIN_COL_GET(nrequests, uint64_val, nrequests) BIN_COL_GET(prof_live_requested, uint64_val, prof_live.req_sum) BIN_COL_GET(prof_live_count, uint64_val, prof_live.count) BIN_COL_GET(prof_accum_requested, uint64_val, prof_accum.req_sum) BIN_COL_GET(prof_accum_count, uint64_val, prof_accum.count) BIN_COL_GET(nshards, unsigned_val, nshards) BIN_COL_GET(curregs, size_val, curregs) BIN_COL_GET(curslabs, size_val, curslabs) BIN_COL_GET(nonfull_slabs, size_val, nonfull_slabs) BIN_COL_GET(regs, unsigned_val, nregs) BIN_COL_GET(nfills, uint64_val, nfills) BIN_COL_GET(nflushes, uint64_val, nflushes) BIN_COL_GET(nslabs, uint64_val, nslabs) BIN_COL_GET(nreslabs, uint64_val, nreslabs) BIN_COL_GET_RATE(nmalloc_ps, nmalloc) BIN_COL_GET_RATE(ndalloc_ps, ndalloc) BIN_COL_GET_RATE(nrequests_ps, nrequests) BIN_COL_GET_RATE(nfills_ps, nfills) BIN_COL_GET_RATE(nflushes_ps, nflushes) BIN_COL_GET_RATE(nreslabs_ps, nreslabs) #undef BIN_COL_GET #undef BIN_COL_GET_RATE static void stats_bin_col_get_ind(const void *vrow, emitter_col_t *col) { const stats_arena_bin_emit_row_t *row = vrow; col->unsigned_val = row->ind; } static void stats_bin_col_get_allocated(const void *vrow, emitter_col_t *col) { const stats_arena_bin_emit_row_t *row = vrow; col->size_val = row->bin->curregs * row->bin->reg_size; } static void stats_bin_col_get_pgs(const void *vrow, emitter_col_t *col) { const stats_arena_bin_emit_row_t *row = vrow; col->size_val = row->bin->slab_size / row->page; } static void stats_bin_col_get_spacer(const void *vrow, emitter_col_t *col) { (void)vrow; col->str_val = " "; } static void stats_bin_col_get_util(const void *vrow, emitter_col_t *col) { const stats_arena_bin_emit_row_t *row = vrow; col->str_val = row->util; } #define BIN_COL_SIZE 0 #define BIN_DESC(key, label, width, type, flags, name) \ {key, label, emitter_justify_right, width, emitter_type_##type, flags, \ stats_bin_col_get_##name} static const emitter_col_desc_t stats_bin_cols[] = { BIN_DESC("size", "size", 20, size, STATS_COL_FLAG_NONE, size), BIN_DESC("ind", "ind", 4, unsigned, STATS_COL_FLAG_NONE, ind), BIN_DESC("allocated", "allocated", 14, size, STATS_COL_FLAG_NONE, allocated), BIN_DESC("nmalloc", "nmalloc", 14, uint64, STATS_COL_FLAG_NONE, nmalloc), BIN_DESC("nmalloc_ps", "(#/sec)", 8, uint64, STATS_COL_FLAG_NONE, nmalloc_ps), BIN_DESC("ndalloc", "ndalloc", 14, uint64, STATS_COL_FLAG_NONE, ndalloc), BIN_DESC("ndalloc_ps", "(#/sec)", 8, uint64, STATS_COL_FLAG_NONE, ndalloc_ps), BIN_DESC("nrequests", "nrequests", 15, uint64, STATS_COL_FLAG_NONE, nrequests), BIN_DESC("nrequests_ps", "(#/sec)", 10, uint64, STATS_COL_FLAG_NONE, nrequests_ps), BIN_DESC("prof_live_requested", "prof_live_requested", 21, uint64, STATS_COL_FLAG_PROF, prof_live_requested), BIN_DESC("prof_live_count", "prof_live_count", 17, uint64, STATS_COL_FLAG_PROF, prof_live_count), BIN_DESC("prof_accum_requested", "prof_accum_requested", 21, uint64, STATS_COL_FLAG_PROF, prof_accum_requested), BIN_DESC("prof_accum_count", "prof_accum_count", 17, uint64, STATS_COL_FLAG_PROF, prof_accum_count), BIN_DESC("nshards", "nshards", 9, unsigned, STATS_COL_FLAG_NONE, nshards), BIN_DESC("curregs", "curregs", 13, size, STATS_COL_FLAG_NONE, curregs), BIN_DESC("curslabs", "curslabs", 13, size, STATS_COL_FLAG_NONE, curslabs), BIN_DESC("nonfull_slabs", "nonfull_slabs", 15, size, STATS_COL_FLAG_NONE, nonfull_slabs), BIN_DESC("regs", "regs", 5, unsigned, STATS_COL_FLAG_NONE, regs), BIN_DESC("pgs", "pgs", 4, size, STATS_COL_FLAG_NONE, pgs), BIN_DESC(NULL, " ", 1, title, STATS_COL_FLAG_NONE, spacer), BIN_DESC("util", "util", 6, title, STATS_COL_FLAG_NONE, util), BIN_DESC("nfills", "nfills", 13, uint64, STATS_COL_FLAG_NONE, nfills), BIN_DESC("nfills_ps", "(#/sec)", 8, uint64, STATS_COL_FLAG_NONE, nfills_ps), BIN_DESC("nflushes", "nflushes", 13, uint64, STATS_COL_FLAG_NONE, nflushes), BIN_DESC("nflushes_ps", "(#/sec)", 8, uint64, STATS_COL_FLAG_NONE, nflushes_ps), BIN_DESC("nslabs", "nslabs", 13, uint64, STATS_COL_FLAG_NONE, nslabs), BIN_DESC("nreslabs", "nreslabs", 13, uint64, STATS_COL_FLAG_NONE, nreslabs), BIN_DESC("nreslabs_ps", "(#/sec)", 8, uint64, STATS_COL_FLAG_NONE, nreslabs_ps), }; #undef BIN_DESC #define BIN_COL_COUNT (sizeof(stats_bin_cols) / sizeof(stats_bin_cols[0])) static void stats_emit_arena_bin_row(emitter_t *emitter, emitter_row_t *table_row, emitter_col_t *cols, const stats_arena_bin_emit_row_t *row, bool prof_stats_on, bool mutex, emitter_col_t *mutex64, emitter_col_t *mutex32, bool is_gap) { unsigned active_flags = stats_col_active_flags(prof_stats_on); emitter_col_table_fill( stats_bin_cols, BIN_COL_COUNT, active_flags, cols, row); emitter_json_object_begin(emitter); emitter_col_table_emit_json(emitter, stats_bin_cols, BIN_COL_COUNT, active_flags, cols); if (mutex) { emitter_json_object_kv_begin(emitter, "mutex"); mutex_stats_emit(emitter, NULL, mutex64, mutex32); emitter_json_object_end(emitter); } emitter_json_object_end(emitter); emitter_table_sparse_row(emitter, table_row, is_gap); } JEMALLOC_COLD static void stats_arena_bins_print( emitter_t *emitter, bool mutex, unsigned i, uint64_t uptime) { /* * Process one size-class row at a time to preserve O(1) memory. Full * gather-all-rows-before-emit separation would require buffering the * table, so gathering and emission remain interleaved across rows. The * lextents, extents, and HPA nonfull tables follow the same pattern. */ size_t page; unsigned nbins, j; CTL_GET("arenas.page", &page, size_t); CTL_GET("arenas.nbins", &nbins, unsigned); bool prof_stats_on = config_prof && opt_prof && opt_prof_stats && i == MALLCTL_ARENAS_ALL; emitter_row_t row, header_row; emitter_col_t cols[BIN_COL_COUNT], header_cols[BIN_COL_COUNT]; emitter_col_table_build(stats_bin_cols, BIN_COL_COUNT, stats_col_active_flags(prof_stats_on), &row, cols, &header_row, header_cols); emitter_col_t col_mutex64[mutex_prof_num_uint64_t_counters]; emitter_col_t col_mutex32[mutex_prof_num_uint32_t_counters]; emitter_col_t header_mutex64[mutex_prof_num_uint64_t_counters]; emitter_col_t header_mutex32[mutex_prof_num_uint32_t_counters]; if (mutex) { mutex_stats_init_cols( &row, NULL, NULL, col_mutex64, col_mutex32); mutex_stats_init_cols( &header_row, NULL, NULL, header_mutex64, header_mutex32); } emitter_col_table_header(emitter, &header_row, &header_cols[BIN_COL_SIZE], "bins:", "bins"); size_t stats_arenas_mib[CTL_MAX_DEPTH]; CTL_LEAF_PREPARE(stats_arenas_mib, 0, "stats.arenas"); stats_arenas_mib[2] = i; CTL_LEAF_PREPARE(stats_arenas_mib, 3, "bins"); size_t arenas_bin_mib[CTL_MAX_DEPTH]; CTL_LEAF_PREPARE(arenas_bin_mib, 0, "arenas.bin"); size_t prof_stats_mib[CTL_MAX_DEPTH]; if (prof_stats_on) { CTL_LEAF_PREPARE(prof_stats_mib, 0, "prof.stats.bins"); } emitter_table_sparse_begin(emitter); for (j = 0; j < nbins; j++) { stats_arena_bin_t bin; stats_arenas_mib[4] = j; arenas_bin_mib[2] = j; CTL_LEAF(stats_arenas_mib, 5, "nslabs", &bin.nslabs, uint64_t); if (prof_stats_on) { prof_stats_mib[3] = j; CTL_LEAF(prof_stats_mib, 4, "live", &bin.prof_live, prof_stats_t); CTL_LEAF(prof_stats_mib, 4, "accum", &bin.prof_accum, prof_stats_t); } bool is_gap; if (prof_stats_on) { is_gap = (bin.nslabs == 0 && bin.prof_accum.count == 0); } else { is_gap = (bin.nslabs == 0); } if (is_gap && !emitter_outputs_json(emitter)) { emitter_table_sparse_row(emitter, &row, true); continue; } /* Mutex counters still gather directly into their emitter columns. */ stats_gather_arena_bin(stats_arenas_mib, arenas_bin_mib, &bin); if (mutex) { mutex_stats_read_arena_bin(stats_arenas_mib, 5, col_mutex64, col_mutex32, uptime); } size_t availregs = bin.nregs * bin.curslabs; char util[6]; if (get_rate_str( (uint64_t)bin.curregs, (uint64_t)availregs, util)) { if (availregs == 0) { malloc_snprintf(util, sizeof(util), "1"); } else if (bin.curregs > availregs) { /* * Race detected: the counters were read in * separate mallctl calls and concurrent * operations happened in between. In this case * no meaningful utilization can be computed. */ malloc_snprintf(util, sizeof(util), " race"); } else { not_reached(); } } stats_arena_bin_emit_row_t emit_row = { &bin, j, page, uptime, util}; stats_emit_arena_bin_row(emitter, &row, cols, &emit_row, prof_stats_on, mutex, col_mutex64, col_mutex32, is_gap); } emitter_json_array_end(emitter); /* Close "bins". */ emitter_table_sparse_end(emitter); } typedef struct { const stats_arena_lextent_t *lextent; unsigned ind; uint64_t uptime; } stats_arena_lextent_emit_row_t; #define LEXTENT_COL_GET(name, value_member, field) \ static void \ stats_lextent_col_get_##name(const void *vrow, emitter_col_t *col) { \ const stats_arena_lextent_emit_row_t *row = vrow; \ col->value_member = row->lextent->field; \ } #define LEXTENT_COL_GET_RATE(name, field) \ static void \ stats_lextent_col_get_##name(const void *vrow, emitter_col_t *col) { \ const stats_arena_lextent_emit_row_t *row = vrow; \ col->uint64_val = rate_per_second( \ row->lextent->field, row->uptime); \ } LEXTENT_COL_GET(size, size_val, lextent_size) LEXTENT_COL_GET(nmalloc, uint64_val, nmalloc) LEXTENT_COL_GET(ndalloc, uint64_val, ndalloc) LEXTENT_COL_GET(nrequests, uint64_val, nrequests) LEXTENT_COL_GET(prof_live_requested, uint64_val, prof_live.req_sum) LEXTENT_COL_GET(prof_live_count, uint64_val, prof_live.count) LEXTENT_COL_GET(prof_accum_requested, uint64_val, prof_accum.req_sum) LEXTENT_COL_GET(prof_accum_count, uint64_val, prof_accum.count) LEXTENT_COL_GET(curlextents, size_val, curlextents) LEXTENT_COL_GET_RATE(nmalloc_ps, nmalloc) LEXTENT_COL_GET_RATE(ndalloc_ps, ndalloc) LEXTENT_COL_GET_RATE(nrequests_ps, nrequests) #undef LEXTENT_COL_GET #undef LEXTENT_COL_GET_RATE static void stats_lextent_col_get_ind(const void *vrow, emitter_col_t *col) { const stats_arena_lextent_emit_row_t *row = vrow; col->unsigned_val = row->ind; } static void stats_lextent_col_get_allocated(const void *vrow, emitter_col_t *col) { const stats_arena_lextent_emit_row_t *row = vrow; col->size_val = row->lextent->curlextents * row->lextent->lextent_size; } #define LEXTENT_COL_SIZE 0 #define LEXTENT_DESC(key, label, width, type, flags, name) \ {key, label, emitter_justify_right, width, emitter_type_##type, flags, \ stats_lextent_col_get_##name} static const emitter_col_desc_t stats_lextent_cols[] = { LEXTENT_DESC("size", "size", 20, size, STATS_COL_FLAG_NONE, size), LEXTENT_DESC("ind", "ind", 4, unsigned, STATS_COL_FLAG_NONE, ind), LEXTENT_DESC("allocated", "allocated", 13, size, STATS_COL_FLAG_NONE, allocated), LEXTENT_DESC("nmalloc", "nmalloc", 13, uint64, STATS_COL_FLAG_NONE, nmalloc), LEXTENT_DESC("nmalloc_ps", "(#/sec)", 8, uint64, STATS_COL_FLAG_NONE, nmalloc_ps), LEXTENT_DESC("ndalloc", "ndalloc", 13, uint64, STATS_COL_FLAG_NONE, ndalloc), LEXTENT_DESC("ndalloc_ps", "(#/sec)", 8, uint64, STATS_COL_FLAG_NONE, ndalloc_ps), LEXTENT_DESC("nrequests", "nrequests", 13, uint64, STATS_COL_FLAG_NONE, nrequests), LEXTENT_DESC("nrequests_ps", "(#/sec)", 8, uint64, STATS_COL_FLAG_NONE, nrequests_ps), LEXTENT_DESC("prof_live_requested", "prof_live_requested", 21, uint64, STATS_COL_FLAG_PROF, prof_live_requested), LEXTENT_DESC("prof_live_count", "prof_live_count", 17, uint64, STATS_COL_FLAG_PROF, prof_live_count), LEXTENT_DESC("prof_accum_requested", "prof_accum_requested", 21, uint64, STATS_COL_FLAG_PROF, prof_accum_requested), LEXTENT_DESC("prof_accum_count", "prof_accum_count", 17, uint64, STATS_COL_FLAG_PROF, prof_accum_count), LEXTENT_DESC("curlextents", "curlextents", 13, size, STATS_COL_FLAG_NONE, curlextents), }; #undef LEXTENT_DESC #define LEXTENT_COL_COUNT \ (sizeof(stats_lextent_cols) / sizeof(stats_lextent_cols[0])) static void stats_emit_arena_lextent_row(emitter_t *emitter, emitter_row_t *table_row, emitter_col_t *cols, const stats_arena_lextent_emit_row_t *row, bool prof_stats_on, size_t prev_size, bool is_gap) { unsigned active_flags = stats_col_active_flags(prof_stats_on); emitter_col_table_fill(stats_lextent_cols, LEXTENT_COL_COUNT, active_flags, cols, row); emitter_json_object_begin(emitter); emitter_col_table_emit_json(emitter, stats_lextent_cols, LEXTENT_COL_COUNT, active_flags, cols); emitter_json_object_end(emitter); char size_buf[48]; stats_size_col_set(&cols[LEXTENT_COL_SIZE], row->lextent->lextent_size, prev_size, size_buf, sizeof(size_buf)); emitter_table_sparse_row(emitter, table_row, is_gap); } JEMALLOC_COLD static void stats_arena_lextents_print(emitter_t *emitter, unsigned i, uint64_t uptime) { /* Streaming table; see stats_arena_bins_print for the rationale. */ unsigned nbins, nlextents, j; CTL_GET("arenas.nbins", &nbins, unsigned); CTL_GET("arenas.nlextents", &nlextents, unsigned); bool prof_stats_on = config_prof && opt_prof && opt_prof_stats && i == MALLCTL_ARENAS_ALL; emitter_row_t row, header_row; emitter_col_t cols[LEXTENT_COL_COUNT], header_cols[LEXTENT_COL_COUNT]; emitter_col_table_build(stats_lextent_cols, LEXTENT_COL_COUNT, stats_col_active_flags(prof_stats_on), &row, cols, &header_row, header_cols); emitter_col_table_header(emitter, &header_row, &header_cols[LEXTENT_COL_SIZE], "large:", "lextents"); size_t stats_arenas_mib[CTL_MAX_DEPTH]; CTL_LEAF_PREPARE(stats_arenas_mib, 0, "stats.arenas"); stats_arenas_mib[2] = i; CTL_LEAF_PREPARE(stats_arenas_mib, 3, "lextents"); size_t arenas_lextent_mib[CTL_MAX_DEPTH]; CTL_LEAF_PREPARE(arenas_lextent_mib, 0, "arenas.lextent"); size_t prof_stats_mib[CTL_MAX_DEPTH]; if (prof_stats_on) { CTL_LEAF_PREPARE(prof_stats_mib, 0, "prof.stats.lextents"); } /* * Carry the previous size class's size forward for the "(prev,size]" * range label instead of looking it up by a large size-class index: * with large size classes disabled (the default) that index may be out * of range, and indexing sz_index2size_tab there would be unsafe. The * class before the first large one is the last bin; sz_index2size_compute * derives it arithmetically (no table access). */ size_t prev_size = sz_index2size_compute(nbins - 1); emitter_table_sparse_begin(emitter); for (j = 0; j < nlextents; j++) { stats_arena_lextent_t lext; stats_gather_arena_lextent(stats_arenas_mib, arenas_lextent_mib, prof_stats_mib, j, prof_stats_on, &lext); bool is_gap = (lext.nrequests == 0); stats_arena_lextent_emit_row_t emit_row = { &lext, nbins + j, uptime}; stats_emit_arena_lextent_row(emitter, &row, cols, &emit_row, prof_stats_on, prev_size, is_gap); prev_size = lext.lextent_size; } emitter_json_array_end(emitter); /* Close "lextents". */ emitter_table_sparse_end(emitter); } typedef struct { const stats_arena_extent_t *extent; unsigned ind; size_t size; } stats_arena_extent_emit_row_t; #define EXTENT_COL_GET(name, field) \ static void \ stats_extent_col_get_##name(const void *vrow, emitter_col_t *col) { \ const stats_arena_extent_emit_row_t *row = vrow; \ col->size_val = row->extent->field; \ } EXTENT_COL_GET(ndirty, ndirty) EXTENT_COL_GET(dirty, dirty_bytes) EXTENT_COL_GET(nmuzzy, nmuzzy) EXTENT_COL_GET(muzzy, muzzy_bytes) EXTENT_COL_GET(nretained, nretained) EXTENT_COL_GET(retained, retained_bytes) EXTENT_COL_GET(npinned, npinned) EXTENT_COL_GET(pinned, pinned_bytes) EXTENT_COL_GET(ntotal, ntotal) EXTENT_COL_GET(total, total_bytes) #undef EXTENT_COL_GET static void stats_extent_col_get_size(const void *vrow, emitter_col_t *col) { const stats_arena_extent_emit_row_t *row = vrow; col->size_val = row->size; } static void stats_extent_col_get_ind(const void *vrow, emitter_col_t *col) { const stats_arena_extent_emit_row_t *row = vrow; col->unsigned_val = row->ind; } #define EXTENT_COL_SIZE 0 #define EXTENT_DESC(key, label, name) \ {key, label, emitter_justify_right, 13, emitter_type_size, \ STATS_COL_FLAG_NONE, stats_extent_col_get_##name} static const emitter_col_desc_t stats_extent_cols[] = { {"size", "size", emitter_justify_right, 20, emitter_type_size, STATS_COL_FLAG_NONE, stats_extent_col_get_size}, {"ind", "ind", emitter_justify_right, 4, emitter_type_unsigned, STATS_COL_FLAG_NONE, stats_extent_col_get_ind}, EXTENT_DESC("ndirty", "ndirty", ndirty), EXTENT_DESC("dirty_bytes", "dirty", dirty), EXTENT_DESC("nmuzzy", "nmuzzy", nmuzzy), EXTENT_DESC("muzzy_bytes", "muzzy", muzzy), EXTENT_DESC("nretained", "nretained", nretained), EXTENT_DESC("retained_bytes", "retained", retained), EXTENT_DESC("npinned", "npinned", npinned), EXTENT_DESC("pinned_bytes", "pinned", pinned), EXTENT_DESC("ntotal", "ntotal", ntotal), EXTENT_DESC("total_bytes", "total", total), }; #undef EXTENT_DESC #define EXTENT_COL_COUNT \ (sizeof(stats_extent_cols) / sizeof(stats_extent_cols[0])) static void stats_emit_arena_extent_row(emitter_t *emitter, emitter_row_t *table_row, emitter_col_t *cols, const stats_arena_extent_emit_row_t *row, size_t prev_size, bool is_gap) { emitter_col_table_fill(stats_extent_cols, EXTENT_COL_COUNT, STATS_COL_FLAG_NONE, cols, row); emitter_json_object_begin(emitter); emitter_col_table_emit_json(emitter, stats_extent_cols, EXTENT_COL_COUNT, STATS_COL_FLAG_NONE, cols); emitter_json_object_end(emitter); char size_buf[48]; stats_size_col_set(&cols[EXTENT_COL_SIZE], row->size, prev_size, size_buf, sizeof(size_buf)); emitter_table_sparse_row(emitter, table_row, is_gap); } JEMALLOC_COLD static void stats_arena_extents_print(emitter_t *emitter, unsigned i) { /* Streaming table; see stats_arena_bins_print for the rationale. */ unsigned j; emitter_row_t row, header_row; emitter_col_t cols[EXTENT_COL_COUNT], header_cols[EXTENT_COL_COUNT]; emitter_col_table_build(stats_extent_cols, EXTENT_COL_COUNT, STATS_COL_FLAG_NONE, &row, cols, &header_row, header_cols); emitter_col_table_header(emitter, &header_row, &header_cols[EXTENT_COL_SIZE], "extents:", "extents"); size_t stats_arenas_mib[CTL_MAX_DEPTH]; CTL_LEAF_PREPARE(stats_arenas_mib, 0, "stats.arenas"); stats_arenas_mib[2] = i; CTL_LEAF_PREPARE(stats_arenas_mib, 3, "extents"); emitter_table_sparse_begin(emitter); for (j = 0; j < SC_NPSIZES; j++) { stats_arena_extent_t e; stats_gather_arena_extent(stats_arenas_mib, j, &e); bool is_gap = (e.ntotal == 0); stats_arena_extent_emit_row_t emit_row = {&e, j, sz_pind2sz(j)}; stats_emit_arena_extent_row(emitter, &row, cols, &emit_row, j > 0 ? sz_pind2sz(j - 1) : 0, is_gap); } emitter_json_array_end(emitter); /* Close "extents". */ emitter_table_sparse_end(emitter); } static void stats_emit_arena_hpa_sec(emitter_t *emitter, const stats_arena_hpa_sec_t *sec) { emitter_kv(emitter, "sec_bytes", "Bytes in small extent cache", emitter_type_size, &sec->sec_bytes); emitter_kv(emitter, "sec_hits", "Total hits in small extent cache", emitter_type_size, &sec->sec_hits); emitter_kv(emitter, "sec_misses", "Total misses in small extent cache", emitter_type_size, &sec->sec_misses); emitter_kv(emitter, "sec_dalloc_noflush", "Dalloc calls without flush in small extent cache", emitter_type_size, &sec->sec_dalloc_noflush); emitter_kv(emitter, "sec_dalloc_flush", "Dalloc calls with flush in small extent cache", emitter_type_size, &sec->sec_dalloc_flush); emitter_kv(emitter, "sec_overfills", "sec_fill calls that went over max_bytes", emitter_type_size, &sec->sec_overfills); } static void stats_arena_hpa_shard_sec_print(emitter_t *emitter, unsigned i) { stats_arena_hpa_sec_t sec; stats_gather_arena_hpa_sec(i, &sec); stats_emit_arena_hpa_sec(emitter, &sec); } static void stats_emit_arena_pac_sec(emitter_t *emitter, const stats_arena_pac_sec_t *sec) { emitter_kv(emitter, "pac_sec_bytes", "Bytes in PAC small extent cache", emitter_type_size, &sec->sec_bytes); emitter_kv(emitter, "pac_sec_hits", "Total hits in PAC small extent cache", emitter_type_size, &sec->sec_hits); emitter_kv(emitter, "pac_sec_misses", "Total misses in PAC small extent cache", emitter_type_size, &sec->sec_misses); emitter_kv(emitter, "pac_sec_dalloc_noflush", "Dalloc calls without flush in PAC small extent cache", emitter_type_size, &sec->sec_dalloc_noflush); emitter_kv(emitter, "pac_sec_dalloc_flush", "Dalloc calls with flush in PAC small extent cache", emitter_type_size, &sec->sec_dalloc_flush); } static void stats_arena_pac_sec_print(emitter_t *emitter, unsigned i) { stats_arena_pac_sec_t sec; stats_gather_arena_pac_sec(i, &sec); stats_emit_arena_pac_sec(emitter, &sec); } static void stats_emit_arena_hpa_counters(emitter_t *emitter, const stats_arena_hpa_counters_t *c, uint64_t uptime) { /* Merged pageslab / page counts (broken down by huginess below). */ emitter_kv(emitter, "npageslabs", "npageslabs", emitter_type_size, &c->npageslabs); emitter_kv(emitter, "nactive", "nactive", emitter_type_size, &c->nactive); emitter_kv(emitter, "ndirty", "ndirty", emitter_type_size, &c->ndirty); uint64_t npurge_passes_ps = rate_per_second(c->npurge_passes, uptime); uint64_t npurges_ps = rate_per_second(c->npurges, uptime); uint64_t nhugifies_ps = rate_per_second(c->nhugifies, uptime); uint64_t nhugify_failures_ps = rate_per_second(c->nhugify_failures, uptime); uint64_t ndehugifies_ps = rate_per_second(c->ndehugifies, uptime); emitter_kv_note(emitter, "npurge_passes", "npurge_passes", emitter_type_uint64, &c->npurge_passes, "per_sec", emitter_type_uint64, &npurge_passes_ps); emitter_kv_note(emitter, "npurges", "npurges", emitter_type_uint64, &c->npurges, "per_sec", emitter_type_uint64, &npurges_ps); emitter_kv_note(emitter, "nhugifies", "nhugifies", emitter_type_uint64, &c->nhugifies, "per_sec", emitter_type_uint64, &nhugifies_ps); emitter_kv_note(emitter, "nhugify_failures", "nhugify_failures", emitter_type_uint64, &c->nhugify_failures, "per_sec", emitter_type_uint64, &nhugify_failures_ps); emitter_kv_note(emitter, "ndehugifies", "ndehugifies", emitter_type_uint64, &c->ndehugifies, "per_sec", emitter_type_uint64, &ndehugifies_ps); emitter_dict_begin(emitter, "slabs", "slabs"); emitter_kv(emitter, "npageslabs_nonhuge", "npageslabs_nonhuge", emitter_type_size, &c->npageslabs_nonhuge); emitter_kv(emitter, "nactive_nonhuge", "nactive_nonhuge", emitter_type_size, &c->nactive_nonhuge); emitter_kv(emitter, "ndirty_nonhuge", "ndirty_nonhuge", emitter_type_size, &c->ndirty_nonhuge); emitter_kv(emitter, "nretained_nonhuge", "nretained_nonhuge", emitter_type_size, &c->nretained_nonhuge); emitter_kv(emitter, "npageslabs_huge", "npageslabs_huge", emitter_type_size, &c->npageslabs_huge); emitter_kv(emitter, "nactive_huge", "nactive_huge", emitter_type_size, &c->nactive_huge); emitter_kv(emitter, "ndirty_huge", "ndirty_huge", emitter_type_size, &c->ndirty_huge); emitter_dict_end(emitter); } static void stats_arena_hpa_shard_counters_print( emitter_t *emitter, unsigned i, uint64_t uptime) { stats_arena_hpa_counters_t c; stats_gather_arena_hpa_counters(i, &c); stats_emit_arena_hpa_counters(emitter, &c, uptime); /* * The extent-allocation distribution below is a small fixed-size * (SEC_MAX_NALLOCS + 1) table with its own leaf ctls, so it is * gathered into stack arrays and emitted (table then JSON) inline * rather than through a stats_gather/stats_emit pair. */ uint64_t hpa_alloc_min_extents[SEC_MAX_NALLOCS + 1]; uint64_t hpa_alloc_max_extents[SEC_MAX_NALLOCS + 1]; uint64_t hpa_alloc_extents[SEC_MAX_NALLOCS + 1]; uint64_t hpa_alloc_ps[SEC_MAX_NALLOCS + 1]; uint64_t hpa_alloc_pages_per_ps[SEC_MAX_NALLOCS + 1]; uint64_t hpa_alloc_extents_per_ps[SEC_MAX_NALLOCS + 1]; uint64_t hpa_alloc_total_elapsed_ns_per_ps[SEC_MAX_NALLOCS + 1]; size_t alloc_mib[CTL_MAX_DEPTH]; CTL_LEAF_PREPARE(alloc_mib, 0, "stats.arenas"); alloc_mib[2] = i; CTL_LEAF_PREPARE(alloc_mib, 3, "hpa_shard.alloc"); for (size_t j = 0; j <= SEC_MAX_NALLOCS; j += 1) { alloc_mib[5] = j; CTL_LEAF(alloc_mib, 6, "min_extents", &hpa_alloc_min_extents[j], uint64_t); CTL_LEAF(alloc_mib, 6, "max_extents", &hpa_alloc_max_extents[j], uint64_t); CTL_LEAF( alloc_mib, 6, "extents", &hpa_alloc_extents[j], uint64_t); CTL_LEAF(alloc_mib, 6, "ps", &hpa_alloc_ps[j], uint64_t); CTL_LEAF(alloc_mib, 6, "pages_per_ps", &hpa_alloc_pages_per_ps[j], uint64_t); CTL_LEAF(alloc_mib, 6, "extents_per_ps", &hpa_alloc_extents_per_ps[j], uint64_t); CTL_LEAF(alloc_mib, 6, "total_elapsed_ns_per_ps", &hpa_alloc_total_elapsed_ns_per_ps[j], uint64_t); } emitter_table_printf(emitter, " extent allocation distribution:\n"); emitter_table_printf(emitter, " %4s %20s %20s %20s %20s %20s %20s %24s %24s\n", "", "min_extents", "max_extents", "extents", "ps", "pages_per_ps", "extents_per_ps", "total_elapsed_ns_per_ps", "elapsed_ns_per_ps"); for (size_t j = 0; j <= SEC_MAX_NALLOCS; j += 1) { const uint64_t extents_per_ps = hpa_alloc_extents_per_ps[j]; const uint64_t total_elapsed_ns_per_ps = hpa_alloc_total_elapsed_ns_per_ps[j]; const uint64_t elapsed_ns_per_ps = (extents_per_ps != 0) ? (total_elapsed_ns_per_ps / extents_per_ps) : 0; emitter_table_printf(emitter, " %4zu %20" FMTu64 " %20" FMTu64 " %20" FMTu64 " %20" FMTu64 " %20" FMTu64 " %20" FMTu64 " %24" FMTu64 " %24" FMTu64 "\n", j, hpa_alloc_min_extents[j], hpa_alloc_max_extents[j], hpa_alloc_extents[j], hpa_alloc_ps[j], hpa_alloc_pages_per_ps[j], extents_per_ps, total_elapsed_ns_per_ps, elapsed_ns_per_ps); } emitter_table_printf(emitter, "\n"); emitter_json_array_kv_begin(emitter, "extent_allocation_distribution"); for (size_t j = 0; j <= SEC_MAX_NALLOCS; j += 1) { emitter_json_object_begin(emitter); emitter_json_kv(emitter, "min_extents", emitter_type_uint64, &hpa_alloc_min_extents[j]); emitter_json_kv(emitter, "max_extents", emitter_type_uint64, &hpa_alloc_max_extents[j]); emitter_json_kv(emitter, "extents", emitter_type_uint64, &hpa_alloc_extents[j]); emitter_json_kv( emitter, "ps", emitter_type_uint64, &hpa_alloc_ps[j]); emitter_json_kv(emitter, "pages_per_ps", emitter_type_uint64, &hpa_alloc_pages_per_ps[j]); emitter_json_kv(emitter, "extents_per_ps", emitter_type_uint64, &hpa_alloc_extents_per_ps[j]); emitter_json_kv(emitter, "total_elapsed_ns_per_ps", emitter_type_uint64, &hpa_alloc_total_elapsed_ns_per_ps[j]); emitter_json_object_end(emitter); } emitter_json_array_end(emitter); /* End "alloc_batch" */ } typedef struct { const stats_arena_hpa_slab_t *slab; const char *size_title; size_t size; size_t prev_size; unsigned ind; } stats_arena_hpa_slab_emit_row_t; #define HPA_SLAB_COL_GET(name) \ static void \ stats_hpa_slab_col_get_##name(const void *vrow, emitter_col_t *col) { \ const stats_arena_hpa_slab_emit_row_t *row = vrow; \ col->size_val = row->slab->name; \ } HPA_SLAB_COL_GET(npageslabs_huge) HPA_SLAB_COL_GET(nactive_huge) HPA_SLAB_COL_GET(ndirty_huge) HPA_SLAB_COL_GET(npageslabs_nonhuge) HPA_SLAB_COL_GET(nactive_nonhuge) HPA_SLAB_COL_GET(ndirty_nonhuge) HPA_SLAB_COL_GET(nretained_nonhuge) #undef HPA_SLAB_COL_GET static void stats_hpa_slab_col_get_size(const void *vrow, emitter_col_t *col) { const stats_arena_hpa_slab_emit_row_t *row = vrow; if (row->size_title != NULL) { col->type = emitter_type_title; col->str_val = row->size_title; } else { col->size_val = row->size; } } static void stats_hpa_slab_col_get_ind(const void *vrow, emitter_col_t *col) { const stats_arena_hpa_slab_emit_row_t *row = vrow; if (row->size_title != NULL) { col->type = emitter_type_title; col->str_val = "-"; } else { col->unsigned_val = row->ind; } } #define HPA_SLAB_COL_SIZE 0 #define HPA_SLAB_DESC(name, width) \ {#name, #name, emitter_justify_right, width, emitter_type_size, \ STATS_COL_FLAG_NONE, stats_hpa_slab_col_get_##name} static const emitter_col_desc_t stats_hpa_slab_cols[] = { {"size", "size", emitter_justify_right, 20, emitter_type_size, STATS_COL_FLAG_NONE, stats_hpa_slab_col_get_size}, {"ind", "ind", emitter_justify_right, 4, emitter_type_unsigned, STATS_COL_FLAG_NONE, stats_hpa_slab_col_get_ind}, HPA_SLAB_DESC(npageslabs_huge, 16), HPA_SLAB_DESC(nactive_huge, 16), HPA_SLAB_DESC(ndirty_huge, 16), HPA_SLAB_DESC(npageslabs_nonhuge, 20), HPA_SLAB_DESC(nactive_nonhuge, 20), HPA_SLAB_DESC(ndirty_nonhuge, 20), HPA_SLAB_DESC(nretained_nonhuge, 20), }; #undef HPA_SLAB_DESC #define HPA_SLAB_COL_COUNT \ (sizeof(stats_hpa_slab_cols) / sizeof(stats_hpa_slab_cols[0])) static void stats_emit_arena_hpa_slab_row(emitter_t *emitter, emitter_row_t *table_row, emitter_col_t *cols, const stats_arena_hpa_slab_emit_row_t *row, const char *json_key, bool sparse, bool is_gap) { emitter_col_table_fill(stats_hpa_slab_cols, HPA_SLAB_COL_COUNT, STATS_COL_FLAG_NONE, cols, row); if (json_key != NULL) { emitter_json_object_kv_begin(emitter, json_key); } else { emitter_json_object_begin(emitter); } emitter_col_table_emit_json(emitter, stats_hpa_slab_cols, HPA_SLAB_COL_COUNT, STATS_COL_FLAG_NONE, cols); emitter_json_object_end(emitter); char size_buf[48]; if (row->size_title == NULL) { stats_size_col_set(&cols[HPA_SLAB_COL_SIZE], row->size, row->prev_size, size_buf, sizeof(size_buf)); } if (sparse) { emitter_table_sparse_row(emitter, table_row, is_gap); } else { emitter_table_row(emitter, table_row); } } static void stats_arena_hpa_shard_slabs_print(emitter_t *emitter, unsigned i) { emitter_row_t row, header_row; emitter_col_t cols[HPA_SLAB_COL_COUNT]; emitter_col_t header_cols[HPA_SLAB_COL_COUNT]; emitter_col_table_build(stats_hpa_slab_cols, HPA_SLAB_COL_COUNT, STATS_COL_FLAG_NONE, &row, cols, &header_row, header_cols); emitter_table_printf(emitter, "pageslabs:\n"); emitter_table_row(emitter, &header_row); /* * Full and empty pageslabs are shown as two symbolic rows (size = * "full"/"empty", no size-class index) at the top of the table; their * JSON stays in the separate full_slabs / empty_slabs objects. */ stats_arena_hpa_slab_t sfull; stats_gather_arena_hpa_slab(i, "full_slabs", &sfull); stats_arena_hpa_slab_emit_row_t full_row = { &sfull, "full", 0, 0, 0}; stats_emit_arena_hpa_slab_row(emitter, &row, cols, &full_row, "full_slabs", false, false); stats_arena_hpa_slab_t sempty; stats_gather_arena_hpa_slab(i, "empty_slabs", &sempty); stats_arena_hpa_slab_emit_row_t empty_row = { &sempty, "empty", 0, 0, 0}; stats_emit_arena_hpa_slab_row(emitter, &row, cols, &empty_row, "empty_slabs", false, false); size_t stats_arenas_mib[CTL_MAX_DEPTH]; CTL_LEAF_PREPARE(stats_arenas_mib, 0, "stats.arenas"); stats_arenas_mib[2] = i; CTL_LEAF_PREPARE(stats_arenas_mib, 3, "hpa_shard.nonfull_slabs"); emitter_json_array_kv_begin(emitter, "nonfull_slabs"); emitter_table_sparse_begin(emitter); for (pszind_t j = 0; j < PSSET_NPSIZES && j < SC_NPSIZES; j++) { stats_arena_hpa_slab_t s; stats_gather_arena_hpa_nonfull(stats_arenas_mib, j, &s); bool is_gap = (s.npageslabs_huge == 0 && s.npageslabs_nonhuge == 0); stats_arena_hpa_slab_emit_row_t emit_row = { &s, NULL, sz_pind2sz(j), j > 0 ? sz_pind2sz(j - 1) : 0, j}; stats_emit_arena_hpa_slab_row(emitter, &row, cols, &emit_row, NULL, true, is_gap); } emitter_json_array_end(emitter); /* End "nonfull_slabs" */ emitter_table_sparse_end(emitter); } static void stats_arena_hpa_shard_print(emitter_t *emitter, unsigned i, uint64_t uptime) { emitter_json_object_kv_begin(emitter, "hpa_shard"); stats_arena_hpa_shard_sec_print(emitter, i); stats_arena_hpa_shard_counters_print(emitter, i, uptime); stats_arena_hpa_shard_slabs_print(emitter, i); emitter_json_object_end(emitter); /* End "hpa_shard" */ } static void stats_arena_mutexes_print( emitter_t *emitter, unsigned arena_ind, uint64_t uptime) { /* * Mutex readers gather counters directly into emitter columns, coupling * collection to the output representation. Emission remains a separate * mutex_stats_emit() call. */ emitter_row_t row; emitter_col_t col_name; emitter_col_t col64[mutex_prof_num_uint64_t_counters]; emitter_col_t col32[mutex_prof_num_uint32_t_counters]; emitter_row_init(&row); mutex_stats_init_cols(&row, "", &col_name, col64, col32); emitter_json_object_kv_begin(emitter, "mutexes"); emitter_table_row(emitter, &row); size_t stats_arenas_mib[CTL_MAX_DEPTH]; CTL_LEAF_PREPARE(stats_arenas_mib, 0, "stats.arenas"); stats_arenas_mib[2] = arena_ind; CTL_LEAF_PREPARE(stats_arenas_mib, 3, "mutexes"); for (mutex_prof_arena_ind_t i = 0; i < mutex_prof_num_arena_mutexes; i++) { const char *name = arena_mutex_names[i]; emitter_json_object_kv_begin(emitter, name); mutex_stats_read( stats_arenas_mib, 4, name, &col_name, col64, col32, uptime); mutex_stats_emit(emitter, &row, col64, col32); emitter_json_object_end(emitter); /* Close the mutex dict. */ } emitter_json_object_end(emitter); /* End "mutexes". */ } static void stats_emit_arena_basics(emitter_t *emitter, const stats_arena_basics_t *b) { if (b->has_name) { const char *namep = b->name; emitter_kv( emitter, "name", "name", emitter_type_string, &namep); } emitter_kv(emitter, "nthreads", "assigned threads", emitter_type_unsigned, &b->nthreads); emitter_kv(emitter, "uptime_ns", "uptime", emitter_type_uint64, &b->uptime); emitter_kv(emitter, "dss", "dss allocation precedence", emitter_type_string, &b->dss); } static uint64_t stats_arena_basics_print(emitter_t *emitter, unsigned i) { stats_arena_basics_t basics; stats_gather_arena_basics(i, &basics); stats_emit_arena_basics(emitter, &basics); return basics.uptime; } static void stats_emit_arena_decay(emitter_t *emitter, const stats_arena_decay_t *d) { emitter_row_t decay_row; emitter_row_init(&decay_row); /* JSON-style emission. */ emitter_json_kv( emitter, "dirty_decay_ms", emitter_type_ssize, &d->dirty_decay_ms); emitter_json_kv( emitter, "muzzy_decay_ms", emitter_type_ssize, &d->muzzy_decay_ms); emitter_json_kv(emitter, "pactive", emitter_type_size, &d->pactive); emitter_json_kv(emitter, "pdirty", emitter_type_size, &d->pdirty); emitter_json_kv(emitter, "pmuzzy", emitter_type_size, &d->pmuzzy); emitter_json_kv( emitter, "dirty_npurge", emitter_type_uint64, &d->dirty_npurge); emitter_json_kv( emitter, "dirty_nmadvise", emitter_type_uint64, &d->dirty_nmadvise); emitter_json_kv( emitter, "dirty_purged", emitter_type_uint64, &d->dirty_purged); emitter_json_kv( emitter, "muzzy_npurge", emitter_type_uint64, &d->muzzy_npurge); emitter_json_kv( emitter, "muzzy_nmadvise", emitter_type_uint64, &d->muzzy_nmadvise); emitter_json_kv( emitter, "muzzy_purged", emitter_type_uint64, &d->muzzy_purged); /* Table-style emission. */ COL(decay_row, decay_type, right, 9, title); col_decay_type.str_val = "decaying:"; COL(decay_row, decay_time, right, 6, title); col_decay_time.str_val = "time"; COL(decay_row, decay_npages, right, 13, title); col_decay_npages.str_val = "npages"; COL(decay_row, decay_sweeps, right, 13, title); col_decay_sweeps.str_val = "sweeps"; COL(decay_row, decay_madvises, right, 13, title); col_decay_madvises.str_val = "madvises"; COL(decay_row, decay_purged, right, 13, title); col_decay_purged.str_val = "purged"; /* Title row. */ emitter_table_row(emitter, &decay_row); /* Dirty row. */ col_decay_type.str_val = "dirty:"; if (d->dirty_decay_ms >= 0) { col_decay_time.type = emitter_type_ssize; col_decay_time.ssize_val = d->dirty_decay_ms; } else { col_decay_time.type = emitter_type_title; col_decay_time.str_val = "N/A"; } col_decay_npages.type = emitter_type_size; col_decay_npages.size_val = d->pdirty; col_decay_sweeps.type = emitter_type_uint64; col_decay_sweeps.uint64_val = d->dirty_npurge; col_decay_madvises.type = emitter_type_uint64; col_decay_madvises.uint64_val = d->dirty_nmadvise; col_decay_purged.type = emitter_type_uint64; col_decay_purged.uint64_val = d->dirty_purged; emitter_table_row(emitter, &decay_row); /* Muzzy row. */ col_decay_type.str_val = "muzzy:"; if (d->muzzy_decay_ms >= 0) { col_decay_time.type = emitter_type_ssize; col_decay_time.ssize_val = d->muzzy_decay_ms; } else { col_decay_time.type = emitter_type_title; col_decay_time.str_val = "N/A"; } col_decay_npages.type = emitter_type_size; col_decay_npages.size_val = d->pmuzzy; col_decay_sweeps.type = emitter_type_uint64; col_decay_sweeps.uint64_val = d->muzzy_npurge; col_decay_madvises.type = emitter_type_uint64; col_decay_madvises.uint64_val = d->muzzy_nmadvise; col_decay_purged.type = emitter_type_uint64; col_decay_purged.uint64_val = d->muzzy_purged; emitter_table_row(emitter, &decay_row); } static size_t stats_arena_decay_print(emitter_t *emitter, unsigned i) { stats_arena_decay_t decay; stats_gather_arena_decay(i, &decay); stats_emit_arena_decay(emitter, &decay); return decay.pactive; } static void stats_emit_arena_alloc(emitter_t *emitter, const stats_arena_alloc_t *small, const stats_arena_alloc_t *large, uint64_t uptime) { emitter_row_t alloc_count_row; emitter_row_init(&alloc_count_row); COL(alloc_count_row, count_title, left, 21, title); col_count_title.str_val = ""; COL(alloc_count_row, count_allocated, right, 16, title); col_count_allocated.str_val = "allocated"; COL(alloc_count_row, count_nmalloc, right, 16, title); col_count_nmalloc.str_val = "nmalloc"; COL(alloc_count_row, count_nmalloc_ps, right, 10, title); col_count_nmalloc_ps.str_val = "(#/sec)"; COL(alloc_count_row, count_ndalloc, right, 16, title); col_count_ndalloc.str_val = "ndalloc"; COL(alloc_count_row, count_ndalloc_ps, right, 10, title); col_count_ndalloc_ps.str_val = "(#/sec)"; COL(alloc_count_row, count_nrequests, right, 16, title); col_count_nrequests.str_val = "nrequests"; COL(alloc_count_row, count_nrequests_ps, right, 10, title); col_count_nrequests_ps.str_val = "(#/sec)"; COL(alloc_count_row, count_nfills, right, 16, title); col_count_nfills.str_val = "nfill"; COL(alloc_count_row, count_nfills_ps, right, 10, title); col_count_nfills_ps.str_val = "(#/sec)"; COL(alloc_count_row, count_nflushes, right, 16, title); col_count_nflushes.str_val = "nflush"; COL(alloc_count_row, count_nflushes_ps, right, 10, title); col_count_nflushes_ps.str_val = "(#/sec)"; emitter_table_row(emitter, &alloc_count_row); col_count_nmalloc_ps.type = emitter_type_uint64; col_count_ndalloc_ps.type = emitter_type_uint64; col_count_nrequests_ps.type = emitter_type_uint64; col_count_nfills_ps.type = emitter_type_uint64; col_count_nflushes_ps.type = emitter_type_uint64; /* * JSON keys for the value columns; emitter_row() emits these as kvs. * The (#/sec) rate columns and the title column stay table-only (no * json_key). */ col_count_allocated.json_key = "allocated"; col_count_nmalloc.json_key = "nmalloc"; col_count_ndalloc.json_key = "ndalloc"; col_count_nrequests.json_key = "nrequests"; col_count_nfills.json_key = "nfills"; col_count_nflushes.json_key = "nflushes"; #define EMIT_ALLOC_STAT(a, name, valtype) \ col_count_##name.type = emitter_type_##valtype; \ col_count_##name.valtype##_val = (a)->name; emitter_json_object_kv_begin(emitter, "small"); col_count_title.str_val = "small:"; EMIT_ALLOC_STAT(small, allocated, size) EMIT_ALLOC_STAT(small, nmalloc, uint64) col_count_nmalloc_ps.uint64_val = rate_per_second( col_count_nmalloc.uint64_val, uptime); EMIT_ALLOC_STAT(small, ndalloc, uint64) col_count_ndalloc_ps.uint64_val = rate_per_second( col_count_ndalloc.uint64_val, uptime); EMIT_ALLOC_STAT(small, nrequests, uint64) col_count_nrequests_ps.uint64_val = rate_per_second( col_count_nrequests.uint64_val, uptime); EMIT_ALLOC_STAT(small, nfills, uint64) col_count_nfills_ps.uint64_val = rate_per_second( col_count_nfills.uint64_val, uptime); EMIT_ALLOC_STAT(small, nflushes, uint64) col_count_nflushes_ps.uint64_val = rate_per_second( col_count_nflushes.uint64_val, uptime); emitter_row(emitter, &alloc_count_row); emitter_json_object_end(emitter); /* Close "small". */ emitter_json_object_kv_begin(emitter, "large"); col_count_title.str_val = "large:"; EMIT_ALLOC_STAT(large, allocated, size) EMIT_ALLOC_STAT(large, nmalloc, uint64) col_count_nmalloc_ps.uint64_val = rate_per_second( col_count_nmalloc.uint64_val, uptime); EMIT_ALLOC_STAT(large, ndalloc, uint64) col_count_ndalloc_ps.uint64_val = rate_per_second( col_count_ndalloc.uint64_val, uptime); EMIT_ALLOC_STAT(large, nrequests, uint64) col_count_nrequests_ps.uint64_val = rate_per_second( col_count_nrequests.uint64_val, uptime); EMIT_ALLOC_STAT(large, nfills, uint64) col_count_nfills_ps.uint64_val = rate_per_second( col_count_nfills.uint64_val, uptime); EMIT_ALLOC_STAT(large, nflushes, uint64) col_count_nflushes_ps.uint64_val = rate_per_second( col_count_nflushes.uint64_val, uptime); emitter_row(emitter, &alloc_count_row); emitter_json_object_end(emitter); /* Close "large". */ #undef EMIT_ALLOC_STAT /* Aggregated small + large stats are emitter only in table mode. */ col_count_title.str_val = "total:"; col_count_allocated.size_val = small->allocated + large->allocated; col_count_nmalloc.uint64_val = small->nmalloc + large->nmalloc; col_count_ndalloc.uint64_val = small->ndalloc + large->ndalloc; col_count_nrequests.uint64_val = small->nrequests + large->nrequests; col_count_nfills.uint64_val = small->nfills + large->nfills; col_count_nflushes.uint64_val = small->nflushes + large->nflushes; col_count_nmalloc_ps.uint64_val = rate_per_second( col_count_nmalloc.uint64_val, uptime); col_count_ndalloc_ps.uint64_val = rate_per_second( col_count_ndalloc.uint64_val, uptime); col_count_nrequests_ps.uint64_val = rate_per_second( col_count_nrequests.uint64_val, uptime); col_count_nfills_ps.uint64_val = rate_per_second( col_count_nfills.uint64_val, uptime); col_count_nflushes_ps.uint64_val = rate_per_second( col_count_nflushes.uint64_val, uptime); emitter_table_row(emitter, &alloc_count_row); } static void stats_arena_alloc_print(emitter_t *emitter, unsigned i, uint64_t uptime) { stats_arena_alloc_t small_alloc, large_alloc; stats_gather_arena_alloc(i, &small_alloc, &large_alloc); stats_emit_arena_alloc(emitter, &small_alloc, &large_alloc, uptime); } static void stats_emit_arena_mem(emitter_t *emitter, const stats_arena_mem_t *mem, size_t active_bytes) { emitter_row_t mem_count_row; emitter_row_init(&mem_count_row); emitter_col_t mem_count_title; emitter_col_init(&mem_count_title, &mem_count_row); mem_count_title.justify = emitter_justify_left; mem_count_title.width = 21; mem_count_title.type = emitter_type_title; mem_count_title.str_val = ""; emitter_col_t mem_count_val; emitter_col_init(&mem_count_val, &mem_count_row); mem_count_val.justify = emitter_justify_right; mem_count_val.width = 16; mem_count_val.type = emitter_type_title; mem_count_val.str_val = ""; /* Blank spacer row (both columns are empty titles; table only). */ emitter_row(emitter, &mem_count_row); mem_count_val.type = emitter_type_size; /* * Active count in bytes is table only: mem_count_val carries no * json_key, so emitter_row() skips it in JSON. */ mem_count_title.str_val = "active:"; mem_count_val.size_val = active_bytes; emitter_row(emitter, &mem_count_row); /* * Each remaining stat is one row: the value column's json_key makes it a * "": value pair in JSON and the aligned value in the table. */ #define EMIT_MEM_STAT(stat) \ mem_count_title.str_val = #stat ":"; \ mem_count_val.json_key = #stat; \ mem_count_val.size_val = mem->stat; \ emitter_row(emitter, &mem_count_row); EMIT_MEM_STAT(mapped) EMIT_MEM_STAT(retained) EMIT_MEM_STAT(pinned) EMIT_MEM_STAT(base) EMIT_MEM_STAT(internal) EMIT_MEM_STAT(metadata_edata) EMIT_MEM_STAT(metadata_rtree) EMIT_MEM_STAT(metadata_thp) EMIT_MEM_STAT(tcache_bytes) EMIT_MEM_STAT(tcache_stashed_bytes) EMIT_MEM_STAT(resident) EMIT_MEM_STAT(abandoned_vm) EMIT_MEM_STAT(extent_avail) #undef EMIT_MEM_STAT } static void stats_arena_mem_print(emitter_t *emitter, unsigned i, size_t pactive) { stats_arena_mem_t m; stats_gather_arena_mem(i, &m); stats_emit_arena_mem(emitter, &m, pactive * m.page); } JEMALLOC_COLD static void stats_arena_print(emitter_t *emitter, unsigned i, bool bins, bool large, bool mutex, bool extents, bool hpa) { uint64_t uptime = stats_arena_basics_print(emitter, i); size_t pactive = stats_arena_decay_print(emitter, i); stats_arena_alloc_print(emitter, i, uptime); stats_arena_mem_print(emitter, i, pactive); if (opt_pac_sec_opts.nshards > 0) { stats_arena_pac_sec_print(emitter, i); } if (mutex) { stats_arena_mutexes_print(emitter, i, uptime); } if (bins) { stats_arena_bins_print(emitter, mutex, i, uptime); } if (large) { stats_arena_lextents_print(emitter, i, uptime); } if (extents) { stats_arena_extents_print(emitter, i); } if (hpa) { stats_arena_hpa_shard_print(emitter, i, uptime); } } static void stats_general_version(emitter_t *emitter) { const char *cpv; CTL_GET("version", &cpv, const char *); emitter_kv(emitter, "version", "Version", emitter_type_string, &cpv); } static void stats_general_config(emitter_t *emitter) { /* * Most config entries are independent scalars that * CONFIG_WRITE_BOOL reads and emits together. Keeping those operations * local is clearer than introducing a gather struct for this flat list. */ bool bv; emitter_dict_begin(emitter, "config", "Build-time option settings"); CONFIG_WRITE_BOOL(cache_oblivious); CONFIG_WRITE_BOOL(debug); CONFIG_WRITE_BOOL(fill); CONFIG_WRITE_BOOL(infallible_new); CONFIG_WRITE_BOOL(lazy_lock); emitter_kv(emitter, "malloc_conf", "config.malloc_conf", emitter_type_string, &config_malloc_conf); CONFIG_WRITE_BOOL(opt_safety_checks); CONFIG_WRITE_BOOL(prof); CONFIG_WRITE_BOOL(prof_libgcc); CONFIG_WRITE_BOOL(prof_libunwind); CONFIG_WRITE_BOOL(prof_frameptr); CONFIG_WRITE_BOOL(stats); CONFIG_WRITE_BOOL(utrace); CONFIG_WRITE_BOOL(xmalloc); emitter_dict_end(emitter); /* Close "config" dict. */ } static void stats_general_system(emitter_t *emitter) { emitter_dict_begin(emitter, "system", "System configuration"); /* * This shows system's THP mode detected at jemalloc's init time. * jemalloc does not re-detect the mode even if it changes after * jemalloc's init. It is assumed that system's THP mode is stable * during the process's lifetime and a violation could lead to * undefined behavior. */ const char *thp_mode_name = system_thp_mode_names[init_system_thp_mode]; emitter_kv(emitter, "thp_mode", "system.thp_mode", emitter_type_string, &thp_mode_name); emitter_dict_end(emitter); /* Close "system". */ } static void stats_general_opts(emitter_t *emitter) { /* * The OPT_WRITE_* macros conditionally read and emit most options. * Options needing special formatting or availability checks stay * explicit below. */ const char *cpv; bool bv, bv2; unsigned uv; uint32_t u32v; uint64_t u64v; int64_t i64v; ssize_t ssv, ssv2; size_t sv, bsz, usz, u32sz, u64sz, i64sz, ssz, sssz, cpsz; bsz = sizeof(bool); usz = sizeof(unsigned); ssz = sizeof(size_t); sssz = sizeof(ssize_t); cpsz = sizeof(const char *); u32sz = sizeof(uint32_t); i64sz = sizeof(int64_t); u64sz = sizeof(uint64_t); emitter_dict_begin(emitter, "opt", "Run-time option settings"); /* * opt.malloc_conf. * * Sources are documented in https://jemalloc.net/jemalloc.3.html#tuning * - (Not Included Here) The string specified via --with-malloc-conf, * which is already printed out above as config.malloc_conf * - (Included) The string pointed to by the global variable malloc_conf * - (Included) The “name” of the file referenced by the symbolic link * named /etc/malloc.conf * - (Included) The value of the environment variable MALLOC_CONF * - (Optional, Unofficial) The string pointed to by the global variable * malloc_conf_2_conf_harder, which is hidden from the public. * * Note: The outputs are strictly ordered by priorities (low -> high). * */ #define MALLOC_CONF_WRITE(name, message) \ if (je_mallctl("opt.malloc_conf." name, (void *)&cpv, &cpsz, NULL, 0) \ != 0) { \ cpv = ""; \ } \ emitter_kv(emitter, name, message, emitter_type_string, &cpv); MALLOC_CONF_WRITE("global_var", "Global variable malloc_conf"); MALLOC_CONF_WRITE("symlink", "Symbolic link malloc.conf"); MALLOC_CONF_WRITE("env_var", "Environment variable MALLOC_CONF"); /* As this config is unofficial, skip the output if it's NULL. */ if (je_malloc_conf_2_conf_harder != NULL) { cpv = je_malloc_conf_2_conf_harder; emitter_kv(emitter, "global_var_2_conf_harder", "Global " "variable malloc_conf_2_conf_harder", emitter_type_string, &cpv); } #undef MALLOC_CONF_WRITE OPT_WRITE_BOOL("abort") OPT_WRITE_BOOL("abort_conf") OPT_WRITE_BOOL("cache_oblivious") OPT_WRITE_BOOL("confirm_conf") OPT_WRITE_BOOL("experimental_hpa_start_huge_if_thp_always") OPT_WRITE_BOOL("experimental_hpa_enforce_hugify") OPT_WRITE_BOOL("retain") OPT_WRITE_CHAR_P("dss") OPT_WRITE_UNSIGNED("narenas") OPT_WRITE_CHAR_P("percpu_arena") OPT_WRITE_SIZE_T("oversize_threshold") OPT_WRITE_BOOL("hpa") OPT_WRITE_SIZE_T("hpa_slab_max_alloc") OPT_WRITE_SIZE_T("hpa_hugification_threshold") OPT_WRITE_UINT64("hpa_hugify_delay_ms") OPT_WRITE_BOOL("hpa_hugify_sync") OPT_WRITE_UINT64("hpa_min_purge_interval_ms") if (je_mallctl("opt.hpa_dirty_mult", (void *)&u32v, &u32sz, NULL, 0) == 0) { /* * We cheat a little and "know" the secret meaning of this * representation. */ if (u32v == (uint32_t)-1) { const char *neg1 = "-1"; emitter_kv(emitter, "hpa_dirty_mult", "opt.hpa_dirty_mult", emitter_type_string, &neg1); } else { char buf[FXP_BUF_SIZE]; fxp_print(u32v, buf); const char *bufp = buf; emitter_kv(emitter, "hpa_dirty_mult", "opt.hpa_dirty_mult", emitter_type_string, &bufp); } } OPT_WRITE_SIZE_T("hpa_purge_threshold") OPT_WRITE_UINT64("hpa_min_purge_delay_ms") OPT_WRITE_CHAR_P("hpa_hugify_style") OPT_WRITE_SIZE_T("hpa_sec_nshards") OPT_WRITE_SIZE_T("hpa_sec_max_alloc") OPT_WRITE_SIZE_T("hpa_sec_max_bytes") OPT_WRITE_SIZE_T("experimental_pac_sec_nshards") OPT_WRITE_SIZE_T("experimental_pac_sec_max_alloc") OPT_WRITE_SIZE_T("experimental_pac_sec_max_bytes") OPT_WRITE_BOOL("huge_arena_pac_thp") OPT_WRITE_CHAR_P("metadata_thp") OPT_WRITE_INT64("mutex_max_spin") OPT_WRITE_BOOL_MUTABLE("background_thread", "background_thread") OPT_WRITE_SSIZE_T_MUTABLE("dirty_decay_ms", "arenas.dirty_decay_ms") OPT_WRITE_SSIZE_T_MUTABLE("muzzy_decay_ms", "arenas.muzzy_decay_ms") OPT_WRITE_SIZE_T("lg_extent_max_active_fit") OPT_WRITE_CHAR_P("junk") OPT_WRITE_BOOL("zero") OPT_WRITE_BOOL("utrace") OPT_WRITE_BOOL("xmalloc") OPT_WRITE_BOOL("experimental_tcache_gc") OPT_WRITE_BOOL("tcache") OPT_WRITE_SIZE_T("tcache_max") OPT_WRITE_UNSIGNED("tcache_nslots_small_min") OPT_WRITE_UNSIGNED("tcache_nslots_small_max") OPT_WRITE_UNSIGNED("tcache_nslots_large") OPT_WRITE_SSIZE_T("lg_tcache_nslots_mul") OPT_WRITE_SIZE_T("tcache_gc_incr_bytes") OPT_WRITE_SIZE_T("tcache_gc_delay_bytes") OPT_WRITE_UNSIGNED("lg_tcache_flush_small_div") OPT_WRITE_UNSIGNED("lg_tcache_flush_large_div") OPT_WRITE_UNSIGNED("debug_double_free_max_scan") OPT_WRITE_CHAR_P("thp") OPT_WRITE_BOOL("prof") OPT_WRITE_UNSIGNED("prof_bt_max") OPT_WRITE_CHAR_P("prof_prefix") OPT_WRITE_BOOL_MUTABLE("prof_active", "prof.active") OPT_WRITE_BOOL_MUTABLE( "prof_thread_active_init", "prof.thread_active_init") OPT_WRITE_SSIZE_T_MUTABLE("lg_prof_sample", "prof.lg_sample") OPT_WRITE_BOOL("prof_accum") OPT_WRITE_SSIZE_T("lg_prof_interval") OPT_WRITE_BOOL("prof_gdump") OPT_WRITE_BOOL("prof_final") OPT_WRITE_BOOL("prof_leak") OPT_WRITE_BOOL("prof_leak_error") OPT_WRITE_BOOL("stats_print") OPT_WRITE_CHAR_P("stats_print_opts") OPT_WRITE_INT64("stats_interval") OPT_WRITE_CHAR_P("stats_interval_opts") OPT_WRITE_CHAR_P("zero_realloc") OPT_WRITE_SIZE_T("process_madvise_max_batch") OPT_WRITE_BOOL("disable_large_size_classes") emitter_dict_end(emitter); /* Close "opt". */ } static void stats_general_prof(emitter_t *emitter) { if (config_prof) { bool bv; uint64_t u64v; ssize_t ssv; emitter_dict_begin(emitter, "prof", "Profiling settings"); CTL_GET("prof.thread_active_init", &bv, bool); emitter_kv(emitter, "thread_active_init", "prof.thread_active_init", emitter_type_bool, &bv); CTL_GET("prof.active", &bv, bool); emitter_kv( emitter, "active", "prof.active", emitter_type_bool, &bv); CTL_GET("prof.gdump", &bv, bool); emitter_kv( emitter, "gdump", "prof.gdump", emitter_type_bool, &bv); CTL_GET("prof.interval", &u64v, uint64_t); emitter_kv(emitter, "interval", "prof.interval", emitter_type_uint64, &u64v); CTL_GET("prof.lg_sample", &ssv, ssize_t); emitter_kv(emitter, "lg_sample", "prof.lg_sample", emitter_type_ssize, &ssv); emitter_dict_end(emitter); /* Close "prof". */ } } static void stats_general_bin_meta_print(emitter_t *emitter, unsigned nbins) { /* * Streaming JSON-only size-class metadata table (per-row gather+emit); * see stats_arena_bins_print for the streaming rationale. */ emitter_json_array_kv_begin(emitter, "bin"); size_t mib[CTL_MAX_DEPTH]; CTL_LEAF_PREPARE(mib, 0, "arenas.bin"); for (unsigned i = 0; i < nbins; i++) { stats_arena_bin_meta_t bm; stats_gather_arena_bin_meta(mib, i, &bm); emitter_json_object_begin(emitter); emitter_json_kv(emitter, "size", emitter_type_size, &bm.size); emitter_json_kv(emitter, "nregs", emitter_type_uint32, &bm.nregs); emitter_json_kv( emitter, "slab_size", emitter_type_size, &bm.slab_size); emitter_json_kv(emitter, "nshards", emitter_type_uint32, &bm.nshards); emitter_json_object_end(emitter); } emitter_json_array_end(emitter); /* Close "bin". */ } static void stats_general_lextent_meta_print(emitter_t *emitter, unsigned nlextents) { /* Streaming JSON-only metadata table; see stats_general_bin_meta_print. */ emitter_json_array_kv_begin(emitter, "lextent"); size_t mib[CTL_MAX_DEPTH]; CTL_LEAF_PREPARE(mib, 0, "arenas.lextent"); for (unsigned i = 0; i < nlextents; i++) { stats_arena_lextent_meta_t lm; stats_gather_arena_lextent_meta(mib, i, &lm); emitter_json_object_begin(emitter); emitter_json_kv(emitter, "size", emitter_type_size, &lm.size); emitter_json_object_end(emitter); } emitter_json_array_end(emitter); /* Close "lextent". */ } static void stats_general_arenas_print(emitter_t *emitter, bool omit_size_class_meta) { stats_arena_config_t cfg; stats_gather_arena_config(&cfg); /* * The json output sticks arena info into an "arenas" dict; the table * output puts them at the top-level. */ emitter_json_object_kv_begin(emitter, "arenas"); emitter_kv( emitter, "narenas", "Arenas", emitter_type_unsigned, &cfg.narenas); /* * Decay settings are emitted only in json mode; in table mode, they * are emitted as notes with the opt output, above. */ emitter_json_kv( emitter, "dirty_decay_ms", emitter_type_ssize, &cfg.dirty_decay_ms); emitter_json_kv( emitter, "muzzy_decay_ms", emitter_type_ssize, &cfg.muzzy_decay_ms); emitter_kv(emitter, "quantum", "Quantum size", emitter_type_size, &cfg.quantum); emitter_kv(emitter, "page", "Page size", emitter_type_size, &cfg.page); emitter_kv(emitter, "hugepage", "Hugepage size", emitter_type_size, &cfg.hugepage); if (cfg.have_tcache_max) { emitter_kv(emitter, "tcache_max", "Maximum thread-cached size class", emitter_type_size, &cfg.tcache_max); } emitter_kv(emitter, "nbins", "Number of bin size classes", emitter_type_unsigned, &cfg.nbins); emitter_kv(emitter, "nhbins", "Number of thread-cache bin size classes", emitter_type_unsigned, &cfg.nhbins); /* * Per-size-class geometry is omitted from table output. It is present * in JSON whenever general output is enabled; when omitted, it is not * gathered either. */ if (!omit_size_class_meta) { stats_general_bin_meta_print(emitter, cfg.nbins); } emitter_kv(emitter, "nlextents", "Number of large size classes", emitter_type_unsigned, &cfg.nlextents); if (!omit_size_class_meta) { stats_general_lextent_meta_print(emitter, cfg.nlextents); } emitter_json_object_end(emitter); /* Close "arenas" */ } JEMALLOC_COLD static void stats_general_print(emitter_t *emitter, bool omit_size_class_meta) { stats_general_version(emitter); stats_general_config(emitter); stats_general_system(emitter); stats_general_opts(emitter); stats_general_prof(emitter); stats_general_arenas_print(emitter, omit_size_class_meta); } static void stats_emit_global(emitter_t *emitter, const stats_global_t *g) { /* Generic global stats. */ emitter_json_kv(emitter, "allocated", emitter_type_size, &g->allocated); emitter_json_kv(emitter, "active", emitter_type_size, &g->active); emitter_json_kv(emitter, "metadata", emitter_type_size, &g->metadata); emitter_json_kv( emitter, "metadata_edata", emitter_type_size, &g->metadata_edata); emitter_json_kv( emitter, "metadata_rtree", emitter_type_size, &g->metadata_rtree); emitter_json_kv( emitter, "metadata_thp", emitter_type_size, &g->metadata_thp); emitter_json_kv(emitter, "resident", emitter_type_size, &g->resident); emitter_json_kv(emitter, "mapped", emitter_type_size, &g->mapped); emitter_json_kv(emitter, "retained", emitter_type_size, &g->retained); emitter_json_kv(emitter, "pinned", emitter_type_size, &g->pinned); emitter_json_kv( emitter, "zero_reallocs", emitter_type_size, &g->zero_reallocs); emitter_table_printf(emitter, "Allocated: %zu, active: %zu, " "metadata: %zu (n_thp %zu, edata %zu, rtree %zu), resident: %zu, " "mapped: %zu, retained: %zu, pinned: %zu\n", g->allocated, g->active, g->metadata, g->metadata_thp, g->metadata_edata, g->metadata_rtree, g->resident, g->mapped, g->retained, g->pinned); /* Strange behaviors */ emitter_table_printf(emitter, "Count of realloc(non-null-ptr, 0) calls: %zu\n", g->zero_reallocs); /* Background thread stats. */ emitter_json_object_kv_begin(emitter, "background_thread"); emitter_json_kv(emitter, "num_threads", emitter_type_size, &g->num_background_threads); emitter_json_kv(emitter, "num_runs", emitter_type_uint64, &g->background_thread_num_runs); emitter_json_kv(emitter, "run_interval", emitter_type_uint64, &g->background_thread_run_interval); emitter_json_object_end(emitter); /* Close "background_thread". */ emitter_table_printf(emitter, "Background threads: %zu, " "num_runs: %" FMTu64 ", run_interval: %" FMTu64 " ns\n", g->num_background_threads, g->background_thread_num_runs, g->background_thread_run_interval); } static void stats_global_mutexes_print(emitter_t *emitter) { /* Counters are gathered into emitter columns; see the arena variant. */ emitter_row_t row; emitter_col_t name; emitter_col_t col64[mutex_prof_num_uint64_t_counters]; emitter_col_t col32[mutex_prof_num_uint32_t_counters]; uint64_t uptime; emitter_row_init(&row); mutex_stats_init_cols(&row, "", &name, col64, col32); emitter_table_row(emitter, &row); emitter_json_object_kv_begin(emitter, "mutexes"); CTL_M2_GET("stats.arenas.0.uptime", 0, &uptime, uint64_t); size_t stats_mutexes_mib[CTL_MAX_DEPTH]; CTL_LEAF_PREPARE(stats_mutexes_mib, 0, "stats.mutexes"); for (int i = 0; i < mutex_prof_num_global_mutexes; i++) { mutex_stats_read(stats_mutexes_mib, 2, global_mutex_names[i], &name, col64, col32, uptime); emitter_json_object_kv_begin(emitter, global_mutex_names[i]); mutex_stats_emit(emitter, &row, col64, col32); emitter_json_object_end(emitter); } emitter_json_object_end(emitter); /* Close "mutexes". */ } static void stats_print_globals(emitter_t *emitter, bool mutex) { stats_global_t g; stats_gather_global(&g); emitter_json_object_kv_begin(emitter, "stats"); stats_emit_global(emitter, &g); if (mutex) { stats_global_mutexes_print(emitter); } emitter_json_object_end(emitter); /* Close "stats". */ } static void stats_print_one_arena(emitter_t *emitter, unsigned arena_ind, const char *json_key, const char *table_header, bool bins, bool large, bool mutex, bool extents, bool hpa) { emitter_json_object_kv_begin(emitter, json_key); emitter_table_printf(emitter, "%s", table_header); stats_arena_print(emitter, arena_ind, bins, large, mutex, extents, hpa); emitter_json_object_end(emitter); } JEMALLOC_COLD static void stats_print_runtime_stats(emitter_t *emitter, bool merged, bool destroyed, bool unmerged, bool bins, bool large, bool mutex, bool extents, bool hpa) { stats_print_globals(emitter, mutex); if (!merged && !destroyed && !unmerged) { return; } unsigned narenas; CTL_GET("arenas.narenas", &narenas, unsigned); VARIABLE_ARRAY_UNSAFE(bool, initialized, narenas); bool destroyed_initialized; unsigned ninitialized = stats_gather_arenas_initialized(narenas, initialized, &destroyed_initialized); emitter_json_object_kv_begin(emitter, "stats.arenas"); /* Merged stats. */ if (merged && (ninitialized > 1 || !unmerged)) { stats_print_one_arena(emitter, MALLCTL_ARENAS_ALL, "merged", "Merged arenas stats:\n", bins, large, mutex, extents, hpa); } /* Destroyed stats. */ if (destroyed_initialized && destroyed) { stats_print_one_arena(emitter, MALLCTL_ARENAS_DESTROYED, "destroyed", "Destroyed arenas stats:\n", bins, large, mutex, extents, hpa); } /* Unmerged stats. */ if (unmerged) { for (unsigned i = 0; i < narenas; i++) { if (!initialized[i]) { continue; } char arena_ind_str[20]; malloc_snprintf(arena_ind_str, sizeof(arena_ind_str), "%u", i); char header[32]; malloc_snprintf(header, sizeof(header), "arenas[%s]:\n", arena_ind_str); stats_print_one_arena(emitter, i, arena_ind_str, header, bins, large, mutex, extents, hpa); } } emitter_json_object_end(emitter); /* Close "stats.arenas". */ } static uint64_t stats_interval_new_event_wait(tsd_t *tsd) { return stats_interval_accum_batch; } static uint64_t stats_interval_postponed_event_wait(tsd_t *tsd) { return TE_MIN_START_WAIT; } static void stats_interval_event_handler(tsd_t *tsd) { uint64_t last_event = thread_allocated_last_event_get(tsd); uint64_t last_sample_event = tsd_stats_interval_last_event_get(tsd); tsd_stats_interval_last_event_set(tsd, last_event); uint64_t elapsed = last_event - last_sample_event; assert(elapsed > 0 && elapsed != TE_INVALID_ELAPSED); if (counter_accum( tsd_tsdn(tsd), &stats_interval_accumulated, elapsed)) { je_malloc_stats_print(NULL, NULL, opt_stats_interval_opts); } } static te_enabled_t stats_interval_enabled(void) { return opt_stats_interval >= 0 ? te_enabled_yes : te_enabled_no; } te_base_cb_t stats_interval_te_handler = { .enabled = &stats_interval_enabled, .new_event_wait = &stats_interval_new_event_wait, .postponed_event_wait = &stats_interval_postponed_event_wait, .event_handler = &stats_interval_event_handler, }; bool stats_boot(void) { uint64_t stats_interval; if (opt_stats_interval < 0) { assert(opt_stats_interval == -1); stats_interval = 0; stats_interval_accum_batch = 0; } else { /* See comments in stats.h */ stats_interval = (opt_stats_interval > 0) ? opt_stats_interval : 1; uint64_t batch = stats_interval >> STATS_INTERVAL_ACCUM_LG_BATCH_SIZE; if (batch > STATS_INTERVAL_ACCUM_BATCH_MAX) { batch = STATS_INTERVAL_ACCUM_BATCH_MAX; } else if (batch == 0) { batch = 1; } stats_interval_accum_batch = batch; } return counter_accum_init(&stats_interval_accumulated, stats_interval); } void stats_prefork(tsdn_t *tsdn) { counter_prefork(tsdn, &stats_interval_accumulated); } void stats_postfork_parent(tsdn_t *tsdn) { counter_postfork_parent(tsdn, &stats_interval_accumulated); } void stats_postfork_child(tsdn_t *tsdn) { counter_postfork_child(tsdn, &stats_interval_accumulated); }