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