Emit retained HPA slab stats in JSON

This commit is contained in:
Slobodan Predolac 2026-03-27 13:58:27 -07:00
parent 8271c7fe86
commit df6d07cda2
2 changed files with 127 additions and 1 deletions

View file

@ -988,6 +988,8 @@ stats_arena_hpa_shard_slabs_print(emitter_t *emitter, unsigned i) {
emitter, "nactive_nonhuge", emitter_type_size, &nactive_nonhuge);
emitter_json_kv(
emitter, "ndirty_nonhuge", emitter_type_size, &ndirty_nonhuge);
emitter_json_kv(emitter, "nretained_nonhuge", emitter_type_size,
&nretained_nonhuge);
emitter_json_object_end(emitter); /* End "full_slabs" */
/* Next, empty slab stats. */
@ -1029,6 +1031,8 @@ stats_arena_hpa_shard_slabs_print(emitter_t *emitter, unsigned i) {
emitter, "nactive_nonhuge", emitter_type_size, &nactive_nonhuge);
emitter_json_kv(
emitter, "ndirty_nonhuge", emitter_type_size, &ndirty_nonhuge);
emitter_json_kv(emitter, "nretained_nonhuge", emitter_type_size,
&nretained_nonhuge);
emitter_json_object_end(emitter); /* End "empty_slabs" */
/* Last, nonfull slab stats. */
@ -1103,6 +1107,8 @@ stats_arena_hpa_shard_slabs_print(emitter_t *emitter, unsigned i) {
&nactive_nonhuge);
emitter_json_kv(emitter, "ndirty_nonhuge", emitter_type_size,
&ndirty_nonhuge);
emitter_json_kv(emitter, "nretained_nonhuge", emitter_type_size,
&nretained_nonhuge);
emitter_json_object_end(emitter);
}
emitter_json_array_end(emitter); /* End "nonfull_slabs" */

View file

@ -196,6 +196,28 @@ json_find_object_end(const char *object_begin) {
if (depth == 0) {
return cur;
}
if (depth < 0) {
return NULL;
}
}
}
return NULL;
}
static const char *
json_find_array_end(const char *array_begin) {
int depth = 0;
for (const char *cur = array_begin; *cur != '\0'; cur++) {
if (*cur == '[') {
depth++;
} else if (*cur == ']') {
depth--;
if (depth == 0) {
return cur;
}
if (depth < 0) {
return NULL;
}
}
}
return NULL;
@ -220,6 +242,52 @@ json_find_previous_hpa_shard_object(
return found;
}
static const char *
json_find_named_object(
const char *json, const char *key, const char **object_end) {
*object_end = NULL;
char search_key[128];
size_t written = malloc_snprintf(
search_key, sizeof(search_key), "\"%s\":{", key);
if (written >= sizeof(search_key)) {
return NULL;
}
const char *object_begin = strstr(json, search_key);
if (object_begin == NULL) {
return NULL;
}
object_begin = strchr(object_begin, '{');
if (object_begin == NULL) {
return NULL;
}
*object_end = json_find_object_end(object_begin);
return object_begin;
}
static const char *
json_find_named_array(
const char *json, const char *key, const char **array_end) {
*array_end = NULL;
char search_key[128];
size_t written = malloc_snprintf(
search_key, sizeof(search_key), "\"%s\":[", key);
if (written >= sizeof(search_key)) {
return NULL;
}
const char *array_begin = strstr(json, search_key);
if (array_begin == NULL) {
return NULL;
}
array_begin = strchr(array_begin, '[');
if (array_begin == NULL) {
return NULL;
}
*array_end = json_find_array_end(array_begin);
return array_begin;
}
TEST_BEGIN(test_json_stats_mutexes) {
test_skip_if(!config_stats);
@ -381,9 +449,61 @@ TEST_BEGIN(test_hpa_shard_json_contains_sec_stats) {
}
TEST_END
TEST_BEGIN(test_hpa_shard_json_contains_retained_stats) {
test_skip_if(!config_stats);
test_skip_if(!hpa_supported());
void *p = mallocx(PAGE, MALLOCX_TCACHE_NONE);
expect_ptr_not_null(p, "Unexpected mallocx failure");
uint64_t epoch = 1;
size_t sz = sizeof(epoch);
expect_d_eq(mallctl("epoch", NULL, NULL, (void *)&epoch, sz), 0,
"Unexpected mallctl() failure");
stats_buf_t sbuf;
stats_buf_init(&sbuf);
malloc_stats_print(stats_buf_write_cb, &sbuf, "J");
const char *full_slabs_end = NULL;
const char *full_slabs = json_find_named_object(
sbuf.buf, "full_slabs", &full_slabs_end);
expect_ptr_not_null(
full_slabs, "JSON output should contain full_slabs");
const char *full_retained = strstr(full_slabs, "\"nretained_nonhuge\"");
expect_true(full_retained != NULL && full_retained < full_slabs_end,
"full_slabs should contain nretained_nonhuge");
const char *empty_slabs_end = NULL;
const char *empty_slabs = json_find_named_object(
sbuf.buf, "empty_slabs", &empty_slabs_end);
expect_ptr_not_null(
empty_slabs, "JSON output should contain empty_slabs");
const char *empty_retained = strstr(
empty_slabs, "\"nretained_nonhuge\"");
expect_true(empty_retained != NULL && empty_retained < empty_slabs_end,
"empty_slabs should contain nretained_nonhuge");
const char *nonfull_slabs_end = NULL;
const char *nonfull_slabs = json_find_named_array(
sbuf.buf, "nonfull_slabs", &nonfull_slabs_end);
expect_ptr_not_null(
nonfull_slabs, "JSON output should contain nonfull_slabs");
const char *nonfull_retained = strstr(
nonfull_slabs, "\"nretained_nonhuge\"");
expect_true(
nonfull_retained != NULL && nonfull_retained < nonfull_slabs_end,
"nonfull_slabs should contain nretained_nonhuge");
stats_buf_fini(&sbuf);
dallocx(p, MALLOCX_TCACHE_NONE);
}
TEST_END
int
main(void) {
return test_no_reentrancy(test_json_stats_mutexes,
test_hpa_shard_json_ndirty_huge,
test_hpa_shard_json_contains_sec_stats);
test_hpa_shard_json_contains_sec_stats,
test_hpa_shard_json_contains_retained_stats);
}