1 /* $NetBSD: mem_test.c,v 1.3 2025/01/26 16:25:49 christos Exp $ */ 2 3 /* 4 * Copyright (C) Internet Systems Consortium, Inc. ("ISC") 5 * 6 * SPDX-License-Identifier: MPL-2.0 7 * 8 * This Source Code Form is subject to the terms of the Mozilla Public 9 * License, v. 2.0. If a copy of the MPL was not distributed with this 10 * file, you can obtain one at https://mozilla.org/MPL/2.0/. 11 * 12 * See the COPYRIGHT file distributed with this work for additional 13 * information regarding copyright ownership. 14 */ 15 16 #include <fcntl.h> 17 #include <inttypes.h> 18 #include <sched.h> /* IWYU pragma: keep */ 19 #include <setjmp.h> 20 #include <stdarg.h> 21 #include <stddef.h> 22 #include <stdlib.h> 23 #include <unistd.h> 24 25 #define UNIT_TESTING 26 #include <cmocka.h> 27 28 #include <isc/atomic.h> 29 #include <isc/file.h> 30 #include <isc/mem.h> 31 #include <isc/mutex.h> 32 #include <isc/os.h> 33 #include <isc/random.h> 34 #include <isc/result.h> 35 #include <isc/stdio.h> 36 #include <isc/thread.h> 37 #include <isc/time.h> 38 #include <isc/util.h> 39 40 #include "mem_p.h" 41 42 #include <tests/isc.h> 43 44 #define MP1_FREEMAX 10 45 #define MP1_FILLCNT 10 46 #define MP1_MAXALLOC 30 47 48 #define MP2_FREEMAX 25 49 #define MP2_FILLCNT 25 50 51 /* general memory system tests */ 52 ISC_RUN_TEST_IMPL(isc_mem_get) { 53 void *items1[50]; 54 void *items2[50]; 55 void *tmp; 56 isc_mempool_t *mp1 = NULL, *mp2 = NULL; 57 unsigned int i, j; 58 int rval; 59 60 isc_mempool_create(mctx, 24, &mp1); 61 isc_mempool_create(mctx, 31, &mp2); 62 63 isc_mempool_setfreemax(mp1, MP1_FREEMAX); 64 isc_mempool_setfillcount(mp1, MP1_FILLCNT); 65 66 /* 67 * Allocate MP1_MAXALLOC items from the pool. This is our max. 68 */ 69 for (i = 0; i < MP1_MAXALLOC; i++) { 70 items1[i] = isc_mempool_get(mp1); 71 assert_non_null(items1[i]); 72 } 73 74 /* 75 * Free the first 11 items. Verify that there are 10 free items on 76 * the free list (which is our max). 77 */ 78 for (i = 0; i < 11; i++) { 79 isc_mempool_put(mp1, items1[i]); 80 items1[i] = NULL; 81 } 82 83 #if !__SANITIZE_ADDRESS__ 84 rval = isc_mempool_getfreecount(mp1); 85 assert_int_equal(rval, 10); 86 #endif /* !__SANITIZE_ADDRESS__ */ 87 88 rval = isc_mempool_getallocated(mp1); 89 assert_int_equal(rval, 19); 90 91 /* 92 * Now, beat up on mp2 for a while. Allocate 50 items, then free 93 * them, then allocate 50 more, etc. 94 */ 95 96 isc_mempool_setfreemax(mp2, 25); 97 isc_mempool_setfillcount(mp2, 25); 98 99 for (j = 0; j < 500000; j++) { 100 for (i = 0; i < 50; i++) { 101 items2[i] = isc_mempool_get(mp2); 102 assert_non_null(items2[i]); 103 } 104 for (i = 0; i < 50; i++) { 105 isc_mempool_put(mp2, items2[i]); 106 items2[i] = NULL; 107 } 108 } 109 110 /* 111 * Free all the other items and blow away this pool. 112 */ 113 for (i = 11; i < MP1_MAXALLOC; i++) { 114 isc_mempool_put(mp1, items1[i]); 115 items1[i] = NULL; 116 } 117 118 isc_mempool_destroy(&mp1); 119 isc_mempool_destroy(&mp2); 120 121 isc_mempool_create(mctx, 2, &mp1); 122 123 tmp = isc_mempool_get(mp1); 124 assert_non_null(tmp); 125 126 isc_mempool_put(mp1, tmp); 127 128 isc_mempool_destroy(&mp1); 129 } 130 131 /* zeroed memory system tests */ 132 ISC_RUN_TEST_IMPL(isc_mem_cget_zero) { 133 uint8_t *ptr; 134 bool zeroed; 135 uint8_t expected[4096] = { 0 }; 136 137 /* Skip the test if the memory is zeroed even in normal case */ 138 zeroed = true; 139 ptr = isc_mem_get(mctx, sizeof(expected)); 140 for (size_t i = 0; i < sizeof(expected); i++) { 141 if (ptr[i] != expected[i]) { 142 zeroed = false; 143 break; 144 } 145 } 146 isc_mem_put(mctx, ptr, sizeof(expected)); 147 if (zeroed) { 148 skip(); 149 return; 150 } 151 152 ptr = isc_mem_cget(mctx, 1, sizeof(expected)); 153 assert_memory_equal(ptr, expected, sizeof(expected)); 154 isc_mem_put(mctx, ptr, sizeof(expected)); 155 } 156 157 ISC_RUN_TEST_IMPL(isc_mem_callocate_zero) { 158 uint8_t *ptr; 159 bool zeroed; 160 uint8_t expected[4096] = { 0 }; 161 162 /* Skip the test if the memory is zeroed even in normal case */ 163 zeroed = true; 164 ptr = isc_mem_get(mctx, sizeof(expected)); 165 for (size_t i = 0; i < sizeof(expected); i++) { 166 if (ptr[i] != expected[i]) { 167 zeroed = false; 168 break; 169 } 170 } 171 isc_mem_put(mctx, ptr, sizeof(expected)); 172 if (zeroed) { 173 skip(); 174 return; 175 } 176 177 ptr = isc_mem_callocate(mctx, 1, sizeof(expected)); 178 assert_memory_equal(ptr, expected, sizeof(expected)); 179 isc_mem_free(mctx, ptr); 180 } 181 182 /* test InUse calculation */ 183 ISC_RUN_TEST_IMPL(isc_mem_inuse) { 184 isc_mem_t *mctx2 = NULL; 185 size_t before, after; 186 ssize_t diff; 187 void *ptr; 188 189 mctx2 = NULL; 190 isc_mem_create(&mctx2); 191 192 before = isc_mem_inuse(mctx2); 193 ptr = isc_mem_allocate(mctx2, 1024000); 194 isc_mem_free(mctx2, ptr); 195 after = isc_mem_inuse(mctx2); 196 197 diff = after - before; 198 199 assert_int_equal(diff, 0); 200 201 isc_mem_destroy(&mctx2); 202 } 203 204 ISC_RUN_TEST_IMPL(isc_mem_zeroget) { 205 uint8_t *data = NULL; 206 207 data = isc_mem_get(mctx, 0); 208 assert_non_null(data); 209 isc_mem_put(mctx, data, 0); 210 } 211 212 #define REGET_INIT_SIZE 1024 213 #define REGET_GROW_SIZE 2048 214 #define REGET_SHRINK_SIZE 512 215 216 ISC_RUN_TEST_IMPL(isc_mem_reget) { 217 uint8_t *data = NULL; 218 219 /* test that we can reget NULL */ 220 data = isc_mem_reget(mctx, NULL, 0, REGET_INIT_SIZE); 221 assert_non_null(data); 222 isc_mem_put(mctx, data, REGET_INIT_SIZE); 223 224 /* test that we can re-get a zero-length allocation */ 225 data = isc_mem_get(mctx, 0); 226 assert_non_null(data); 227 228 data = isc_mem_reget(mctx, data, 0, REGET_INIT_SIZE); 229 assert_non_null(data); 230 231 for (size_t i = 0; i < REGET_INIT_SIZE; i++) { 232 data[i] = i % UINT8_MAX; 233 } 234 235 data = isc_mem_reget(mctx, data, REGET_INIT_SIZE, REGET_GROW_SIZE); 236 assert_non_null(data); 237 238 for (size_t i = 0; i < REGET_INIT_SIZE; i++) { 239 assert_int_equal(data[i], i % UINT8_MAX); 240 } 241 242 for (size_t i = REGET_GROW_SIZE; i > 0; i--) { 243 data[i - 1] = i % UINT8_MAX; 244 } 245 246 data = isc_mem_reget(mctx, data, REGET_GROW_SIZE, REGET_SHRINK_SIZE); 247 assert_non_null(data); 248 249 for (size_t i = REGET_SHRINK_SIZE; i > 0; i--) { 250 assert_int_equal(data[i - 1], i % UINT8_MAX); 251 } 252 253 isc_mem_put(mctx, data, REGET_SHRINK_SIZE); 254 } 255 256 ISC_RUN_TEST_IMPL(isc_mem_reallocate) { 257 uint8_t *data = NULL; 258 259 /* test that we can reallocate NULL */ 260 data = isc_mem_reallocate(mctx, NULL, REGET_INIT_SIZE); 261 assert_non_null(data); 262 isc_mem_free(mctx, data); 263 264 /* test that we can re-get a zero-length allocation */ 265 data = isc_mem_allocate(mctx, 0); 266 assert_non_null(data); 267 268 data = isc_mem_reallocate(mctx, data, REGET_INIT_SIZE); 269 assert_non_null(data); 270 271 for (size_t i = 0; i < REGET_INIT_SIZE; i++) { 272 data[i] = i % UINT8_MAX; 273 } 274 275 data = isc_mem_reallocate(mctx, data, REGET_GROW_SIZE); 276 assert_non_null(data); 277 278 for (size_t i = 0; i < REGET_INIT_SIZE; i++) { 279 assert_int_equal(data[i], i % UINT8_MAX); 280 } 281 282 for (size_t i = REGET_GROW_SIZE; i > 0; i--) { 283 data[i - 1] = i % UINT8_MAX; 284 } 285 286 data = isc_mem_reallocate(mctx, data, REGET_SHRINK_SIZE); 287 assert_non_null(data); 288 289 for (size_t i = REGET_SHRINK_SIZE; i > 0; i--) { 290 assert_int_equal(data[i - 1], i % UINT8_MAX); 291 } 292 293 isc_mem_free(mctx, data); 294 } 295 296 ISC_RUN_TEST_IMPL(isc_mem_overmem) { 297 isc_mem_t *omctx = NULL; 298 isc_mem_create(&omctx); 299 assert_non_null(omctx); 300 301 isc_mem_setwater(omctx, 1024, 512); 302 303 /* inuse < lo_water */ 304 void *data1 = isc_mem_allocate(omctx, 256); 305 assert_false(isc_mem_isovermem(omctx)); 306 307 /* lo_water < inuse < hi_water */ 308 void *data2 = isc_mem_allocate(omctx, 512); 309 assert_false(isc_mem_isovermem(omctx)); 310 311 /* hi_water < inuse */ 312 void *data3 = isc_mem_allocate(omctx, 512); 313 assert_true(isc_mem_isovermem(omctx)); 314 315 /* lo_water < inuse < hi_water */ 316 isc_mem_free(omctx, data2); 317 assert_true(isc_mem_isovermem(omctx)); 318 319 /* inuse < lo_water */ 320 isc_mem_free(omctx, data3); 321 assert_false(isc_mem_isovermem(omctx)); 322 323 /* inuse == 0 */ 324 isc_mem_free(omctx, data1); 325 assert_false(isc_mem_isovermem(omctx)); 326 327 isc_mem_destroy(&omctx); 328 } 329 330 #if ISC_MEM_TRACKLINES 331 332 /* test mem with no flags */ 333 ISC_RUN_TEST_IMPL(isc_mem_noflags) { 334 isc_result_t result; 335 isc_mem_t *mctx2 = NULL; 336 char buf[4096], *p; 337 FILE *f; 338 void *ptr; 339 340 result = isc_stdio_open("mem.output", "w", &f); 341 assert_int_equal(result, ISC_R_SUCCESS); 342 343 isc_mem_debugging = 0; 344 isc_mem_create(&mctx2); 345 ptr = isc_mem_get(mctx2, 2048); 346 assert_non_null(ptr); 347 isc__mem_printactive(mctx2, f); 348 isc_mem_put(mctx2, ptr, 2048); 349 isc_mem_destroy(&mctx2); 350 isc_mem_debugging = ISC_MEM_DEBUGRECORD; 351 isc_stdio_close(f); 352 353 memset(buf, 0, sizeof(buf)); 354 result = isc_stdio_open("mem.output", "r", &f); 355 assert_int_equal(result, ISC_R_SUCCESS); 356 result = isc_stdio_read(buf, sizeof(buf), 1, f, NULL); 357 assert_int_equal(result, ISC_R_EOF); 358 isc_stdio_close(f); 359 isc_file_remove("mem.output"); 360 361 buf[sizeof(buf) - 1] = 0; 362 363 p = strchr(buf, '\n'); 364 assert_null(p); 365 } 366 367 /* test mem with record flag */ 368 ISC_RUN_TEST_IMPL(isc_mem_recordflag) { 369 isc_result_t result; 370 isc_mem_t *mctx2 = NULL; 371 char buf[4096], *p; 372 FILE *f; 373 void *ptr; 374 375 result = isc_stdio_open("mem.output", "w", &f); 376 assert_int_equal(result, ISC_R_SUCCESS); 377 378 isc_mem_create(&mctx2); 379 ptr = isc_mem_get(mctx2, 2048); 380 assert_non_null(ptr); 381 isc__mem_printactive(mctx2, f); 382 isc_mem_put(mctx2, ptr, 2048); 383 isc_mem_destroy(&mctx2); 384 isc_stdio_close(f); 385 386 memset(buf, 0, sizeof(buf)); 387 result = isc_stdio_open("mem.output", "r", &f); 388 assert_int_equal(result, ISC_R_SUCCESS); 389 result = isc_stdio_read(buf, sizeof(buf), 1, f, NULL); 390 assert_int_equal(result, ISC_R_EOF); 391 isc_stdio_close(f); 392 isc_file_remove("mem.output"); 393 394 buf[sizeof(buf) - 1] = 0; 395 396 p = strchr(buf, '\n'); 397 assert_non_null(p); 398 assert_in_range(p, 0, buf + sizeof(buf) - 3); 399 assert_memory_equal(p + 2, "ptr ", 4); 400 p = strchr(p + 1, '\n'); 401 assert_non_null(p); 402 assert_int_equal(strlen(p), 1); 403 } 404 405 /* test mem with trace flag */ 406 ISC_RUN_TEST_IMPL(isc_mem_traceflag) { 407 isc_result_t result; 408 isc_mem_t *mctx2 = NULL; 409 char buf[4096], *p; 410 FILE *f; 411 void *ptr; 412 413 /* redirect stderr so we can check trace output */ 414 f = freopen("mem.output", "w", stderr); 415 assert_non_null(f); 416 417 isc_mem_debugging = ISC_MEM_DEBUGRECORD | ISC_MEM_DEBUGTRACE; 418 isc_mem_create(&mctx2); 419 ptr = isc_mem_get(mctx2, 2048); 420 assert_non_null(ptr); 421 isc__mem_printactive(mctx2, f); 422 isc_mem_put(mctx2, ptr, 2048); 423 isc_mem_destroy(&mctx2); 424 isc_mem_debugging = ISC_MEM_DEBUGRECORD; 425 isc_stdio_close(f); 426 427 memset(buf, 0, sizeof(buf)); 428 result = isc_stdio_open("mem.output", "r", &f); 429 assert_int_equal(result, ISC_R_SUCCESS); 430 result = isc_stdio_read(buf, sizeof(buf), 1, f, NULL); 431 assert_int_equal(result, ISC_R_EOF); 432 isc_stdio_close(f); 433 isc_file_remove("mem.output"); 434 435 /* return stderr to TTY so we can see errors */ 436 f = freopen("/dev/tty", "w", stderr); 437 438 buf[sizeof(buf) - 1] = 0; 439 440 assert_memory_equal(buf, "create ", 6); 441 p = strchr(buf, '\n'); 442 assert_non_null(p); 443 444 assert_memory_equal(p + 1, "add ", 4); 445 p = strchr(p + 1, '\n'); 446 assert_non_null(p); 447 p = strchr(p + 1, '\n'); 448 assert_non_null(p); 449 assert_in_range(p, 0, buf + sizeof(buf) - 3); 450 assert_memory_equal(p + 2, "ptr ", 4); 451 p = strchr(p + 1, '\n'); 452 assert_non_null(p); 453 assert_memory_equal(p + 1, "del ", 4); 454 } 455 #endif /* if ISC_MEM_TRACKLINES */ 456 457 #if !defined(__SANITIZE_THREAD__) 458 459 #define ITERS 512 460 #define NUM_ITEMS 1024 /* 768 */ 461 #define ITEM_SIZE 65534 462 463 static atomic_size_t mem_size; 464 465 static void * 466 mem_thread(void *arg) { 467 isc_mem_t *mctx2 = (isc_mem_t *)arg; 468 void *items[NUM_ITEMS]; 469 size_t size = atomic_load(&mem_size); 470 while (!atomic_compare_exchange_weak(&mem_size, &size, size / 2)) { 471 ; 472 } 473 474 for (int i = 0; i < ITERS; i++) { 475 for (int j = 0; j < NUM_ITEMS; j++) { 476 items[j] = isc_mem_get(mctx2, size); 477 } 478 for (int j = 0; j < NUM_ITEMS; j++) { 479 isc_mem_put(mctx2, items[j], size); 480 } 481 } 482 483 return NULL; 484 } 485 486 ISC_RUN_TEST_IMPL(isc_mem_benchmark) { 487 int nthreads = ISC_MAX(ISC_MIN(isc_os_ncpus(), 32), 1); 488 isc_thread_t threads[32]; 489 isc_time_t ts1, ts2; 490 double t; 491 492 atomic_init(&mem_size, ITEM_SIZE); 493 494 ts1 = isc_time_now(); 495 496 for (int i = 0; i < nthreads; i++) { 497 isc_thread_create(mem_thread, mctx, &threads[i]); 498 } 499 for (int i = 0; i < nthreads; i++) { 500 isc_thread_join(threads[i], NULL); 501 } 502 503 ts2 = isc_time_now(); 504 505 t = isc_time_microdiff(&ts2, &ts1); 506 507 printf("[ TIME ] isc_mem_benchmark: " 508 "%d isc_mem_{get,put} calls, %f seconds, %f " 509 "calls/second\n", 510 nthreads * ITERS * NUM_ITEMS, t / 1000000.0, 511 (nthreads * ITERS * NUM_ITEMS) / (t / 1000000.0)); 512 } 513 514 #endif /* __SANITIZE_THREAD */ 515 516 ISC_TEST_LIST_START 517 518 ISC_TEST_ENTRY(isc_mem_get) 519 ISC_TEST_ENTRY(isc_mem_cget_zero) 520 ISC_TEST_ENTRY(isc_mem_callocate_zero) 521 ISC_TEST_ENTRY(isc_mem_inuse) 522 ISC_TEST_ENTRY(isc_mem_zeroget) 523 ISC_TEST_ENTRY(isc_mem_reget) 524 ISC_TEST_ENTRY(isc_mem_reallocate) 525 ISC_TEST_ENTRY(isc_mem_overmem) 526 527 #if ISC_MEM_TRACKLINES 528 ISC_TEST_ENTRY(isc_mem_noflags) 529 ISC_TEST_ENTRY(isc_mem_recordflag) 530 /* 531 * traceflag_test closes stderr, which causes weird 532 * side effects for any next test trying to use libuv. 533 * This test has to be the last one to avoid problems. 534 */ 535 ISC_TEST_ENTRY(isc_mem_traceflag) 536 #endif /* if ISC_MEM_TRACKLINES */ 537 #if !defined(__SANITIZE_THREAD__) 538 ISC_TEST_ENTRY(isc_mem_benchmark) 539 #endif /* __SANITIZE_THREAD__ */ 540 541 ISC_TEST_LIST_END 542 543 ISC_TEST_MAIN 544