xref: /netbsd-src/external/mpl/bind/dist/tests/isc/mem_test.c (revision bcda20f65a8566e103791ec395f7f499ef322704)
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