Home | History | Annotate | Line # | Download | only in unit
      1 #include "test/jemalloc_test.h"
      2 
      3 typedef struct {
      4 	char  *buf;
      5 	size_t len;
      6 	size_t capacity;
      7 } stats_buf_t;
      8 
      9 static void
     10 stats_buf_init(stats_buf_t *sbuf) {
     11 	/* 1MB buffer should be enough since per-arena stats are omitted. */
     12 	sbuf->capacity = 1 << 20;
     13 	sbuf->buf = mallocx(sbuf->capacity, MALLOCX_TCACHE_NONE);
     14 	assert_ptr_not_null(sbuf->buf, "Failed to allocate stats buffer");
     15 	sbuf->len = 0;
     16 	sbuf->buf[0] = '\0';
     17 }
     18 
     19 static void
     20 stats_buf_fini(stats_buf_t *sbuf) {
     21 	dallocx(sbuf->buf, MALLOCX_TCACHE_NONE);
     22 }
     23 
     24 static void
     25 stats_buf_write_cb(void *opaque, const char *str) {
     26 	stats_buf_t *sbuf = (stats_buf_t *)opaque;
     27 	size_t       slen = strlen(str);
     28 
     29 	if (sbuf->len + slen + 1 > sbuf->capacity) {
     30 		return;
     31 	}
     32 	memcpy(&sbuf->buf[sbuf->len], str, slen + 1);
     33 	sbuf->len += slen;
     34 }
     35 
     36 static bool
     37 json_extract_uint64(const char *json, const char *key, uint64_t *result) {
     38 	char   search_key[128];
     39 	size_t key_len;
     40 
     41 	key_len = snprintf(search_key, sizeof(search_key), "\"%s\":", key);
     42 	if (key_len >= sizeof(search_key)) {
     43 		return true;
     44 	}
     45 
     46 	const char *pos = strstr(json, search_key);
     47 	if (pos == NULL) {
     48 		return true;
     49 	}
     50 
     51 	pos += key_len;
     52 	while (*pos == ' ' || *pos == '\t' || *pos == '\n') {
     53 		pos++;
     54 	}
     55 
     56 	char    *endptr;
     57 	uint64_t value = strtoull(pos, &endptr, 10);
     58 	if (endptr == pos) {
     59 		return true;
     60 	}
     61 
     62 	*result = value;
     63 	return false;
     64 }
     65 
     66 static const char *
     67 json_find_section(const char *json, const char *section_name) {
     68 	char   search_pattern[128];
     69 	size_t pattern_len;
     70 
     71 	pattern_len = snprintf(
     72 	    search_pattern, sizeof(search_pattern), "\"%s\":", section_name);
     73 	if (pattern_len >= sizeof(search_pattern)) {
     74 		return NULL;
     75 	}
     76 
     77 	return strstr(json, search_pattern);
     78 }
     79 
     80 static void
     81 verify_mutex_json(const char *mutexes_section, const char *mallctl_prefix,
     82     const char *mutex_name) {
     83 	char   mallctl_path[128];
     84 	size_t sz;
     85 
     86 	const char *mutex_section = json_find_section(
     87 	    mutexes_section, mutex_name);
     88 	expect_ptr_not_null(mutex_section,
     89 	    "Could not find %s mutex section in JSON", mutex_name);
     90 
     91 	uint64_t ctl_num_ops, ctl_num_wait, ctl_num_spin_acq;
     92 	uint64_t ctl_num_owner_switch, ctl_total_wait_time, ctl_max_wait_time;
     93 	uint32_t ctl_max_num_thds;
     94 
     95 	sz = sizeof(uint64_t);
     96 	snprintf(mallctl_path, sizeof(mallctl_path), "%s.%s.num_ops",
     97 	    mallctl_prefix, mutex_name);
     98 	expect_d_eq(mallctl(mallctl_path, &ctl_num_ops, &sz, NULL, 0), 0,
     99 	    "Unexpected mallctl() failure for %s", mallctl_path);
    100 
    101 	snprintf(mallctl_path, sizeof(mallctl_path), "%s.%s.num_wait",
    102 	    mallctl_prefix, mutex_name);
    103 	expect_d_eq(mallctl(mallctl_path, &ctl_num_wait, &sz, NULL, 0), 0,
    104 	    "Unexpected mallctl() failure for %s", mallctl_path);
    105 
    106 	snprintf(mallctl_path, sizeof(mallctl_path), "%s.%s.num_spin_acq",
    107 	    mallctl_prefix, mutex_name);
    108 	expect_d_eq(mallctl(mallctl_path, &ctl_num_spin_acq, &sz, NULL, 0), 0,
    109 	    "Unexpected mallctl() failure for %s", mallctl_path);
    110 
    111 	snprintf(mallctl_path, sizeof(mallctl_path), "%s.%s.num_owner_switch",
    112 	    mallctl_prefix, mutex_name);
    113 	expect_d_eq(mallctl(mallctl_path, &ctl_num_owner_switch, &sz, NULL, 0),
    114 	    0, "Unexpected mallctl() failure for %s", mallctl_path);
    115 
    116 	snprintf(mallctl_path, sizeof(mallctl_path), "%s.%s.total_wait_time",
    117 	    mallctl_prefix, mutex_name);
    118 	expect_d_eq(mallctl(mallctl_path, &ctl_total_wait_time, &sz, NULL, 0),
    119 	    0, "Unexpected mallctl() failure for %s", mallctl_path);
    120 
    121 	snprintf(mallctl_path, sizeof(mallctl_path), "%s.%s.max_wait_time",
    122 	    mallctl_prefix, mutex_name);
    123 	expect_d_eq(mallctl(mallctl_path, &ctl_max_wait_time, &sz, NULL, 0), 0,
    124 	    "Unexpected mallctl() failure for %s", mallctl_path);
    125 
    126 	sz = sizeof(uint32_t);
    127 	snprintf(mallctl_path, sizeof(mallctl_path), "%s.%s.max_num_thds",
    128 	    mallctl_prefix, mutex_name);
    129 	expect_d_eq(mallctl(mallctl_path, &ctl_max_num_thds, &sz, NULL, 0), 0,
    130 	    "Unexpected mallctl() failure for %s", mallctl_path);
    131 
    132 	uint64_t json_num_ops, json_num_wait, json_num_spin_acq;
    133 	uint64_t json_num_owner_switch, json_total_wait_time,
    134 	    json_max_wait_time;
    135 	uint64_t json_max_num_thds;
    136 
    137 	expect_false(
    138 	    json_extract_uint64(mutex_section, "num_ops", &json_num_ops),
    139 	    "%s: num_ops not found in JSON", mutex_name);
    140 	expect_false(
    141 	    json_extract_uint64(mutex_section, "num_wait", &json_num_wait),
    142 	    "%s: num_wait not found in JSON", mutex_name);
    143 	expect_false(json_extract_uint64(
    144 	                 mutex_section, "num_spin_acq", &json_num_spin_acq),
    145 	    "%s: num_spin_acq not found in JSON", mutex_name);
    146 	expect_false(json_extract_uint64(mutex_section, "num_owner_switch",
    147 	                 &json_num_owner_switch),
    148 	    "%s: num_owner_switch not found in JSON", mutex_name);
    149 	expect_false(json_extract_uint64(mutex_section, "total_wait_time",
    150 	                 &json_total_wait_time),
    151 	    "%s: total_wait_time not found in JSON", mutex_name);
    152 	expect_false(json_extract_uint64(
    153 	                 mutex_section, "max_wait_time", &json_max_wait_time),
    154 	    "%s: max_wait_time not found in JSON", mutex_name);
    155 	expect_false(json_extract_uint64(
    156 	                 mutex_section, "max_num_thds", &json_max_num_thds),
    157 	    "%s: max_num_thds not found in JSON", mutex_name);
    158 
    159 	expect_u64_eq(json_num_ops, ctl_num_ops,
    160 	    "%s: JSON num_ops doesn't match mallctl", mutex_name);
    161 	expect_u64_eq(json_num_wait, ctl_num_wait,
    162 	    "%s: JSON num_wait doesn't match mallctl", mutex_name);
    163 	expect_u64_eq(json_num_spin_acq, ctl_num_spin_acq,
    164 	    "%s: JSON num_spin_acq doesn't match mallctl", mutex_name);
    165 	expect_u64_eq(json_num_owner_switch, ctl_num_owner_switch,
    166 	    "%s: JSON num_owner_switch doesn't match mallctl", mutex_name);
    167 	expect_u64_eq(json_total_wait_time, ctl_total_wait_time,
    168 	    "%s: JSON total_wait_time doesn't match mallctl", mutex_name);
    169 	expect_u64_eq(json_max_wait_time, ctl_max_wait_time,
    170 	    "%s: JSON max_wait_time doesn't match mallctl", mutex_name);
    171 	expect_u32_eq((uint32_t)json_max_num_thds, ctl_max_num_thds,
    172 	    "%s: JSON max_num_thds doesn't match mallctl", mutex_name);
    173 }
    174 
    175 static const char  *global_mutex_names[] = {"background_thread",
    176      "max_per_bg_thd", "ctl", "prof", "prof_thds_data", "prof_dump",
    177      "prof_recent_alloc", "prof_recent_dump", "prof_stats"};
    178 static const size_t num_global_mutexes = sizeof(global_mutex_names)
    179     / sizeof(global_mutex_names[0]);
    180 
    181 static const char  *arena_mutex_names[] = {"large", "extent_avail",
    182      "extents_dirty", "extents_muzzy", "extents_retained", "decay_dirty",
    183      "decay_muzzy", "base", "tcache_list", "hpa_shard", "hpa_shard_grow",
    184      "hpa_sec"};
    185 static const size_t num_arena_mutexes = sizeof(arena_mutex_names)
    186     / sizeof(arena_mutex_names[0]);
    187 
    188 static const char *
    189 json_find_object_end(const char *object_begin) {
    190 	int depth = 0;
    191 	for (const char *cur = object_begin; *cur != '\0'; cur++) {
    192 		if (*cur == '{') {
    193 			depth++;
    194 		} else if (*cur == '}') {
    195 			depth--;
    196 			if (depth == 0) {
    197 				return cur;
    198 			}
    199 			if (depth < 0) {
    200 				return NULL;
    201 			}
    202 		}
    203 	}
    204 	return NULL;
    205 }
    206 
    207 static const char *
    208 json_find_array_end(const char *array_begin) {
    209 	int depth = 0;
    210 	for (const char *cur = array_begin; *cur != '\0'; cur++) {
    211 		if (*cur == '[') {
    212 			depth++;
    213 		} else if (*cur == ']') {
    214 			depth--;
    215 			if (depth == 0) {
    216 				return cur;
    217 			}
    218 			if (depth < 0) {
    219 				return NULL;
    220 			}
    221 		}
    222 	}
    223 	return NULL;
    224 }
    225 
    226 static const char *
    227 json_find_previous_hpa_shard_object(
    228     const char *json, const char *pos, const char **object_end) {
    229 	*object_end = NULL;
    230 	const char *found = NULL;
    231 	const char *cur = json;
    232 	const char *next;
    233 
    234 	while ((next = strstr(cur, "\"hpa_shard\":{")) != NULL && next < pos) {
    235 		found = strchr(next, '{');
    236 		cur = next + 1;
    237 	}
    238 	if (found == NULL) {
    239 		return NULL;
    240 	}
    241 	*object_end = json_find_object_end(found);
    242 	return found;
    243 }
    244 
    245 static const char *
    246 json_find_named_object(
    247     const char *json, const char *key, const char **object_end) {
    248 	*object_end = NULL;
    249 	char   search_key[128];
    250 	size_t written = malloc_snprintf(
    251 	    search_key, sizeof(search_key), "\"%s\":{", key);
    252 	if (written >= sizeof(search_key)) {
    253 		return NULL;
    254 	}
    255 
    256 	const char *object_begin = strstr(json, search_key);
    257 	if (object_begin == NULL) {
    258 		return NULL;
    259 	}
    260 	object_begin = strchr(object_begin, '{');
    261 	if (object_begin == NULL) {
    262 		return NULL;
    263 	}
    264 	*object_end = json_find_object_end(object_begin);
    265 	return object_begin;
    266 }
    267 
    268 static const char *
    269 json_find_named_array(
    270     const char *json, const char *key, const char **array_end) {
    271 	*array_end = NULL;
    272 	char   search_key[128];
    273 	size_t written = malloc_snprintf(
    274 	    search_key, sizeof(search_key), "\"%s\":[", key);
    275 	if (written >= sizeof(search_key)) {
    276 		return NULL;
    277 	}
    278 
    279 	const char *array_begin = strstr(json, search_key);
    280 	if (array_begin == NULL) {
    281 		return NULL;
    282 	}
    283 	array_begin = strchr(array_begin, '[');
    284 	if (array_begin == NULL) {
    285 		return NULL;
    286 	}
    287 	*array_end = json_find_array_end(array_begin);
    288 	return array_begin;
    289 }
    290 
    291 TEST_BEGIN(test_json_stats_mutexes) {
    292 	test_skip_if(!config_stats);
    293 
    294 	uint64_t epoch;
    295 	expect_d_eq(mallctl("epoch", NULL, NULL, (void *)&epoch, sizeof(epoch)),
    296 	    0, "Unexpected mallctl() failure");
    297 
    298 	stats_buf_t sbuf;
    299 	stats_buf_init(&sbuf);
    300 	/* "J" for JSON format, "a" to omit per-arena stats. */
    301 	malloc_stats_print(stats_buf_write_cb, &sbuf, "Ja");
    302 
    303 	/* Verify global mutexes under stats.mutexes. */
    304 	const char *global_mutexes_section = json_find_section(
    305 	    sbuf.buf, "mutexes");
    306 	expect_ptr_not_null(global_mutexes_section,
    307 	    "Could not find global mutexes section in JSON output");
    308 
    309 	for (size_t i = 0; i < num_global_mutexes; i++) {
    310 		verify_mutex_json(global_mutexes_section, "stats.mutexes",
    311 		    global_mutex_names[i]);
    312 	}
    313 
    314 	/* Verify arena mutexes under stats.arenas.merged.mutexes. */
    315 	const char *arenas_section = json_find_section(
    316 	    sbuf.buf, "stats.arenas");
    317 	expect_ptr_not_null(arenas_section,
    318 	    "Could not find stats.arenas section in JSON output");
    319 
    320 	const char *merged_section = json_find_section(
    321 	    arenas_section, "merged");
    322 	expect_ptr_not_null(
    323 	    merged_section, "Could not find merged section in JSON output");
    324 
    325 	const char *arena_mutexes_section = json_find_section(
    326 	    merged_section, "mutexes");
    327 	expect_ptr_not_null(arena_mutexes_section,
    328 	    "Could not find arena mutexes section in JSON output");
    329 
    330 	for (size_t i = 0; i < num_arena_mutexes; i++) {
    331 		/*
    332 		 * MALLCTL_ARENAS_ALL is 4096 representing all arenas in
    333 		 * mallctl queries.
    334 		 */
    335 		verify_mutex_json(arena_mutexes_section,
    336 		    "stats.arenas.4096.mutexes", arena_mutex_names[i]);
    337 	}
    338 
    339 	stats_buf_fini(&sbuf);
    340 }
    341 TEST_END
    342 
    343 /*
    344  * Verify that hpa_shard JSON stats contain "ndirty_huge" key in both
    345  * full_slabs and empty_slabs sections.  A previous bug emitted duplicate
    346  * "nactive_huge" instead of "ndirty_huge".
    347  */
    348 TEST_BEGIN(test_hpa_shard_json_ndirty_huge) {
    349 	test_skip_if(!config_stats);
    350 	test_skip_if(!hpa_supported());
    351 
    352 	/* Do some allocation to create HPA state. */
    353 	void *p = mallocx(PAGE, MALLOCX_TCACHE_NONE);
    354 	expect_ptr_not_null(p, "Unexpected mallocx failure");
    355 
    356 	uint64_t epoch = 1;
    357 	size_t   sz = sizeof(epoch);
    358 	expect_d_eq(mallctl("epoch", NULL, NULL, (void *)&epoch, sz), 0,
    359 	    "Unexpected mallctl() failure");
    360 
    361 	stats_buf_t sbuf;
    362 	stats_buf_init(&sbuf);
    363 	/* "J" for JSON, include per-arena HPA stats. */
    364 	malloc_stats_print(stats_buf_write_cb, &sbuf, "J");
    365 
    366 	/*
    367 	 * Find "full_slabs" and check it contains "ndirty_huge".
    368 	 */
    369 	const char *full_slabs = strstr(sbuf.buf, "\"full_slabs\"");
    370 	if (full_slabs != NULL) {
    371 		const char *empty_slabs = strstr(full_slabs, "\"empty_slabs\"");
    372 		const char *search_end = empty_slabs != NULL
    373 		    ? empty_slabs
    374 		    : sbuf.buf + sbuf.len;
    375 		/*
    376 		 * Search for "ndirty_huge" between full_slabs and
    377 		 * empty_slabs.
    378 		 */
    379 		const char *ndirty = full_slabs;
    380 		bool        found = false;
    381 		while (ndirty < search_end) {
    382 			ndirty = strstr(ndirty, "\"ndirty_huge\"");
    383 			if (ndirty != NULL && ndirty < search_end) {
    384 				found = true;
    385 				break;
    386 			}
    387 			break;
    388 		}
    389 		expect_true(
    390 		    found, "full_slabs section should contain ndirty_huge key");
    391 	}
    392 
    393 	/*
    394 	 * Find "empty_slabs" and check it contains "ndirty_huge".
    395 	 */
    396 	const char *empty_slabs = strstr(sbuf.buf, "\"empty_slabs\"");
    397 	if (empty_slabs != NULL) {
    398 		/* Find the end of the empty_slabs object. */
    399 		const char *nonfull = strstr(empty_slabs, "\"nonfull_slabs\"");
    400 		const char *search_end = nonfull != NULL ? nonfull
    401 		                                         : sbuf.buf + sbuf.len;
    402 		const char *ndirty = strstr(empty_slabs, "\"ndirty_huge\"");
    403 		bool        found = (ndirty != NULL && ndirty < search_end);
    404 		expect_true(found,
    405 		    "empty_slabs section should contain ndirty_huge key");
    406 	}
    407 
    408 	stats_buf_fini(&sbuf);
    409 	dallocx(p, MALLOCX_TCACHE_NONE);
    410 }
    411 TEST_END
    412 
    413 TEST_BEGIN(test_hpa_shard_json_contains_sec_stats) {
    414 	test_skip_if(!config_stats);
    415 	test_skip_if(!hpa_supported());
    416 
    417 	void *p = mallocx(PAGE, MALLOCX_TCACHE_NONE);
    418 	expect_ptr_not_null(p, "Unexpected mallocx failure");
    419 
    420 	uint64_t epoch = 1;
    421 	size_t   sz = sizeof(epoch);
    422 	expect_d_eq(mallctl("epoch", NULL, NULL, (void *)&epoch, sz), 0,
    423 	    "Unexpected mallctl() failure");
    424 
    425 	stats_buf_t sbuf;
    426 	stats_buf_init(&sbuf);
    427 	malloc_stats_print(stats_buf_write_cb, &sbuf, "J");
    428 
    429 	const char *sec_bytes = strstr(sbuf.buf, "\"sec_bytes\"");
    430 	expect_ptr_not_null(sec_bytes, "JSON output should contain sec_bytes");
    431 	const char *hpa_shard_end = NULL;
    432 	const char *hpa_shard = json_find_previous_hpa_shard_object(
    433 	    sbuf.buf, sec_bytes, &hpa_shard_end);
    434 	expect_ptr_not_null(hpa_shard,
    435 	    "sec_bytes should be associated with an hpa_shard JSON object");
    436 	expect_ptr_not_null(hpa_shard_end,
    437 	    "Could not find end of enclosing hpa_shard JSON object");
    438 	expect_true(sec_bytes != NULL && sec_bytes < hpa_shard_end,
    439 	    "sec_bytes should be nested inside hpa_shard JSON object");
    440 	const char *sec_hits = strstr(hpa_shard, "\"sec_hits\"");
    441 	expect_true(sec_hits != NULL && sec_hits < hpa_shard_end,
    442 	    "sec_hits should be nested inside hpa_shard JSON object");
    443 	const char *sec_misses = strstr(hpa_shard, "\"sec_misses\"");
    444 	expect_true(sec_misses != NULL && sec_misses < hpa_shard_end,
    445 	    "sec_misses should be nested inside hpa_shard JSON object");
    446 
    447 	stats_buf_fini(&sbuf);
    448 	dallocx(p, MALLOCX_TCACHE_NONE);
    449 }
    450 TEST_END
    451 
    452 TEST_BEGIN(test_hpa_shard_json_contains_retained_stats) {
    453 	test_skip_if(!config_stats);
    454 	test_skip_if(!hpa_supported());
    455 
    456 	void *p = mallocx(PAGE, MALLOCX_TCACHE_NONE);
    457 	expect_ptr_not_null(p, "Unexpected mallocx failure");
    458 
    459 	uint64_t epoch = 1;
    460 	size_t   sz = sizeof(epoch);
    461 	expect_d_eq(mallctl("epoch", NULL, NULL, (void *)&epoch, sz), 0,
    462 	    "Unexpected mallctl() failure");
    463 
    464 	stats_buf_t sbuf;
    465 	stats_buf_init(&sbuf);
    466 	malloc_stats_print(stats_buf_write_cb, &sbuf, "J");
    467 
    468 	const char *full_slabs_end = NULL;
    469 	const char *full_slabs = json_find_named_object(
    470 	    sbuf.buf, "full_slabs", &full_slabs_end);
    471 	expect_ptr_not_null(
    472 	    full_slabs, "JSON output should contain full_slabs");
    473 	const char *full_retained = strstr(full_slabs, "\"nretained_nonhuge\"");
    474 	expect_true(full_retained != NULL && full_retained < full_slabs_end,
    475 	    "full_slabs should contain nretained_nonhuge");
    476 
    477 	const char *empty_slabs_end = NULL;
    478 	const char *empty_slabs = json_find_named_object(
    479 	    sbuf.buf, "empty_slabs", &empty_slabs_end);
    480 	expect_ptr_not_null(
    481 	    empty_slabs, "JSON output should contain empty_slabs");
    482 	const char *empty_retained = strstr(
    483 	    empty_slabs, "\"nretained_nonhuge\"");
    484 	expect_true(empty_retained != NULL && empty_retained < empty_slabs_end,
    485 	    "empty_slabs should contain nretained_nonhuge");
    486 
    487 	const char *nonfull_slabs_end = NULL;
    488 	const char *nonfull_slabs = json_find_named_array(
    489 	    sbuf.buf, "nonfull_slabs", &nonfull_slabs_end);
    490 	expect_ptr_not_null(
    491 	    nonfull_slabs, "JSON output should contain nonfull_slabs");
    492 	const char *nonfull_retained = strstr(
    493 	    nonfull_slabs, "\"nretained_nonhuge\"");
    494 	expect_true(
    495 	    nonfull_retained != NULL && nonfull_retained < nonfull_slabs_end,
    496 	    "nonfull_slabs should contain nretained_nonhuge");
    497 
    498 	stats_buf_fini(&sbuf);
    499 	dallocx(p, MALLOCX_TCACHE_NONE);
    500 }
    501 TEST_END
    502 
    503 int
    504 main(void) {
    505 	return test_no_reentrancy(test_json_stats_mutexes,
    506 	    test_hpa_shard_json_ndirty_huge,
    507 	    test_hpa_shard_json_contains_sec_stats,
    508 	    test_hpa_shard_json_contains_retained_stats);
    509 }
    510