xref: /freebsd/sys/contrib/openzfs/tests/zfs-tests/cmd/btree_test.c (revision 4e8d558c9d1cf3e7e424e3fb123b01979c3d57f2)
1 /*
2  * This file and its contents are supplied under the terms of the
3  * Common Development and Distribution License ("CDDL"), version 1.0.
4  * You may only use this file in accordance with the terms of version
5  * 1.0 of the CDDL.
6  *
7  * A full copy of the text of the CDDL should have accompanied this
8  * source.  A copy of the CDDL is also available via the Internet at
9  * http://www.illumos.org/license/CDDL.
10  */
11 
12 /*
13  * Copyright (c) 2019 by Delphix. All rights reserved.
14  */
15 
16 #include <stdio.h>
17 #include <stdlib.h>
18 #include <string.h>
19 #include <sys/avl.h>
20 #include <sys/btree.h>
21 #include <sys/time.h>
22 #include <sys/resource.h>
23 
24 #define	BUFSIZE 256
25 
26 static int seed = 0;
27 static int stress_timeout = 180;
28 static int contents_frequency = 100;
29 static int tree_limit = 64 * 1024;
30 static boolean_t stress_only = B_FALSE;
31 
32 static void
usage(int exit_value)33 usage(int exit_value)
34 {
35 	(void) fprintf(stderr, "Usage:\tbtree_test -n <test_name>\n");
36 	(void) fprintf(stderr, "\tbtree_test -s [-r <seed>] [-l <limit>] "
37 	    "[-t timeout>] [-c check_contents]\n");
38 	(void) fprintf(stderr, "\tbtree_test [-r <seed>] [-l <limit>] "
39 	    "[-t timeout>] [-c check_contents]\n");
40 	(void) fprintf(stderr, "\n    With the -n option, run the named "
41 	    "negative test. With the -s option,\n");
42 	(void) fprintf(stderr, "    run the stress test according to the "
43 	    "other options passed. With\n");
44 	(void) fprintf(stderr, "    neither, run all the positive tests, "
45 	    "including the stress test with\n");
46 	(void) fprintf(stderr, "    the default options.\n");
47 	(void) fprintf(stderr, "\n    Options that control the stress test\n");
48 	(void) fprintf(stderr, "\t-c stress iterations after which to compare "
49 	    "tree contents [default: 100]\n");
50 	(void) fprintf(stderr, "\t-l the largest value to allow in the tree "
51 	    "[default: 1M]\n");
52 	(void) fprintf(stderr, "\t-r random seed [default: from "
53 	    "gettimeofday()]\n");
54 	(void) fprintf(stderr, "\t-t seconds to let the stress test run "
55 	    "[default: 180]\n");
56 	exit(exit_value);
57 }
58 
59 typedef struct int_node {
60 	avl_node_t node;
61 	uint64_t data;
62 } int_node_t;
63 
64 /*
65  * Utility functions
66  */
67 
68 static int
avl_compare(const void * v1,const void * v2)69 avl_compare(const void *v1, const void *v2)
70 {
71 	const int_node_t *n1 = v1;
72 	const int_node_t *n2 = v2;
73 	uint64_t a = n1->data;
74 	uint64_t b = n2->data;
75 
76 	return (TREE_CMP(a, b));
77 }
78 
79 static int
zfs_btree_compare(const void * v1,const void * v2)80 zfs_btree_compare(const void *v1, const void *v2)
81 {
82 	const uint64_t *a = v1;
83 	const uint64_t *b = v2;
84 
85 	return (TREE_CMP(*a, *b));
86 }
87 
88 static void
verify_contents(avl_tree_t * avl,zfs_btree_t * bt)89 verify_contents(avl_tree_t *avl, zfs_btree_t *bt)
90 {
91 	static int count = 0;
92 	zfs_btree_index_t bt_idx = {0};
93 	int_node_t *node;
94 	uint64_t *data;
95 
96 	boolean_t forward = count % 2 == 0 ? B_TRUE : B_FALSE;
97 	count++;
98 
99 	ASSERT3U(avl_numnodes(avl), ==, zfs_btree_numnodes(bt));
100 	if (forward == B_TRUE) {
101 		node = avl_first(avl);
102 		data = zfs_btree_first(bt, &bt_idx);
103 	} else {
104 		node = avl_last(avl);
105 		data = zfs_btree_last(bt, &bt_idx);
106 	}
107 
108 	while (node != NULL) {
109 		ASSERT3U(*data, ==, node->data);
110 		if (forward == B_TRUE) {
111 			data = zfs_btree_next(bt, &bt_idx, &bt_idx);
112 			node = AVL_NEXT(avl, node);
113 		} else {
114 			data = zfs_btree_prev(bt, &bt_idx, &bt_idx);
115 			node = AVL_PREV(avl, node);
116 		}
117 	}
118 }
119 
120 static void
verify_node(avl_tree_t * avl,zfs_btree_t * bt,int_node_t * node)121 verify_node(avl_tree_t *avl, zfs_btree_t *bt, int_node_t *node)
122 {
123 	zfs_btree_index_t bt_idx = {0};
124 	zfs_btree_index_t bt_idx2 = {0};
125 	int_node_t *inp;
126 	uint64_t data = node->data;
127 	uint64_t *rv = NULL;
128 
129 	ASSERT3U(avl_numnodes(avl), ==, zfs_btree_numnodes(bt));
130 	ASSERT3P((rv = (uint64_t *)zfs_btree_find(bt, &data, &bt_idx)), !=,
131 	    NULL);
132 	ASSERT3S(*rv, ==, data);
133 	ASSERT3P(zfs_btree_get(bt, &bt_idx), !=, NULL);
134 	ASSERT3S(data, ==, *(uint64_t *)zfs_btree_get(bt, &bt_idx));
135 
136 	if ((inp = AVL_NEXT(avl, node)) != NULL) {
137 		ASSERT3P((rv = zfs_btree_next(bt, &bt_idx, &bt_idx2)), !=,
138 		    NULL);
139 		ASSERT3P(rv, ==, zfs_btree_get(bt, &bt_idx2));
140 		ASSERT3S(inp->data, ==, *rv);
141 	} else {
142 		ASSERT3U(data, ==, *(uint64_t *)zfs_btree_last(bt, &bt_idx));
143 	}
144 
145 	if ((inp = AVL_PREV(avl, node)) != NULL) {
146 		ASSERT3P((rv = zfs_btree_prev(bt, &bt_idx, &bt_idx2)), !=,
147 		    NULL);
148 		ASSERT3P(rv, ==, zfs_btree_get(bt, &bt_idx2));
149 		ASSERT3S(inp->data, ==, *rv);
150 	} else {
151 		ASSERT3U(data, ==, *(uint64_t *)zfs_btree_first(bt, &bt_idx));
152 	}
153 }
154 
155 /*
156  * Tests
157  */
158 
159 /* Verify that zfs_btree_find works correctly with a NULL index. */
160 static int
find_without_index(zfs_btree_t * bt,char * why)161 find_without_index(zfs_btree_t *bt, char *why)
162 {
163 	u_longlong_t *p, i = 12345;
164 
165 	zfs_btree_add(bt, &i);
166 	if ((p = (u_longlong_t *)zfs_btree_find(bt, &i, NULL)) == NULL ||
167 	    *p != i) {
168 		(void) snprintf(why, BUFSIZE, "Unexpectedly found %llu\n",
169 		    p == NULL ? 0 : *p);
170 		return (1);
171 	}
172 
173 	i++;
174 
175 	if ((p = (u_longlong_t *)zfs_btree_find(bt, &i, NULL)) != NULL) {
176 		(void) snprintf(why, BUFSIZE, "Found bad value: %llu\n", *p);
177 		return (1);
178 	}
179 
180 	return (0);
181 }
182 
183 /* Verify simple insertion and removal from the tree. */
184 static int
insert_find_remove(zfs_btree_t * bt,char * why)185 insert_find_remove(zfs_btree_t *bt, char *why)
186 {
187 	u_longlong_t *p, i = 12345;
188 	zfs_btree_index_t bt_idx = {0};
189 
190 	/* Insert 'i' into the tree, and attempt to find it again. */
191 	zfs_btree_add(bt, &i);
192 	if ((p = (u_longlong_t *)zfs_btree_find(bt, &i, &bt_idx)) == NULL) {
193 		(void) snprintf(why, BUFSIZE, "Didn't find value in tree\n");
194 		return (1);
195 	} else if (*p != i) {
196 		(void) snprintf(why, BUFSIZE, "Found (%llu) in tree\n", *p);
197 		return (1);
198 	}
199 	ASSERT3S(zfs_btree_numnodes(bt), ==, 1);
200 	zfs_btree_verify(bt);
201 
202 	/* Remove 'i' from the tree, and verify it is not found. */
203 	zfs_btree_remove(bt, &i);
204 	if ((p = (u_longlong_t *)zfs_btree_find(bt, &i, &bt_idx)) != NULL) {
205 		(void) snprintf(why, BUFSIZE,
206 		    "Found removed value (%llu)\n", *p);
207 		return (1);
208 	}
209 	ASSERT3S(zfs_btree_numnodes(bt), ==, 0);
210 	zfs_btree_verify(bt);
211 
212 	return (0);
213 }
214 
215 /*
216  * Add a number of random entries into a btree and avl tree. Then walk them
217  * backwards and forwards while emptying the tree, verifying the trees look
218  * the same.
219  */
220 static int
drain_tree(zfs_btree_t * bt,char * why)221 drain_tree(zfs_btree_t *bt, char *why)
222 {
223 	avl_tree_t avl;
224 	int i = 0;
225 	int_node_t *node;
226 	avl_index_t avl_idx = {0};
227 	zfs_btree_index_t bt_idx = {0};
228 
229 	avl_create(&avl, avl_compare, sizeof (int_node_t),
230 	    offsetof(int_node_t, node));
231 
232 	/* Fill both trees with the same data */
233 	for (i = 0; i < 64 * 1024; i++) {
234 		u_longlong_t randval = random();
235 		if (zfs_btree_find(bt, &randval, &bt_idx) != NULL) {
236 			continue;
237 		}
238 		zfs_btree_add_idx(bt, &randval, &bt_idx);
239 
240 		node = malloc(sizeof (int_node_t));
241 		if (node == NULL) {
242 			perror("malloc");
243 			exit(EXIT_FAILURE);
244 		}
245 
246 		node->data = randval;
247 		if (avl_find(&avl, node, &avl_idx) != NULL) {
248 			(void) snprintf(why, BUFSIZE,
249 			    "Found in avl: %llu\n", randval);
250 			return (1);
251 		}
252 		avl_insert(&avl, node, avl_idx);
253 	}
254 
255 	/* Remove data from either side of the trees, comparing the data */
256 	while (avl_numnodes(&avl) != 0) {
257 		uint64_t *data;
258 
259 		ASSERT3U(avl_numnodes(&avl), ==, zfs_btree_numnodes(bt));
260 		if (avl_numnodes(&avl) % 2 == 0) {
261 			node = avl_first(&avl);
262 			data = zfs_btree_first(bt, &bt_idx);
263 		} else {
264 			node = avl_last(&avl);
265 			data = zfs_btree_last(bt, &bt_idx);
266 		}
267 		ASSERT3U(node->data, ==, *data);
268 		zfs_btree_remove_idx(bt, &bt_idx);
269 		avl_remove(&avl, node);
270 
271 		if (avl_numnodes(&avl) == 0) {
272 			break;
273 		}
274 
275 		node = avl_first(&avl);
276 		ASSERT3U(node->data, ==,
277 		    *(uint64_t *)zfs_btree_first(bt, NULL));
278 		node = avl_last(&avl);
279 		ASSERT3U(node->data, ==, *(uint64_t *)zfs_btree_last(bt, NULL));
280 	}
281 	ASSERT3S(zfs_btree_numnodes(bt), ==, 0);
282 
283 	void *avl_cookie = NULL;
284 	while ((node = avl_destroy_nodes(&avl, &avl_cookie)) != NULL)
285 		free(node);
286 	avl_destroy(&avl);
287 
288 	return (0);
289 }
290 
291 /*
292  * This test uses an avl and btree, and continually processes new random
293  * values. Each value is either removed or inserted, depending on whether
294  * or not it is found in the tree. The test periodically checks that both
295  * trees have the same data and does consistency checks. This stress
296  * option can also be run on its own from the command line.
297  */
298 static int
stress_tree(zfs_btree_t * bt,char * why)299 stress_tree(zfs_btree_t *bt, char *why)
300 {
301 	(void) why;
302 	avl_tree_t avl;
303 	int_node_t *node;
304 	struct timeval tp;
305 	time_t t0;
306 	int insertions = 0, removals = 0, iterations = 0;
307 	u_longlong_t max = 0, min = UINT64_MAX;
308 
309 	(void) gettimeofday(&tp, NULL);
310 	t0 = tp.tv_sec;
311 
312 	avl_create(&avl, avl_compare, sizeof (int_node_t),
313 	    offsetof(int_node_t, node));
314 
315 	while (1) {
316 		zfs_btree_index_t bt_idx = {0};
317 		avl_index_t avl_idx = {0};
318 
319 		uint64_t randval = random() % tree_limit;
320 		node = malloc(sizeof (*node));
321 		if (node == NULL) {
322 			perror("malloc");
323 			exit(EXIT_FAILURE);
324 		}
325 		node->data = randval;
326 
327 		max = randval > max ? randval : max;
328 		min = randval < min ? randval : min;
329 
330 		void *ret = avl_find(&avl, node, &avl_idx);
331 		if (ret == NULL) {
332 			insertions++;
333 			avl_insert(&avl, node, avl_idx);
334 			ASSERT3P(zfs_btree_find(bt, &randval, &bt_idx), ==,
335 			    NULL);
336 			zfs_btree_add_idx(bt, &randval, &bt_idx);
337 			verify_node(&avl, bt, node);
338 		} else {
339 			removals++;
340 			verify_node(&avl, bt, ret);
341 			zfs_btree_remove(bt, &randval);
342 			avl_remove(&avl, ret);
343 			free(ret);
344 			free(node);
345 		}
346 
347 		zfs_btree_verify(bt);
348 
349 		iterations++;
350 		if (iterations % contents_frequency == 0) {
351 			verify_contents(&avl, bt);
352 		}
353 
354 		zfs_btree_verify(bt);
355 
356 		(void) gettimeofday(&tp, NULL);
357 		if (tp.tv_sec > t0 + stress_timeout) {
358 			fprintf(stderr, "insertions/removals: %u/%u\nmax/min: "
359 			    "%llu/%llu\n", insertions, removals, max, min);
360 			break;
361 		}
362 	}
363 
364 	void *avl_cookie = NULL;
365 	while ((node = avl_destroy_nodes(&avl, &avl_cookie)) != NULL)
366 		free(node);
367 	avl_destroy(&avl);
368 
369 	if (stress_only) {
370 		zfs_btree_index_t *idx = NULL;
371 		while (zfs_btree_destroy_nodes(bt, &idx) != NULL)
372 			;
373 		zfs_btree_verify(bt);
374 	}
375 
376 	return (0);
377 }
378 
379 /*
380  * Verify inserting a duplicate value will cause a crash.
381  * Note: negative test; return of 0 is a failure.
382  */
383 static int
insert_duplicate(zfs_btree_t * bt)384 insert_duplicate(zfs_btree_t *bt)
385 {
386 	uint64_t i = 23456;
387 	zfs_btree_index_t bt_idx = {0};
388 
389 	if (zfs_btree_find(bt, &i, &bt_idx) != NULL) {
390 		fprintf(stderr, "Found value in empty tree.\n");
391 		return (0);
392 	}
393 	zfs_btree_add_idx(bt, &i, &bt_idx);
394 	if (zfs_btree_find(bt, &i, &bt_idx) == NULL) {
395 		fprintf(stderr, "Did not find expected value.\n");
396 		return (0);
397 	}
398 
399 	/* Crash on inserting a duplicate */
400 	zfs_btree_add_idx(bt, &i, NULL);
401 
402 	return (0);
403 }
404 
405 /*
406  * Verify removing a non-existent value will cause a crash.
407  * Note: negative test; return of 0 is a failure.
408  */
409 static int
remove_missing(zfs_btree_t * bt)410 remove_missing(zfs_btree_t *bt)
411 {
412 	uint64_t i = 23456;
413 	zfs_btree_index_t bt_idx = {0};
414 
415 	if (zfs_btree_find(bt, &i, &bt_idx) != NULL) {
416 		fprintf(stderr, "Found value in empty tree.\n");
417 		return (0);
418 	}
419 
420 	/* Crash removing a nonexistent entry */
421 	zfs_btree_remove(bt, &i);
422 
423 	return (0);
424 }
425 
426 static int
do_negative_test(zfs_btree_t * bt,char * test_name)427 do_negative_test(zfs_btree_t *bt, char *test_name)
428 {
429 	int rval = 0;
430 	struct rlimit rlim = {0};
431 
432 	(void) setrlimit(RLIMIT_CORE, &rlim);
433 
434 	if (strcmp(test_name, "insert_duplicate") == 0) {
435 		rval = insert_duplicate(bt);
436 	} else if (strcmp(test_name, "remove_missing") == 0) {
437 		rval = remove_missing(bt);
438 	}
439 
440 	/*
441 	 * Return 0, since callers will expect non-zero return values for
442 	 * these tests, and we should have crashed before getting here anyway.
443 	 */
444 	(void) fprintf(stderr, "Test: %s returned %d.\n", test_name, rval);
445 	return (0);
446 }
447 
448 typedef struct btree_test {
449 	const char	*name;
450 	int		(*func)(zfs_btree_t *, char *);
451 } btree_test_t;
452 
453 static btree_test_t test_table[] = {
454 	{ "insert_find_remove",		insert_find_remove	},
455 	{ "find_without_index",		find_without_index	},
456 	{ "drain_tree",			drain_tree		},
457 	{ "stress_tree",		stress_tree		},
458 	{ NULL,				NULL			}
459 };
460 
461 int
main(int argc,char * argv[])462 main(int argc, char *argv[])
463 {
464 	char *negative_test = NULL;
465 	int failed_tests = 0;
466 	struct timeval tp;
467 	zfs_btree_t bt;
468 	int c;
469 
470 	while ((c = getopt(argc, argv, "c:l:n:r:st:")) != -1) {
471 		switch (c) {
472 		case 'c':
473 			contents_frequency = atoi(optarg);
474 			break;
475 		case 'l':
476 			tree_limit = atoi(optarg);
477 			break;
478 		case 'n':
479 			negative_test = optarg;
480 			break;
481 		case 'r':
482 			seed = atoi(optarg);
483 			break;
484 		case 's':
485 			stress_only = B_TRUE;
486 			break;
487 		case 't':
488 			stress_timeout = atoi(optarg);
489 			break;
490 		case 'h':
491 		default:
492 			usage(1);
493 			break;
494 		}
495 	}
496 
497 	if (seed == 0) {
498 		(void) gettimeofday(&tp, NULL);
499 		seed = tp.tv_sec;
500 	}
501 	srandom(seed);
502 
503 	zfs_btree_init();
504 	zfs_btree_create(&bt, zfs_btree_compare, NULL, sizeof (uint64_t));
505 
506 	/*
507 	 * This runs the named negative test. None of them should
508 	 * return, as they both cause crashes.
509 	 */
510 	if (negative_test) {
511 		return (do_negative_test(&bt, negative_test));
512 	}
513 
514 	fprintf(stderr, "Seed: %u\n", seed);
515 
516 	/*
517 	 * This is a stress test that does operations on a btree over the
518 	 * requested timeout period, verifying them against identical
519 	 * operations in an avl tree.
520 	 */
521 	if (stress_only != 0) {
522 		return (stress_tree(&bt, NULL));
523 	}
524 
525 	/* Do the positive tests */
526 	btree_test_t *test = &test_table[0];
527 	while (test->name) {
528 		int retval;
529 		char why[BUFSIZE] = {0};
530 		zfs_btree_index_t *idx = NULL;
531 
532 		(void) fprintf(stdout, "%-20s", test->name);
533 		retval = test->func(&bt, why);
534 
535 		if (retval == 0) {
536 			(void) fprintf(stdout, "ok\n");
537 		} else {
538 			(void) fprintf(stdout, "failed with %d\n", retval);
539 			if (strlen(why) != 0)
540 				(void) fprintf(stdout, "\t%s\n", why);
541 			why[0] = '\0';
542 			failed_tests++;
543 		}
544 
545 		/* Remove all the elements and re-verify the tree */
546 		while (zfs_btree_destroy_nodes(&bt, &idx) != NULL)
547 			;
548 		zfs_btree_verify(&bt);
549 
550 		test++;
551 	}
552 
553 	zfs_btree_verify(&bt);
554 	zfs_btree_fini();
555 
556 	return (failed_tests);
557 }
558