jemalloc/src/stats.c

2574 lines
93 KiB
C

#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"][<arena>].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(
"<jemalloc>: Memory allocation failure in "
"mallctl(\"epoch\", ...)\n");
return;
}
malloc_write(
"<jemalloc>: 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.<i>.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.<i>.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.<i>.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
* "<stat>": 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);
}