mirror of
https://github.com/jemalloc/jemalloc.git
synced 2026-07-22 12:43:16 +00:00
1406 lines
47 KiB
C
1406 lines
47 KiB
C
#include "test/jemalloc_test.h"
|
|
#include "jemalloc/internal/prof_stats.h"
|
|
|
|
typedef struct {
|
|
char *buf;
|
|
size_t len;
|
|
size_t capacity;
|
|
} stats_buf_t;
|
|
|
|
static void
|
|
stats_buf_init(stats_buf_t *sbuf) {
|
|
/* 1MB is enough for the small number of arenas used by these tests. */
|
|
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 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", "extents_pinned",
|
|
"decay_dirty", "decay_muzzy", "base", "tcache_list", "hpa_shard",
|
|
"hpa_shard_grow", "hpa_sec", "pac_sec"};
|
|
static const size_t num_arena_mutexes = sizeof(arena_mutex_names)
|
|
/ sizeof(arena_mutex_names[0]);
|
|
|
|
typedef struct {
|
|
const char *begin;
|
|
const char *end;
|
|
} json_fragment_t;
|
|
|
|
static const char *
|
|
json_skip_ws(const char *cur, const char *end) {
|
|
while (cur < end
|
|
&& (*cur == ' ' || *cur == '\t' || *cur == '\n' || *cur == '\r')) {
|
|
cur++;
|
|
}
|
|
return cur;
|
|
}
|
|
|
|
static const char *
|
|
json_string_end(const char *begin, const char *end) {
|
|
if (begin >= end || *begin != '"') {
|
|
return NULL;
|
|
}
|
|
for (const char *cur = begin + 1; cur < end; cur++) {
|
|
if (*cur == '\\') {
|
|
cur++;
|
|
if (cur >= end) {
|
|
return NULL;
|
|
}
|
|
} else if (*cur == '"') {
|
|
return cur + 1;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static const char *
|
|
json_value_end(const char *begin, const char *end) {
|
|
begin = json_skip_ws(begin, end);
|
|
if (begin >= end) {
|
|
return NULL;
|
|
}
|
|
if (*begin == '"') {
|
|
return json_string_end(begin, end);
|
|
}
|
|
if (*begin != '{' && *begin != '[') {
|
|
const char *cur = begin;
|
|
while (cur < end && *cur != ',' && *cur != '}' && *cur != ']') {
|
|
cur++;
|
|
}
|
|
while (cur > begin
|
|
&& (cur[-1] == ' ' || cur[-1] == '\t' || cur[-1] == '\n'
|
|
|| cur[-1] == '\r')) {
|
|
cur--;
|
|
}
|
|
return cur;
|
|
}
|
|
|
|
char opening = *begin;
|
|
char closing = opening == '{' ? '}' : ']';
|
|
unsigned depth = 0;
|
|
for (const char *cur = begin; cur < end; cur++) {
|
|
if (*cur == '"') {
|
|
cur = json_string_end(cur, end);
|
|
if (cur == NULL) {
|
|
return NULL;
|
|
}
|
|
cur--;
|
|
continue;
|
|
}
|
|
if (*cur == opening) {
|
|
depth++;
|
|
} else if (*cur == closing && --depth == 0) {
|
|
return cur + 1;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static bool
|
|
json_fragment_init(const char *begin, const char *end, json_fragment_t *out) {
|
|
begin = json_skip_ws(begin, end);
|
|
const char *value_end = json_value_end(begin, end);
|
|
if (value_end == NULL) {
|
|
return true;
|
|
}
|
|
out->begin = begin;
|
|
out->end = value_end;
|
|
return false;
|
|
}
|
|
|
|
static bool
|
|
json_object_member(json_fragment_t object, const char *key,
|
|
json_fragment_t *value) {
|
|
if (object.begin >= object.end || *object.begin != '{') {
|
|
return true;
|
|
}
|
|
const char *cur = object.begin + 1;
|
|
while (true) {
|
|
cur = json_skip_ws(cur, object.end);
|
|
if (cur >= object.end || *cur == '}') {
|
|
return true;
|
|
}
|
|
const char *key_end = json_string_end(cur, object.end);
|
|
if (key_end == NULL) {
|
|
return true;
|
|
}
|
|
size_t key_len = (size_t)(key_end - cur - 2);
|
|
bool matches = strlen(key) == key_len
|
|
&& strncmp(cur + 1, key, key_len) == 0;
|
|
cur = json_skip_ws(key_end, object.end);
|
|
if (cur >= object.end || *cur++ != ':') {
|
|
return true;
|
|
}
|
|
cur = json_skip_ws(cur, object.end);
|
|
const char *value_end = json_value_end(cur, object.end);
|
|
if (value_end == NULL) {
|
|
return true;
|
|
}
|
|
if (matches) {
|
|
value->begin = cur;
|
|
value->end = value_end;
|
|
return false;
|
|
}
|
|
cur = json_skip_ws(value_end, object.end);
|
|
if (cur < object.end && *cur == ',') {
|
|
cur++;
|
|
} else if (cur >= object.end || *cur != '}') {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
static size_t
|
|
json_object_size(json_fragment_t object) {
|
|
if (object.begin >= object.end || *object.begin != '{') {
|
|
return SIZE_T_MAX;
|
|
}
|
|
size_t count = 0;
|
|
const char *cur = object.begin + 1;
|
|
while (true) {
|
|
cur = json_skip_ws(cur, object.end);
|
|
if (cur < object.end && *cur == '}') {
|
|
return count;
|
|
}
|
|
const char *key_end = json_string_end(cur, object.end);
|
|
if (key_end == NULL) {
|
|
return SIZE_T_MAX;
|
|
}
|
|
cur = json_skip_ws(key_end, object.end);
|
|
if (cur >= object.end || *cur++ != ':') {
|
|
return SIZE_T_MAX;
|
|
}
|
|
cur = json_skip_ws(cur, object.end);
|
|
cur = json_value_end(cur, object.end);
|
|
if (cur == NULL) {
|
|
return SIZE_T_MAX;
|
|
}
|
|
count++;
|
|
cur = json_skip_ws(cur, object.end);
|
|
if (cur < object.end && *cur == ',') {
|
|
cur++;
|
|
} else if (cur >= object.end || *cur != '}') {
|
|
return SIZE_T_MAX;
|
|
}
|
|
}
|
|
}
|
|
|
|
static bool
|
|
json_array_element(json_fragment_t array, size_t ind, json_fragment_t *value) {
|
|
if (array.begin >= array.end || *array.begin != '[') {
|
|
return true;
|
|
}
|
|
size_t current = 0;
|
|
const char *cur = array.begin + 1;
|
|
while (true) {
|
|
cur = json_skip_ws(cur, array.end);
|
|
if (cur >= array.end || *cur == ']') {
|
|
return true;
|
|
}
|
|
const char *value_end = json_value_end(cur, array.end);
|
|
if (value_end == NULL) {
|
|
return true;
|
|
}
|
|
if (current++ == ind) {
|
|
value->begin = cur;
|
|
value->end = value_end;
|
|
return false;
|
|
}
|
|
cur = json_skip_ws(value_end, array.end);
|
|
if (cur < array.end && *cur == ',') {
|
|
cur++;
|
|
} else if (cur >= array.end || *cur != ']') {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
static size_t
|
|
json_array_size(json_fragment_t array) {
|
|
if (array.begin >= array.end || *array.begin != '[') {
|
|
return SIZE_T_MAX;
|
|
}
|
|
size_t count = 0;
|
|
const char *cur = array.begin + 1;
|
|
while (true) {
|
|
cur = json_skip_ws(cur, array.end);
|
|
if (cur < array.end && *cur == ']') {
|
|
return count;
|
|
}
|
|
cur = json_value_end(cur, array.end);
|
|
if (cur == NULL) {
|
|
return SIZE_T_MAX;
|
|
}
|
|
count++;
|
|
cur = json_skip_ws(cur, array.end);
|
|
if (cur < array.end && *cur == ',') {
|
|
cur++;
|
|
} else if (cur >= array.end || *cur != ']') {
|
|
return SIZE_T_MAX;
|
|
}
|
|
}
|
|
}
|
|
|
|
static bool
|
|
json_fragment_uint64(json_fragment_t value, uint64_t *result) {
|
|
char *end;
|
|
errno = 0;
|
|
uint64_t parsed = strtoull(value.begin, &end, 10);
|
|
if (errno != 0 || end != value.end) {
|
|
return true;
|
|
}
|
|
*result = parsed;
|
|
return false;
|
|
}
|
|
|
|
static bool
|
|
json_fragment_int64(json_fragment_t value, int64_t *result) {
|
|
char *end;
|
|
errno = 0;
|
|
int64_t parsed = strtoll(value.begin, &end, 10);
|
|
if (errno != 0 || end != value.end) {
|
|
return true;
|
|
}
|
|
*result = parsed;
|
|
return false;
|
|
}
|
|
|
|
static bool
|
|
json_fragment_string_eq(json_fragment_t value, const char *expected) {
|
|
if (value.end - value.begin < 2 || *value.begin != '"'
|
|
|| value.end[-1] != '"') {
|
|
return false;
|
|
}
|
|
size_t len = (size_t)(value.end - value.begin - 2);
|
|
return strlen(expected) == len
|
|
&& strncmp(value.begin + 1, expected, len) == 0;
|
|
}
|
|
|
|
static void
|
|
expect_json_object_has_keys(json_fragment_t object, const char *const *keys,
|
|
size_t nkeys, const char *name) {
|
|
for (size_t i = 0; i < nkeys; i++) {
|
|
json_fragment_t ignored;
|
|
expect_false(json_object_member(object, keys[i], &ignored),
|
|
"%s is missing key %s", name, keys[i]);
|
|
}
|
|
}
|
|
|
|
static void
|
|
expect_json_object_keys(json_fragment_t object, const char *const *keys,
|
|
size_t nkeys, const char *name) {
|
|
expect_zu_eq(json_object_size(object), nkeys,
|
|
"%s has an unexpected number of keys", name);
|
|
expect_json_object_has_keys(object, keys, nkeys, name);
|
|
}
|
|
|
|
typedef enum {
|
|
ctl_field_size,
|
|
ctl_field_uint64,
|
|
ctl_field_uint32,
|
|
ctl_field_unsigned,
|
|
ctl_field_ssize,
|
|
ctl_field_string,
|
|
} ctl_field_type_t;
|
|
|
|
typedef struct {
|
|
const char *json_key;
|
|
const char *ctl_suffix;
|
|
ctl_field_type_t type;
|
|
} ctl_field_t;
|
|
|
|
static void
|
|
expect_json_ctl_fields(json_fragment_t object, const char *ctl_prefix,
|
|
const ctl_field_t *fields, size_t nfields) {
|
|
for (size_t i = 0; i < nfields; i++) {
|
|
const ctl_field_t *field = &fields[i];
|
|
json_fragment_t value;
|
|
bool missing = json_object_member(object, field->json_key, &value);
|
|
expect_false(missing, "JSON is missing %s", field->json_key);
|
|
if (missing) {
|
|
continue;
|
|
}
|
|
|
|
char ctl_name[256];
|
|
size_t written = malloc_snprintf(ctl_name, sizeof(ctl_name),
|
|
"%s.%s", ctl_prefix, field->ctl_suffix);
|
|
expect_zu_lt(written, sizeof(ctl_name), "mallctl name is too long");
|
|
|
|
size_t sz;
|
|
uint64_t json_u64 = 0;
|
|
int64_t json_i64 = 0;
|
|
switch (field->type) {
|
|
case ctl_field_size: {
|
|
size_t expected;
|
|
sz = sizeof(expected);
|
|
expect_d_eq(mallctl(ctl_name, &expected, &sz, NULL, 0), 0,
|
|
"mallctl failed for %s", ctl_name);
|
|
expect_false(json_fragment_uint64(value, &json_u64),
|
|
"JSON value for %s is not unsigned", field->json_key);
|
|
expect_zu_eq((size_t)json_u64, expected,
|
|
"JSON value does not match %s", ctl_name);
|
|
break;
|
|
}
|
|
case ctl_field_uint64: {
|
|
uint64_t expected;
|
|
sz = sizeof(expected);
|
|
expect_d_eq(mallctl(ctl_name, &expected, &sz, NULL, 0), 0,
|
|
"mallctl failed for %s", ctl_name);
|
|
expect_false(json_fragment_uint64(value, &json_u64),
|
|
"JSON value for %s is not unsigned", field->json_key);
|
|
expect_u64_eq(json_u64, expected,
|
|
"JSON value does not match %s", ctl_name);
|
|
break;
|
|
}
|
|
case ctl_field_uint32: {
|
|
uint32_t expected;
|
|
sz = sizeof(expected);
|
|
expect_d_eq(mallctl(ctl_name, &expected, &sz, NULL, 0), 0,
|
|
"mallctl failed for %s", ctl_name);
|
|
expect_false(json_fragment_uint64(value, &json_u64),
|
|
"JSON value for %s is not unsigned", field->json_key);
|
|
expect_u32_eq((uint32_t)json_u64, expected,
|
|
"JSON value does not match %s", ctl_name);
|
|
break;
|
|
}
|
|
case ctl_field_unsigned: {
|
|
unsigned expected;
|
|
sz = sizeof(expected);
|
|
expect_d_eq(mallctl(ctl_name, &expected, &sz, NULL, 0), 0,
|
|
"mallctl failed for %s", ctl_name);
|
|
expect_false(json_fragment_uint64(value, &json_u64),
|
|
"JSON value for %s is not unsigned", field->json_key);
|
|
expect_u_eq((unsigned)json_u64, expected,
|
|
"JSON value does not match %s", ctl_name);
|
|
break;
|
|
}
|
|
case ctl_field_ssize: {
|
|
ssize_t expected;
|
|
sz = sizeof(expected);
|
|
expect_d_eq(mallctl(ctl_name, &expected, &sz, NULL, 0), 0,
|
|
"mallctl failed for %s", ctl_name);
|
|
expect_false(json_fragment_int64(value, &json_i64),
|
|
"JSON value for %s is not signed", field->json_key);
|
|
expect_zd_eq((ssize_t)json_i64, expected,
|
|
"JSON value does not match %s", ctl_name);
|
|
break;
|
|
}
|
|
case ctl_field_string: {
|
|
const char *expected;
|
|
sz = sizeof(expected);
|
|
expect_d_eq(mallctl(ctl_name, &expected, &sz, NULL, 0), 0,
|
|
"mallctl failed for %s", ctl_name);
|
|
expect_true(json_fragment_string_eq(value, expected),
|
|
"JSON value does not match %s", ctl_name);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#define ARRAY_COUNT(array) (sizeof(array) / sizeof((array)[0]))
|
|
|
|
static void
|
|
expect_json_uint_eq(json_fragment_t object, const char *key,
|
|
uint64_t 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;
|
|
}
|
|
uint64_t actual = 0;
|
|
expect_false(json_fragment_uint64(value, &actual),
|
|
"%s.%s is not an unsigned integer", name, key);
|
|
expect_u64_eq(actual, expected, "%s.%s has an unexpected value", name,
|
|
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) {
|
|
stats_buf_init(sbuf);
|
|
malloc_stats_print(stats_buf_write_cb, sbuf, opts);
|
|
|
|
json_fragment_t root;
|
|
if (json_fragment_init(sbuf->buf, sbuf->buf + sbuf->len, &root)
|
|
|| json_object_member(root, "jemalloc", jemalloc)) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static bool
|
|
stats_json_print(stats_buf_t *sbuf, json_fragment_t *stats,
|
|
json_fragment_t *merged) {
|
|
json_fragment_t jemalloc, stats_arenas;
|
|
if (stats_json_print_opts(sbuf, "Ja", &jemalloc)
|
|
|| json_object_member(jemalloc, "stats", stats)
|
|
|| json_object_member(jemalloc, "stats.arenas", &stats_arenas)
|
|
|| json_object_member(stats_arenas, "merged", merged)) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static const ctl_field_t mutex_fields[] = {
|
|
{"num_ops", "num_ops", ctl_field_uint64},
|
|
{"num_wait", "num_wait", ctl_field_uint64},
|
|
{"num_spin_acq", "num_spin_acq", ctl_field_uint64},
|
|
{"num_owner_switch", "num_owner_switch", ctl_field_uint64},
|
|
{"total_wait_time", "total_wait_time", ctl_field_uint64},
|
|
{"max_wait_time", "max_wait_time", ctl_field_uint64},
|
|
{"max_num_thds", "max_num_thds", ctl_field_uint32},
|
|
};
|
|
static const char *const mutex_keys[] = {"num_ops", "num_wait",
|
|
"num_spin_acq", "num_owner_switch", "total_wait_time",
|
|
"max_wait_time", "max_num_thds"};
|
|
|
|
static void
|
|
expect_mutex_object(json_fragment_t mutex, const char *ctl_prefix) {
|
|
expect_json_object_keys(
|
|
mutex, mutex_keys, ARRAY_COUNT(mutex_keys), ctl_prefix);
|
|
expect_json_ctl_fields(
|
|
mutex, ctl_prefix, mutex_fields, ARRAY_COUNT(mutex_fields));
|
|
}
|
|
|
|
static const ctl_field_t global_fields[] = {
|
|
{"allocated", "allocated", ctl_field_size},
|
|
{"active", "active", ctl_field_size},
|
|
{"metadata", "metadata", ctl_field_size},
|
|
{"metadata_edata", "metadata_edata", ctl_field_size},
|
|
{"metadata_rtree", "metadata_rtree", ctl_field_size},
|
|
{"metadata_thp", "metadata_thp", ctl_field_size},
|
|
{"resident", "resident", ctl_field_size},
|
|
{"mapped", "mapped", ctl_field_size},
|
|
{"retained", "retained", ctl_field_size},
|
|
{"pinned", "pinned", ctl_field_size},
|
|
{"zero_reallocs", "zero_reallocs", ctl_field_size},
|
|
};
|
|
|
|
static const ctl_field_t background_thread_fields[] = {
|
|
{"num_threads", "num_threads", ctl_field_size},
|
|
{"num_runs", "num_runs", ctl_field_uint64},
|
|
{"run_interval", "run_interval", ctl_field_uint64},
|
|
};
|
|
|
|
static const ctl_field_t arena_fields[] = {
|
|
{"nthreads", "nthreads", ctl_field_unsigned},
|
|
{"uptime_ns", "uptime", ctl_field_uint64},
|
|
{"dss", "dss", ctl_field_string},
|
|
{"dirty_decay_ms", "dirty_decay_ms", ctl_field_ssize},
|
|
{"muzzy_decay_ms", "muzzy_decay_ms", ctl_field_ssize},
|
|
{"pactive", "pactive", ctl_field_size},
|
|
{"pdirty", "pdirty", ctl_field_size},
|
|
{"pmuzzy", "pmuzzy", ctl_field_size},
|
|
{"dirty_npurge", "dirty_npurge", ctl_field_uint64},
|
|
{"dirty_nmadvise", "dirty_nmadvise", ctl_field_uint64},
|
|
{"dirty_purged", "dirty_purged", ctl_field_uint64},
|
|
{"muzzy_npurge", "muzzy_npurge", ctl_field_uint64},
|
|
{"muzzy_nmadvise", "muzzy_nmadvise", ctl_field_uint64},
|
|
{"muzzy_purged", "muzzy_purged", ctl_field_uint64},
|
|
{"mapped", "mapped", ctl_field_size},
|
|
{"retained", "retained", ctl_field_size},
|
|
{"pinned", "pinned", ctl_field_size},
|
|
{"base", "base", ctl_field_size},
|
|
{"internal", "internal", ctl_field_size},
|
|
{"metadata_edata", "metadata_edata", ctl_field_size},
|
|
{"metadata_rtree", "metadata_rtree", ctl_field_size},
|
|
{"metadata_thp", "metadata_thp", ctl_field_size},
|
|
{"tcache_bytes", "tcache_bytes", ctl_field_size},
|
|
{"tcache_stashed_bytes", "tcache_stashed_bytes", ctl_field_size},
|
|
{"resident", "resident", ctl_field_size},
|
|
{"abandoned_vm", "abandoned_vm", ctl_field_size},
|
|
{"extent_avail", "extent_avail", ctl_field_size},
|
|
};
|
|
|
|
static const ctl_field_t arena_alloc_fields[] = {
|
|
{"allocated", "allocated", ctl_field_size},
|
|
{"nmalloc", "nmalloc", ctl_field_uint64},
|
|
{"ndalloc", "ndalloc", ctl_field_uint64},
|
|
{"nrequests", "nrequests", ctl_field_uint64},
|
|
{"nfills", "nfills", ctl_field_uint64},
|
|
{"nflushes", "nflushes", ctl_field_uint64},
|
|
};
|
|
static const char *const arena_alloc_keys[] = {"allocated", "nmalloc",
|
|
"ndalloc", "nrequests", "nfills", "nflushes"};
|
|
|
|
static const ctl_field_t pac_sec_fields[] = {
|
|
{"pac_sec_bytes", "pac_sec_bytes", ctl_field_size},
|
|
{"pac_sec_hits", "pac_sec_hits", ctl_field_size},
|
|
{"pac_sec_misses", "pac_sec_misses", ctl_field_size},
|
|
{"pac_sec_dalloc_noflush", "pac_sec_dalloc_noflush", ctl_field_size},
|
|
{"pac_sec_dalloc_flush", "pac_sec_dalloc_flush", ctl_field_size},
|
|
};
|
|
|
|
static const char *const merged_keys[] = {"nthreads", "uptime_ns", "dss",
|
|
"dirty_decay_ms", "muzzy_decay_ms", "pactive", "pdirty", "pmuzzy",
|
|
"dirty_npurge", "dirty_nmadvise", "dirty_purged", "muzzy_npurge",
|
|
"muzzy_nmadvise", "muzzy_purged", "small", "large", "mapped",
|
|
"retained", "pinned", "base", "internal", "metadata_edata",
|
|
"metadata_rtree", "metadata_thp", "tcache_bytes",
|
|
"tcache_stashed_bytes", "resident", "abandoned_vm", "extent_avail",
|
|
"mutexes", "bins", "lextents", "extents", "hpa_shard"};
|
|
static const char *const pac_sec_keys[] = {"pac_sec_bytes", "pac_sec_hits",
|
|
"pac_sec_misses", "pac_sec_dalloc_noflush", "pac_sec_dalloc_flush"};
|
|
|
|
static const ctl_field_t arena_sample_fields[] = {
|
|
{"nthreads", "nthreads", ctl_field_unsigned},
|
|
{"dss", "dss", ctl_field_string},
|
|
{"pactive", "pactive", ctl_field_size},
|
|
{"mapped", "mapped", ctl_field_size},
|
|
};
|
|
|
|
static unsigned
|
|
create_manual_arena(void) {
|
|
unsigned arena_ind = 0;
|
|
size_t sz = sizeof(arena_ind);
|
|
assert_d_eq(mallctl("arenas.create", &arena_ind, &sz, NULL, 0), 0,
|
|
"Could not create manual arena");
|
|
return arena_ind;
|
|
}
|
|
|
|
static void
|
|
destroy_manual_arena(unsigned arena_ind) {
|
|
size_t mib[3];
|
|
size_t miblen = ARRAY_COUNT(mib);
|
|
assert_d_eq(mallctlnametomib("arena.0.destroy", mib, &miblen), 0,
|
|
"Could not resolve arena destroy mallctl");
|
|
mib[1] = arena_ind;
|
|
assert_d_eq(mallctlbymib(mib, miblen, NULL, NULL, NULL, 0), 0,
|
|
"Could not destroy arena %u", arena_ind);
|
|
}
|
|
|
|
static void
|
|
expect_arena_sample(json_fragment_t stats_arenas, const char *json_key,
|
|
unsigned ctl_ind, bool has_name) {
|
|
json_fragment_t arena;
|
|
bool missing = json_object_member(stats_arenas, json_key, &arena);
|
|
expect_false(missing, "stats.arenas is missing %s", json_key);
|
|
if (missing) {
|
|
return;
|
|
}
|
|
|
|
char ctl_prefix[64];
|
|
malloc_snprintf(ctl_prefix, sizeof(ctl_prefix), "stats.arenas.%u",
|
|
ctl_ind);
|
|
expect_json_ctl_fields(arena, ctl_prefix, arena_sample_fields,
|
|
ARRAY_COUNT(arena_sample_fields));
|
|
|
|
json_fragment_t name;
|
|
bool name_missing = json_object_member(arena, "name", &name);
|
|
if (has_name) {
|
|
expect_false(name_missing, "stats.arenas.%s is missing name",
|
|
json_key);
|
|
} else {
|
|
expect_true(name_missing,
|
|
"stats.arenas.%s unexpectedly contains name", json_key);
|
|
}
|
|
}
|
|
|
|
typedef enum {
|
|
option_parent_jemalloc,
|
|
option_parent_stats,
|
|
option_parent_arenas,
|
|
option_parent_merged,
|
|
} option_parent_t;
|
|
|
|
typedef struct {
|
|
const char *opts;
|
|
option_parent_t parent;
|
|
const char *omitted_key;
|
|
} option_case_t;
|
|
|
|
static void
|
|
expect_option_omits(const option_case_t *test_case) {
|
|
stats_buf_t sbuf;
|
|
json_fragment_t jemalloc;
|
|
bool malformed = stats_json_print_opts(
|
|
&sbuf, test_case->opts, &jemalloc);
|
|
expect_false(malformed, "Malformed JSON for opts=%s", test_case->opts);
|
|
if (malformed) {
|
|
stats_buf_fini(&sbuf);
|
|
return;
|
|
}
|
|
|
|
json_fragment_t stats, stats_arenas, merged;
|
|
json_fragment_t parent = jemalloc;
|
|
if (test_case->parent >= option_parent_stats) {
|
|
assert_false(json_object_member(jemalloc, "stats", &stats),
|
|
"opts=%s omitted stats", test_case->opts);
|
|
parent = stats;
|
|
}
|
|
if (test_case->parent >= option_parent_arenas) {
|
|
assert_false(json_object_member(
|
|
jemalloc, "stats.arenas", &stats_arenas),
|
|
"opts=%s omitted stats.arenas", test_case->opts);
|
|
parent = stats_arenas;
|
|
}
|
|
if (test_case->parent >= option_parent_merged) {
|
|
assert_false(json_object_member(stats_arenas, "merged", &merged),
|
|
"opts=%s omitted merged stats", test_case->opts);
|
|
parent = merged;
|
|
}
|
|
|
|
json_fragment_t ignored;
|
|
expect_true(json_object_member(
|
|
parent, test_case->omitted_key, &ignored),
|
|
"opts=%s did not omit %s", test_case->opts,
|
|
test_case->omitted_key);
|
|
|
|
if (strcmp(test_case->opts, "Jx") == 0) {
|
|
assert_false(json_object_member(
|
|
jemalloc, "stats.arenas", &stats_arenas),
|
|
"opts=Jx omitted stats.arenas");
|
|
assert_false(json_object_member(stats_arenas, "merged", &merged),
|
|
"opts=Jx omitted merged stats");
|
|
expect_true(json_object_member(merged, "mutexes", &ignored),
|
|
"opts=Jx did not omit arena mutexes");
|
|
}
|
|
stats_buf_fini(&sbuf);
|
|
}
|
|
|
|
TEST_BEGIN(test_json_stats_arenas_and_options) {
|
|
test_skip_if(!config_stats);
|
|
|
|
unsigned manual_ind = create_manual_arena();
|
|
unsigned destroyed_ind = create_manual_arena();
|
|
void *manual_alloc = mallocx(1,
|
|
MALLOCX_ARENA(manual_ind) | MALLOCX_TCACHE_NONE);
|
|
assert_ptr_not_null(manual_alloc, "Manual arena allocation failed");
|
|
void *destroyed_alloc = mallocx(1,
|
|
MALLOCX_ARENA(destroyed_ind) | MALLOCX_TCACHE_NONE);
|
|
assert_ptr_not_null(destroyed_alloc,
|
|
"Destroyed arena allocation failed");
|
|
dallocx(destroyed_alloc, MALLOCX_TCACHE_NONE);
|
|
destroy_manual_arena(destroyed_ind);
|
|
|
|
stats_buf_t sbuf;
|
|
json_fragment_t jemalloc;
|
|
bool malformed = stats_json_print_opts(&sbuf, "J", &jemalloc);
|
|
assert_false(malformed, "Malformed default JSON stats");
|
|
json_fragment_t stats_arenas;
|
|
assert_false(json_object_member(
|
|
jemalloc, "stats.arenas", &stats_arenas),
|
|
"Default JSON stats omitted stats.arenas");
|
|
|
|
char manual_key[32];
|
|
malloc_snprintf(
|
|
manual_key, sizeof(manual_key), "%u", manual_ind);
|
|
expect_arena_sample(stats_arenas, "0", 0, true);
|
|
expect_arena_sample(
|
|
stats_arenas, manual_key, manual_ind, true);
|
|
expect_arena_sample(stats_arenas, "destroyed",
|
|
MALLCTL_ARENAS_DESTROYED, false);
|
|
stats_buf_fini(&sbuf);
|
|
|
|
const option_case_t cases[] = {
|
|
{"Jg", option_parent_jemalloc, "version"},
|
|
{"Jm", option_parent_arenas, "merged"},
|
|
{"Jd", option_parent_arenas, "destroyed"},
|
|
{"Ja", option_parent_arenas, manual_key},
|
|
{"Jb", option_parent_merged, "bins"},
|
|
{"Jl", option_parent_merged, "lextents"},
|
|
{"Jx", option_parent_stats, "mutexes"},
|
|
{"Je", option_parent_merged, "extents"},
|
|
{"Jh", option_parent_merged, "hpa_shard"},
|
|
{"Jmda", option_parent_jemalloc, "stats.arenas"},
|
|
};
|
|
for (size_t i = 0; i < ARRAY_COUNT(cases); i++) {
|
|
expect_option_omits(&cases[i]);
|
|
}
|
|
|
|
dallocx(manual_alloc, MALLOCX_TCACHE_NONE);
|
|
destroy_manual_arena(manual_ind);
|
|
}
|
|
TEST_END
|
|
|
|
TEST_BEGIN(test_json_stats_global_and_arena) {
|
|
test_skip_if(!config_stats);
|
|
|
|
stats_buf_t sbuf;
|
|
json_fragment_t stats, merged;
|
|
bool malformed = stats_json_print(&sbuf, &stats, &merged);
|
|
expect_false(malformed, "Could not find runtime stats in JSON output");
|
|
if (malformed) {
|
|
stats_buf_fini(&sbuf);
|
|
return;
|
|
}
|
|
|
|
const char *const stats_keys[] = {"allocated", "active", "metadata",
|
|
"metadata_edata", "metadata_rtree", "metadata_thp", "resident",
|
|
"mapped", "retained", "pinned", "zero_reallocs",
|
|
"background_thread", "mutexes"};
|
|
expect_json_object_keys(
|
|
stats, stats_keys, ARRAY_COUNT(stats_keys), "stats");
|
|
expect_json_ctl_fields(
|
|
stats, "stats", global_fields, ARRAY_COUNT(global_fields));
|
|
|
|
json_fragment_t background_thread;
|
|
expect_false(json_object_member(
|
|
stats, "background_thread", &background_thread),
|
|
"stats is missing background_thread");
|
|
const char *const background_thread_keys[] = {
|
|
"num_threads", "num_runs", "run_interval"};
|
|
expect_json_object_keys(background_thread, background_thread_keys,
|
|
ARRAY_COUNT(background_thread_keys), "stats.background_thread");
|
|
if (have_background_thread) {
|
|
expect_json_ctl_fields(background_thread, "stats.background_thread",
|
|
background_thread_fields,
|
|
ARRAY_COUNT(background_thread_fields));
|
|
} else {
|
|
for (size_t i = 0; i < ARRAY_COUNT(background_thread_keys); i++) {
|
|
expect_json_uint_eq(background_thread,
|
|
background_thread_keys[i], 0, "stats.background_thread");
|
|
}
|
|
}
|
|
|
|
char arena_prefix[64];
|
|
malloc_snprintf(arena_prefix, sizeof(arena_prefix), "stats.arenas.%u",
|
|
MALLCTL_ARENAS_ALL);
|
|
size_t expected_merged_keys = ARRAY_COUNT(merged_keys);
|
|
if (opt_pac_sec_opts.nshards > 0) {
|
|
expected_merged_keys += ARRAY_COUNT(pac_sec_keys);
|
|
}
|
|
expect_zu_eq(json_object_size(merged), expected_merged_keys,
|
|
"merged arena stats has an unexpected number of keys");
|
|
expect_json_object_has_keys(merged, merged_keys, ARRAY_COUNT(merged_keys),
|
|
"merged arena stats");
|
|
expect_json_ctl_fields(
|
|
merged, arena_prefix, arena_fields, ARRAY_COUNT(arena_fields));
|
|
if (opt_pac_sec_opts.nshards > 0) {
|
|
expect_json_object_has_keys(merged, pac_sec_keys,
|
|
ARRAY_COUNT(pac_sec_keys), "merged arena stats");
|
|
expect_json_ctl_fields(merged, arena_prefix, pac_sec_fields,
|
|
ARRAY_COUNT(pac_sec_fields));
|
|
}
|
|
|
|
json_fragment_t small, large;
|
|
expect_false(json_object_member(merged, "small", &small),
|
|
"merged arena stats are missing small");
|
|
expect_false(json_object_member(merged, "large", &large),
|
|
"merged arena stats are missing large");
|
|
expect_json_object_keys(small, arena_alloc_keys,
|
|
ARRAY_COUNT(arena_alloc_keys), "stats.arenas.merged.small");
|
|
expect_json_object_keys(large, arena_alloc_keys,
|
|
ARRAY_COUNT(arena_alloc_keys), "stats.arenas.merged.large");
|
|
char alloc_prefix[96];
|
|
malloc_snprintf(
|
|
alloc_prefix, sizeof(alloc_prefix), "%s.small", arena_prefix);
|
|
expect_json_ctl_fields(small, alloc_prefix, arena_alloc_fields,
|
|
ARRAY_COUNT(arena_alloc_fields));
|
|
malloc_snprintf(
|
|
alloc_prefix, sizeof(alloc_prefix), "%s.large", arena_prefix);
|
|
expect_json_ctl_fields(large, alloc_prefix, arena_alloc_fields,
|
|
ARRAY_COUNT(arena_alloc_fields));
|
|
|
|
json_fragment_t mutexes;
|
|
expect_false(json_object_member(stats, "mutexes", &mutexes),
|
|
"stats is missing mutexes");
|
|
expect_zu_eq(json_object_size(mutexes), num_global_mutexes,
|
|
"stats.mutexes has an unexpected number of mutexes");
|
|
for (size_t i = 0; i < num_global_mutexes; i++) {
|
|
json_fragment_t mutex;
|
|
expect_false(json_object_member(
|
|
mutexes, global_mutex_names[i], &mutex),
|
|
"stats.mutexes is missing %s", global_mutex_names[i]);
|
|
char prefix[128];
|
|
malloc_snprintf(prefix, sizeof(prefix), "stats.mutexes.%s",
|
|
global_mutex_names[i]);
|
|
expect_mutex_object(mutex, prefix);
|
|
}
|
|
|
|
expect_false(json_object_member(merged, "mutexes", &mutexes),
|
|
"merged arena stats are missing mutexes");
|
|
expect_zu_eq(json_object_size(mutexes), num_arena_mutexes,
|
|
"merged arena mutexes has an unexpected number of mutexes");
|
|
for (size_t i = 0; i < num_arena_mutexes; i++) {
|
|
json_fragment_t mutex;
|
|
expect_false(json_object_member(
|
|
mutexes, arena_mutex_names[i], &mutex),
|
|
"merged arena mutexes is missing %s", arena_mutex_names[i]);
|
|
char prefix[160];
|
|
malloc_snprintf(prefix, sizeof(prefix), "%s.mutexes.%s",
|
|
arena_prefix, arena_mutex_names[i]);
|
|
expect_mutex_object(mutex, prefix);
|
|
}
|
|
|
|
stats_buf_fini(&sbuf);
|
|
}
|
|
TEST_END
|
|
|
|
static const ctl_field_t hpa_fields[] = {
|
|
{"npageslabs", "npageslabs", ctl_field_size},
|
|
{"nactive", "nactive", ctl_field_size},
|
|
{"ndirty", "ndirty", ctl_field_size},
|
|
{"npurge_passes", "npurge_passes", ctl_field_uint64},
|
|
{"npurges", "npurges", ctl_field_uint64},
|
|
{"nhugifies", "nhugifies", ctl_field_uint64},
|
|
{"nhugify_failures", "nhugify_failures", ctl_field_uint64},
|
|
{"ndehugifies", "ndehugifies", ctl_field_uint64},
|
|
};
|
|
|
|
static const ctl_field_t hpa_sec_fields[] = {
|
|
{"sec_bytes", "hpa_sec_bytes", ctl_field_size},
|
|
{"sec_hits", "hpa_sec_hits", ctl_field_size},
|
|
{"sec_misses", "hpa_sec_misses", ctl_field_size},
|
|
{"sec_dalloc_noflush", "hpa_sec_dalloc_noflush", ctl_field_size},
|
|
{"sec_dalloc_flush", "hpa_sec_dalloc_flush", ctl_field_size},
|
|
{"sec_overfills", "hpa_sec_overfills", ctl_field_size},
|
|
};
|
|
|
|
static const ctl_field_t hpa_slab_fields[] = {
|
|
{"npageslabs_huge", "npageslabs_huge", ctl_field_size},
|
|
{"nactive_huge", "nactive_huge", ctl_field_size},
|
|
{"ndirty_huge", "ndirty_huge", ctl_field_size},
|
|
{"npageslabs_nonhuge", "npageslabs_nonhuge", ctl_field_size},
|
|
{"nactive_nonhuge", "nactive_nonhuge", ctl_field_size},
|
|
{"ndirty_nonhuge", "ndirty_nonhuge", ctl_field_size},
|
|
};
|
|
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, 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));
|
|
|
|
size_t npageslabs, nactive, ndirty;
|
|
size_t sz = sizeof(size_t);
|
|
char ctl_name[192];
|
|
malloc_snprintf(ctl_name, sizeof(ctl_name),
|
|
"%s.npageslabs_nonhuge", ctl_prefix);
|
|
expect_d_eq(mallctl(ctl_name, &npageslabs, &sz, NULL, 0), 0,
|
|
"mallctl failed for %s", ctl_name);
|
|
sz = sizeof(size_t);
|
|
malloc_snprintf(
|
|
ctl_name, sizeof(ctl_name), "%s.nactive_nonhuge", ctl_prefix);
|
|
expect_d_eq(mallctl(ctl_name, &nactive, &sz, NULL, 0), 0,
|
|
"mallctl failed for %s", ctl_name);
|
|
sz = sizeof(size_t);
|
|
malloc_snprintf(
|
|
ctl_name, sizeof(ctl_name), "%s.ndirty_nonhuge", ctl_prefix);
|
|
expect_d_eq(mallctl(ctl_name, &ndirty, &sz, NULL, 0), 0,
|
|
"mallctl failed for %s", ctl_name);
|
|
expect_json_uint_eq(slab, "nretained_nonhuge",
|
|
npageslabs * HUGEPAGE_PAGES - nactive - ndirty, name);
|
|
}
|
|
|
|
static const ctl_field_t hpa_alloc_fields[] = {
|
|
{"min_extents", "min_extents", ctl_field_uint64},
|
|
{"max_extents", "max_extents", ctl_field_uint64},
|
|
{"extents", "extents", ctl_field_uint64},
|
|
{"ps", "ps", ctl_field_uint64},
|
|
{"pages_per_ps", "pages_per_ps", ctl_field_uint64},
|
|
{"extents_per_ps", "extents_per_ps", ctl_field_uint64},
|
|
{"total_elapsed_ns_per_ps", "total_elapsed_ns_per_ps",
|
|
ctl_field_uint64},
|
|
};
|
|
static const char *const hpa_alloc_keys[] = {"min_extents", "max_extents",
|
|
"extents", "ps", "pages_per_ps", "extents_per_ps",
|
|
"total_elapsed_ns_per_ps"};
|
|
|
|
TEST_BEGIN(test_json_stats_hpa) {
|
|
test_skip_if(!config_stats);
|
|
test_skip_if(!hpa_supported());
|
|
|
|
stats_buf_t sbuf;
|
|
json_fragment_t stats, merged;
|
|
bool malformed = stats_json_print(&sbuf, &stats, &merged);
|
|
expect_false(malformed, "Could not find runtime stats in JSON output");
|
|
if (malformed) {
|
|
stats_buf_fini(&sbuf);
|
|
return;
|
|
}
|
|
|
|
json_fragment_t hpa;
|
|
expect_false(json_object_member(merged, "hpa_shard", &hpa),
|
|
"merged arena stats are missing hpa_shard");
|
|
const char *const hpa_keys[] = {"sec_bytes", "sec_hits", "sec_misses",
|
|
"sec_dalloc_noflush", "sec_dalloc_flush", "sec_overfills",
|
|
"npageslabs", "nactive", "ndirty", "npurge_passes", "npurges",
|
|
"nhugifies", "nhugify_failures", "ndehugifies", "slabs",
|
|
"extent_allocation_distribution", "full_slabs", "empty_slabs",
|
|
"nonfull_slabs"};
|
|
expect_json_object_keys(
|
|
hpa, hpa_keys, ARRAY_COUNT(hpa_keys), "hpa_shard");
|
|
|
|
char arena_prefix[64];
|
|
malloc_snprintf(arena_prefix, sizeof(arena_prefix), "stats.arenas.%u",
|
|
MALLCTL_ARENAS_ALL);
|
|
expect_json_ctl_fields(hpa, arena_prefix, hpa_sec_fields,
|
|
ARRAY_COUNT(hpa_sec_fields));
|
|
char hpa_prefix[96];
|
|
malloc_snprintf(
|
|
hpa_prefix, sizeof(hpa_prefix), "%s.hpa_shard", arena_prefix);
|
|
expect_json_ctl_fields(
|
|
hpa, hpa_prefix, hpa_fields, ARRAY_COUNT(hpa_fields));
|
|
|
|
json_fragment_t slabs;
|
|
expect_false(json_object_member(hpa, "slabs", &slabs),
|
|
"hpa_shard is missing slabs");
|
|
char slab_prefix[128];
|
|
malloc_snprintf(
|
|
slab_prefix, sizeof(slab_prefix), "%s.slabs", hpa_prefix);
|
|
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++) {
|
|
json_fragment_t summary;
|
|
expect_false(json_object_member(hpa, summary_names[i], &summary),
|
|
"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], 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;
|
|
expect_false(json_object_member(hpa, "nonfull_slabs", &nonfull),
|
|
"hpa_shard is missing nonfull_slabs");
|
|
size_t nonfull_count = PSSET_NPSIZES < SC_NPSIZES ? PSSET_NPSIZES
|
|
: SC_NPSIZES;
|
|
expect_zu_eq(json_array_size(nonfull), nonfull_count,
|
|
"nonfull_slabs has an unexpected number of rows");
|
|
for (size_t i = 0; i < nonfull_count; i++) {
|
|
json_fragment_t slab;
|
|
expect_false(json_array_element(nonfull, i, &slab),
|
|
"nonfull_slabs is missing row %zu", i);
|
|
malloc_snprintf(slab_prefix, sizeof(slab_prefix),
|
|
"%s.nonfull_slabs.%zu", hpa_prefix, i);
|
|
char row_name[64];
|
|
malloc_snprintf(
|
|
row_name, sizeof(row_name), "nonfull_slabs[%zu]", i);
|
|
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;
|
|
expect_false(json_object_member(
|
|
hpa, "extent_allocation_distribution", &distribution),
|
|
"hpa_shard is missing extent_allocation_distribution");
|
|
expect_zu_eq(json_array_size(distribution), SEC_MAX_NALLOCS + 1,
|
|
"extent_allocation_distribution has an unexpected row count");
|
|
for (size_t i = 0; i <= SEC_MAX_NALLOCS; i++) {
|
|
json_fragment_t row;
|
|
expect_false(json_array_element(distribution, i, &row),
|
|
"extent_allocation_distribution is missing row %zu", i);
|
|
char row_name[80];
|
|
malloc_snprintf(row_name, sizeof(row_name),
|
|
"extent_allocation_distribution[%zu]", i);
|
|
expect_json_object_keys(row, hpa_alloc_keys,
|
|
ARRAY_COUNT(hpa_alloc_keys), row_name);
|
|
char ctl_prefix[128];
|
|
malloc_snprintf(ctl_prefix, sizeof(ctl_prefix),
|
|
"%s.alloc.%zu", hpa_prefix, i);
|
|
expect_json_ctl_fields(row, ctl_prefix, hpa_alloc_fields,
|
|
ARRAY_COUNT(hpa_alloc_fields));
|
|
}
|
|
|
|
stats_buf_fini(&sbuf);
|
|
}
|
|
TEST_END
|
|
|
|
static const ctl_field_t bin_fields[] = {
|
|
{"nmalloc", "nmalloc", ctl_field_uint64},
|
|
{"ndalloc", "ndalloc", ctl_field_uint64},
|
|
{"curregs", "curregs", ctl_field_size},
|
|
{"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[] = {"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[] = {"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},
|
|
{"nmuzzy", "nmuzzy", ctl_field_size},
|
|
{"nretained", "nretained", ctl_field_size},
|
|
{"npinned", "npinned", ctl_field_size},
|
|
{"dirty_bytes", "dirty_bytes", ctl_field_size},
|
|
{"muzzy_bytes", "muzzy_bytes", ctl_field_size},
|
|
{"retained_bytes", "retained_bytes", ctl_field_size},
|
|
{"pinned_bytes", "pinned_bytes", ctl_field_size},
|
|
};
|
|
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"};
|
|
|
|
static void
|
|
expect_prof_stats(json_fragment_t object, const char *ctl_prefix) {
|
|
prof_stats_t live, accum;
|
|
size_t sz = sizeof(prof_stats_t);
|
|
char ctl_name[128];
|
|
malloc_snprintf(
|
|
ctl_name, sizeof(ctl_name), "%s.live", ctl_prefix);
|
|
expect_d_eq(mallctl(ctl_name, &live, &sz, NULL, 0), 0,
|
|
"mallctl failed for %s", ctl_name);
|
|
sz = sizeof(prof_stats_t);
|
|
malloc_snprintf(
|
|
ctl_name, sizeof(ctl_name), "%s.accum", ctl_prefix);
|
|
expect_d_eq(mallctl(ctl_name, &accum, &sz, NULL, 0), 0,
|
|
"mallctl failed for %s", ctl_name);
|
|
expect_json_uint_eq(object, "prof_live_requested", live.req_sum,
|
|
ctl_prefix);
|
|
expect_json_uint_eq(
|
|
object, "prof_live_count", live.count, ctl_prefix);
|
|
expect_json_uint_eq(object, "prof_accum_requested", accum.req_sum,
|
|
ctl_prefix);
|
|
expect_json_uint_eq(
|
|
object, "prof_accum_count", accum.count, ctl_prefix);
|
|
}
|
|
|
|
TEST_BEGIN(test_json_stats_arena_tables) {
|
|
test_skip_if(!config_stats);
|
|
|
|
stats_buf_t sbuf;
|
|
json_fragment_t stats, merged;
|
|
bool malformed = stats_json_print(&sbuf, &stats, &merged);
|
|
expect_false(malformed, "Could not find runtime stats in JSON output");
|
|
if (malformed) {
|
|
stats_buf_fini(&sbuf);
|
|
return;
|
|
}
|
|
|
|
unsigned nbins, nlextents;
|
|
size_t sz = sizeof(nbins);
|
|
expect_d_eq(mallctl("arenas.nbins", &nbins, &sz, NULL, 0), 0,
|
|
"mallctl failed for arenas.nbins");
|
|
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),
|
|
"merged arena stats are missing bins");
|
|
expect_zu_eq(json_array_size(bins), nbins,
|
|
"bins has an unexpected number of rows");
|
|
json_fragment_t first_bin, ignored;
|
|
expect_false(json_array_element(bins, 0, &first_bin),
|
|
"bins is missing its first row");
|
|
bool prof_stats_present = !json_object_member(
|
|
first_bin, "prof_live_requested", &ignored);
|
|
for (unsigned i = 0; i < nbins; i++) {
|
|
json_fragment_t bin;
|
|
expect_false(json_array_element(bins, i, &bin),
|
|
"bins is missing row %u", i);
|
|
char row_name[64];
|
|
malloc_snprintf(row_name, sizeof(row_name), "bins[%u]", i);
|
|
expect_zu_eq(json_object_size(bin), ARRAY_COUNT(bin_keys)
|
|
+ (prof_stats_present ? ARRAY_COUNT(prof_keys) : 0),
|
|
"%s has an unexpected number of keys", row_name);
|
|
expect_json_object_has_keys(
|
|
bin, bin_keys, ARRAY_COUNT(bin_keys), row_name);
|
|
char ctl_prefix[128];
|
|
malloc_snprintf(ctl_prefix, sizeof(ctl_prefix),
|
|
"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);
|
|
char prof_prefix[128];
|
|
malloc_snprintf(prof_prefix, sizeof(prof_prefix),
|
|
"prof.stats.bins.%u", i);
|
|
expect_prof_stats(bin, prof_prefix);
|
|
}
|
|
|
|
json_fragment_t mutex;
|
|
expect_false(json_object_member(bin, "mutex", &mutex),
|
|
"%s is missing mutex", row_name);
|
|
char mutex_prefix[160];
|
|
malloc_snprintf(mutex_prefix, sizeof(mutex_prefix), "%s.mutex",
|
|
ctl_prefix);
|
|
expect_mutex_object(mutex, mutex_prefix);
|
|
}
|
|
|
|
json_fragment_t lextents;
|
|
expect_false(json_object_member(merged, "lextents", &lextents),
|
|
"merged arena stats are missing lextents");
|
|
expect_zu_eq(json_array_size(lextents), nlextents,
|
|
"lextents has an unexpected number of rows");
|
|
for (unsigned i = 0; i < nlextents; i++) {
|
|
json_fragment_t lextent;
|
|
expect_false(json_array_element(lextents, i, &lextent),
|
|
"lextents is missing row %u", i);
|
|
char row_name[64];
|
|
malloc_snprintf(row_name, sizeof(row_name), "lextents[%u]", i);
|
|
expect_zu_eq(json_object_size(lextent), ARRAY_COUNT(lextent_keys)
|
|
+ (prof_stats_present ? ARRAY_COUNT(prof_keys) : 0),
|
|
"%s has an unexpected number of keys", row_name);
|
|
expect_json_object_has_keys(lextent, lextent_keys,
|
|
ARRAY_COUNT(lextent_keys), row_name);
|
|
char ctl_prefix[128];
|
|
malloc_snprintf(ctl_prefix, sizeof(ctl_prefix),
|
|
"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);
|
|
char prof_prefix[128];
|
|
malloc_snprintf(prof_prefix, sizeof(prof_prefix),
|
|
"prof.stats.lextents.%u", i);
|
|
expect_prof_stats(lextent, prof_prefix);
|
|
}
|
|
}
|
|
|
|
json_fragment_t extents;
|
|
expect_false(json_object_member(merged, "extents", &extents),
|
|
"merged arena stats are missing extents");
|
|
expect_zu_eq(json_array_size(extents), SC_NPSIZES,
|
|
"extents has an unexpected number of rows");
|
|
for (unsigned i = 0; i < SC_NPSIZES; i++) {
|
|
json_fragment_t extent;
|
|
expect_false(json_array_element(extents, i, &extent),
|
|
"extents is missing row %u", i);
|
|
char row_name[64];
|
|
malloc_snprintf(row_name, sizeof(row_name), "extents[%u]", i);
|
|
expect_json_object_keys(
|
|
extent, extent_keys, ARRAY_COUNT(extent_keys), row_name);
|
|
char ctl_prefix[128];
|
|
malloc_snprintf(ctl_prefix, sizeof(ctl_prefix),
|
|
"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);
|
|
}
|
|
TEST_END
|
|
|
|
int
|
|
main(void) {
|
|
return test_no_reentrancy(test_json_stats_global_and_arena,
|
|
test_json_stats_arena_tables, test_json_stats_hpa,
|
|
test_json_stats_arenas_and_options);
|
|
}
|