mirror of
https://git.proxmox.com/git/mirror_zfs.git
synced 2024-11-17 18:11:00 +03:00
523fc80069
Additional test cases for the btree implementation, see #9181. Reviewed-by: Paul Dagnelie <pcd@delphix.com> Reviewed-by: Brian Behlendorf <behlendorf1@llnl.gov> Signed-off-by: John Kennedy <john.kennedy@delphix.com> Closes #9717
555 lines
13 KiB
C
555 lines
13 KiB
C
/*
|
|
* This file and its contents are supplied under the terms of the
|
|
* Common Development and Distribution License ("CDDL"), version 1.0.
|
|
* You may only use this file in accordance with the terms of version
|
|
* 1.0 of the CDDL.
|
|
*
|
|
* A full copy of the text of the CDDL should have accompanied this
|
|
* source. A copy of the CDDL is also available via the Internet at
|
|
* http://www.illumos.org/license/CDDL.
|
|
*/
|
|
|
|
/*
|
|
* Copyright (c) 2019 by Delphix. All rights reserved.
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <sys/avl.h>
|
|
#include <sys/btree.h>
|
|
#include <sys/time.h>
|
|
#include <sys/resource.h>
|
|
|
|
#define BUFSIZE 256
|
|
|
|
int seed = 0;
|
|
int stress_timeout = 180;
|
|
int contents_frequency = 100;
|
|
int tree_limit = 64 * 1024;
|
|
boolean_t stress_only = B_FALSE;
|
|
|
|
static void
|
|
usage(int exit_value)
|
|
{
|
|
(void) fprintf(stderr, "Usage:\tbtree_test -n <test_name>\n");
|
|
(void) fprintf(stderr, "\tbtree_test -s [-r <seed>] [-l <limit>] "
|
|
"[-t timeout>] [-c check_contents]\n");
|
|
(void) fprintf(stderr, "\tbtree_test [-r <seed>] [-l <limit>] "
|
|
"[-t timeout>] [-c check_contents]\n");
|
|
(void) fprintf(stderr, "\n With the -n option, run the named "
|
|
"negative test. With the -s option,\n");
|
|
(void) fprintf(stderr, " run the stress test according to the "
|
|
"other options passed. With\n");
|
|
(void) fprintf(stderr, " neither, run all the positive tests, "
|
|
"including the stress test with\n");
|
|
(void) fprintf(stderr, " the default options.\n");
|
|
(void) fprintf(stderr, "\n Options that control the stress test\n");
|
|
(void) fprintf(stderr, "\t-c stress iterations after which to compare "
|
|
"tree contents [default: 100]\n");
|
|
(void) fprintf(stderr, "\t-l the largest value to allow in the tree "
|
|
"[default: 1M]\n");
|
|
(void) fprintf(stderr, "\t-r random seed [default: from "
|
|
"gettimeofday()]\n");
|
|
(void) fprintf(stderr, "\t-t seconds to let the stress test run "
|
|
"[default: 180]\n");
|
|
exit(exit_value);
|
|
}
|
|
|
|
typedef struct int_node {
|
|
avl_node_t node;
|
|
uint64_t data;
|
|
} int_node_t;
|
|
|
|
/*
|
|
* Utility functions
|
|
*/
|
|
|
|
static int
|
|
avl_compare(const void *v1, const void *v2)
|
|
{
|
|
const int_node_t *n1 = v1;
|
|
const int_node_t *n2 = v2;
|
|
uint64_t a = n1->data;
|
|
uint64_t b = n2->data;
|
|
|
|
return (TREE_CMP(a, b));
|
|
}
|
|
|
|
static int
|
|
zfs_btree_compare(const void *v1, const void *v2)
|
|
{
|
|
const uint64_t *a = v1;
|
|
const uint64_t *b = v2;
|
|
|
|
return (TREE_CMP(*a, *b));
|
|
}
|
|
|
|
static void
|
|
verify_contents(avl_tree_t *avl, zfs_btree_t *bt)
|
|
{
|
|
static int count = 0;
|
|
zfs_btree_index_t bt_idx = {0};
|
|
int_node_t *node;
|
|
uint64_t *data;
|
|
|
|
boolean_t forward = count % 2 == 0 ? B_TRUE : B_FALSE;
|
|
count++;
|
|
|
|
ASSERT3U(avl_numnodes(avl), ==, zfs_btree_numnodes(bt));
|
|
if (forward == B_TRUE) {
|
|
node = avl_first(avl);
|
|
data = zfs_btree_first(bt, &bt_idx);
|
|
} else {
|
|
node = avl_last(avl);
|
|
data = zfs_btree_last(bt, &bt_idx);
|
|
}
|
|
|
|
while (node != NULL) {
|
|
ASSERT3U(*data, ==, node->data);
|
|
if (forward == B_TRUE) {
|
|
data = zfs_btree_next(bt, &bt_idx, &bt_idx);
|
|
node = AVL_NEXT(avl, node);
|
|
} else {
|
|
data = zfs_btree_prev(bt, &bt_idx, &bt_idx);
|
|
node = AVL_PREV(avl, node);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
verify_node(avl_tree_t *avl, zfs_btree_t *bt, int_node_t *node)
|
|
{
|
|
zfs_btree_index_t bt_idx = {0};
|
|
zfs_btree_index_t bt_idx2 = {0};
|
|
int_node_t *inp;
|
|
uint64_t data = node->data;
|
|
uint64_t *rv = NULL;
|
|
|
|
ASSERT3U(avl_numnodes(avl), ==, zfs_btree_numnodes(bt));
|
|
ASSERT3P((rv = (uint64_t *)zfs_btree_find(bt, &data, &bt_idx)), !=,
|
|
NULL);
|
|
ASSERT3S(*rv, ==, data);
|
|
ASSERT3P(zfs_btree_get(bt, &bt_idx), !=, NULL);
|
|
ASSERT3S(data, ==, *(uint64_t *)zfs_btree_get(bt, &bt_idx));
|
|
|
|
if ((inp = AVL_NEXT(avl, node)) != NULL) {
|
|
ASSERT3P((rv = zfs_btree_next(bt, &bt_idx, &bt_idx2)), !=,
|
|
NULL);
|
|
ASSERT3P(rv, ==, zfs_btree_get(bt, &bt_idx2));
|
|
ASSERT3S(inp->data, ==, *rv);
|
|
} else {
|
|
ASSERT3U(data, ==, *(uint64_t *)zfs_btree_last(bt, &bt_idx));
|
|
}
|
|
|
|
if ((inp = AVL_PREV(avl, node)) != NULL) {
|
|
ASSERT3P((rv = zfs_btree_prev(bt, &bt_idx, &bt_idx2)), !=,
|
|
NULL);
|
|
ASSERT3P(rv, ==, zfs_btree_get(bt, &bt_idx2));
|
|
ASSERT3S(inp->data, ==, *rv);
|
|
} else {
|
|
ASSERT3U(data, ==, *(uint64_t *)zfs_btree_first(bt, &bt_idx));
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Tests
|
|
*/
|
|
|
|
/* Verify that zfs_btree_find works correctly with a NULL index. */
|
|
static int
|
|
find_without_index(zfs_btree_t *bt, char *why)
|
|
{
|
|
u_longlong_t *p, i = 12345;
|
|
|
|
zfs_btree_add(bt, &i);
|
|
if ((p = (u_longlong_t *)zfs_btree_find(bt, &i, NULL)) == NULL ||
|
|
*p != i) {
|
|
snprintf(why, BUFSIZE, "Unexpectedly found %llu\n",
|
|
p == NULL ? 0 : *p);
|
|
return (1);
|
|
}
|
|
|
|
i++;
|
|
|
|
if ((p = (u_longlong_t *)zfs_btree_find(bt, &i, NULL)) != NULL) {
|
|
snprintf(why, BUFSIZE, "Found bad value: %llu\n", *p);
|
|
return (1);
|
|
}
|
|
|
|
return (0);
|
|
}
|
|
|
|
/* Verify simple insertion and removal from the tree. */
|
|
static int
|
|
insert_find_remove(zfs_btree_t *bt, char *why)
|
|
{
|
|
u_longlong_t *p, i = 12345;
|
|
zfs_btree_index_t bt_idx = {0};
|
|
|
|
/* Insert 'i' into the tree, and attempt to find it again. */
|
|
zfs_btree_add(bt, &i);
|
|
if ((p = (u_longlong_t *)zfs_btree_find(bt, &i, &bt_idx)) == NULL) {
|
|
snprintf(why, BUFSIZE, "Didn't find value in tree\n");
|
|
return (1);
|
|
} else if (*p != i) {
|
|
snprintf(why, BUFSIZE, "Found (%llu) in tree\n", *p);
|
|
return (1);
|
|
}
|
|
ASSERT3S(zfs_btree_numnodes(bt), ==, 1);
|
|
zfs_btree_verify(bt);
|
|
|
|
/* Remove 'i' from the tree, and verify it is not found. */
|
|
zfs_btree_remove(bt, &i);
|
|
if ((p = (u_longlong_t *)zfs_btree_find(bt, &i, &bt_idx)) != NULL) {
|
|
snprintf(why, BUFSIZE, "Found removed value (%llu)\n", *p);
|
|
return (1);
|
|
}
|
|
ASSERT3S(zfs_btree_numnodes(bt), ==, 0);
|
|
zfs_btree_verify(bt);
|
|
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* Add a number of random entries into a btree and avl tree. Then walk them
|
|
* backwards and forwards while emptying the tree, verifying the trees look
|
|
* the same.
|
|
*/
|
|
static int
|
|
drain_tree(zfs_btree_t *bt, char *why)
|
|
{
|
|
uint64_t *p;
|
|
avl_tree_t avl;
|
|
int i = 0;
|
|
int_node_t *node;
|
|
avl_index_t avl_idx = {0};
|
|
zfs_btree_index_t bt_idx = {0};
|
|
|
|
avl_create(&avl, avl_compare, sizeof (int_node_t),
|
|
offsetof(int_node_t, node));
|
|
|
|
/* Fill both trees with the same data */
|
|
for (i = 0; i < 64 * 1024; i++) {
|
|
void *ret;
|
|
|
|
u_longlong_t randval = random();
|
|
node = malloc(sizeof (int_node_t));
|
|
if ((p = (uint64_t *)zfs_btree_find(bt, &randval, &bt_idx)) !=
|
|
NULL) {
|
|
continue;
|
|
}
|
|
zfs_btree_add_idx(bt, &randval, &bt_idx);
|
|
|
|
node->data = randval;
|
|
if ((ret = avl_find(&avl, node, &avl_idx)) != NULL) {
|
|
snprintf(why, BUFSIZE, "Found in avl: %llu\n", randval);
|
|
return (1);
|
|
}
|
|
avl_insert(&avl, node, avl_idx);
|
|
}
|
|
|
|
/* Remove data from either side of the trees, comparing the data */
|
|
while (avl_numnodes(&avl) != 0) {
|
|
uint64_t *data;
|
|
|
|
ASSERT3U(avl_numnodes(&avl), ==, zfs_btree_numnodes(bt));
|
|
if (avl_numnodes(&avl) % 2 == 0) {
|
|
node = avl_first(&avl);
|
|
data = zfs_btree_first(bt, &bt_idx);
|
|
} else {
|
|
node = avl_last(&avl);
|
|
data = zfs_btree_last(bt, &bt_idx);
|
|
}
|
|
ASSERT3U(node->data, ==, *data);
|
|
zfs_btree_remove_idx(bt, &bt_idx);
|
|
avl_remove(&avl, node);
|
|
|
|
if (avl_numnodes(&avl) == 0) {
|
|
break;
|
|
}
|
|
|
|
node = avl_first(&avl);
|
|
ASSERT3U(node->data, ==,
|
|
*(uint64_t *)zfs_btree_first(bt, NULL));
|
|
node = avl_last(&avl);
|
|
ASSERT3U(node->data, ==, *(uint64_t *)zfs_btree_last(bt, NULL));
|
|
}
|
|
ASSERT3S(zfs_btree_numnodes(bt), ==, 0);
|
|
|
|
void *avl_cookie = NULL;
|
|
while ((node = avl_destroy_nodes(&avl, &avl_cookie)) != NULL)
|
|
free(node);
|
|
avl_destroy(&avl);
|
|
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* This test uses an avl and btree, and continually processes new random
|
|
* values. Each value is either removed or inserted, depending on whether
|
|
* or not it is found in the tree. The test periodically checks that both
|
|
* trees have the same data and does consistency checks. This stress
|
|
* option can also be run on its own from the command line.
|
|
*/
|
|
static int
|
|
stress_tree(zfs_btree_t *bt, char *why)
|
|
{
|
|
avl_tree_t avl;
|
|
int_node_t *node;
|
|
struct timeval tp;
|
|
time_t t0;
|
|
int insertions = 0, removals = 0, iterations = 0;
|
|
u_longlong_t max = 0, min = UINT64_MAX;
|
|
|
|
(void) gettimeofday(&tp, NULL);
|
|
t0 = tp.tv_sec;
|
|
|
|
avl_create(&avl, avl_compare, sizeof (int_node_t),
|
|
offsetof(int_node_t, node));
|
|
|
|
while (1) {
|
|
zfs_btree_index_t bt_idx = {0};
|
|
avl_index_t avl_idx = {0};
|
|
|
|
uint64_t randval = random() % tree_limit;
|
|
node = malloc(sizeof (*node));
|
|
node->data = randval;
|
|
|
|
max = randval > max ? randval : max;
|
|
min = randval < min ? randval : min;
|
|
|
|
void *ret = avl_find(&avl, node, &avl_idx);
|
|
if (ret == NULL) {
|
|
insertions++;
|
|
avl_insert(&avl, node, avl_idx);
|
|
ASSERT3P(zfs_btree_find(bt, &randval, &bt_idx), ==,
|
|
NULL);
|
|
zfs_btree_add_idx(bt, &randval, &bt_idx);
|
|
verify_node(&avl, bt, node);
|
|
} else {
|
|
removals++;
|
|
verify_node(&avl, bt, ret);
|
|
zfs_btree_remove(bt, &randval);
|
|
avl_remove(&avl, ret);
|
|
free(ret);
|
|
free(node);
|
|
}
|
|
|
|
zfs_btree_verify(bt);
|
|
|
|
iterations++;
|
|
if (iterations % contents_frequency == 0) {
|
|
verify_contents(&avl, bt);
|
|
}
|
|
|
|
zfs_btree_verify(bt);
|
|
|
|
(void) gettimeofday(&tp, NULL);
|
|
if (tp.tv_sec > t0 + stress_timeout) {
|
|
fprintf(stderr, "insertions/removals: %u/%u\nmax/min: "
|
|
"%llu/%llu\n", insertions, removals, max, min);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void *avl_cookie = NULL;
|
|
while ((node = avl_destroy_nodes(&avl, &avl_cookie)) != NULL)
|
|
free(node);
|
|
avl_destroy(&avl);
|
|
|
|
if (stress_only) {
|
|
zfs_btree_index_t *idx = NULL;
|
|
uint64_t *rv;
|
|
|
|
while ((rv = zfs_btree_destroy_nodes(bt, &idx)) != NULL)
|
|
;
|
|
zfs_btree_verify(bt);
|
|
}
|
|
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* Verify inserting a duplicate value will cause a crash.
|
|
* Note: negative test; return of 0 is a failure.
|
|
*/
|
|
static int
|
|
insert_duplicate(zfs_btree_t *bt)
|
|
{
|
|
uint64_t *p, i = 23456;
|
|
zfs_btree_index_t bt_idx = {0};
|
|
|
|
if ((p = (uint64_t *)zfs_btree_find(bt, &i, &bt_idx)) != NULL) {
|
|
fprintf(stderr, "Found value in empty tree.\n");
|
|
return (0);
|
|
}
|
|
zfs_btree_add_idx(bt, &i, &bt_idx);
|
|
if ((p = (uint64_t *)zfs_btree_find(bt, &i, &bt_idx)) == NULL) {
|
|
fprintf(stderr, "Did not find expected value.\n");
|
|
return (0);
|
|
}
|
|
|
|
/* Crash on inserting a duplicate */
|
|
zfs_btree_add_idx(bt, &i, NULL);
|
|
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* Verify removing a non-existent value will cause a crash.
|
|
* Note: negative test; return of 0 is a failure.
|
|
*/
|
|
static int
|
|
remove_missing(zfs_btree_t *bt)
|
|
{
|
|
uint64_t *p, i = 23456;
|
|
zfs_btree_index_t bt_idx = {0};
|
|
|
|
if ((p = (uint64_t *)zfs_btree_find(bt, &i, &bt_idx)) != NULL) {
|
|
fprintf(stderr, "Found value in empty tree.\n");
|
|
return (0);
|
|
}
|
|
|
|
/* Crash removing a nonexistent entry */
|
|
zfs_btree_remove(bt, &i);
|
|
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
do_negative_test(zfs_btree_t *bt, char *test_name)
|
|
{
|
|
int rval = 0;
|
|
struct rlimit rlim = {0};
|
|
setrlimit(RLIMIT_CORE, &rlim);
|
|
|
|
if (strcmp(test_name, "insert_duplicate") == 0) {
|
|
rval = insert_duplicate(bt);
|
|
} else if (strcmp(test_name, "remove_missing") == 0) {
|
|
rval = remove_missing(bt);
|
|
}
|
|
|
|
/*
|
|
* Return 0, since callers will expect non-zero return values for
|
|
* these tests, and we should have crashed before getting here anyway.
|
|
*/
|
|
(void) fprintf(stderr, "Test: %s returned %d.\n", test_name, rval);
|
|
return (0);
|
|
}
|
|
|
|
typedef struct btree_test {
|
|
const char *name;
|
|
int (*func)(zfs_btree_t *, char *);
|
|
} btree_test_t;
|
|
|
|
static btree_test_t test_table[] = {
|
|
{ "insert_find_remove", insert_find_remove },
|
|
{ "find_without_index", find_without_index },
|
|
{ "drain_tree", drain_tree },
|
|
{ "stress_tree", stress_tree },
|
|
{ NULL, NULL }
|
|
};
|
|
|
|
int
|
|
main(int argc, char *argv[])
|
|
{
|
|
char *negative_test = NULL;
|
|
int failed_tests = 0;
|
|
struct timeval tp;
|
|
zfs_btree_t bt;
|
|
char c;
|
|
|
|
while ((c = getopt(argc, argv, "c:l:n:r:st:")) != -1) {
|
|
switch (c) {
|
|
case 'c':
|
|
contents_frequency = atoi(optarg);
|
|
break;
|
|
case 'l':
|
|
tree_limit = atoi(optarg);
|
|
break;
|
|
case 'n':
|
|
negative_test = optarg;
|
|
break;
|
|
case 'r':
|
|
seed = atoi(optarg);
|
|
break;
|
|
case 's':
|
|
stress_only = B_TRUE;
|
|
break;
|
|
case 't':
|
|
stress_timeout = atoi(optarg);
|
|
break;
|
|
case 'h':
|
|
default:
|
|
usage(1);
|
|
break;
|
|
}
|
|
}
|
|
argc -= optind;
|
|
argv += optind;
|
|
optind = 1;
|
|
|
|
|
|
if (seed == 0) {
|
|
(void) gettimeofday(&tp, NULL);
|
|
seed = tp.tv_sec;
|
|
}
|
|
srandom(seed);
|
|
|
|
zfs_btree_init();
|
|
zfs_btree_create(&bt, zfs_btree_compare, sizeof (uint64_t));
|
|
|
|
/*
|
|
* This runs the named negative test. None of them should
|
|
* return, as they both cause crashes.
|
|
*/
|
|
if (negative_test) {
|
|
return (do_negative_test(&bt, negative_test));
|
|
}
|
|
|
|
fprintf(stderr, "Seed: %u\n", seed);
|
|
|
|
/*
|
|
* This is a stress test that does operations on a btree over the
|
|
* requested timeout period, verifying them against identical
|
|
* operations in an avl tree.
|
|
*/
|
|
if (stress_only != 0) {
|
|
return (stress_tree(&bt, NULL));
|
|
}
|
|
|
|
/* Do the positive tests */
|
|
btree_test_t *test = &test_table[0];
|
|
while (test->name) {
|
|
int retval;
|
|
uint64_t *rv;
|
|
char why[BUFSIZE] = {0};
|
|
zfs_btree_index_t *idx = NULL;
|
|
|
|
(void) fprintf(stdout, "%-20s", test->name);
|
|
retval = test->func(&bt, why);
|
|
|
|
if (retval == 0) {
|
|
(void) fprintf(stdout, "ok\n");
|
|
} else {
|
|
(void) fprintf(stdout, "failed with %d\n", retval);
|
|
if (strlen(why) != 0)
|
|
(void) fprintf(stdout, "\t%s\n", why);
|
|
why[0] = '\0';
|
|
failed_tests++;
|
|
}
|
|
|
|
/* Remove all the elements and re-verify the tree */
|
|
while ((rv = zfs_btree_destroy_nodes(&bt, &idx)) != NULL)
|
|
;
|
|
zfs_btree_verify(&bt);
|
|
|
|
test++;
|
|
}
|
|
|
|
zfs_btree_verify(&bt);
|
|
zfs_btree_fini();
|
|
|
|
return (failed_tests);
|
|
}
|