3 Commits

Author SHA1 Message Date
Slobodan Predolac
04aad97faa Expose stats table columns in JSON output and simplify code 2026-07-21 09:38:27 -04:00
Slobodan Predolac
80c8fcb4f9 Refactor stats tables around descriptor-driven row emission 2026-07-21 09:38:27 -04:00
Slobodan Predolac
7aab2c0e01 Expand JSON stats test coverage 2026-07-21 09:38:27 -04:00
4 changed files with 1980 additions and 754 deletions

View File

@@ -590,6 +590,99 @@ emitter_sparse_row(emitter_t *emitter, emitter_row_t *row, bool is_gap) {
} }
} }
/*
* Descriptor-driven column tables. A descriptor array defines table and JSON
* order; columns with a NULL json_key are table-only. The getter lets one
* engine serve different row types while keeping derived values (rates,
* utilization, and so on) with the table-specific code. A column is active
* when all its required_flags are present in the active_flags supplied by the
* caller; the emitter assigns no domain-specific meaning to those bits.
*/
typedef struct emitter_col_desc_s emitter_col_desc_t;
struct emitter_col_desc_s {
const char *json_key;
const char *table_label;
emitter_justify_t justify;
int width;
emitter_type_t type;
unsigned required_flags;
void (*get)(const void *row, emitter_col_t *col);
};
static inline bool
emitter_col_desc_active(const emitter_col_desc_t *desc,
unsigned active_flags) {
return (desc->required_flags & ~active_flags) == 0;
}
static inline void
emitter_col_table_build(const emitter_col_desc_t *descs, size_t ndescs,
unsigned active_flags, emitter_row_t *row, emitter_col_t *cols,
emitter_row_t *header_row, emitter_col_t *header_cols) {
emitter_row_init(row);
emitter_row_init(header_row);
for (size_t i = 0; i < ndescs; i++) {
if (!emitter_col_desc_active(&descs[i], active_flags)) {
continue;
}
emitter_col_init(&cols[i], row);
cols[i].justify = descs[i].justify;
cols[i].width = descs[i].width;
cols[i].type = descs[i].type;
emitter_col_init(&header_cols[i], header_row);
header_cols[i].justify = descs[i].justify;
header_cols[i].width = descs[i].width;
header_cols[i].type = emitter_type_title;
header_cols[i].str_val = descs[i].table_label != NULL
? descs[i].table_label : descs[i].json_key;
}
}
static inline void
emitter_col_table_header(emitter_t *emitter, emitter_row_t *header_row,
emitter_col_t *first_header_col, const char *table_prefix,
const char *json_key) {
first_header_col->width -= (int)strlen(table_prefix);
emitter_table_printf(emitter, "%s", table_prefix);
emitter_table_row(emitter, header_row);
emitter_json_array_kv_begin(emitter, json_key);
}
static inline void
emitter_col_table_fill(const emitter_col_desc_t *descs, size_t ndescs,
unsigned active_flags, emitter_col_t *cols, const void *row) {
for (size_t i = 0; i < ndescs; i++) {
if (!emitter_col_desc_active(&descs[i], active_flags)) {
continue;
}
cols[i].type = descs[i].type;
descs[i].get(row, &cols[i]);
}
}
static inline void
emitter_col_table_emit_json(emitter_t *emitter,
const emitter_col_desc_t *descs, size_t ndescs, unsigned active_flags,
emitter_col_t *cols) {
if (!emitter_outputs_json(emitter)) {
return;
}
for (size_t i = 0; i < ndescs; i++) {
const emitter_col_desc_t *desc = &descs[i];
if (!emitter_col_desc_active(desc, active_flags)) {
continue;
}
if (desc->json_key != NULL) {
emitter_type_t json_type = cols[i].type == emitter_type_title
? emitter_type_string : cols[i].type;
emitter_json_kv(emitter, desc->json_key, json_type,
(const void *)&cols[i].bool_val);
}
}
}
static inline void static inline void
emitter_begin(emitter_t *emitter) { emitter_begin(emitter_t *emitter) {
if (emitter_outputs_json(emitter)) { if (emitter_outputs_json(emitter)) {

View File

@@ -695,18 +695,180 @@ stats_size_col_set(emitter_col_t *col, size_t size, size_t prev_size, char *buf,
} }
} }
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 JEMALLOC_COLD
static void static void
stats_arena_bins_print( stats_arena_bins_print(
emitter_t *emitter, bool mutex, unsigned i, uint64_t uptime) { emitter_t *emitter, bool mutex, unsigned i, uint64_t uptime) {
/* /*
* Streaming table (deviates from the gather/emit split): the SC_NBINS * Process one size-class row at a time to preserve O(1) memory. Full
* rows are gathered and emitted one at a time (stats_gather_arena_bin * gather-all-rows-before-emit separation would require buffering the
* per row + inline emit, with all-zero runs collapsed to "---"). This * table, so gathering and emission remain interleaved across rows. The
* preserves O(1) memory. Full gather-all-rows-before-emit separation * lextents, extents, and HPA nonfull tables follow the same pattern.
* would require buffering the table; a separate per-row emit helper
* would not. The lextents / extents / hpa-nonfull tables follow the
* same pattern.
*/ */
size_t page; size_t page;
unsigned nbins, j; unsigned nbins, j;
@@ -715,54 +877,13 @@ stats_arena_bins_print(
CTL_GET("arenas.nbins", &nbins, unsigned); CTL_GET("arenas.nbins", &nbins, unsigned);
emitter_row_t header_row;
emitter_row_init(&header_row);
emitter_row_t row;
emitter_row_init(&row);
bool prof_stats_on = config_prof && opt_prof && opt_prof_stats bool prof_stats_on = config_prof && opt_prof && opt_prof_stats
&& i == MALLCTL_ARENAS_ALL; && i == MALLCTL_ARENAS_ALL;
emitter_row_t row, header_row;
COL_HDR(row, size, NULL, right, 20, size) emitter_col_t cols[BIN_COL_COUNT], header_cols[BIN_COL_COUNT];
COL_HDR(row, ind, NULL, right, 4, unsigned) emitter_col_table_build(stats_bin_cols, BIN_COL_COUNT,
COL_HDR(row, allocated, NULL, right, 14, size) stats_col_active_flags(prof_stats_on), &row, cols, &header_row,
COL_HDR(row, nmalloc, NULL, right, 14, uint64) header_cols);
COL_HDR(row, nmalloc_ps, "(#/sec)", right, 8, uint64)
COL_HDR(row, ndalloc, NULL, right, 14, uint64)
COL_HDR(row, ndalloc_ps, "(#/sec)", right, 8, uint64)
COL_HDR(row, nrequests, NULL, right, 15, uint64)
COL_HDR(row, nrequests_ps, "(#/sec)", right, 10, uint64)
COL_HDR_DECLARE(prof_live_requested);
COL_HDR_DECLARE(prof_live_count);
COL_HDR_DECLARE(prof_accum_requested);
COL_HDR_DECLARE(prof_accum_count);
if (prof_stats_on) {
COL_HDR_INIT(row, prof_live_requested, NULL, right, 21, uint64)
COL_HDR_INIT(row, prof_live_count, NULL, right, 17, uint64)
COL_HDR_INIT(row, prof_accum_requested, NULL, right, 21, uint64)
COL_HDR_INIT(row, prof_accum_count, NULL, right, 17, uint64)
}
COL_HDR(row, nshards, NULL, right, 9, unsigned)
COL_HDR(row, curregs, NULL, right, 13, size)
COL_HDR(row, curslabs, NULL, right, 13, size)
COL_HDR(row, nonfull_slabs, NULL, right, 15, size)
COL_HDR(row, regs, NULL, right, 5, unsigned)
COL_HDR(row, pgs, NULL, right, 4, size)
/* To buffer a right- and left-justified column. */
COL_HDR(row, justify_spacer, NULL, right, 1, title)
COL_HDR(row, util, NULL, right, 6, title)
COL_HDR(row, nfills, NULL, right, 13, uint64)
COL_HDR(row, nfills_ps, "(#/sec)", right, 8, uint64)
COL_HDR(row, nflushes, NULL, right, 13, uint64)
COL_HDR(row, nflushes_ps, "(#/sec)", right, 8, uint64)
COL_HDR(row, nslabs, NULL, right, 13, uint64)
COL_HDR(row, nreslabs, NULL, right, 13, uint64)
COL_HDR(row, nreslabs_ps, "(#/sec)", right, 8, uint64)
/* Don't want to actually print the name. */
header_justify_spacer.str_val = " ";
col_justify_spacer.str_val = " ";
emitter_col_t col_mutex64[mutex_prof_num_uint64_t_counters]; 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 col_mutex32[mutex_prof_num_uint32_t_counters];
@@ -777,14 +898,8 @@ stats_arena_bins_print(
&header_row, NULL, NULL, header_mutex64, header_mutex32); &header_row, NULL, NULL, header_mutex64, header_mutex32);
} }
/* emitter_col_table_header(emitter, &header_row,
* We print a "bins:" header as part of the table row; we need to adjust &header_cols[BIN_COL_SIZE], "bins:", "bins");
* the header size column to compensate.
*/
header_size.width -= 5;
emitter_table_printf(emitter, "bins:");
emitter_table_row(emitter, &header_row);
emitter_json_array_kv_begin(emitter, "bins");
size_t stats_arenas_mib[CTL_MAX_DEPTH]; size_t stats_arenas_mib[CTL_MAX_DEPTH];
CTL_LEAF_PREPARE(stats_arenas_mib, 0, "stats.arenas"); CTL_LEAF_PREPARE(stats_arenas_mib, 0, "stats.arenas");
@@ -827,15 +942,7 @@ stats_arena_bins_print(
continue; continue;
} }
/* /* Mutex counters still gather directly into their emitter columns. */
* Per-size-class tables stream: gather one row, emit it, reuse
* buffers. Full gather-all-rows-before-emit separation would
* require table-sized storage, so gathering and emission remain
* interleaved across rows. In particular the per-bin mutex
* counters are read here (the mutex subsystem reads straight into
* emitter columns) rather than up front. A separate per-row emit
* helper could further decouple the code without buffering.
*/
stats_gather_arena_bin(stats_arenas_mib, arenas_bin_mib, &bin); stats_gather_arena_bin(stats_arenas_mib, arenas_bin_mib, &bin);
if (mutex) { if (mutex) {
@@ -843,43 +950,6 @@ stats_arena_bins_print(
col_mutex64, col_mutex32, uptime); col_mutex64, col_mutex32, uptime);
} }
emitter_json_object_begin(emitter);
emitter_json_kv(
emitter, "nmalloc", emitter_type_uint64, &bin.nmalloc);
emitter_json_kv(
emitter, "ndalloc", emitter_type_uint64, &bin.ndalloc);
emitter_json_kv(
emitter, "curregs", emitter_type_size, &bin.curregs);
emitter_json_kv(
emitter, "nrequests", emitter_type_uint64, &bin.nrequests);
if (prof_stats_on) {
emitter_json_kv(emitter, "prof_live_requested",
emitter_type_uint64, &bin.prof_live.req_sum);
emitter_json_kv(emitter, "prof_live_count",
emitter_type_uint64, &bin.prof_live.count);
emitter_json_kv(emitter, "prof_accum_requested",
emitter_type_uint64, &bin.prof_accum.req_sum);
emitter_json_kv(emitter, "prof_accum_count",
emitter_type_uint64, &bin.prof_accum.count);
}
emitter_json_kv(
emitter, "nfills", emitter_type_uint64, &bin.nfills);
emitter_json_kv(
emitter, "nflushes", emitter_type_uint64, &bin.nflushes);
emitter_json_kv(
emitter, "nreslabs", emitter_type_uint64, &bin.nreslabs);
emitter_json_kv(
emitter, "curslabs", emitter_type_size, &bin.curslabs);
emitter_json_kv(emitter, "nonfull_slabs", emitter_type_size,
&bin.nonfull_slabs);
if (mutex) {
emitter_json_object_kv_begin(emitter, "mutex");
mutex_stats_emit(
emitter, NULL, col_mutex64, col_mutex32);
emitter_json_object_end(emitter);
}
emitter_json_object_end(emitter);
size_t availregs = bin.nregs * bin.curslabs; size_t availregs = bin.nregs * bin.curslabs;
char util[6]; char util[6];
if (get_rate_str( if (get_rate_str(
@@ -899,50 +969,118 @@ stats_arena_bins_print(
} }
} }
col_size.size_val = bin.reg_size; stats_arena_bin_emit_row_t emit_row = {
col_ind.unsigned_val = j; &bin, j, page, uptime, util};
col_allocated.size_val = bin.curregs * bin.reg_size; stats_emit_arena_bin_row(emitter, &row, cols, &emit_row,
col_nmalloc.uint64_val = bin.nmalloc; prof_stats_on, mutex, col_mutex64, col_mutex32, is_gap);
col_nmalloc_ps.uint64_val = rate_per_second(bin.nmalloc, uptime);
col_ndalloc.uint64_val = bin.ndalloc;
col_ndalloc_ps.uint64_val = rate_per_second(bin.ndalloc, uptime);
col_nrequests.uint64_val = bin.nrequests;
col_nrequests_ps.uint64_val = rate_per_second(
bin.nrequests, uptime);
if (prof_stats_on) {
col_prof_live_requested.uint64_val = bin.prof_live.req_sum;
col_prof_live_count.uint64_val = bin.prof_live.count;
col_prof_accum_requested.uint64_val =
bin.prof_accum.req_sum;
col_prof_accum_count.uint64_val = bin.prof_accum.count;
}
col_nshards.unsigned_val = bin.nshards;
col_curregs.size_val = bin.curregs;
col_curslabs.size_val = bin.curslabs;
col_nonfull_slabs.size_val = bin.nonfull_slabs;
col_regs.unsigned_val = bin.nregs;
col_pgs.size_val = bin.slab_size / page;
col_util.str_val = util;
col_nfills.uint64_val = bin.nfills;
col_nfills_ps.uint64_val = rate_per_second(bin.nfills, uptime);
col_nflushes.uint64_val = bin.nflushes;
col_nflushes_ps.uint64_val = rate_per_second(bin.nflushes, uptime);
col_nslabs.uint64_val = bin.nslabs;
col_nreslabs.uint64_val = bin.nreslabs;
col_nreslabs_ps.uint64_val = rate_per_second(bin.nreslabs, uptime);
/*
* Note that mutex columns were initialized above, if mutex ==
* true.
*/
emitter_table_sparse_row(emitter, &row, is_gap);
} }
emitter_json_array_end(emitter); /* Close "bins". */ emitter_json_array_end(emitter); /* Close "bins". */
emitter_table_sparse_end(emitter); 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, char *size_buf,
size_t size_buf_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);
stats_size_col_set(&cols[LEXTENT_COL_SIZE], row->lextent->lextent_size,
prev_size, size_buf, size_buf_size);
emitter_table_sparse_row(emitter, table_row, is_gap);
}
JEMALLOC_COLD JEMALLOC_COLD
static void static void
stats_arena_lextents_print(emitter_t *emitter, unsigned i, uint64_t uptime) { stats_arena_lextents_print(emitter_t *emitter, unsigned i, uint64_t uptime) {
@@ -952,45 +1090,16 @@ stats_arena_lextents_print(emitter_t *emitter, unsigned i, uint64_t uptime) {
CTL_GET("arenas.nbins", &nbins, unsigned); CTL_GET("arenas.nbins", &nbins, unsigned);
CTL_GET("arenas.nlextents", &nlextents, unsigned); CTL_GET("arenas.nlextents", &nlextents, unsigned);
emitter_row_t header_row;
emitter_row_init(&header_row);
emitter_row_t row;
emitter_row_init(&row);
bool prof_stats_on = config_prof && opt_prof && opt_prof_stats bool prof_stats_on = config_prof && opt_prof && opt_prof_stats
&& i == MALLCTL_ARENAS_ALL; && i == MALLCTL_ARENAS_ALL;
emitter_row_t row, header_row;
COL_HDR(row, size, NULL, right, 20, size) emitter_col_t cols[LEXTENT_COL_COUNT], header_cols[LEXTENT_COL_COUNT];
COL_HDR(row, ind, NULL, right, 4, unsigned) char size_buf[48];
COL_HDR(row, allocated, NULL, right, 13, size) emitter_col_table_build(stats_lextent_cols, LEXTENT_COL_COUNT,
COL_HDR(row, nmalloc, NULL, right, 13, uint64) stats_col_active_flags(prof_stats_on), &row, cols, &header_row,
COL_HDR(row, nmalloc_ps, "(#/sec)", right, 8, uint64) header_cols);
COL_HDR(row, ndalloc, NULL, right, 13, uint64) emitter_col_table_header(emitter, &header_row,
COL_HDR(row, ndalloc_ps, "(#/sec)", right, 8, uint64) &header_cols[LEXTENT_COL_SIZE], "large:", "lextents");
COL_HDR(row, nrequests, NULL, right, 13, uint64)
COL_HDR(row, nrequests_ps, "(#/sec)", right, 8, uint64)
COL_HDR_DECLARE(prof_live_requested)
COL_HDR_DECLARE(prof_live_count)
COL_HDR_DECLARE(prof_accum_requested)
COL_HDR_DECLARE(prof_accum_count)
if (prof_stats_on) {
COL_HDR_INIT(row, prof_live_requested, NULL, right, 21, uint64)
COL_HDR_INIT(row, prof_live_count, NULL, right, 17, uint64)
COL_HDR_INIT(row, prof_accum_requested, NULL, right, 21, uint64)
COL_HDR_INIT(row, prof_accum_count, NULL, right, 17, uint64)
col_prof_live_requested.json_key = "prof_live_requested";
col_prof_live_count.json_key = "prof_live_count";
col_prof_accum_requested.json_key = "prof_accum_requested";
col_prof_accum_count.json_key = "prof_accum_count";
}
COL_HDR(row, curlextents, NULL, right, 13, size)
col_curlextents.json_key = "curlextents";
/* As with bins, we label the large extents table. */
header_size.width -= 6;
emitter_table_printf(emitter, "large:");
emitter_table_row(emitter, &header_row);
emitter_json_array_kv_begin(emitter, "lextents");
size_t stats_arenas_mib[CTL_MAX_DEPTH]; size_t stats_arenas_mib[CTL_MAX_DEPTH];
CTL_LEAF_PREPARE(stats_arenas_mib, 0, "stats.arenas"); CTL_LEAF_PREPARE(stats_arenas_mib, 0, "stats.arenas");
@@ -1022,64 +1131,104 @@ stats_arena_lextents_print(emitter_t *emitter, unsigned i, uint64_t uptime) {
bool is_gap = (lext.nrequests == 0); bool is_gap = (lext.nrequests == 0);
char size_buf[48]; stats_arena_lextent_emit_row_t emit_row = {
stats_size_col_set(&col_size, lext.lextent_size, prev_size, &lext, nbins + j, uptime};
size_buf, sizeof(size_buf)); stats_emit_arena_lextent_row(emitter, &row, cols, &emit_row,
prof_stats_on, prev_size, size_buf, sizeof(size_buf), is_gap);
prev_size = lext.lextent_size; prev_size = lext.lextent_size;
col_ind.unsigned_val = nbins + j;
col_allocated.size_val = lext.curlextents * lext.lextent_size;
col_nmalloc.uint64_val = lext.nmalloc;
col_nmalloc_ps.uint64_val = rate_per_second(lext.nmalloc, uptime);
col_ndalloc.uint64_val = lext.ndalloc;
col_ndalloc_ps.uint64_val = rate_per_second(lext.ndalloc, uptime);
col_nrequests.uint64_val = lext.nrequests;
col_nrequests_ps.uint64_val = rate_per_second(
lext.nrequests, uptime);
if (prof_stats_on) {
col_prof_live_requested.uint64_val = lext.prof_live.req_sum;
col_prof_live_count.uint64_val = lext.prof_live.count;
col_prof_accum_requested.uint64_val =
lext.prof_accum.req_sum;
col_prof_accum_count.uint64_val = lext.prof_accum.count;
}
col_curlextents.size_val = lext.curlextents;
emitter_json_object_begin(emitter);
emitter_sparse_row(emitter, &row, is_gap);
emitter_json_object_end(emitter);
} }
emitter_json_array_end(emitter); /* Close "lextents". */ emitter_json_array_end(emitter); /* Close "lextents". */
emitter_table_sparse_end(emitter); 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, char *size_buf, size_t size_buf_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);
stats_size_col_set(&cols[EXTENT_COL_SIZE], row->size, prev_size,
size_buf, size_buf_size);
emitter_table_sparse_row(emitter, table_row, is_gap);
}
JEMALLOC_COLD JEMALLOC_COLD
static void static void
stats_arena_extents_print(emitter_t *emitter, unsigned i) { stats_arena_extents_print(emitter_t *emitter, unsigned i) {
/* Streaming table; see stats_arena_bins_print for the rationale. */ /* Streaming table; see stats_arena_bins_print for the rationale. */
unsigned j; unsigned j;
emitter_row_t header_row; emitter_row_t row, header_row;
emitter_row_init(&header_row); emitter_col_t cols[EXTENT_COL_COUNT], header_cols[EXTENT_COL_COUNT];
emitter_row_t row; char size_buf[48];
emitter_row_init(&row); emitter_col_table_build(stats_extent_cols, EXTENT_COL_COUNT,
STATS_COL_FLAG_NONE, &row, cols, &header_row, header_cols);
COL_HDR(row, size, NULL, right, 20, size) emitter_col_table_header(emitter, &header_row,
COL_HDR(row, ind, NULL, right, 4, unsigned) &header_cols[EXTENT_COL_SIZE], "extents:", "extents");
COL_HDR(row, ndirty, NULL, right, 13, size)
COL_HDR(row, dirty, NULL, right, 13, size)
COL_HDR(row, nmuzzy, NULL, right, 13, size)
COL_HDR(row, muzzy, NULL, right, 13, size)
COL_HDR(row, nretained, NULL, right, 13, size)
COL_HDR(row, retained, NULL, right, 13, size)
COL_HDR(row, npinned, NULL, right, 13, size)
COL_HDR(row, pinned, NULL, right, 13, size)
COL_HDR(row, ntotal, NULL, right, 13, size)
COL_HDR(row, total, NULL, right, 13, size)
/* Label this section. */
header_size.width -= 8;
emitter_table_printf(emitter, "extents:");
emitter_table_row(emitter, &header_row);
emitter_json_array_kv_begin(emitter, "extents");
size_t stats_arenas_mib[CTL_MAX_DEPTH]; size_t stats_arenas_mib[CTL_MAX_DEPTH];
CTL_LEAF_PREPARE(stats_arenas_mib, 0, "stats.arenas"); CTL_LEAF_PREPARE(stats_arenas_mib, 0, "stats.arenas");
@@ -1093,40 +1242,10 @@ stats_arena_extents_print(emitter_t *emitter, unsigned i) {
bool is_gap = (e.ntotal == 0); bool is_gap = (e.ntotal == 0);
emitter_json_object_begin(emitter); stats_arena_extent_emit_row_t emit_row = {&e, j, sz_pind2sz(j)};
emitter_json_kv(emitter, "ndirty", emitter_type_size, &e.ndirty); stats_emit_arena_extent_row(emitter, &row, cols, &emit_row,
emitter_json_kv(emitter, "nmuzzy", emitter_type_size, &e.nmuzzy); j > 0 ? sz_pind2sz(j - 1) : 0, size_buf, sizeof(size_buf),
emitter_json_kv( is_gap);
emitter, "nretained", emitter_type_size, &e.nretained);
emitter_json_kv(
emitter, "npinned", emitter_type_size, &e.npinned);
emitter_json_kv(
emitter, "dirty_bytes", emitter_type_size, &e.dirty_bytes);
emitter_json_kv(
emitter, "muzzy_bytes", emitter_type_size, &e.muzzy_bytes);
emitter_json_kv(emitter, "retained_bytes", emitter_type_size,
&e.retained_bytes);
emitter_json_kv(emitter, "pinned_bytes", emitter_type_size,
&e.pinned_bytes);
emitter_json_object_end(emitter);
char size_buf[48];
stats_size_col_set(&col_size, sz_pind2sz(j),
j > 0 ? sz_pind2sz(j - 1) : 0, size_buf, sizeof(size_buf));
col_ind.unsigned_val = j;
col_ndirty.size_val = e.ndirty;
col_dirty.size_val = e.dirty_bytes;
col_nmuzzy.size_val = e.nmuzzy;
col_muzzy.size_val = e.muzzy_bytes;
col_nretained.size_val = e.nretained;
col_retained.size_val = e.retained_bytes;
col_npinned.size_val = e.npinned;
col_pinned.size_val = e.pinned_bytes;
col_ntotal.size_val = e.ntotal;
col_total.size_val = e.total_bytes;
emitter_table_sparse_row(emitter, &row, is_gap);
} }
emitter_json_array_end(emitter); /* Close "extents". */ emitter_json_array_end(emitter); /* Close "extents". */
emitter_table_sparse_end(emitter); emitter_table_sparse_end(emitter);
@@ -1319,59 +1438,107 @@ stats_arena_hpa_shard_counters_print(
emitter_json_array_end(emitter); /* End "alloc_batch" */ 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 static void
stats_emit_arena_hpa_slab_json(emitter_t *emitter, stats_hpa_slab_col_get_size(const void *vrow, emitter_col_t *col) {
const stats_arena_hpa_slab_t *s, const char *json_key) { const stats_arena_hpa_slab_emit_row_t *row = vrow;
emitter_json_object_kv_begin(emitter, json_key); if (row->size_title != NULL) {
emitter_json_kv( col->type = emitter_type_title;
emitter, "npageslabs_huge", emitter_type_size, &s->npageslabs_huge); col->str_val = row->size_title;
emitter_json_kv( } else {
emitter, "nactive_huge", emitter_type_size, &s->nactive_huge); col->size_val = row->size;
emitter_json_kv( }
emitter, "ndirty_huge", emitter_type_size, &s->ndirty_huge);
emitter_json_kv(emitter, "npageslabs_nonhuge", emitter_type_size,
&s->npageslabs_nonhuge);
emitter_json_kv(
emitter, "nactive_nonhuge", emitter_type_size, &s->nactive_nonhuge);
emitter_json_kv(
emitter, "ndirty_nonhuge", emitter_type_size, &s->ndirty_nonhuge);
emitter_json_kv(emitter, "nretained_nonhuge", emitter_type_size,
&s->nretained_nonhuge);
emitter_json_object_end(emitter);
} }
/* Fill the shared count columns of one pageslab row from s. */
static void static void
stats_hpa_slab_cols_set(const stats_arena_hpa_slab_t *s, stats_hpa_slab_col_get_ind(const void *vrow, emitter_col_t *col) {
emitter_col_t *npageslabs_huge, emitter_col_t *nactive_huge, const stats_arena_hpa_slab_emit_row_t *row = vrow;
emitter_col_t *ndirty_huge, emitter_col_t *npageslabs_nonhuge, if (row->size_title != NULL) {
emitter_col_t *nactive_nonhuge, emitter_col_t *ndirty_nonhuge, col->type = emitter_type_title;
emitter_col_t *nretained_nonhuge) { col->str_val = "-";
npageslabs_huge->size_val = s->npageslabs_huge; } else {
nactive_huge->size_val = s->nactive_huge; col->unsigned_val = row->ind;
ndirty_huge->size_val = s->ndirty_huge; }
npageslabs_nonhuge->size_val = s->npageslabs_nonhuge; }
nactive_nonhuge->size_val = s->nactive_nonhuge;
ndirty_nonhuge->size_val = s->ndirty_nonhuge; #define HPA_SLAB_COL_SIZE 0
nretained_nonhuge->size_val = s->nretained_nonhuge;
#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,
char *size_buf, size_t size_buf_size, 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);
if (row->size_title == NULL) {
stats_size_col_set(&cols[HPA_SLAB_COL_SIZE], row->size,
row->prev_size, size_buf, size_buf_size);
}
if (sparse) {
emitter_table_sparse_row(emitter, table_row, is_gap);
} else {
emitter_table_row(emitter, table_row);
}
} }
static void static void
stats_arena_hpa_shard_slabs_print(emitter_t *emitter, unsigned i) { stats_arena_hpa_shard_slabs_print(emitter_t *emitter, unsigned i) {
emitter_row_t header_row; emitter_row_t row, header_row;
emitter_row_init(&header_row); emitter_col_t cols[HPA_SLAB_COL_COUNT];
emitter_row_t row; emitter_col_t header_cols[HPA_SLAB_COL_COUNT];
emitter_row_init(&row); char size_buf[48];
emitter_col_table_build(stats_hpa_slab_cols, HPA_SLAB_COL_COUNT,
COL_HDR(row, size, NULL, right, 20, size) STATS_COL_FLAG_NONE, &row, cols, &header_row, header_cols);
COL_HDR(row, ind, NULL, right, 4, unsigned)
COL_HDR(row, npageslabs_huge, NULL, right, 16, size)
COL_HDR(row, nactive_huge, NULL, right, 16, size)
COL_HDR(row, ndirty_huge, NULL, right, 16, size)
COL_HDR(row, npageslabs_nonhuge, NULL, right, 20, size)
COL_HDR(row, nactive_nonhuge, NULL, right, 20, size)
COL_HDR(row, ndirty_nonhuge, NULL, right, 20, size)
COL_HDR(row, nretained_nonhuge, NULL, right, 20, size)
emitter_table_printf(emitter, "pageslabs:\n"); emitter_table_printf(emitter, "pageslabs:\n");
emitter_table_row(emitter, &header_row); emitter_table_row(emitter, &header_row);
@@ -1381,31 +1548,19 @@ stats_arena_hpa_shard_slabs_print(emitter_t *emitter, unsigned i) {
* "full"/"empty", no size-class index) at the top of the table; their * "full"/"empty", no size-class index) at the top of the table; their
* JSON stays in the separate full_slabs / empty_slabs objects. * JSON stays in the separate full_slabs / empty_slabs objects.
*/ */
col_ind.type = emitter_type_title;
col_ind.str_val = "-";
col_size.type = emitter_type_title;
stats_arena_hpa_slab_t sfull; stats_arena_hpa_slab_t sfull;
stats_gather_arena_hpa_slab(i, "full_slabs", &sfull); stats_gather_arena_hpa_slab(i, "full_slabs", &sfull);
stats_emit_arena_hpa_slab_json(emitter, &sfull, "full_slabs"); stats_arena_hpa_slab_emit_row_t full_row = {
col_size.str_val = "full"; &sfull, "full", 0, 0, 0};
stats_hpa_slab_cols_set(&sfull, &col_npageslabs_huge, &col_nactive_huge, stats_emit_arena_hpa_slab_row(emitter, &row, cols, &full_row,
&col_ndirty_huge, &col_npageslabs_nonhuge, &col_nactive_nonhuge, "full_slabs", size_buf, sizeof(size_buf), false, false);
&col_ndirty_nonhuge, &col_nretained_nonhuge);
emitter_table_row(emitter, &row);
stats_arena_hpa_slab_t sempty; stats_arena_hpa_slab_t sempty;
stats_gather_arena_hpa_slab(i, "empty_slabs", &sempty); stats_gather_arena_hpa_slab(i, "empty_slabs", &sempty);
stats_emit_arena_hpa_slab_json(emitter, &sempty, "empty_slabs"); stats_arena_hpa_slab_emit_row_t empty_row = {
col_size.str_val = "empty"; &sempty, "empty", 0, 0, 0};
stats_hpa_slab_cols_set(&sempty, &col_npageslabs_huge, &col_nactive_huge, stats_emit_arena_hpa_slab_row(emitter, &row, cols, &empty_row,
&col_ndirty_huge, &col_npageslabs_nonhuge, &col_nactive_nonhuge, "empty_slabs", size_buf, sizeof(size_buf), false, false);
&col_ndirty_nonhuge, &col_nretained_nonhuge);
emitter_table_row(emitter, &row);
/* Numeric size/ind for the per-size-class rows below. */
col_size.type = emitter_type_size;
col_ind.type = emitter_type_unsigned;
size_t stats_arenas_mib[CTL_MAX_DEPTH]; size_t stats_arenas_mib[CTL_MAX_DEPTH];
CTL_LEAF_PREPARE(stats_arenas_mib, 0, "stats.arenas"); CTL_LEAF_PREPARE(stats_arenas_mib, 0, "stats.arenas");
@@ -1420,35 +1575,11 @@ stats_arena_hpa_shard_slabs_print(emitter_t *emitter, unsigned i) {
bool is_gap = (s.npageslabs_huge == 0 && s.npageslabs_nonhuge == 0); bool is_gap = (s.npageslabs_huge == 0 && s.npageslabs_nonhuge == 0);
char size_buf[48]; stats_arena_hpa_slab_emit_row_t emit_row = {
stats_size_col_set(&col_size, sz_pind2sz(j), &s, NULL, sz_pind2sz(j),
j > 0 ? sz_pind2sz(j - 1) : 0, size_buf, sizeof(size_buf)); j > 0 ? sz_pind2sz(j - 1) : 0, j};
col_ind.unsigned_val = j; stats_emit_arena_hpa_slab_row(emitter, &row, cols, &emit_row,
col_npageslabs_huge.size_val = s.npageslabs_huge; NULL, size_buf, sizeof(size_buf), true, is_gap);
col_nactive_huge.size_val = s.nactive_huge;
col_ndirty_huge.size_val = s.ndirty_huge;
col_npageslabs_nonhuge.size_val = s.npageslabs_nonhuge;
col_nactive_nonhuge.size_val = s.nactive_nonhuge;
col_ndirty_nonhuge.size_val = s.ndirty_nonhuge;
col_nretained_nonhuge.size_val = s.nretained_nonhuge;
emitter_table_sparse_row(emitter, &row, is_gap);
emitter_json_object_begin(emitter);
emitter_json_kv(emitter, "npageslabs_huge", emitter_type_size,
&s.npageslabs_huge);
emitter_json_kv(
emitter, "nactive_huge", emitter_type_size, &s.nactive_huge);
emitter_json_kv(
emitter, "ndirty_huge", emitter_type_size, &s.ndirty_huge);
emitter_json_kv(emitter, "npageslabs_nonhuge",
emitter_type_size, &s.npageslabs_nonhuge);
emitter_json_kv(emitter, "nactive_nonhuge", emitter_type_size,
&s.nactive_nonhuge);
emitter_json_kv(emitter, "ndirty_nonhuge", emitter_type_size,
&s.ndirty_nonhuge);
emitter_json_kv(emitter, "nretained_nonhuge", emitter_type_size,
&s.nretained_nonhuge);
emitter_json_object_end(emitter);
} }
emitter_json_array_end(emitter); /* End "nonfull_slabs" */ emitter_json_array_end(emitter); /* End "nonfull_slabs" */
emitter_table_sparse_end(emitter); emitter_table_sparse_end(emitter);

