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