From 04aad97faafcb4f15125d4fbd6e92f7fd0aec323 Mon Sep 17 00:00:00 2001 From: Slobodan Predolac Date: Fri, 17 Jul 2026 09:24:26 -0700 Subject: [PATCH] Expose stats table columns in JSON output and simplify code --- include/jemalloc/internal/emitter.h | 30 ++-- src/stats.c | 242 +++++++--------------------- test/unit/emitter.c | 37 +++-- test/unit/json_stats.c | 212 ++++++++++++++++++++++-- 4 files changed, 295 insertions(+), 226 deletions(-) diff --git a/include/jemalloc/internal/emitter.h b/include/jemalloc/internal/emitter.h index ec7fa1c4..578f5913 100644 --- a/include/jemalloc/internal/emitter.h +++ b/include/jemalloc/internal/emitter.h @@ -591,13 +591,12 @@ emitter_sparse_row(emitter_t *emitter, emitter_row_t *row, bool is_gap) { } /* - * Descriptor-driven column tables. A descriptor array defines table order; - * callers provide a separate index array when JSON requires a different - * subset or ordering. 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. + * 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 { @@ -666,20 +665,21 @@ emitter_col_table_fill(const emitter_col_desc_t *descs, size_t ndescs, 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, const unsigned *json_order, size_t json_order_len) { + emitter_col_t *cols) { if (!emitter_outputs_json(emitter)) { return; } - for (size_t i = 0; i < json_order_len; i++) { - unsigned col_ind = json_order[i]; - assert(col_ind < ndescs); - const emitter_col_desc_t *desc = &descs[col_ind]; + 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; } - assert(desc->json_key != NULL); - emitter_json_kv(emitter, desc->json_key, cols[col_ind].type, - (const void *)&cols[col_ind].bool_val); + 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); + } } } diff --git a/src/stats.c b/src/stats.c index 65c9ed4d..911e0440 100644 --- a/src/stats.c +++ b/src/stats.c @@ -782,54 +782,27 @@ stats_bin_col_get_util(const void *vrow, emitter_col_t *col) { col->str_val = row->util; } -enum { - BIN_COL_SIZE, - BIN_COL_IND, - BIN_COL_ALLOCATED, - BIN_COL_NMALLOC, - BIN_COL_NMALLOC_PS, - BIN_COL_NDALLOC, - BIN_COL_NDALLOC_PS, - BIN_COL_NREQUESTS, - BIN_COL_NREQUESTS_PS, - BIN_COL_PROF_LIVE_REQUESTED, - BIN_COL_PROF_LIVE_COUNT, - BIN_COL_PROF_ACCUM_REQUESTED, - BIN_COL_PROF_ACCUM_COUNT, - BIN_COL_NSHARDS, - BIN_COL_CURREGS, - BIN_COL_CURSLABS, - BIN_COL_NONFULL_SLABS, - BIN_COL_REGS, - BIN_COL_PGS, - BIN_COL_SPACER, - BIN_COL_UTIL, - BIN_COL_NFILLS, - BIN_COL_NFILLS_PS, - BIN_COL_NFLUSHES, - BIN_COL_NFLUSHES_PS, - BIN_COL_NSLABS, - BIN_COL_NRESLABS, - BIN_COL_NRESLABS_PS, - BIN_COL_COUNT -}; +#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(NULL, "size", 20, size, STATS_COL_FLAG_NONE, size), - BIN_DESC(NULL, "ind", 4, unsigned, STATS_COL_FLAG_NONE, ind), - BIN_DESC(NULL, "allocated", 14, size, STATS_COL_FLAG_NONE, allocated), + 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(NULL, "(#/sec)", 8, uint64, STATS_COL_FLAG_NONE, nmalloc_ps), + 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(NULL, "(#/sec)", 8, uint64, STATS_COL_FLAG_NONE, ndalloc_ps), + 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(NULL, "(#/sec)", 10, uint64, STATS_COL_FLAG_NONE, + 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), @@ -839,48 +812,33 @@ static const emitter_col_desc_t stats_bin_cols[] = { 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(NULL, "nshards", 9, unsigned, STATS_COL_FLAG_NONE, nshards), + 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(NULL, "regs", 5, unsigned, STATS_COL_FLAG_NONE, regs), - BIN_DESC(NULL, "pgs", 4, size, STATS_COL_FLAG_NONE, pgs), + 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(NULL, "util", 6, title, STATS_COL_FLAG_NONE, util), + BIN_DESC("util", "util", 6, title, STATS_COL_FLAG_NONE, util), BIN_DESC("nfills", "nfills", 13, uint64, STATS_COL_FLAG_NONE, nfills), - BIN_DESC(NULL, "(#/sec)", 8, uint64, STATS_COL_FLAG_NONE, nfills_ps), + 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(NULL, "(#/sec)", 8, uint64, STATS_COL_FLAG_NONE, + BIN_DESC("nflushes_ps", "(#/sec)", 8, uint64, STATS_COL_FLAG_NONE, nflushes_ps), - BIN_DESC(NULL, "nslabs", 13, uint64, STATS_COL_FLAG_NONE, nslabs), + BIN_DESC("nslabs", "nslabs", 13, uint64, STATS_COL_FLAG_NONE, nslabs), BIN_DESC("nreslabs", "nreslabs", 13, uint64, STATS_COL_FLAG_NONE, nreslabs), - BIN_DESC(NULL, "(#/sec)", 8, uint64, STATS_COL_FLAG_NONE, + BIN_DESC("nreslabs_ps", "(#/sec)", 8, uint64, STATS_COL_FLAG_NONE, nreslabs_ps), }; #undef BIN_DESC -_Static_assert(sizeof(stats_bin_cols) / sizeof(stats_bin_cols[0]) == - BIN_COL_COUNT, "stats_bin_cols must match BIN_COL_COUNT"); - -static const unsigned stats_bin_json_order[] = { - BIN_COL_NMALLOC, - BIN_COL_NDALLOC, - BIN_COL_CURREGS, - BIN_COL_NREQUESTS, - BIN_COL_PROF_LIVE_REQUESTED, - BIN_COL_PROF_LIVE_COUNT, - BIN_COL_PROF_ACCUM_REQUESTED, - BIN_COL_PROF_ACCUM_COUNT, - BIN_COL_NFILLS, - BIN_COL_NFLUSHES, - BIN_COL_NRESLABS, - BIN_COL_CURSLABS, - BIN_COL_NONFULL_SLABS, -}; +#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, @@ -892,8 +850,7 @@ stats_emit_arena_bin_row(emitter_t *emitter, emitter_row_t *table_row, 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, stats_bin_json_order, - sizeof(stats_bin_json_order) / sizeof(stats_bin_json_order[0])); + active_flags, cols); if (mutex) { emitter_json_object_kv_begin(emitter, "mutex"); mutex_stats_emit(emitter, NULL, mutex64, mutex32); @@ -1068,41 +1025,29 @@ stats_lextent_col_get_allocated(const void *vrow, emitter_col_t *col) { col->size_val = row->lextent->curlextents * row->lextent->lextent_size; } -enum { - LEXTENT_COL_SIZE, - LEXTENT_COL_IND, - LEXTENT_COL_ALLOCATED, - LEXTENT_COL_NMALLOC, - LEXTENT_COL_NMALLOC_PS, - LEXTENT_COL_NDALLOC, - LEXTENT_COL_NDALLOC_PS, - LEXTENT_COL_NREQUESTS, - LEXTENT_COL_NREQUESTS_PS, - LEXTENT_COL_PROF_LIVE_REQUESTED, - LEXTENT_COL_PROF_LIVE_COUNT, - LEXTENT_COL_PROF_ACCUM_REQUESTED, - LEXTENT_COL_PROF_ACCUM_COUNT, - LEXTENT_COL_CURLEXTENTS, - LEXTENT_COL_COUNT -}; +#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(NULL, "size", 20, size, STATS_COL_FLAG_NONE, size), - LEXTENT_DESC(NULL, "ind", 4, unsigned, STATS_COL_FLAG_NONE, ind), - LEXTENT_DESC(NULL, "allocated", 13, size, STATS_COL_FLAG_NONE, + 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(NULL, "nmalloc", 13, uint64, STATS_COL_FLAG_NONE, nmalloc), - LEXTENT_DESC(NULL, "(#/sec)", 8, uint64, STATS_COL_FLAG_NONE, + 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(NULL, "ndalloc", 13, uint64, STATS_COL_FLAG_NONE, ndalloc), - LEXTENT_DESC(NULL, "(#/sec)", 8, uint64, STATS_COL_FLAG_NONE, + 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(NULL, "nrequests", 13, uint64, STATS_COL_FLAG_NONE, + LEXTENT_DESC("nrequests", "nrequests", 13, uint64, + STATS_COL_FLAG_NONE, nrequests), - LEXTENT_DESC(NULL, "(#/sec)", 8, uint64, STATS_COL_FLAG_NONE, + 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), @@ -1116,16 +1061,8 @@ static const emitter_col_desc_t stats_lextent_cols[] = { STATS_COL_FLAG_NONE, curlextents), }; #undef LEXTENT_DESC -_Static_assert(sizeof(stats_lextent_cols) / sizeof(stats_lextent_cols[0]) == - LEXTENT_COL_COUNT, "stats_lextent_cols must match LEXTENT_COL_COUNT"); - -static const unsigned stats_lextent_json_order[] = { - LEXTENT_COL_PROF_LIVE_REQUESTED, - LEXTENT_COL_PROF_LIVE_COUNT, - LEXTENT_COL_PROF_ACCUM_REQUESTED, - LEXTENT_COL_PROF_ACCUM_COUNT, - LEXTENT_COL_CURLEXTENTS, -}; +#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, @@ -1135,15 +1072,13 @@ stats_emit_arena_lextent_row(emitter_t *emitter, emitter_row_t *table_row, unsigned active_flags = stats_col_active_flags(prof_stats_on); emitter_col_table_fill(stats_lextent_cols, LEXTENT_COL_COUNT, active_flags, cols, row); - stats_size_col_set(&cols[LEXTENT_COL_SIZE], row->lextent->lextent_size, - prev_size, size_buf, size_buf_size); emitter_json_object_begin(emitter); emitter_col_table_emit_json(emitter, stats_lextent_cols, - LEXTENT_COL_COUNT, active_flags, cols, stats_lextent_json_order, - sizeof(stats_lextent_json_order) / - sizeof(stats_lextent_json_order[0])); - emitter_table_sparse_row(emitter, table_row, is_gap); + 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 @@ -1242,29 +1177,15 @@ stats_extent_col_get_ind(const void *vrow, emitter_col_t *col) { col->unsigned_val = row->ind; } -enum { - EXTENT_COL_SIZE, - EXTENT_COL_IND, - EXTENT_COL_NDIRTY, - EXTENT_COL_DIRTY, - EXTENT_COL_NMUZZY, - EXTENT_COL_MUZZY, - EXTENT_COL_NRETAINED, - EXTENT_COL_RETAINED, - EXTENT_COL_NPINNED, - EXTENT_COL_PINNED, - EXTENT_COL_NTOTAL, - EXTENT_COL_TOTAL, - EXTENT_COL_COUNT -}; +#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[] = { - {NULL, "size", emitter_justify_right, 20, emitter_type_size, + {"size", "size", emitter_justify_right, 20, emitter_type_size, STATS_COL_FLAG_NONE, stats_extent_col_get_size}, - {NULL, "ind", emitter_justify_right, 4, emitter_type_unsigned, + {"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), @@ -1274,23 +1195,12 @@ static const emitter_col_desc_t stats_extent_cols[] = { EXTENT_DESC("retained_bytes", "retained", retained), EXTENT_DESC("npinned", "npinned", npinned), EXTENT_DESC("pinned_bytes", "pinned", pinned), - EXTENT_DESC(NULL, "ntotal", ntotal), - EXTENT_DESC(NULL, "total", total), + EXTENT_DESC("ntotal", "ntotal", ntotal), + EXTENT_DESC("total_bytes", "total", total), }; #undef EXTENT_DESC -_Static_assert(sizeof(stats_extent_cols) / sizeof(stats_extent_cols[0]) == - EXTENT_COL_COUNT, "stats_extent_cols must match EXTENT_COL_COUNT"); - -static const unsigned stats_extent_json_order[] = { - EXTENT_COL_NDIRTY, - EXTENT_COL_NMUZZY, - EXTENT_COL_NRETAINED, - EXTENT_COL_NPINNED, - EXTENT_COL_DIRTY, - EXTENT_COL_MUZZY, - EXTENT_COL_RETAINED, - EXTENT_COL_PINNED, -}; +#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, @@ -1298,14 +1208,12 @@ stats_emit_arena_extent_row(emitter_t *emitter, emitter_row_t *table_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); - stats_size_col_set(&cols[EXTENT_COL_SIZE], row->size, prev_size, - size_buf, size_buf_size); emitter_json_object_begin(emitter); emitter_col_table_emit_json(emitter, stats_extent_cols, EXTENT_COL_COUNT, - STATS_COL_FLAG_NONE, cols, stats_extent_json_order, - sizeof(stats_extent_json_order) / - sizeof(stats_extent_json_order[0])); + 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); } @@ -1575,26 +1483,15 @@ stats_hpa_slab_col_get_ind(const void *vrow, emitter_col_t *col) { } } -enum { - HPA_SLAB_COL_SIZE, - HPA_SLAB_COL_IND, - HPA_SLAB_COL_NPAGESLABS_HUGE, - HPA_SLAB_COL_NACTIVE_HUGE, - HPA_SLAB_COL_NDIRTY_HUGE, - HPA_SLAB_COL_NPAGESLABS_NONHUGE, - HPA_SLAB_COL_NACTIVE_NONHUGE, - HPA_SLAB_COL_NDIRTY_NONHUGE, - HPA_SLAB_COL_NRETAINED_NONHUGE, - HPA_SLAB_COL_COUNT -}; +#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[] = { - {NULL, "size", emitter_justify_right, 20, emitter_type_size, + {"size", "size", emitter_justify_right, 20, emitter_type_size, STATS_COL_FLAG_NONE, stats_hpa_slab_col_get_size}, - {NULL, "ind", emitter_justify_right, 4, emitter_type_unsigned, + {"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), @@ -1605,20 +1502,8 @@ static const emitter_col_desc_t stats_hpa_slab_cols[] = { HPA_SLAB_DESC(nretained_nonhuge, 20), }; #undef HPA_SLAB_DESC -_Static_assert( - sizeof(stats_hpa_slab_cols) / sizeof(stats_hpa_slab_cols[0]) == - HPA_SLAB_COL_COUNT, - "stats_hpa_slab_cols must match HPA_SLAB_COL_COUNT"); - -static const unsigned stats_hpa_slab_json_order[] = { - HPA_SLAB_COL_NPAGESLABS_HUGE, - HPA_SLAB_COL_NACTIVE_HUGE, - HPA_SLAB_COL_NDIRTY_HUGE, - HPA_SLAB_COL_NPAGESLABS_NONHUGE, - HPA_SLAB_COL_NACTIVE_NONHUGE, - HPA_SLAB_COL_NDIRTY_NONHUGE, - HPA_SLAB_COL_NRETAINED_NONHUGE, -}; +#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, @@ -1627,21 +1512,18 @@ stats_emit_arena_hpa_slab_row(emitter_t *emitter, 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 (row->size_title == NULL) { - stats_size_col_set(&cols[HPA_SLAB_COL_SIZE], row->size, - row->prev_size, size_buf, size_buf_size); - } 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, - stats_hpa_slab_json_order, - sizeof(stats_hpa_slab_json_order) / - sizeof(stats_hpa_slab_json_order[0])); + 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 { diff --git a/test/unit/emitter.c b/test/unit/emitter.c index e7728563..3cfcf5d5 100644 --- a/test/unit/emitter.c +++ b/test/unit/emitter.c @@ -669,10 +669,11 @@ static const char *sparse_row_table = " ---\n"; typedef struct { - size_t size; - uint64_t count; - uint64_t prof_count; - uint64_t requests; + 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) \ @@ -684,6 +685,7 @@ typedef struct { 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 @@ -691,6 +693,7 @@ 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 }; @@ -704,6 +707,8 @@ static const emitter_col_desc_t col_table_test_descs[] = { 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}, }; @@ -726,15 +731,9 @@ TEST_BEGIN(test_col_desc_flags) { } TEST_END -static const unsigned col_table_test_json_order[] = { - COL_TABLE_TEST_REQUESTS, - COL_TABLE_TEST_PROF_COUNT, - COL_TABLE_TEST_COUNT, -}; - static void emit_col_table(emitter_t *emitter) { - col_table_test_row_t value = {42, 7, 100, 9}; + 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]; @@ -748,9 +747,7 @@ emit_col_table(emitter_t *emitter) { 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, - col_table_test_json_order, sizeof(col_table_test_json_order) / - sizeof(col_table_test_json_order[0])); + 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); @@ -761,17 +758,19 @@ static const char *col_table_json = "{\n" "\t\"rows\": [\n" "\t\t{\n" - "\t\t\t\"requests\": 9,\n" + "\t\t\t\"count\": 7,\n" "\t\t\t\"prof_count\": 100,\n" - "\t\t\t\"count\": 7\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\":[{\"requests\":9,\"prof_count\":100,\"count\":7}]}"; + "{\"rows\":[{\"count\":7,\"prof_count\":100,\"label\":\"row\"," + "\"requests\":9}]}"; static const char *col_table_table = - "mock:size count prof requests\n" - " 42 7 100 9\n"; + "mock:size count prof label requests\n" + " 42 7 100 row 9\n"; #define GENERATE_TEST(feature) \ TEST_BEGIN(test_##feature) { \ diff --git a/test/unit/json_stats.c b/test/unit/json_stats.c index 8ad99114..f5af83ad 100644 --- a/test/unit/json_stats.c +++ b/test/unit/json_stats.c @@ -442,6 +442,95 @@ expect_json_uint_eq(json_fragment_t object, const char *key, key); } +static void +expect_json_string_eq(json_fragment_t object, const char *key, + const char *expected, const char *name) { + json_fragment_t value; + bool missing = json_object_member(object, key, &value); + expect_false(missing, "%s is missing %s", name, key); + if (missing) { + return; + } + expect_true(json_fragment_string_eq(value, expected), + "%s.%s has an unexpected value", name, key); +} + +static uint64_t +expected_rate(uint64_t value, uint64_t uptime_ns) { + const uint64_t billion = 1000000000; + if (uptime_ns == 0 || value == 0) { + return 0; + } + return uptime_ns < billion ? value : value / (uptime_ns / billion); +} + +static void +expected_utilization(size_t curregs, size_t availregs, char result[6]) { + if (availregs == 0) { + malloc_snprintf(result, 6, "1"); + return; + } + assert_zu_le(curregs, availregs, "Cached bin stats should be consistent"); + unsigned n = (unsigned)(((uint64_t)curregs * 1000) / availregs); + if (n < 10) { + malloc_snprintf(result, 6, "0.00%u", n); + } else if (n < 100) { + malloc_snprintf(result, 6, "0.0%u", n); + } else if (n < 1000) { + malloc_snprintf(result, 6, "0.%u", n); + } else { + malloc_snprintf(result, 6, "1"); + } +} + +static size_t +read_size_ctl(const char *name) { + size_t value; + size_t sz = sizeof(value); + assert_d_eq(mallctl(name, &value, &sz, NULL, 0), 0, + "mallctl failed for %s", name); + return value; +} + +static uint64_t +read_uint64_ctl(const char *name) { + uint64_t value; + size_t sz = sizeof(value); + assert_d_eq(mallctl(name, &value, &sz, NULL, 0), 0, + "mallctl failed for %s", name); + return value; +} + +static uint32_t +read_uint32_ctl(const char *name) { + uint32_t value; + size_t sz = sizeof(value); + assert_d_eq(mallctl(name, &value, &sz, NULL, 0), 0, + "mallctl failed for %s", name); + return value; +} + +static size_t +read_size_field(const char *prefix, const char *suffix) { + char name[192]; + malloc_snprintf(name, sizeof(name), "%s.%s", prefix, suffix); + return read_size_ctl(name); +} + +static uint64_t +read_uint64_field(const char *prefix, const char *suffix) { + char name[192]; + malloc_snprintf(name, sizeof(name), "%s.%s", prefix, suffix); + return read_uint64_ctl(name); +} + +static uint32_t +read_uint32_field(const char *prefix, const char *suffix) { + char name[192]; + malloc_snprintf(name, sizeof(name), "%s.%s", prefix, suffix); + return read_uint32_ctl(name); +} + static bool stats_json_print_opts(stats_buf_t *sbuf, const char *opts, json_fragment_t *jemalloc) { @@ -886,12 +975,20 @@ static const ctl_field_t hpa_slab_fields[] = { static const char *const hpa_slab_keys[] = {"npageslabs_huge", "nactive_huge", "ndirty_huge", "npageslabs_nonhuge", "nactive_nonhuge", "ndirty_nonhuge", "nretained_nonhuge"}; +static const char *const hpa_slab_row_keys[] = {"size", "ind"}; static void expect_hpa_slab(json_fragment_t slab, const char *ctl_prefix, - const char *name) { - expect_json_object_keys( + const char *name, bool table_row) { + expect_zu_eq(json_object_size(slab), ARRAY_COUNT(hpa_slab_keys) + + (table_row ? ARRAY_COUNT(hpa_slab_row_keys) : 0), + "%s has an unexpected number of keys", name); + expect_json_object_has_keys( slab, hpa_slab_keys, ARRAY_COUNT(hpa_slab_keys), name); + if (table_row) { + expect_json_object_has_keys(slab, hpa_slab_row_keys, + ARRAY_COUNT(hpa_slab_row_keys), name); + } expect_json_ctl_fields(slab, ctl_prefix, hpa_slab_fields, ARRAY_COUNT(hpa_slab_fields)); @@ -972,7 +1069,7 @@ TEST_BEGIN(test_json_stats_hpa) { char slab_prefix[128]; malloc_snprintf( slab_prefix, sizeof(slab_prefix), "%s.slabs", hpa_prefix); - expect_hpa_slab(slabs, slab_prefix, "hpa_shard.slabs"); + expect_hpa_slab(slabs, slab_prefix, "hpa_shard.slabs", false); const char *const summary_names[] = {"full_slabs", "empty_slabs"}; for (size_t i = 0; i < ARRAY_COUNT(summary_names); i++) { @@ -981,7 +1078,10 @@ TEST_BEGIN(test_json_stats_hpa) { "hpa_shard is missing %s", summary_names[i]); malloc_snprintf(slab_prefix, sizeof(slab_prefix), "%s.%s", hpa_prefix, summary_names[i]); - expect_hpa_slab(summary, slab_prefix, summary_names[i]); + expect_hpa_slab(summary, slab_prefix, summary_names[i], true); + expect_json_string_eq( + summary, "size", i == 0 ? "full" : "empty", summary_names[i]); + expect_json_string_eq(summary, "ind", "-", summary_names[i]); } json_fragment_t nonfull; @@ -1000,7 +1100,10 @@ TEST_BEGIN(test_json_stats_hpa) { char row_name[64]; malloc_snprintf( row_name, sizeof(row_name), "nonfull_slabs[%zu]", i); - expect_hpa_slab(slab, slab_prefix, row_name); + expect_hpa_slab(slab, slab_prefix, row_name, true); + expect_json_uint_eq( + slab, "size", sz_pind2sz((pszind_t)i), row_name); + expect_json_uint_eq(slab, "ind", i, row_name); } json_fragment_t distribution; @@ -1036,18 +1139,26 @@ static const ctl_field_t bin_fields[] = { {"nrequests", "nrequests", ctl_field_uint64}, {"nfills", "nfills", ctl_field_uint64}, {"nflushes", "nflushes", ctl_field_uint64}, + {"nslabs", "nslabs", ctl_field_uint64}, {"nreslabs", "nreslabs", ctl_field_uint64}, {"curslabs", "curslabs", ctl_field_size}, {"nonfull_slabs", "nonfull_slabs", ctl_field_size}, }; -static const char *const bin_keys[] = {"nmalloc", "ndalloc", "curregs", - "nrequests", "nfills", "nflushes", "nreslabs", "curslabs", - "nonfull_slabs", "mutex"}; +static const char *const bin_keys[] = {"size", "ind", "allocated", + "nmalloc", "nmalloc_ps", "ndalloc", "ndalloc_ps", "nrequests", + "nrequests_ps", "nshards", "curregs", "curslabs", "nonfull_slabs", + "regs", "pgs", "util", "nfills", "nfills_ps", "nflushes", + "nflushes_ps", "nslabs", "nreslabs", "nreslabs_ps", "mutex"}; static const ctl_field_t lextent_fields[] = { + {"nmalloc", "nmalloc", ctl_field_uint64}, + {"ndalloc", "ndalloc", ctl_field_uint64}, + {"nrequests", "nrequests", ctl_field_uint64}, {"curlextents", "curlextents", ctl_field_size}, }; -static const char *const lextent_keys[] = {"curlextents"}; +static const char *const lextent_keys[] = {"size", "ind", "allocated", + "nmalloc", "nmalloc_ps", "ndalloc", "ndalloc_ps", "nrequests", + "nrequests_ps", "curlextents"}; static const ctl_field_t extent_fields[] = { {"ndirty", "ndirty", ctl_field_size}, @@ -1059,9 +1170,10 @@ static const ctl_field_t extent_fields[] = { {"retained_bytes", "retained_bytes", ctl_field_size}, {"pinned_bytes", "pinned_bytes", ctl_field_size}, }; -static const char *const extent_keys[] = {"ndirty", "nmuzzy", "nretained", - "npinned", "dirty_bytes", "muzzy_bytes", "retained_bytes", - "pinned_bytes"}; +static const char *const extent_keys[] = {"size", "ind", "ndirty", + "dirty_bytes", "nmuzzy", "muzzy_bytes", "nretained", + "retained_bytes", "npinned", "pinned_bytes", "ntotal", + "total_bytes"}; static const char *const prof_keys[] = {"prof_live_requested", "prof_live_count", "prof_accum_requested", "prof_accum_count"}; @@ -1109,6 +1221,10 @@ TEST_BEGIN(test_json_stats_arena_tables) { sz = sizeof(nlextents); expect_d_eq(mallctl("arenas.nlextents", &nlextents, &sz, NULL, 0), 0, "mallctl failed for arenas.nlextents"); + char arena_prefix[64]; + malloc_snprintf(arena_prefix, sizeof(arena_prefix), "stats.arenas.%u", + MALLCTL_ARENAS_ALL); + uint64_t uptime = read_uint64_field(arena_prefix, "uptime"); json_fragment_t bins; expect_false(json_object_member(merged, "bins", &bins), @@ -1136,6 +1252,44 @@ TEST_BEGIN(test_json_stats_arena_tables) { "stats.arenas.%u.bins.%u", MALLCTL_ARENAS_ALL, i); expect_json_ctl_fields( bin, ctl_prefix, bin_fields, ARRAY_COUNT(bin_fields)); + + char meta_prefix[64]; + malloc_snprintf( + meta_prefix, sizeof(meta_prefix), "arenas.bin.%u", i); + size_t bin_size = read_size_field(meta_prefix, "size"); + uint32_t nregs = read_uint32_field(meta_prefix, "nregs"); + size_t slab_size = read_size_field(meta_prefix, "slab_size"); + uint32_t nshards = read_uint32_field(meta_prefix, "nshards"); + size_t curregs = read_size_field(ctl_prefix, "curregs"); + size_t curslabs = read_size_field(ctl_prefix, "curslabs"); + uint64_t nmalloc = read_uint64_field(ctl_prefix, "nmalloc"); + uint64_t ndalloc = read_uint64_field(ctl_prefix, "ndalloc"); + uint64_t nrequests = read_uint64_field(ctl_prefix, "nrequests"); + uint64_t nfills = read_uint64_field(ctl_prefix, "nfills"); + uint64_t nflushes = read_uint64_field(ctl_prefix, "nflushes"); + uint64_t nreslabs = read_uint64_field(ctl_prefix, "nreslabs"); + expect_json_uint_eq(bin, "size", bin_size, row_name); + expect_json_uint_eq(bin, "ind", i, row_name); + expect_json_uint_eq( + bin, "allocated", curregs * bin_size, row_name); + expect_json_uint_eq(bin, "nmalloc_ps", + expected_rate(nmalloc, uptime), row_name); + expect_json_uint_eq(bin, "ndalloc_ps", + expected_rate(ndalloc, uptime), row_name); + expect_json_uint_eq(bin, "nrequests_ps", + expected_rate(nrequests, uptime), row_name); + expect_json_uint_eq(bin, "nshards", nshards, row_name); + expect_json_uint_eq(bin, "regs", nregs, row_name); + expect_json_uint_eq(bin, "pgs", slab_size / PAGE, row_name); + expect_json_uint_eq(bin, "nfills_ps", + expected_rate(nfills, uptime), row_name); + expect_json_uint_eq(bin, "nflushes_ps", + expected_rate(nflushes, uptime), row_name); + expect_json_uint_eq(bin, "nreslabs_ps", + expected_rate(nreslabs, uptime), row_name); + char util[6]; + expected_utilization(curregs, nregs * curslabs, util); + expect_json_string_eq(bin, "util", util, row_name); if (prof_stats_present) { expect_json_object_has_keys( bin, prof_keys, ARRAY_COUNT(prof_keys), row_name); @@ -1175,6 +1329,24 @@ TEST_BEGIN(test_json_stats_arena_tables) { "stats.arenas.%u.lextents.%u", MALLCTL_ARENAS_ALL, i); expect_json_ctl_fields(lextent, ctl_prefix, lextent_fields, ARRAY_COUNT(lextent_fields)); + char meta_prefix[64]; + malloc_snprintf(meta_prefix, sizeof(meta_prefix), + "arenas.lextent.%u", i); + size_t lextent_size = read_size_field(meta_prefix, "size"); + size_t curlextents = read_size_field(ctl_prefix, "curlextents"); + uint64_t nmalloc = read_uint64_field(ctl_prefix, "nmalloc"); + uint64_t ndalloc = read_uint64_field(ctl_prefix, "ndalloc"); + uint64_t nrequests = read_uint64_field(ctl_prefix, "nrequests"); + expect_json_uint_eq(lextent, "size", lextent_size, row_name); + expect_json_uint_eq(lextent, "ind", nbins + i, row_name); + expect_json_uint_eq(lextent, "allocated", + curlextents * lextent_size, row_name); + expect_json_uint_eq(lextent, "nmalloc_ps", + expected_rate(nmalloc, uptime), row_name); + expect_json_uint_eq(lextent, "ndalloc_ps", + expected_rate(ndalloc, uptime), row_name); + expect_json_uint_eq(lextent, "nrequests_ps", + expected_rate(nrequests, uptime), row_name); if (prof_stats_present) { expect_json_object_has_keys(lextent, prof_keys, ARRAY_COUNT(prof_keys), row_name); @@ -1203,6 +1375,22 @@ TEST_BEGIN(test_json_stats_arena_tables) { "stats.arenas.%u.extents.%u", MALLCTL_ARENAS_ALL, i); expect_json_ctl_fields(extent, ctl_prefix, extent_fields, ARRAY_COUNT(extent_fields)); + size_t ndirty = read_size_field(ctl_prefix, "ndirty"); + size_t nmuzzy = read_size_field(ctl_prefix, "nmuzzy"); + size_t nretained = read_size_field(ctl_prefix, "nretained"); + size_t npinned = read_size_field(ctl_prefix, "npinned"); + size_t dirty_bytes = read_size_field(ctl_prefix, "dirty_bytes"); + size_t muzzy_bytes = read_size_field(ctl_prefix, "muzzy_bytes"); + size_t retained_bytes = read_size_field( + ctl_prefix, "retained_bytes"); + size_t pinned_bytes = read_size_field(ctl_prefix, "pinned_bytes"); + expect_json_uint_eq(extent, "size", sz_pind2sz(i), row_name); + expect_json_uint_eq(extent, "ind", i, row_name); + expect_json_uint_eq(extent, "ntotal", + ndirty + nmuzzy + nretained + npinned, row_name); + expect_json_uint_eq(extent, "total_bytes", + dirty_bytes + muzzy_bytes + retained_bytes + pinned_bytes, + row_name); } stats_buf_fini(&sbuf);