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