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
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.
13 * Copyright (c) 2019 by Delphix. All rights reserved.
20 #include <sys/btree.h>
22 #include <sys/resource.h>
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
;
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 "
52 (void) fprintf(stderr
, "\t-r random seed [default: from "
54 (void) fprintf(stderr
, "\t-t seconds to let the stress test run "
59 typedef struct int_node
{
69 avl_compare(const void *v1
, const void *v2
)
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
;
76 return (TREE_CMP(a
, b
));
80 zfs_btree_compare(const void *v1
, const void *v2
)
82 const uint64_t *a
= v1
;
83 const uint64_t *b
= v2
;
85 return (TREE_CMP(*a
, *b
));
89 verify_contents(avl_tree_t
*avl
, zfs_btree_t
*bt
)
92 zfs_btree_index_t bt_idx
= {0};
96 boolean_t forward
= count
% 2 == 0 ? B_TRUE
: B_FALSE
;
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
);
104 node
= avl_last(avl
);
105 data
= zfs_btree_last(bt
, &bt_idx
);
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
);
114 data
= zfs_btree_prev(bt
, &bt_idx
, &bt_idx
);
115 node
= AVL_PREV(avl
, node
);
121 verify_node(avl_tree_t
*avl
, zfs_btree_t
*bt
, int_node_t
*node
)
123 zfs_btree_index_t bt_idx
= {0};
124 zfs_btree_index_t bt_idx2
= {0};
126 uint64_t data
= node
->data
;
129 ASSERT3U(avl_numnodes(avl
), ==, zfs_btree_numnodes(bt
));
130 ASSERT3P((rv
= (uint64_t *)zfs_btree_find(bt
, &data
, &bt_idx
)), !=,
132 ASSERT3S(*rv
, ==, data
);
133 ASSERT3P(zfs_btree_get(bt
, &bt_idx
), !=, NULL
);
134 ASSERT3S(data
, ==, *(uint64_t *)zfs_btree_get(bt
, &bt_idx
));
136 if ((inp
= AVL_NEXT(avl
, node
)) != NULL
) {
137 ASSERT3P((rv
= zfs_btree_next(bt
, &bt_idx
, &bt_idx2
)), !=,
139 ASSERT3P(rv
, ==, zfs_btree_get(bt
, &bt_idx2
));
140 ASSERT3S(inp
->data
, ==, *rv
);
142 ASSERT3U(data
, ==, *(uint64_t *)zfs_btree_last(bt
, &bt_idx
));
145 if ((inp
= AVL_PREV(avl
, node
)) != NULL
) {
146 ASSERT3P((rv
= zfs_btree_prev(bt
, &bt_idx
, &bt_idx2
)), !=,
148 ASSERT3P(rv
, ==, zfs_btree_get(bt
, &bt_idx2
));
149 ASSERT3S(inp
->data
, ==, *rv
);
151 ASSERT3U(data
, ==, *(uint64_t *)zfs_btree_first(bt
, &bt_idx
));
159 /* Verify that zfs_btree_find works correctly with a NULL index. */
161 find_without_index(zfs_btree_t
*bt
, char *why
)
163 u_longlong_t
*p
, i
= 12345;
165 zfs_btree_add(bt
, &i
);
166 if ((p
= (u_longlong_t
*)zfs_btree_find(bt
, &i
, NULL
)) == NULL
||
168 (void) snprintf(why
, BUFSIZE
, "Unexpectedly found %llu\n",
175 if ((p
= (u_longlong_t
*)zfs_btree_find(bt
, &i
, NULL
)) != NULL
) {
176 (void) snprintf(why
, BUFSIZE
, "Found bad value: %llu\n", *p
);
183 /* Verify simple insertion and removal from the tree. */
185 insert_find_remove(zfs_btree_t
*bt
, char *why
)
187 u_longlong_t
*p
, i
= 12345;
188 zfs_btree_index_t bt_idx
= {0};
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");
195 } else if (*p
!= i
) {
196 (void) snprintf(why
, BUFSIZE
, "Found (%llu) in tree\n", *p
);
199 ASSERT3S(zfs_btree_numnodes(bt
), ==, 1);
200 zfs_btree_verify(bt
);
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
);
209 ASSERT3S(zfs_btree_numnodes(bt
), ==, 0);
210 zfs_btree_verify(bt
);
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
221 drain_tree(zfs_btree_t
*bt
, char *why
)
226 avl_index_t avl_idx
= {0};
227 zfs_btree_index_t bt_idx
= {0};
229 avl_create(&avl
, avl_compare
, sizeof (int_node_t
),
230 offsetof(int_node_t
, node
));
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
) {
238 zfs_btree_add_idx(bt
, &randval
, &bt_idx
);
240 node
= malloc(sizeof (int_node_t
));
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
);
252 avl_insert(&avl
, node
, avl_idx
);
255 /* Remove data from either side of the trees, comparing the data */
256 while (avl_numnodes(&avl
) != 0) {
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
);
264 node
= avl_last(&avl
);
265 data
= zfs_btree_last(bt
, &bt_idx
);
267 ASSERT3U(node
->data
, ==, *data
);
268 zfs_btree_remove_idx(bt
, &bt_idx
);
269 avl_remove(&avl
, node
);
271 if (avl_numnodes(&avl
) == 0) {
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
));
281 ASSERT3S(zfs_btree_numnodes(bt
), ==, 0);
283 void *avl_cookie
= NULL
;
284 while ((node
= avl_destroy_nodes(&avl
, &avl_cookie
)) != NULL
)
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.
299 stress_tree(zfs_btree_t
*bt
, char *why
)
306 int insertions
= 0, removals
= 0, iterations
= 0;
307 u_longlong_t max
= 0, min
= UINT64_MAX
;
309 (void) gettimeofday(&tp
, NULL
);
312 avl_create(&avl
, avl_compare
, sizeof (int_node_t
),
313 offsetof(int_node_t
, node
));
316 zfs_btree_index_t bt_idx
= {0};
317 avl_index_t avl_idx
= {0};
319 uint64_t randval
= random() % tree_limit
;
320 node
= malloc(sizeof (*node
));
325 node
->data
= randval
;
327 max
= randval
> max
? randval
: max
;
328 min
= randval
< min
? randval
: min
;
330 void *ret
= avl_find(&avl
, node
, &avl_idx
);
333 avl_insert(&avl
, node
, avl_idx
);
334 ASSERT3P(zfs_btree_find(bt
, &randval
, &bt_idx
), ==,
336 zfs_btree_add_idx(bt
, &randval
, &bt_idx
);
337 verify_node(&avl
, bt
, node
);
340 verify_node(&avl
, bt
, ret
);
341 zfs_btree_remove(bt
, &randval
);
342 avl_remove(&avl
, ret
);
347 zfs_btree_verify(bt
);
350 if (iterations
% contents_frequency
== 0) {
351 verify_contents(&avl
, bt
);
354 zfs_btree_verify(bt
);
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
);
364 void *avl_cookie
= NULL
;
365 while ((node
= avl_destroy_nodes(&avl
, &avl_cookie
)) != NULL
)
370 zfs_btree_index_t
*idx
= NULL
;
371 while (zfs_btree_destroy_nodes(bt
, &idx
) != NULL
)
373 zfs_btree_verify(bt
);
380 * Verify inserting a duplicate value will cause a crash.
381 * Note: negative test; return of 0 is a failure.
384 insert_duplicate(zfs_btree_t
*bt
)
387 zfs_btree_index_t bt_idx
= {0};
389 if (zfs_btree_find(bt
, &i
, &bt_idx
) != NULL
) {
390 fprintf(stderr
, "Found value in empty tree.\n");
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");
399 /* Crash on inserting a duplicate */
400 zfs_btree_add_idx(bt
, &i
, NULL
);
406 * Verify removing a non-existent value will cause a crash.
407 * Note: negative test; return of 0 is a failure.
410 remove_missing(zfs_btree_t
*bt
)
413 zfs_btree_index_t bt_idx
= {0};
415 if (zfs_btree_find(bt
, &i
, &bt_idx
) != NULL
) {
416 fprintf(stderr
, "Found value in empty tree.\n");
420 /* Crash removing a nonexistent entry */
421 zfs_btree_remove(bt
, &i
);
427 do_negative_test(zfs_btree_t
*bt
, char *test_name
)
430 struct rlimit rlim
= {0};
432 (void) setrlimit(RLIMIT_CORE
, &rlim
);
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
);
441 * Return 0, since callers will expect non-zero return values for
442 * these tests, and we should have crashed before getting here anyway.
444 (void) fprintf(stderr
, "Test: %s returned %d.\n", test_name
, rval
);
448 typedef struct btree_test
{
450 int (*func
)(zfs_btree_t
*, char *);
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
},
462 main(int argc
, char *argv
[])
464 char *negative_test
= NULL
;
465 int failed_tests
= 0;
470 while ((c
= getopt(argc
, argv
, "c:l:n:r:st:")) != -1) {
473 contents_frequency
= atoi(optarg
);
476 tree_limit
= atoi(optarg
);
479 negative_test
= optarg
;
485 stress_only
= B_TRUE
;
488 stress_timeout
= atoi(optarg
);
498 (void) gettimeofday(&tp
, NULL
);
504 zfs_btree_create(&bt
, zfs_btree_compare
, NULL
, sizeof (uint64_t));
507 * This runs the named negative test. None of them should
508 * return, as they both cause crashes.
511 return (do_negative_test(&bt
, negative_test
));
514 fprintf(stderr
, "Seed: %u\n", seed
);
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.
521 if (stress_only
!= 0) {
522 return (stress_tree(&bt
, NULL
));
525 /* Do the positive tests */
526 btree_test_t
*test
= &test_table
[0];
529 char why
[BUFSIZE
] = {0};
530 zfs_btree_index_t
*idx
= NULL
;
532 (void) fprintf(stdout
, "%-20s", test
->name
);
533 retval
= test
->func(&bt
, why
);
536 (void) fprintf(stdout
, "ok\n");
538 (void) fprintf(stdout
, "failed with %d\n", retval
);
539 if (strlen(why
) != 0)
540 (void) fprintf(stdout
, "\t%s\n", why
);
545 /* Remove all the elements and re-verify the tree */
546 while (zfs_btree_destroy_nodes(&bt
, &idx
) != NULL
)
548 zfs_btree_verify(&bt
);
553 zfs_btree_verify(&bt
);
556 return (failed_tests
);