Path: blob/main/sys/contrib/openzfs/tests/zfs-tests/cmd/btree_test.c
48529 views
// SPDX-License-Identifier: CDDL-1.01/*2* This file and its contents are supplied under the terms of the3* Common Development and Distribution License ("CDDL"), version 1.0.4* You may only use this file in accordance with the terms of version5* 1.0 of the CDDL.6*7* A full copy of the text of the CDDL should have accompanied this8* source. A copy of the CDDL is also available via the Internet at9* http://www.illumos.org/license/CDDL.10*/1112/*13* Copyright (c) 2019 by Delphix. All rights reserved.14*/1516#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>2324#define BUFSIZE 2562526static int seed = 0;27static int stress_timeout = 180;28static int contents_frequency = 100;29static int tree_limit = 64 * 1024;30static boolean_t stress_only = B_FALSE;3132static void33usage(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");56exit(exit_value);57}5859typedef struct int_node {60avl_node_t node;61uint64_t data;62} int_node_t;6364/*65* Utility functions66*/6768static int69avl_compare(const void *v1, const void *v2)70{71const int_node_t *n1 = v1;72const int_node_t *n2 = v2;73uint64_t a = n1->data;74uint64_t b = n2->data;7576return (TREE_CMP(a, b));77}7879static int80zfs_btree_compare(const void *v1, const void *v2)81{82const uint64_t *a = v1;83const uint64_t *b = v2;8485return (TREE_CMP(*a, *b));86}8788static void89verify_contents(avl_tree_t *avl, zfs_btree_t *bt)90{91static int count = 0;92zfs_btree_index_t bt_idx = {0};93int_node_t *node;94uint64_t *data;9596boolean_t forward = count % 2 == 0 ? B_TRUE : B_FALSE;97count++;9899ASSERT3U(avl_numnodes(avl), ==, zfs_btree_numnodes(bt));100if (forward == B_TRUE) {101node = avl_first(avl);102data = zfs_btree_first(bt, &bt_idx);103} else {104node = avl_last(avl);105data = zfs_btree_last(bt, &bt_idx);106}107108while (node != NULL) {109ASSERT3U(*data, ==, node->data);110if (forward == B_TRUE) {111data = zfs_btree_next(bt, &bt_idx, &bt_idx);112node = AVL_NEXT(avl, node);113} else {114data = zfs_btree_prev(bt, &bt_idx, &bt_idx);115node = AVL_PREV(avl, node);116}117}118}119120static void121verify_node(avl_tree_t *avl, zfs_btree_t *bt, int_node_t *node)122{123zfs_btree_index_t bt_idx = {0};124zfs_btree_index_t bt_idx2 = {0};125int_node_t *inp;126uint64_t data = node->data;127uint64_t *rv = NULL;128129ASSERT3U(avl_numnodes(avl), ==, zfs_btree_numnodes(bt));130ASSERT3P((rv = (uint64_t *)zfs_btree_find(bt, &data, &bt_idx)), !=,131NULL);132ASSERT3S(*rv, ==, data);133ASSERT3P(zfs_btree_get(bt, &bt_idx), !=, NULL);134ASSERT3S(data, ==, *(uint64_t *)zfs_btree_get(bt, &bt_idx));135136if ((inp = AVL_NEXT(avl, node)) != NULL) {137ASSERT3P((rv = zfs_btree_next(bt, &bt_idx, &bt_idx2)), !=,138NULL);139ASSERT3P(rv, ==, zfs_btree_get(bt, &bt_idx2));140ASSERT3S(inp->data, ==, *rv);141} else {142ASSERT3U(data, ==, *(uint64_t *)zfs_btree_last(bt, &bt_idx));143}144145if ((inp = AVL_PREV(avl, node)) != NULL) {146ASSERT3P((rv = zfs_btree_prev(bt, &bt_idx, &bt_idx2)), !=,147NULL);148ASSERT3P(rv, ==, zfs_btree_get(bt, &bt_idx2));149ASSERT3S(inp->data, ==, *rv);150} else {151ASSERT3U(data, ==, *(uint64_t *)zfs_btree_first(bt, &bt_idx));152}153}154155/*156* Tests157*/158159/* Verify that zfs_btree_find works correctly with a NULL index. */160static int161find_without_index(zfs_btree_t *bt, char *why)162{163u_longlong_t *p, i = 12345;164165zfs_btree_add(bt, &i);166if ((p = (u_longlong_t *)zfs_btree_find(bt, &i, NULL)) == NULL ||167*p != i) {168(void) snprintf(why, BUFSIZE, "Unexpectedly found %llu\n",169p == NULL ? 0 : *p);170return (1);171}172173i++;174175if ((p = (u_longlong_t *)zfs_btree_find(bt, &i, NULL)) != NULL) {176(void) snprintf(why, BUFSIZE, "Found bad value: %llu\n", *p);177return (1);178}179180return (0);181}182183/* Verify simple insertion and removal from the tree. */184static int185insert_find_remove(zfs_btree_t *bt, char *why)186{187u_longlong_t *p, i = 12345;188zfs_btree_index_t bt_idx = {0};189190/* Insert 'i' into the tree, and attempt to find it again. */191zfs_btree_add(bt, &i);192if ((p = (u_longlong_t *)zfs_btree_find(bt, &i, &bt_idx)) == NULL) {193(void) snprintf(why, BUFSIZE, "Didn't find value in tree\n");194return (1);195} else if (*p != i) {196(void) snprintf(why, BUFSIZE, "Found (%llu) in tree\n", *p);197return (1);198}199ASSERT3S(zfs_btree_numnodes(bt), ==, 1);200zfs_btree_verify(bt);201202/* Remove 'i' from the tree, and verify it is not found. */203zfs_btree_remove(bt, &i);204if ((p = (u_longlong_t *)zfs_btree_find(bt, &i, &bt_idx)) != NULL) {205(void) snprintf(why, BUFSIZE,206"Found removed value (%llu)\n", *p);207return (1);208}209ASSERT0(zfs_btree_numnodes(bt));210zfs_btree_verify(bt);211212return (0);213}214215/*216* Add a number of random entries into a btree and avl tree. Then walk them217* backwards and forwards while emptying the tree, verifying the trees look218* the same.219*/220static int221drain_tree(zfs_btree_t *bt, char *why)222{223avl_tree_t avl;224int i = 0;225int_node_t *node;226avl_index_t avl_idx = {0};227zfs_btree_index_t bt_idx = {0};228229avl_create(&avl, avl_compare, sizeof (int_node_t),230offsetof(int_node_t, node));231232/* Fill both trees with the same data */233for (i = 0; i < 64 * 1024; i++) {234u_longlong_t randval = random();235if (zfs_btree_find(bt, &randval, &bt_idx) != NULL) {236continue;237}238zfs_btree_add_idx(bt, &randval, &bt_idx);239240node = malloc(sizeof (int_node_t));241if (node == NULL) {242perror("malloc");243exit(EXIT_FAILURE);244}245246node->data = randval;247if (avl_find(&avl, node, &avl_idx) != NULL) {248(void) snprintf(why, BUFSIZE,249"Found in avl: %llu\n", randval);250return (1);251}252avl_insert(&avl, node, avl_idx);253}254255/* Remove data from either side of the trees, comparing the data */256while (avl_numnodes(&avl) != 0) {257uint64_t *data;258259ASSERT3U(avl_numnodes(&avl), ==, zfs_btree_numnodes(bt));260if (avl_numnodes(&avl) % 2 == 0) {261node = avl_first(&avl);262data = zfs_btree_first(bt, &bt_idx);263} else {264node = avl_last(&avl);265data = zfs_btree_last(bt, &bt_idx);266}267ASSERT3U(node->data, ==, *data);268zfs_btree_remove_idx(bt, &bt_idx);269avl_remove(&avl, node);270271if (avl_numnodes(&avl) == 0) {272break;273}274275node = avl_first(&avl);276ASSERT3U(node->data, ==,277*(uint64_t *)zfs_btree_first(bt, NULL));278node = avl_last(&avl);279ASSERT3U(node->data, ==, *(uint64_t *)zfs_btree_last(bt, NULL));280}281ASSERT0(zfs_btree_numnodes(bt));282283void *avl_cookie = NULL;284while ((node = avl_destroy_nodes(&avl, &avl_cookie)) != NULL)285free(node);286avl_destroy(&avl);287288return (0);289}290291/*292* This test uses an avl and btree, and continually processes new random293* values. Each value is either removed or inserted, depending on whether294* or not it is found in the tree. The test periodically checks that both295* trees have the same data and does consistency checks. This stress296* option can also be run on its own from the command line.297*/298static int299stress_tree(zfs_btree_t *bt, char *why)300{301(void) why;302avl_tree_t avl;303int_node_t *node;304struct timeval tp;305time_t t0;306int insertions = 0, removals = 0, iterations = 0;307u_longlong_t max = 0, min = UINT64_MAX;308309(void) gettimeofday(&tp, NULL);310t0 = tp.tv_sec;311312avl_create(&avl, avl_compare, sizeof (int_node_t),313offsetof(int_node_t, node));314315while (1) {316zfs_btree_index_t bt_idx = {0};317avl_index_t avl_idx = {0};318319uint64_t randval = random() % tree_limit;320node = malloc(sizeof (*node));321if (node == NULL) {322perror("malloc");323exit(EXIT_FAILURE);324}325node->data = randval;326327max = randval > max ? randval : max;328min = randval < min ? randval : min;329330void *ret = avl_find(&avl, node, &avl_idx);331if (ret == NULL) {332insertions++;333avl_insert(&avl, node, avl_idx);334ASSERT3P(zfs_btree_find(bt, &randval, &bt_idx), ==,335NULL);336zfs_btree_add_idx(bt, &randval, &bt_idx);337verify_node(&avl, bt, node);338} else {339removals++;340verify_node(&avl, bt, ret);341zfs_btree_remove(bt, &randval);342avl_remove(&avl, ret);343free(ret);344free(node);345}346347zfs_btree_verify(bt);348349iterations++;350if (iterations % contents_frequency == 0) {351verify_contents(&avl, bt);352}353354zfs_btree_verify(bt);355356(void) gettimeofday(&tp, NULL);357if (tp.tv_sec > t0 + stress_timeout) {358fprintf(stderr, "insertions/removals: %u/%u\nmax/min: "359"%llu/%llu\n", insertions, removals, max, min);360break;361}362}363364void *avl_cookie = NULL;365while ((node = avl_destroy_nodes(&avl, &avl_cookie)) != NULL)366free(node);367avl_destroy(&avl);368369if (stress_only) {370zfs_btree_index_t *idx = NULL;371while (zfs_btree_destroy_nodes(bt, &idx) != NULL)372;373zfs_btree_verify(bt);374}375376return (0);377}378379/*380* Verify inserting a duplicate value will cause a crash.381* Note: negative test; return of 0 is a failure.382*/383static int384insert_duplicate(zfs_btree_t *bt)385{386uint64_t i = 23456;387zfs_btree_index_t bt_idx = {0};388389if (zfs_btree_find(bt, &i, &bt_idx) != NULL) {390fprintf(stderr, "Found value in empty tree.\n");391return (0);392}393zfs_btree_add_idx(bt, &i, &bt_idx);394if (zfs_btree_find(bt, &i, &bt_idx) == NULL) {395fprintf(stderr, "Did not find expected value.\n");396return (0);397}398399/* Crash on inserting a duplicate */400zfs_btree_add_idx(bt, &i, NULL);401402return (0);403}404405/*406* Verify removing a non-existent value will cause a crash.407* Note: negative test; return of 0 is a failure.408*/409static int410remove_missing(zfs_btree_t *bt)411{412uint64_t i = 23456;413zfs_btree_index_t bt_idx = {0};414415if (zfs_btree_find(bt, &i, &bt_idx) != NULL) {416fprintf(stderr, "Found value in empty tree.\n");417return (0);418}419420/* Crash removing a nonexistent entry */421zfs_btree_remove(bt, &i);422423return (0);424}425426static int427do_negative_test(zfs_btree_t *bt, char *test_name)428{429int rval = 0;430struct rlimit rlim = {0};431432(void) setrlimit(RLIMIT_CORE, &rlim);433434if (strcmp(test_name, "insert_duplicate") == 0) {435rval = insert_duplicate(bt);436} else if (strcmp(test_name, "remove_missing") == 0) {437rval = remove_missing(bt);438}439440/*441* Return 0, since callers will expect non-zero return values for442* these tests, and we should have crashed before getting here anyway.443*/444(void) fprintf(stderr, "Test: %s returned %d.\n", test_name, rval);445return (0);446}447448typedef struct btree_test {449const char *name;450int (*func)(zfs_btree_t *, char *);451} btree_test_t;452453static 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};460461int462main(int argc, char *argv[])463{464char *negative_test = NULL;465int failed_tests = 0;466struct timeval tp;467zfs_btree_t bt;468int c;469470while ((c = getopt(argc, argv, "c:l:n:r:st:")) != -1) {471switch (c) {472case 'c':473contents_frequency = atoi(optarg);474break;475case 'l':476tree_limit = atoi(optarg);477break;478case 'n':479negative_test = optarg;480break;481case 'r':482seed = atoi(optarg);483break;484case 's':485stress_only = B_TRUE;486break;487case 't':488stress_timeout = atoi(optarg);489break;490case 'h':491default:492usage(1);493break;494}495}496497if (seed == 0) {498(void) gettimeofday(&tp, NULL);499seed = tp.tv_sec;500}501srandom(seed);502503zfs_btree_init();504zfs_btree_create(&bt, zfs_btree_compare, NULL, sizeof (uint64_t));505506/*507* This runs the named negative test. None of them should508* return, as they both cause crashes.509*/510if (negative_test) {511return (do_negative_test(&bt, negative_test));512}513514fprintf(stderr, "Seed: %u\n", seed);515516/*517* This is a stress test that does operations on a btree over the518* requested timeout period, verifying them against identical519* operations in an avl tree.520*/521if (stress_only != 0) {522return (stress_tree(&bt, NULL));523}524525/* Do the positive tests */526btree_test_t *test = &test_table[0];527while (test->name) {528int retval;529char why[BUFSIZE] = {0};530zfs_btree_index_t *idx = NULL;531532(void) fprintf(stdout, "%-20s", test->name);533retval = test->func(&bt, why);534535if (retval == 0) {536(void) fprintf(stdout, "ok\n");537} else {538(void) fprintf(stdout, "failed with %d\n", retval);539if (strlen(why) != 0)540(void) fprintf(stdout, "\t%s\n", why);541why[0] = '\0';542failed_tests++;543}544545/* Remove all the elements and re-verify the tree */546while (zfs_btree_destroy_nodes(&bt, &idx) != NULL)547;548zfs_btree_verify(&bt);549550test++;551}552553zfs_btree_verify(&bt);554zfs_btree_fini();555556return (failed_tests);557}558559560