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