Add a test to check the output in JSON-based stats is consistent with mallctl results.

This commit is contained in:
guangli-dai 2026-02-21 11:36:55 -08:00
parent 8cc076a08e
commit e4ecb264bd
2 changed files with 244 additions and 0 deletions

View file

@ -240,6 +240,7 @@ TESTS_UNIT := \
$(srcroot)test/unit/junk.c \
$(srcroot)test/unit/junk_alloc.c \
$(srcroot)test/unit/junk_free.c \
$(srcroot)test/unit/json_stats.c \
$(srcroot)test/unit/log.c \
$(srcroot)test/unit/mallctl.c \
$(srcroot)test/unit/malloc_conf_2.c \

243
test/unit/json_stats.c Normal file
View file

@ -0,0 +1,243 @@
#include "test/jemalloc_test.h"
typedef struct {
char *buf;
size_t len;
size_t capacity;
} stats_buf_t;
static void
stats_buf_init(stats_buf_t *sbuf) {
/* 1MB buffer should be enough since per-arena stats are omitted. */
sbuf->capacity = 1 << 20;
sbuf->buf = mallocx(sbuf->capacity, MALLOCX_TCACHE_NONE);
assert_ptr_not_null(sbuf->buf, "Failed to allocate stats buffer");
sbuf->len = 0;
sbuf->buf[0] = '\0';
}
static void
stats_buf_fini(stats_buf_t *sbuf) {
dallocx(sbuf->buf, MALLOCX_TCACHE_NONE);
}
static void
stats_buf_write_cb(void *opaque, const char *str) {
stats_buf_t *sbuf = (stats_buf_t *)opaque;
size_t slen = strlen(str);
if (sbuf->len + slen + 1 > sbuf->capacity) {
return;
}
memcpy(&sbuf->buf[sbuf->len], str, slen + 1);
sbuf->len += slen;
}
static bool
json_extract_uint64(const char *json, const char *key, uint64_t *result) {
char search_key[128];
size_t key_len;
key_len = snprintf(search_key, sizeof(search_key), "\"%s\":", key);
if (key_len >= sizeof(search_key)) {
return true;
}
const char *pos = strstr(json, search_key);
if (pos == NULL) {
return true;
}
pos += key_len;
while (*pos == ' ' || *pos == '\t' || *pos == '\n') {
pos++;
}
char *endptr;
uint64_t value = strtoull(pos, &endptr, 10);
if (endptr == pos) {
return true;
}
*result = value;
return false;
}
static const char *
json_find_section(const char *json, const char *section_name) {
char search_pattern[128];
size_t pattern_len;
pattern_len = snprintf(
search_pattern, sizeof(search_pattern), "\"%s\":", section_name);
if (pattern_len >= sizeof(search_pattern)) {
return NULL;
}
return strstr(json, search_pattern);
}
static void
verify_mutex_json(const char *mutexes_section, const char *mallctl_prefix,
const char *mutex_name) {
char mallctl_path[128];
size_t sz;
const char *mutex_section = json_find_section(
mutexes_section, mutex_name);
expect_ptr_not_null(mutex_section,
"Could not find %s mutex section in JSON", mutex_name);
uint64_t ctl_num_ops, ctl_num_wait, ctl_num_spin_acq;
uint64_t ctl_num_owner_switch, ctl_total_wait_time, ctl_max_wait_time;
uint32_t ctl_max_num_thds;
sz = sizeof(uint64_t);
snprintf(mallctl_path, sizeof(mallctl_path), "%s.%s.num_ops",
mallctl_prefix, mutex_name);
expect_d_eq(mallctl(mallctl_path, &ctl_num_ops, &sz, NULL, 0), 0,
"Unexpected mallctl() failure for %s", mallctl_path);
snprintf(mallctl_path, sizeof(mallctl_path), "%s.%s.num_wait",
mallctl_prefix, mutex_name);
expect_d_eq(mallctl(mallctl_path, &ctl_num_wait, &sz, NULL, 0), 0,
"Unexpected mallctl() failure for %s", mallctl_path);
snprintf(mallctl_path, sizeof(mallctl_path), "%s.%s.num_spin_acq",
mallctl_prefix, mutex_name);
expect_d_eq(mallctl(mallctl_path, &ctl_num_spin_acq, &sz, NULL, 0), 0,
"Unexpected mallctl() failure for %s", mallctl_path);
snprintf(mallctl_path, sizeof(mallctl_path), "%s.%s.num_owner_switch",
mallctl_prefix, mutex_name);
expect_d_eq(mallctl(mallctl_path, &ctl_num_owner_switch, &sz, NULL, 0),
0, "Unexpected mallctl() failure for %s", mallctl_path);
snprintf(mallctl_path, sizeof(mallctl_path), "%s.%s.total_wait_time",
mallctl_prefix, mutex_name);
expect_d_eq(mallctl(mallctl_path, &ctl_total_wait_time, &sz, NULL, 0),
0, "Unexpected mallctl() failure for %s", mallctl_path);
snprintf(mallctl_path, sizeof(mallctl_path), "%s.%s.max_wait_time",
mallctl_prefix, mutex_name);
expect_d_eq(mallctl(mallctl_path, &ctl_max_wait_time, &sz, NULL, 0), 0,
"Unexpected mallctl() failure for %s", mallctl_path);
sz = sizeof(uint32_t);
snprintf(mallctl_path, sizeof(mallctl_path), "%s.%s.max_num_thds",
mallctl_prefix, mutex_name);
expect_d_eq(mallctl(mallctl_path, &ctl_max_num_thds, &sz, NULL, 0), 0,
"Unexpected mallctl() failure for %s", mallctl_path);
uint64_t json_num_ops, json_num_wait, json_num_spin_acq;
uint64_t json_num_owner_switch, json_total_wait_time,
json_max_wait_time;
uint64_t json_max_num_thds;
expect_false(
json_extract_uint64(mutex_section, "num_ops", &json_num_ops),
"%s: num_ops not found in JSON", mutex_name);
expect_false(
json_extract_uint64(mutex_section, "num_wait", &json_num_wait),
"%s: num_wait not found in JSON", mutex_name);
expect_false(json_extract_uint64(
mutex_section, "num_spin_acq", &json_num_spin_acq),
"%s: num_spin_acq not found in JSON", mutex_name);
expect_false(json_extract_uint64(mutex_section, "num_owner_switch",
&json_num_owner_switch),
"%s: num_owner_switch not found in JSON", mutex_name);
expect_false(json_extract_uint64(mutex_section, "total_wait_time",
&json_total_wait_time),
"%s: total_wait_time not found in JSON", mutex_name);
expect_false(json_extract_uint64(
mutex_section, "max_wait_time", &json_max_wait_time),
"%s: max_wait_time not found in JSON", mutex_name);
expect_false(json_extract_uint64(
mutex_section, "max_num_thds", &json_max_num_thds),
"%s: max_num_thds not found in JSON", mutex_name);
expect_u64_eq(json_num_ops, ctl_num_ops,
"%s: JSON num_ops doesn't match mallctl", mutex_name);
expect_u64_eq(json_num_wait, ctl_num_wait,
"%s: JSON num_wait doesn't match mallctl", mutex_name);
expect_u64_eq(json_num_spin_acq, ctl_num_spin_acq,
"%s: JSON num_spin_acq doesn't match mallctl", mutex_name);
expect_u64_eq(json_num_owner_switch, ctl_num_owner_switch,
"%s: JSON num_owner_switch doesn't match mallctl", mutex_name);
expect_u64_eq(json_total_wait_time, ctl_total_wait_time,
"%s: JSON total_wait_time doesn't match mallctl", mutex_name);
expect_u64_eq(json_max_wait_time, ctl_max_wait_time,
"%s: JSON max_wait_time doesn't match mallctl", mutex_name);
expect_u32_eq((uint32_t)json_max_num_thds, ctl_max_num_thds,
"%s: JSON max_num_thds doesn't match mallctl", mutex_name);
}
static const char *global_mutex_names[] = {"background_thread",
"max_per_bg_thd", "ctl", "prof", "prof_thds_data", "prof_dump",
"prof_recent_alloc", "prof_recent_dump", "prof_stats"};
static const size_t num_global_mutexes = sizeof(global_mutex_names)
/ sizeof(global_mutex_names[0]);
static const char *arena_mutex_names[] = {"large", "extent_avail",
"extents_dirty", "extents_muzzy", "extents_retained", "decay_dirty",
"decay_muzzy", "base", "tcache_list", "hpa_shard", "hpa_shard_grow",
"hpa_sec"};
static const size_t num_arena_mutexes = sizeof(arena_mutex_names)
/ sizeof(arena_mutex_names[0]);
TEST_BEGIN(test_json_stats_mutexes) {
test_skip_if(!config_stats);
uint64_t epoch;
expect_d_eq(mallctl("epoch", NULL, NULL, (void *)&epoch, sizeof(epoch)),
0, "Unexpected mallctl() failure");
stats_buf_t sbuf;
stats_buf_init(&sbuf);
/* "J" for JSON format, "a" to omit per-arena stats. */
malloc_stats_print(stats_buf_write_cb, &sbuf, "Ja");
/* Verify global mutexes under stats.mutexes. */
const char *global_mutexes_section = json_find_section(
sbuf.buf, "mutexes");
expect_ptr_not_null(global_mutexes_section,
"Could not find global mutexes section in JSON output");
for (size_t i = 0; i < num_global_mutexes; i++) {
verify_mutex_json(global_mutexes_section, "stats.mutexes",
global_mutex_names[i]);
}
/* Verify arena mutexes under stats.arenas.merged.mutexes. */
const char *arenas_section = json_find_section(
sbuf.buf, "stats.arenas");
expect_ptr_not_null(arenas_section,
"Could not find stats.arenas section in JSON output");
const char *merged_section = json_find_section(
arenas_section, "merged");
expect_ptr_not_null(
merged_section, "Could not find merged section in JSON output");
const char *arena_mutexes_section = json_find_section(
merged_section, "mutexes");
expect_ptr_not_null(arena_mutexes_section,
"Could not find arena mutexes section in JSON output");
for (size_t i = 0; i < num_arena_mutexes; i++) {
/*
* MALLCTL_ARENAS_ALL is 4096 representing all arenas in
* mallctl queries.
*/
verify_mutex_json(arena_mutexes_section,
"stats.arenas.4096.mutexes", arena_mutex_names[i]);
}
stats_buf_fini(&sbuf);
}
TEST_END
int
main(void) {
return test(test_json_stats_mutexes);
}