1/* $NetBSD: mem_test.c,v 1.2 2024/02/21 22:52:51 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/print.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 */ 52ISC_RUN_TEST_IMPL(isc_mem) { 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 UNUSED(state); 61 62 isc_mempool_create(mctx, 24, &mp1); 63 isc_mempool_create(mctx, 31, &mp2); 64 65 isc_mempool_setfreemax(mp1, MP1_FREEMAX); 66 isc_mempool_setfillcount(mp1, MP1_FILLCNT); 67 68 /* 69 * Allocate MP1_MAXALLOC items from the pool. This is our max. 70 */ 71 for (i = 0; i < MP1_MAXALLOC; i++) { 72 items1[i] = isc_mempool_get(mp1); 73 assert_non_null(items1[i]); 74 } 75 76 /* 77 * Free the first 11 items. Verify that there are 10 free items on 78 * the free list (which is our max). 79 */ 80 for (i = 0; i < 11; i++) { 81 isc_mempool_put(mp1, items1[i]); 82 items1[i] = NULL; 83 } 84 85#if !__SANITIZE_ADDRESS__ 86 rval = isc_mempool_getfreecount(mp1); 87 assert_int_equal(rval, 10); 88#endif /* !__SANITIZE_ADDRESS__ */ 89 90 rval = isc_mempool_getallocated(mp1); 91 assert_int_equal(rval, 19); 92 93 /* 94 * Now, beat up on mp2 for a while. Allocate 50 items, then free 95 * them, then allocate 50 more, etc. 96 */ 97 98 isc_mempool_setfreemax(mp2, 25); 99 isc_mempool_setfillcount(mp2, 25); 100 101 for (j = 0; j < 500000; j++) { 102 for (i = 0; i < 50; i++) { 103 items2[i] = isc_mempool_get(mp2); 104 assert_non_null(items2[i]); 105 } 106 for (i = 0; i < 50; i++) { 107 isc_mempool_put(mp2, items2[i]); 108 items2[i] = NULL; 109 } 110 } 111 112 /* 113 * Free all the other items and blow away this pool. 114 */ 115 for (i = 11; i < MP1_MAXALLOC; i++) { 116 isc_mempool_put(mp1, items1[i]); 117 items1[i] = NULL; 118 } 119 120 isc_mempool_destroy(&mp1); 121 isc_mempool_destroy(&mp2); 122 123 isc_mempool_create(mctx, 2, &mp1); 124 125 tmp = isc_mempool_get(mp1); 126 assert_non_null(tmp); 127 128 isc_mempool_put(mp1, tmp); 129 130 isc_mempool_destroy(&mp1); 131} 132 133#if defined(HAVE_MALLOC_NP_H) || defined(HAVE_JEMALLOC) 134/* aligned memory system tests */ 135ISC_RUN_TEST_IMPL(isc_mem_aligned) { 136 isc_mem_t *mctx2 = NULL; 137 void *ptr; 138 size_t alignment; 139 uintptr_t aligned; 140 141 UNUSED(state); 142 143 /* Check different alignment sizes up to the page size */ 144 for (alignment = sizeof(void *); alignment <= 4096; alignment *= 2) { 145 size_t size = alignment / 2 - 1; 146 ptr = isc_mem_get_aligned(mctx, size, alignment); 147 148 /* Check if the pointer is properly aligned */ 149 aligned = (((uintptr_t)ptr / alignment) * alignment); 150 assert_ptr_equal(aligned, (uintptr_t)ptr); 151 152 /* Check if we can resize to <alignment, 2*alignment> range */ 153 ptr = isc_mem_reget_aligned(mctx, ptr, size, 154 size * 2 + alignment, alignment); 155 156 /* Check if the pointer is still properly aligned */ 157 aligned = (((uintptr_t)ptr / alignment) * alignment); 158 assert_ptr_equal(aligned, (uintptr_t)ptr); 159 160 isc_mem_put_aligned(mctx, ptr, size * 2 + alignment, alignment); 161 162 /* Check whether isc_mem_putanddetach_detach() also works */ 163 isc_mem_create(&mctx2); 164 ptr = isc_mem_get_aligned(mctx2, size, alignment); 165 isc_mem_putanddetach_aligned(&mctx2, ptr, size, alignment); 166 } 167} 168#endif /* defined(HAVE_MALLOC_NP_H) || defined(HAVE_JEMALLOC) */ 169 170/* test TotalUse calculation */ 171ISC_RUN_TEST_IMPL(isc_mem_total) { 172 isc_mem_t *mctx2 = NULL; 173 size_t before, after; 174 ssize_t diff; 175 int i; 176 177 UNUSED(state); 178 179 /* Local alloc, free */ 180 mctx2 = NULL; 181 isc_mem_create(&mctx2); 182 183 before = isc_mem_total(mctx2); 184 185 for (i = 0; i < 100000; i++) { 186 void *ptr; 187 188 ptr = isc_mem_get(mctx2, 2048); 189 isc_mem_put(mctx2, ptr, 2048); 190 } 191 192 after = isc_mem_total(mctx2); 193 diff = after - before; 194 195 assert_int_equal(diff, (2048) * 100000); 196 197 /* ISC_MEMFLAG_INTERNAL */ 198 199 before = isc_mem_total(mctx); 200 201 for (i = 0; i < 100000; i++) { 202 void *ptr; 203 204 ptr = isc_mem_get(mctx, 2048); 205 isc_mem_put(mctx, ptr, 2048); 206 } 207 208 after = isc_mem_total(mctx); 209 diff = after - before; 210 211 assert_int_equal(diff, (2048) * 100000); 212 213 isc_mem_destroy(&mctx2); 214} 215 216/* test InUse calculation */ 217ISC_RUN_TEST_IMPL(isc_mem_inuse) { 218 isc_mem_t *mctx2 = NULL; 219 size_t before, after; 220 ssize_t diff; 221 void *ptr; 222 223 UNUSED(state); 224 225 mctx2 = NULL; 226 isc_mem_create(&mctx2); 227 228 before = isc_mem_inuse(mctx2); 229 ptr = isc_mem_allocate(mctx2, 1024000); 230 isc_mem_free(mctx2, ptr); 231 after = isc_mem_inuse(mctx2); 232 233 diff = after - before; 234 235 assert_int_equal(diff, 0); 236 237 isc_mem_destroy(&mctx2); 238} 239 240ISC_RUN_TEST_IMPL(isc_mem_zeroget) { 241 uint8_t *data = NULL; 242 UNUSED(state); 243 244 data = isc_mem_get(mctx, 0); 245 assert_non_null(data); 246 isc_mem_put(mctx, data, 0); 247} 248 249#define REGET_INIT_SIZE 1024 250#define REGET_GROW_SIZE 2048 251#define REGET_SHRINK_SIZE 512 252 253ISC_RUN_TEST_IMPL(isc_mem_reget) { 254 uint8_t *data = NULL; 255 256 UNUSED(state); 257 258 /* test that we can reget NULL */ 259 data = isc_mem_reget(mctx, NULL, 0, REGET_INIT_SIZE); 260 assert_non_null(data); 261 isc_mem_put(mctx, data, REGET_INIT_SIZE); 262 263 /* test that we can re-get a zero-length allocation */ 264 data = isc_mem_get(mctx, 0); 265 assert_non_null(data); 266 267 data = isc_mem_reget(mctx, data, 0, REGET_INIT_SIZE); 268 assert_non_null(data); 269 270 for (size_t i = 0; i < REGET_INIT_SIZE; i++) { 271 data[i] = i % UINT8_MAX; 272 } 273 274 data = isc_mem_reget(mctx, data, REGET_INIT_SIZE, REGET_GROW_SIZE); 275 assert_non_null(data); 276 277 for (size_t i = 0; i < REGET_INIT_SIZE; i++) { 278 assert_int_equal(data[i], i % UINT8_MAX); 279 } 280 281 for (size_t i = REGET_GROW_SIZE; i > 0; i--) { 282 data[i - 1] = i % UINT8_MAX; 283 } 284 285 data = isc_mem_reget(mctx, data, REGET_GROW_SIZE, REGET_SHRINK_SIZE); 286 assert_non_null(data); 287 288 for (size_t i = REGET_SHRINK_SIZE; i > 0; i--) { 289 assert_int_equal(data[i - 1], i % UINT8_MAX); 290 } 291 292 isc_mem_put(mctx, data, REGET_SHRINK_SIZE); 293} 294 295#if ISC_MEM_TRACKLINES 296 297/* test mem with no flags */ 298ISC_RUN_TEST_IMPL(isc_mem_noflags) { 299 isc_result_t result; 300 isc_mem_t *mctx2 = NULL; 301 char buf[4096], *p, *q; 302 FILE *f; 303 void *ptr; 304 305 result = isc_stdio_open("mem.output", "w", &f); 306 assert_int_equal(result, ISC_R_SUCCESS); 307 308 UNUSED(state); 309 310 isc_mem_create(&mctx2); 311 isc_mem_debugging = 0; 312 ptr = isc_mem_get(mctx2, 2048); 313 assert_non_null(ptr); 314 isc__mem_printactive(mctx2, f); 315 isc_mem_put(mctx2, ptr, 2048); 316 isc_mem_destroy(&mctx2); 317 isc_stdio_close(f); 318 319 memset(buf, 0, sizeof(buf)); 320 result = isc_stdio_open("mem.output", "r", &f); 321 assert_int_equal(result, ISC_R_SUCCESS); 322 result = isc_stdio_read(buf, sizeof(buf), 1, f, NULL); 323 assert_int_equal(result, ISC_R_EOF); 324 isc_stdio_close(f); 325 isc_file_remove("mem.output"); 326 327 buf[sizeof(buf) - 1] = 0; 328 329 p = strchr(buf, '\n'); 330 assert_non_null(p); 331 assert_in_range(p, 0, buf + sizeof(buf) - 3); 332 p += 2; 333 q = strchr(p, '\n'); 334 assert_non_null(q); 335 *q = '\0'; 336 assert_string_equal(p, "None."); 337 338 isc_mem_debugging = ISC_MEM_DEBUGRECORD; 339} 340 341/* test mem with record flag */ 342ISC_RUN_TEST_IMPL(isc_mem_recordflag) { 343 isc_result_t result; 344 isc_mem_t *mctx2 = NULL; 345 char buf[4096], *p; 346 FILE *f; 347 void *ptr; 348 349 result = isc_stdio_open("mem.output", "w", &f); 350 assert_int_equal(result, ISC_R_SUCCESS); 351 352 UNUSED(state); 353 354 isc_mem_create(&mctx2); 355 ptr = isc_mem_get(mctx2, 2048); 356 assert_non_null(ptr); 357 isc__mem_printactive(mctx2, f); 358 isc_mem_put(mctx2, ptr, 2048); 359 isc_mem_destroy(&mctx2); 360 isc_stdio_close(f); 361 362 memset(buf, 0, sizeof(buf)); 363 result = isc_stdio_open("mem.output", "r", &f); 364 assert_int_equal(result, ISC_R_SUCCESS); 365 result = isc_stdio_read(buf, sizeof(buf), 1, f, NULL); 366 assert_int_equal(result, ISC_R_EOF); 367 isc_stdio_close(f); 368 isc_file_remove("mem.output"); 369 370 buf[sizeof(buf) - 1] = 0; 371 372 p = strchr(buf, '\n'); 373 assert_non_null(p); 374 assert_in_range(p, 0, buf + sizeof(buf) - 3); 375 assert_memory_equal(p + 2, "ptr ", 4); 376 p = strchr(p + 1, '\n'); 377 assert_non_null(p); 378 assert_int_equal(strlen(p), 1); 379} 380 381/* test mem with trace flag */ 382ISC_RUN_TEST_IMPL(isc_mem_traceflag) { 383 isc_result_t result; 384 isc_mem_t *mctx2 = NULL; 385 char buf[4096], *p; 386 FILE *f; 387 void *ptr; 388 389 /* redirect stderr so we can check trace output */ 390 f = freopen("mem.output", "w", stderr); 391 assert_non_null(f); 392 393 UNUSED(state); 394 395 isc_mem_create(&mctx2); 396 isc_mem_debugging = ISC_MEM_DEBUGTRACE; 397 ptr = isc_mem_get(mctx2, 2048); 398 assert_non_null(ptr); 399 isc__mem_printactive(mctx2, f); 400 isc_mem_put(mctx2, ptr, 2048); 401 isc_mem_destroy(&mctx2); 402 isc_stdio_close(f); 403 404 memset(buf, 0, sizeof(buf)); 405 result = isc_stdio_open("mem.output", "r", &f); 406 assert_int_equal(result, ISC_R_SUCCESS); 407 result = isc_stdio_read(buf, sizeof(buf), 1, f, NULL); 408 assert_int_equal(result, ISC_R_EOF); 409 isc_stdio_close(f); 410 isc_file_remove("mem.output"); 411 412 /* return stderr to TTY so we can see errors */ 413 f = freopen("/dev/tty", "w", stderr); 414 415 buf[sizeof(buf) - 1] = 0; 416 417 assert_memory_equal(buf, "add ", 4); 418 p = strchr(buf, '\n'); 419 assert_non_null(p); 420 p = strchr(p + 1, '\n'); 421 assert_non_null(p); 422 assert_in_range(p, 0, buf + sizeof(buf) - 3); 423 assert_memory_equal(p + 2, "ptr ", 4); 424 p = strchr(p + 1, '\n'); 425 assert_non_null(p); 426 assert_memory_equal(p + 1, "del ", 4); 427 428 isc_mem_debugging = ISC_MEM_DEBUGRECORD; 429} 430#endif /* if ISC_MEM_TRACKLINES */ 431 432#if !defined(__SANITIZE_THREAD__) 433 434#define ITERS 512 435#define NUM_ITEMS 1024 /* 768 */ 436#define ITEM_SIZE 65534 437 438static atomic_size_t mem_size; 439 440static isc_threadresult_t 441mem_thread(isc_threadarg_t arg) { 442 isc_mem_t *mctx2 = (isc_mem_t *)arg; 443 void *items[NUM_ITEMS]; 444 size_t size = atomic_load(&mem_size); 445 while (!atomic_compare_exchange_weak(&mem_size, &size, size / 2)) { 446 ; 447 } 448 449 for (int i = 0; i < ITERS; i++) { 450 for (int j = 0; j < NUM_ITEMS; j++) { 451 items[j] = isc_mem_get(mctx2, size); 452 } 453 for (int j = 0; j < NUM_ITEMS; j++) { 454 isc_mem_put(mctx2, items[j], size); 455 } 456 } 457 458 return ((isc_threadresult_t)0); 459} 460 461ISC_RUN_TEST_IMPL(isc_mem_benchmark) { 462 int nthreads = ISC_MAX(ISC_MIN(isc_os_ncpus(), 32), 1); 463 isc_thread_t threads[32]; 464 isc_time_t ts1, ts2; 465 double t; 466 isc_result_t result; 467 468 UNUSED(state); 469 470 atomic_init(&mem_size, ITEM_SIZE); 471 472 result = isc_time_now(&ts1); 473 assert_int_equal(result, ISC_R_SUCCESS); 474 475 for (int i = 0; i < nthreads; i++) { 476 isc_thread_create(mem_thread, mctx, &threads[i]); 477 } 478 for (int i = 0; i < nthreads; i++) { 479 isc_thread_join(threads[i], NULL); 480 } 481 482 result = isc_time_now(&ts2); 483 assert_int_equal(result, ISC_R_SUCCESS); 484 485 t = isc_time_microdiff(&ts2, &ts1); 486 487 printf("[ TIME ] isc_mem_benchmark: " 488 "%d isc_mem_{get,put} calls, %f seconds, %f " 489 "calls/second\n", 490 nthreads * ITERS * NUM_ITEMS, t / 1000000.0, 491 (nthreads * ITERS * NUM_ITEMS) / (t / 1000000.0)); 492} 493 494#endif /* __SANITIZE_THREAD */ 495 496ISC_TEST_LIST_START 497 498ISC_TEST_ENTRY(isc_mem) 499#if defined(HAVE_MALLOC_NP_H) || defined(HAVE_JEMALLOC) 500ISC_TEST_ENTRY(isc_mem_aligned) 501#endif /* defined(HAVE_MALLOC_NP_H) || defined(HAVE_JEMALLOC) */ 502ISC_TEST_ENTRY(isc_mem_total) 503ISC_TEST_ENTRY(isc_mem_inuse) 504ISC_TEST_ENTRY(isc_mem_zeroget) 505ISC_TEST_ENTRY(isc_mem_reget) 506 507#if !defined(__SANITIZE_THREAD__) 508ISC_TEST_ENTRY(isc_mem_benchmark) 509#endif /* __SANITIZE_THREAD__ */ 510#if ISC_MEM_TRACKLINES 511ISC_TEST_ENTRY(isc_mem_noflags) 512ISC_TEST_ENTRY(isc_mem_recordflag) 513/* 514 * traceflag_test closes stderr, which causes weird 515 * side effects for any next test trying to use libuv. 516 * This test has to be the last one to avoid problems. 517 */ 518ISC_TEST_ENTRY(isc_mem_traceflag) 519#endif /* if ISC_MEM_TRACKLINES */ 520 521ISC_TEST_LIST_END 522 523ISC_TEST_MAIN 524