This commit is contained in:
Slobodan Predolac 2026-07-17 07:20:27 -07:00 committed by GitHub
commit f22f73e0a9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 1810 additions and 1133 deletions

View file

@ -58,6 +58,11 @@ struct emitter_col_s {
ssize_t ssize_val;
const char *str_val;
};
/*
* Optional JSON key. When set, emitter_row() emits this column as a
* "json_key": value pair in JSON mode; columns left NULL are table-only.
*/
const char *json_key;
/* Filled in by initialization. */
ql_elm(emitter_col_t) link;
@ -79,6 +84,8 @@ struct emitter_s {
bool item_at_depth;
/* True if we emitted a key and will emit corresponding value next. */
bool emitted_key;
/* Gap-collapsing state for the emitter_table_sparse_row() bracket. */
bool table_sparse_in_gap;
};
static inline bool
@ -270,6 +277,7 @@ emitter_init(emitter_t *emitter, emitter_output_t emitter_output,
emitter->item_at_depth = false;
emitter->emitted_key = false;
emitter->nesting_depth = 0;
emitter->table_sparse_in_gap = false;
}
/******************************************************************************/
@ -447,6 +455,46 @@ static inline void
emitter_col_init(emitter_col_t *col, emitter_row_t *row) {
ql_elm_new(col, link);
ql_tail_insert(&row->cols, col, link);
col->json_key = NULL;
}
/*
* Sparse table rows. Some tables have one row per size class but most rows
* are all-zero; in table output those runs are collapsed to a single "---"
* separator. Bracket such a table with emitter_table_sparse_begin() /
* _end() and emit each row with emitter_table_sparse_row(), passing whether
* that row is a gap (all-zero). The emitter prints the separator on a
* gap->non-gap transition and at a trailing gap, and suppresses the gap rows
* themselves; callers no longer track gap state. Table output only -- a
* no-op in JSON, where callers emit every row's object separately.
*/
static inline void
emitter_table_sparse_begin(emitter_t *emitter) {
emitter->table_sparse_in_gap = false;
}
static inline void
emitter_table_sparse_row(emitter_t *emitter, emitter_row_t *row, bool is_gap) {
if (emitter->output != emitter_output_table) {
return;
}
bool prev_in_gap = emitter->table_sparse_in_gap;
emitter->table_sparse_in_gap = is_gap;
if (prev_in_gap && !is_gap) {
emitter_printf(emitter, " ---\n");
}
if (!is_gap) {
emitter_table_row(emitter, row);
}
}
static inline void
emitter_table_sparse_end(emitter_t *emitter) {
if (emitter->output == emitter_output_table
&& emitter->table_sparse_in_gap) {
emitter_printf(emitter, " ---\n");
}
emitter->table_sparse_in_gap = false;
}
/******************************************************************************/
@ -499,6 +547,49 @@ emitter_dict_end(emitter_t *emitter) {
}
}
/* Internal: emit a row's json-keyed columns as JSON kvs, in column order. */
static inline void
emitter_row_json(emitter_t *emitter, emitter_row_t *row) {
emitter_col_t *col;
ql_foreach (col, &row->cols, link) {
if (col->json_key != NULL) {
emitter_json_kv(emitter, col->json_key, col->type,
(const void *)&col->bool_val);
}
}
}
/*
* Emit a row of columns to whichever format is active. In table mode, prints
* the aligned table row (see emitter_table_row). In JSON mode, emits each
* column that has a non-NULL json_key as a "json_key": value pair, in column
* order (columns without a json_key are table-only). This lets a caller
* describe a row once and render it to either format. For JSON objects (one
* per row), wrap the call in emitter_json_object_begin()/_end().
*/
static inline void
emitter_row(emitter_t *emitter, emitter_row_t *row) {
emitter_table_row(emitter, row);
if (emitter_outputs_json(emitter)) {
emitter_row_json(emitter, row);
}
}
/*
* Sparse counterpart of emitter_row() for per-size-class tables: the table
* side collapses all-zero ("gap") rows (see emitter_table_sparse_row), while
* the JSON side still emits every row's json-keyed columns. Bracket the loop
* with emitter_table_sparse_begin()/_end(), and wrap each row's JSON in
* emitter_json_object_begin()/_end() when the rows form an array of objects.
*/
static inline void
emitter_sparse_row(emitter_t *emitter, emitter_row_t *row, bool is_gap) {
emitter_table_sparse_row(emitter, row, is_gap);
if (emitter_outputs_json(emitter)) {
emitter_row_json(emitter, row);
}
}
static inline void
emitter_begin(emitter_t *emitter) {
if (emitter_outputs_json(emitter)) {

View file

@ -0,0 +1,340 @@
#ifndef JEMALLOC_INTERNAL_STATS_INTERNAL_H
#define JEMALLOC_INTERNAL_STATS_INTERNAL_H
#include "jemalloc/internal/jemalloc_preamble.h"
#include "jemalloc/internal/arena.h" /* ARENA_NAME_LEN */
#include "jemalloc/internal/assert.h"
#include "jemalloc/internal/ctl.h"
#include "jemalloc/internal/emitter.h"
#include "jemalloc/internal/prof_stats.h"
/*
* Internal implementation support for src/stats.c only -- do NOT include from
* other translation units.
*
* This header holds noisy local scaffolding for malloc_stats_print(): ctl
* gather plumbing, gather structs, emitter table-column helpers, and option
* list helpers. It is deliberately not a reusable stats-gather API.
*/
#define CTL_GET(n, v, t) \
do { \
size_t sz = sizeof(t); \
xmallctl(n, (void *)v, &sz, NULL, 0); \
} while (0)
#define CTL_LEAF_PREPARE(mib, miblen, name) \
do { \
assert(miblen < CTL_MAX_DEPTH); \
size_t miblen_new = CTL_MAX_DEPTH; \
xmallctlmibnametomib(mib, miblen, name, &miblen_new); \
assert(miblen_new > miblen); \
} while (0)
#define CTL_LEAF(mib, miblen, leaf, v, t) \
do { \
assert(miblen < CTL_MAX_DEPTH); \
size_t miblen_new = CTL_MAX_DEPTH; \
size_t sz = sizeof(t); \
xmallctlbymibname( \
mib, miblen, leaf, &miblen_new, (void *)v, &sz, NULL, 0); \
assert(miblen_new == miblen + 1); \
} while (0)
#define CTL_MIB_GET(n, i, v, t, ind) \
do { \
size_t mib[CTL_MAX_DEPTH]; \
size_t miblen = sizeof(mib) / sizeof(size_t); \
size_t sz = sizeof(t); \
xmallctlnametomib(n, mib, &miblen); \
mib[(ind)] = (i); \
xmallctlbymib(mib, miblen, (void *)v, &sz, NULL, 0); \
} while (0)
#define CTL_M1_GET(n, i, v, t) CTL_MIB_GET(n, i, v, t, 1)
#define CTL_M2_GET(n, i, v, t) CTL_MIB_GET(n, i, v, t, 2)
/******************************************************************************/
/*
* Per-section gather structs. The stats_gather_*() functions that fill them
* are static in src/stats.c.
*/
/* One arena's memory-accounting stats (stats.arenas.<i>.*). */
typedef struct stats_arena_mem_s {
size_t mapped;
size_t retained;
size_t pinned;
size_t base;
size_t internal;
size_t metadata_edata;
size_t metadata_rtree;
size_t metadata_thp;
size_t tcache_bytes;
size_t tcache_stashed_bytes;
size_t resident;
size_t abandoned_vm;
size_t extent_avail;
size_t page; /* arenas.page, for the active-bytes conversion */
} stats_arena_mem_t;
/* One arena extents row (stats.arenas.<i>.extents.<pszind>.*). */
typedef struct stats_arena_extent_s {
size_t ndirty;
size_t nmuzzy;
size_t nretained;
size_t npinned;
size_t ntotal;
size_t dirty_bytes;
size_t muzzy_bytes;
size_t retained_bytes;
size_t pinned_bytes;
size_t total_bytes;
} stats_arena_extent_t;
/* One arena's "basics" (stats.arenas.<i>.{nthreads,uptime,dss}). */
typedef struct stats_arena_basics_s {
unsigned nthreads;
uint64_t uptime;
const char *dss;
char name[ARENA_NAME_LEN];
bool has_name; /* false for the merged/destroyed pseudo-arenas */
} stats_arena_basics_t;
/* One arena's decay / page stats (stats.arenas.<i>.*). */
typedef struct stats_arena_decay_s {
ssize_t dirty_decay_ms;
ssize_t muzzy_decay_ms;
size_t pactive;
size_t pdirty;
size_t pmuzzy;
uint64_t dirty_npurge;
uint64_t dirty_nmadvise;
uint64_t dirty_purged;
uint64_t muzzy_npurge;
uint64_t muzzy_nmadvise;
uint64_t muzzy_purged;
} stats_arena_decay_t;
/* One size class of arena allocation counts (stats.arenas.<i>.{small,large}.*). */
typedef struct stats_arena_alloc_s {
size_t allocated;
uint64_t nmalloc;
uint64_t ndalloc;
uint64_t nrequests;
uint64_t nfills;
uint64_t nflushes;
} stats_arena_alloc_t;
/*
* One arena bin row (stats.arenas.<i>.bins.<j>.* + arenas.bin.<j>.*,
* plus prof.stats.bins.<j>.{live,accum} when profiling stats are enabled).
*/
typedef struct stats_arena_bin_s {
uint64_t nslabs;
uint64_t nmalloc;
uint64_t ndalloc;
uint64_t nrequests;
uint64_t nfills;
uint64_t nflushes;
uint64_t nreslabs;
size_t reg_size;
size_t slab_size;
size_t curregs;
size_t curslabs;
size_t nonfull_slabs;
uint32_t nregs;
uint32_t nshards;
prof_stats_t prof_live;
prof_stats_t prof_accum;
} stats_arena_bin_t;
/*
* One arena large-extent row (stats.arenas.<i>.lextents.<j>.* +
* arenas.lextent.<j>.*, plus prof.stats.lextents.<j>.{live,accum} when
* profiling stats are enabled).
*/
typedef struct stats_arena_lextent_s {
uint64_t nmalloc;
uint64_t ndalloc;
uint64_t nrequests;
size_t lextent_size;
size_t curlextents;
prof_stats_t prof_live;
prof_stats_t prof_accum;
} stats_arena_lextent_t;
/* HPA shard small-extent cache (stats.arenas.<i>.hpa_sec_*). */
typedef struct stats_arena_hpa_sec_s {
size_t sec_bytes;
size_t sec_hits;
size_t sec_misses;
size_t sec_dalloc_flush;
size_t sec_dalloc_noflush;
size_t sec_overfills;
} stats_arena_hpa_sec_t;
/* PAC small-extent cache (stats.arenas.<i>.pac_sec_*). */
typedef struct stats_arena_pac_sec_s {
size_t sec_bytes;
size_t sec_hits;
size_t sec_misses;
size_t sec_dalloc_flush;
size_t sec_dalloc_noflush;
} stats_arena_pac_sec_t;
/* HPA shard counters (stats.arenas.<i>.hpa_shard.*). */
typedef struct stats_arena_hpa_counters_s {
size_t npageslabs;
size_t nactive;
size_t ndirty;
size_t npageslabs_nonhuge;
size_t nactive_nonhuge;
size_t ndirty_nonhuge;
size_t nretained_nonhuge;
size_t npageslabs_huge;
size_t nactive_huge;
size_t ndirty_huge;
uint64_t npurge_passes;
uint64_t npurges;
uint64_t nhugifies;
uint64_t nhugify_failures;
uint64_t ndehugifies;
} stats_arena_hpa_counters_t;
/* One HPA slab class (full/empty/nonfull) row. */
typedef struct stats_arena_hpa_slab_s {
size_t npageslabs_huge;
size_t nactive_huge;
size_t ndirty_huge;
size_t npageslabs_nonhuge;
size_t nactive_nonhuge;
size_t ndirty_nonhuge;
size_t nretained_nonhuge;
} stats_arena_hpa_slab_t;
/* Arena configuration / size-class scalars (arenas.*). */
typedef struct stats_arena_config_s {
unsigned narenas;
ssize_t dirty_decay_ms;
ssize_t muzzy_decay_ms;
size_t quantum;
size_t page;
size_t hugepage;
bool have_tcache_max;
size_t tcache_max;
unsigned nbins;
unsigned nhbins;
unsigned nlextents;
} stats_arena_config_t;
/* Per-size-class geometry (emitted in JSON only). */
typedef struct stats_arena_bin_meta_s {
size_t size;
uint32_t nregs;
size_t slab_size;
uint32_t nshards;
} stats_arena_bin_meta_t;
typedef struct stats_arena_lextent_meta_s {
size_t size;
} stats_arena_lextent_meta_t;
/* Process-wide global stats (stats.* + stats.background_thread.*). */
typedef struct stats_global_s {
size_t allocated;
size_t active;
size_t metadata;
size_t metadata_edata;
size_t metadata_rtree;
size_t metadata_thp;
size_t resident;
size_t mapped;
size_t retained;
size_t pinned;
size_t zero_reallocs;
size_t num_background_threads;
uint64_t background_thread_num_runs;
uint64_t background_thread_run_interval;
} stats_global_t;
/******************************************************************************/
/*
* Emitter table-column helper macros: declare an emitter_col_t (and, for
* COL_HDR, its header column) and wire it into a row.
*/
#define COL_DECLARE(column_name) emitter_col_t col_##column_name;
#define COL_INIT(row_name, column_name, left_or_right, col_width, etype) \
emitter_col_init(&col_##column_name, &row_name); \
col_##column_name.justify = emitter_justify_##left_or_right; \
col_##column_name.width = col_width; \
col_##column_name.type = emitter_type_##etype;
#define COL(row_name, column_name, left_or_right, col_width, etype) \
COL_DECLARE(column_name); \
COL_INIT(row_name, column_name, left_or_right, col_width, etype)
#define COL_HDR_DECLARE(column_name) \
COL_DECLARE(column_name); \
emitter_col_t header_##column_name;
#define COL_HDR_INIT( \
row_name, column_name, human, left_or_right, col_width, etype) \
COL_INIT(row_name, column_name, left_or_right, col_width, etype) \
emitter_col_init(&header_##column_name, &header_##row_name); \
header_##column_name.justify = emitter_justify_##left_or_right; \
header_##column_name.width = col_width; \
header_##column_name.type = emitter_type_title; \
header_##column_name.str_val = human ? human : #column_name;
#define COL_HDR(row_name, column_name, human, left_or_right, col_width, etype) \
COL_HDR_DECLARE(column_name) \
COL_HDR_INIT( \
row_name, column_name, human, left_or_right, col_width, etype)
/*
* stats_general_print() config/option list helpers. Unhygienic by design:
* they assume `emitter` and the standard scratch locals are in scope at the
* call site -- bv/bsz (bool), cpv/cpsz (const char *), uv/usz (unsigned),
* i64v/i64sz, u64v/u64sz, sv/ssz (size_t), ssv/sssz (ssize_t), plus bv2/ssv2
* for the *_MUTABLE variants. (CONFIG_WRITE_BOOL uses CTL_GET above; the
* OPT_WRITE_* variants use je_mallctl directly.)
*/
#define CONFIG_WRITE_BOOL(name) \
do { \
CTL_GET("config." #name, &bv, bool); \
emitter_kv( \
emitter, #name, "config." #name, emitter_type_bool, &bv); \
} while (0)
#define OPT_WRITE(name, var, size, emitter_type) \
if (je_mallctl("opt." name, (void *)&var, &size, NULL, 0) == 0) { \
emitter_kv(emitter, name, "opt." name, emitter_type, &var); \
}
#define OPT_WRITE_MUTABLE(name, var1, var2, size, emitter_type, altname) \
if (je_mallctl("opt." name, (void *)&var1, &size, NULL, 0) == 0 \
&& je_mallctl(altname, (void *)&var2, &size, NULL, 0) == 0) { \
emitter_kv_note(emitter, name, "opt." name, emitter_type, \
&var1, altname, emitter_type, &var2); \
}
#define OPT_WRITE_BOOL(name) OPT_WRITE(name, bv, bsz, emitter_type_bool)
#define OPT_WRITE_BOOL_MUTABLE(name, altname) \
OPT_WRITE_MUTABLE(name, bv, bv2, bsz, emitter_type_bool, altname)
#define OPT_WRITE_UNSIGNED(name) OPT_WRITE(name, uv, usz, emitter_type_unsigned)
#define OPT_WRITE_INT64(name) OPT_WRITE(name, i64v, i64sz, emitter_type_int64)
#define OPT_WRITE_UINT64(name) OPT_WRITE(name, u64v, u64sz, emitter_type_uint64)
#define OPT_WRITE_SIZE_T(name) OPT_WRITE(name, sv, ssz, emitter_type_size)
#define OPT_WRITE_SSIZE_T(name) OPT_WRITE(name, ssv, sssz, emitter_type_ssize)
#define OPT_WRITE_SSIZE_T_MUTABLE(name, altname) \
OPT_WRITE_MUTABLE(name, ssv, ssv2, sssz, emitter_type_ssize, altname)
#define OPT_WRITE_CHAR_P(name) OPT_WRITE(name, cpv, cpsz, emitter_type_string)
#endif /* JEMALLOC_INTERNAL_STATS_INTERNAL_H */

File diff suppressed because it is too large Load diff

View file

@ -498,13 +498,13 @@ emit_table_row(emitter_t *emitter) {
emitter_begin(emitter);
emitter_row_t row;
emitter_col_t abc = {
emitter_justify_left, 10, emitter_type_title, {0}, {0, 0}};
emitter_justify_left, 10, emitter_type_title};
abc.str_val = "ABC title";
emitter_col_t def = {
emitter_justify_right, 15, emitter_type_title, {0}, {0, 0}};
emitter_justify_right, 15, emitter_type_title};
def.str_val = "DEF title";
emitter_col_t ghi = {
emitter_justify_right, 5, emitter_type_title, {0}, {0, 0}};
emitter_justify_right, 5, emitter_type_title};
ghi.str_val = "GHI";
emitter_row_init(&row);
@ -548,6 +548,126 @@ static const char *table_row_table =
"789 false 1011\n"
"\"a string\" false ghi\n";
/*
* emitter_row(): columns that carry a json_key render both to the aligned
* table (all columns) and, in JSON mode, to "json_key": value pairs in column
* order; columns without a json_key are table-only.
*/
static void
emit_row(emitter_t *emitter) {
emitter_begin(emitter);
emitter_row_t row;
emitter_row_init(&row);
/* Table-only column (no json_key set). */
emitter_col_t col_label;
emitter_col_init(&col_label, &row);
col_label.justify = emitter_justify_left;
col_label.width = 6;
col_label.type = emitter_type_title;
col_label.str_val = "row:";
/* Dual columns: rendered in both table and JSON. */
emitter_col_t col_x;
emitter_col_init(&col_x, &row);
col_x.justify = emitter_justify_right;
col_x.width = 6;
col_x.type = emitter_type_unsigned;
col_x.unsigned_val = 10;
col_x.json_key = "x";
emitter_col_t col_y;
emitter_col_init(&col_y, &row);
col_y.justify = emitter_justify_right;
col_y.width = 6;
col_y.type = emitter_type_unsigned;
col_y.unsigned_val = 20;
col_y.json_key = "y";
emitter_json_object_kv_begin(emitter, "obj");
emitter_row(emitter, &row);
emitter_json_object_end(emitter);
emitter_end(emitter);
}
static const char *row_json =
"{\n"
"\t\"obj\": {\n"
"\t\t\"x\": 10,\n"
"\t\t\"y\": 20\n"
"\t}\n"
"}\n";
static const char *row_json_compact = "{\"obj\":{\"x\":10,\"y\":20}}";
static const char *row_table = "row: 10 20\n";
/*
* emitter_sparse_row(): the table side collapses runs of all-zero ("gap") rows
* to a single "---" separator (on a gap->non-gap transition and at a trailing
* gap), while the JSON side still emits every row's json-keyed columns.
*/
static void
emit_sparse_row(emitter_t *emitter) {
emitter_begin(emitter);
emitter_row_t row;
emitter_row_init(&row);
emitter_col_t col_v;
emitter_col_init(&col_v, &row);
col_v.justify = emitter_justify_right;
col_v.width = 6;
col_v.type = emitter_type_unsigned;
col_v.json_key = "v";
unsigned vals[] = {1, 0, 0, 2, 0, 0};
bool gap[] = {false, true, true, false, true, true};
emitter_json_array_kv_begin(emitter, "arr");
emitter_table_sparse_begin(emitter);
for (unsigned i = 0; i < sizeof(vals) / sizeof(vals[0]); i++) {
col_v.unsigned_val = vals[i];
emitter_json_object_begin(emitter);
emitter_sparse_row(emitter, &row, gap[i]);
emitter_json_object_end(emitter);
}
emitter_table_sparse_end(emitter);
emitter_json_array_end(emitter);
emitter_end(emitter);
}
static const char *sparse_row_json =
"{\n"
"\t\"arr\": [\n"
"\t\t{\n"
"\t\t\t\"v\": 1\n"
"\t\t},\n"
"\t\t{\n"
"\t\t\t\"v\": 0\n"
"\t\t},\n"
"\t\t{\n"
"\t\t\t\"v\": 0\n"
"\t\t},\n"
"\t\t{\n"
"\t\t\t\"v\": 2\n"
"\t\t},\n"
"\t\t{\n"
"\t\t\t\"v\": 0\n"
"\t\t},\n"
"\t\t{\n"
"\t\t\t\"v\": 0\n"
"\t\t}\n"
"\t]\n"
"}\n";
static const char *sparse_row_json_compact =
"{\"arr\":[{\"v\":1},{\"v\":0},{\"v\":0},{\"v\":2},{\"v\":0},{\"v\":0}]}";
static const char *sparse_row_table =
" 1\n"
" ---\n"
" 2\n"
" ---\n";
#define GENERATE_TEST(feature) \
TEST_BEGIN(test_##feature) { \
expect_emit_output(emit_##feature, feature##_json, \
@ -563,10 +683,12 @@ GENERATE_TEST(modal)
GENERATE_TEST(json_array)
GENERATE_TEST(json_nested_array)
GENERATE_TEST(table_row)
GENERATE_TEST(row)
GENERATE_TEST(sparse_row)
int
main(void) {
return test_no_reentrancy(test_dict, test_table_printf,
test_nested_dict, test_types, test_modal, test_json_array,
test_json_nested_array, test_table_row);
test_json_nested_array, test_table_row, test_row, test_sparse_row);
}