View File

@@ -668,6 +668,110 @@ static const char *sparse_row_table =
" 2\n" " 2\n"
" ---\n"; " ---\n";
typedef struct {
size_t size;
uint64_t count;
uint64_t prof_count;
const char *label;
uint64_t requests;
} col_table_test_row_t;
#define COL_TABLE_TEST_GETTER(name, member, value_member) \
static void \
col_table_test_get_##name(const void *vrow, emitter_col_t *col) { \
const col_table_test_row_t *row = vrow; \
col->value_member = row->member; \
}
COL_TABLE_TEST_GETTER(size, size, size_val)
COL_TABLE_TEST_GETTER(count, count, uint64_val)
COL_TABLE_TEST_GETTER(prof_count, prof_count, uint64_val)
COL_TABLE_TEST_GETTER(label, label, str_val)
COL_TABLE_TEST_GETTER(requests, requests, uint64_val)
#undef COL_TABLE_TEST_GETTER
enum {
COL_TABLE_TEST_SIZE,
COL_TABLE_TEST_COUNT,
COL_TABLE_TEST_PROF_COUNT,
COL_TABLE_TEST_LABEL,
COL_TABLE_TEST_REQUESTS,
COL_TABLE_TEST_NCOLS
};
#define COL_TABLE_TEST_FLAG_PROF (1U << 0)
static const emitter_col_desc_t col_table_test_descs[] = {
{NULL, "size", emitter_justify_right, 8, emitter_type_size, 0,
col_table_test_get_size},
{"count", "count", emitter_justify_right, 8, emitter_type_uint64,
0, col_table_test_get_count},
{"prof_count", "prof", emitter_justify_right, 6, emitter_type_uint64,
COL_TABLE_TEST_FLAG_PROF, col_table_test_get_prof_count},
{"label", "label", emitter_justify_right, 7, emitter_type_title, 0,
col_table_test_get_label},
{"requests", "requests", emitter_justify_right, 10,
emitter_type_uint64, 0, col_table_test_get_requests},
};
_Static_assert(
sizeof(col_table_test_descs) / sizeof(col_table_test_descs[0]) ==
COL_TABLE_TEST_NCOLS,
"col_table_test_descs must match COL_TABLE_TEST_NCOLS");
TEST_BEGIN(test_col_desc_flags) {
expect_true(emitter_col_desc_active(
&col_table_test_descs[COL_TABLE_TEST_COUNT], 0),
"A column without requirements should always be active");
expect_false(emitter_col_desc_active(
&col_table_test_descs[COL_TABLE_TEST_PROF_COUNT], 0),
"A column with an unmet requirement should be inactive");
expect_true(emitter_col_desc_active(
&col_table_test_descs[COL_TABLE_TEST_PROF_COUNT],
COL_TABLE_TEST_FLAG_PROF),
"A column with a satisfied requirement should be active");
}
TEST_END
static void
emit_col_table(emitter_t *emitter) {
col_table_test_row_t value = {42, 7, 100, "row", 9};
emitter_row_t row, header_row;
emitter_col_t cols[COL_TABLE_TEST_NCOLS];
emitter_col_t header_cols[COL_TABLE_TEST_NCOLS];
emitter_begin(emitter);
emitter_col_table_build(col_table_test_descs, COL_TABLE_TEST_NCOLS,
COL_TABLE_TEST_FLAG_PROF, &row, cols, &header_row, header_cols);
emitter_col_table_header(
emitter, &header_row, &header_cols[0], "mock:", "rows");
emitter_col_table_fill(col_table_test_descs, COL_TABLE_TEST_NCOLS,
COL_TABLE_TEST_FLAG_PROF, cols, &value);
emitter_json_object_begin(emitter);
emitter_col_table_emit_json(emitter, col_table_test_descs,
COL_TABLE_TEST_NCOLS, COL_TABLE_TEST_FLAG_PROF, cols);
emitter_json_object_end(emitter);
emitter_table_row(emitter, &row);
emitter_json_array_end(emitter);
emitter_end(emitter);
}
static const char *col_table_json =
"{\n"
"\t\"rows\": [\n"
"\t\t{\n"
"\t\t\t\"count\": 7,\n"
"\t\t\t\"prof_count\": 100,\n"
"\t\t\t\"label\": \"row\",\n"
"\t\t\t\"requests\": 9\n"
"\t\t}\n"
"\t]\n"
"}\n";
static const char *col_table_json_compact =
"{\"rows\":[{\"count\":7,\"prof_count\":100,\"label\":\"row\","
"\"requests\":9}]}";
static const char *col_table_table =
"mock:size count prof label requests\n"
" 42 7 100 row 9\n";
#define GENERATE_TEST(feature) \ #define GENERATE_TEST(feature) \
TEST_BEGIN(test_##feature) { \ TEST_BEGIN(test_##feature) { \
expect_emit_output(emit_##feature, feature##_json, \ expect_emit_output(emit_##feature, feature##_json, \
@@ -685,10 +789,12 @@ GENERATE_TEST(json_nested_array)
GENERATE_TEST(table_row) GENERATE_TEST(table_row)
GENERATE_TEST(row) GENERATE_TEST(row)
GENERATE_TEST(sparse_row) GENERATE_TEST(sparse_row)
GENERATE_TEST(col_table)
int int
main(void) { main(void) {
return test_no_reentrancy(test_dict, test_table_printf, return test_no_reentrancy(test_dict, test_table_printf,
test_nested_dict, test_types, test_modal, test_json_array, test_nested_dict, test_types, test_modal, test_json_array,
test_json_nested_array, test_table_row, test_row, test_sparse_row); test_json_nested_array, test_table_row, test_row, test_sparse_row,
test_col_table, test_col_desc_flags);
} }

File diff suppressed because it is too large Load Diff