1 /* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 // vim: ft=cpp:expandtab:ts=8:sw=4:softtabstop=4:
3 #ident "$Id$"
4 /*======
5 This file is part of PerconaFT.
6 
7 
8 Copyright (c) 2006, 2015, Percona and/or its affiliates. All rights reserved.
9 
10     PerconaFT is free software: you can redistribute it and/or modify
11     it under the terms of the GNU General Public License, version 2,
12     as published by the Free Software Foundation.
13 
14     PerconaFT is distributed in the hope that it will be useful,
15     but WITHOUT ANY WARRANTY; without even the implied warranty of
16     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17     GNU General Public License for more details.
18 
19     You should have received a copy of the GNU General Public License
20     along with PerconaFT.  If not, see <http://www.gnu.org/licenses/>.
21 
22 ----------------------------------------
23 
24     PerconaFT is free software: you can redistribute it and/or modify
25     it under the terms of the GNU Affero General Public License, version 3,
26     as published by the Free Software Foundation.
27 
28     PerconaFT is distributed in the hope that it will be useful,
29     but WITHOUT ANY WARRANTY; without even the implied warranty of
30     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
31     GNU Affero General Public License for more details.
32 
33     You should have received a copy of the GNU Affero General Public License
34     along with PerconaFT.  If not, see <http://www.gnu.org/licenses/>.
35 ======= */
36 
37 #ident "Copyright (c) 2006, 2015, Percona and/or its affiliates. All rights reserved."
38 
39 #include "test.h"
40 
41 // verify that key_range64 returns reasonable results after inserting rows into a tree.
42 // variations include:
43 // 1. trickle load versus bulk load
44 // 2. sequential keys versus random keys
45 // 3. basements on disk versus basements in memory
46 
47 #include <db.h>
48 #include <unistd.h>
49 #include <sys/stat.h>
50 
51 static DB_ENV *env = NULL;
52 static DB_TXN *txn = NULL;
53 static DB *db = NULL;
54 static uint32_t db_page_size = 4096;
55 static uint32_t db_basement_size = 4096;
56 static const char *envdir = TOKU_TEST_FILENAME;
57 static uint64_t nrows = 30000;
58 static bool get_all = true;
59 static bool use_loader = false;
60 static bool random_keys = false;
61 
62 static int
my_compare(DB * this_db UU (),const DBT * a UU (),const DBT * b UU ())63 my_compare(DB *this_db UU(), const DBT *a UU(), const DBT *b UU()) {
64     assert(a->size == b->size);
65     return memcmp(a->data, b->data, a->size);
66 }
67 
68 static int
my_generate_row(DB * dest_db UU (),DB * src_db UU (),DBT_ARRAY * dest_keys UU (),DBT_ARRAY * dest_vals UU (),const DBT * src_key UU (),const DBT * src_val UU ())69 my_generate_row(DB *dest_db UU(), DB *src_db UU(), DBT_ARRAY *dest_keys UU(), DBT_ARRAY *dest_vals UU(), const DBT *src_key UU(), const DBT *src_val UU()) {
70     toku_dbt_array_resize(dest_keys, 1);
71     toku_dbt_array_resize(dest_vals, 1);
72     DBT *dest_key = &dest_keys->dbts[0];
73     DBT *dest_val = &dest_vals->dbts[0];
74 
75     assert(dest_key->flags == DB_DBT_REALLOC);
76     dest_key->data = toku_realloc(dest_key->data, src_key->size);
77     memcpy(dest_key->data, src_key->data, src_key->size);
78     dest_key->size = src_key->size;
79     assert(dest_val->flags == DB_DBT_REALLOC);
80     dest_val->data = toku_realloc(dest_val->data, src_val->size);
81     memcpy(dest_val->data, src_val->data, src_val->size);
82     dest_val->size = src_val->size;
83     return 0;
84 }
85 
86 static void
swap(uint64_t keys[],uint64_t i,uint64_t j)87 swap(uint64_t keys[], uint64_t i, uint64_t j) {
88     uint64_t t = keys[i]; keys[i] = keys[j]; keys[j] = t;
89 }
90 
91 static uint64_t
max64(uint64_t a,uint64_t b)92 max64(uint64_t a, uint64_t b) {
93     return a < b ? b : a;
94 }
95 
open_env(void)96 static void open_env(void) {
97     int r = db_env_create(&env, 0); CKERR(r);
98     env->set_errfile(env, stderr);
99     r = env->set_redzone(env, 0); CKERR(r);
100     r = env->set_generate_row_callback_for_put(env, my_generate_row); CKERR(r);
101     r = env->set_default_bt_compare(env, my_compare); CKERR(r);
102     r = env->open(env, envdir, DB_INIT_LOCK|DB_INIT_LOG|DB_INIT_MPOOL|DB_INIT_TXN|DB_CREATE|DB_PRIVATE, S_IRWXU+S_IRWXG+S_IRWXO); CKERR(r);
103 }
104 
105 static void
run_test(void)106 run_test(void) {
107     if (verbose) printf("%s %" PRIu64 "\n", __FUNCTION__, nrows);
108 
109     size_t key_size = 9;
110     size_t val_size = 9;
111     size_t est_row_size_with_overhead = 8 + key_size + 4 + val_size + 4 + 5; // xid + key + key_len + val + val_len + mvcc overhead
112     size_t rows_per_basement = db_basement_size / est_row_size_with_overhead;
113 
114     open_env();
115     int r;
116     r = db_create(&db, env, 0); CKERR(r);
117     r = db->set_pagesize(db, db_page_size); CKERR(r);
118     r = db->set_readpagesize(db, db_basement_size); CKERR(r);
119     r = env->txn_begin(env, 0, &txn, 0); CKERR(r);
120     r = db->open(db, txn, "foo.db", 0, DB_BTREE, DB_CREATE, S_IRWXU+S_IRWXG+S_IRWXO); CKERR(r);
121     r = txn->commit(txn, 0);    CKERR(r);
122 
123     uint64_t *XMALLOC_N(nrows, keys);
124     for (uint64_t i = 0; i < nrows; i++)
125         keys[i] = 2*i + 1;
126 
127     if (random_keys)
128         for (uint64_t i = 0; i < nrows; i++)
129             swap(keys, random() % nrows, random() % nrows);
130 
131     // insert keys 1, 3, 5, ... 2*(nrows-1) + 1
132     r = env->txn_begin(env, 0, &txn, 0); CKERR(r);
133     if (use_loader) {
134         DB_LOADER *loader = NULL;
135         r = env->create_loader(env, txn, &loader, db, 1, &db, NULL, NULL, 0); CKERR(r);
136         for (uint64_t i=0; i<nrows; i++) {
137             char key[100],val[100];
138             snprintf(key, sizeof key, "%08llu", (unsigned long long)keys[i]);
139             snprintf(val, sizeof val, "%08llu", (unsigned long long)keys[i]);
140 	    assert(1+strlen(key) == key_size && 1+strlen(val) == val_size);
141             DBT k,v;
142             r = loader->put(loader, dbt_init(&k, key, 1+strlen(key)), dbt_init(&v,val, 1+strlen(val))); CKERR(r);
143         }
144         r = loader->close(loader); CKERR(r);
145     } else {
146         for (uint64_t i=0; i<nrows; i++) {
147             char key[100],val[100];
148             snprintf(key, sizeof key, "%08llu", (unsigned long long)keys[i]);
149             snprintf(val, sizeof val, "%08llu", (unsigned long long)keys[i]);
150 	    assert(1+strlen(key) == key_size && 1+strlen(val) == val_size);
151             DBT k,v;
152             r = db->put(db, txn, dbt_init(&k, key, 1+strlen(key)), dbt_init(&v,val, 1+strlen(val)), 0); CKERR(r);
153         }
154     }
155     r = txn->commit(txn, 0);    CKERR(r);
156 
157     // close and reopen to get rid of basements
158     r = db->close(db, 0); CKERR(r); // close MUST flush the nodes of this db out of the cache table for this test to be valid
159     r = env->close(env, 0);   CKERR(r);
160     env = NULL;
161     open_env();
162 
163     r = db_create(&db, env, 0); CKERR(r);
164     r = env->txn_begin(env, 0, &txn, 0); CKERR(r);
165     r = db->open(db, txn, "foo.db", 0, DB_BTREE, 0, S_IRWXU+S_IRWXG+S_IRWXO); CKERR(r);
166     r = txn->commit(txn, 0); CKERR(r);
167 
168     r = env->txn_begin(env, 0, &txn, 0); CKERR(r);
169 
170     if (get_all) {
171         // read the basements into memory
172         for (uint64_t i=0; i<nrows; i++) {
173             char key[100];
174             snprintf(key, 100, "%08llu", (unsigned long long)2*i+1);
175             DBT k,v;
176             memset(&v, 0, sizeof(v));
177             r = db->get(db, txn, dbt_init(&k, key, 1+strlen(key)), &v, 0); CKERR(r);
178         }
179     }
180 
181     DB_BTREE_STAT64 s64;
182     r = db->stat64(db, txn, &s64); CKERR(r);
183     if (verbose)
184         printf("stats %" PRId64 " %" PRId64 "\n", s64.bt_nkeys, s64.bt_dsize);
185     if (use_loader) {
186 	assert(s64.bt_nkeys == nrows);
187 	assert(s64.bt_dsize == nrows * (key_size + val_size));
188     } else {
189 	assert(0 < s64.bt_nkeys && s64.bt_nkeys <= nrows);
190 	assert(0 < s64.bt_dsize && s64.bt_dsize <= nrows * (key_size + val_size));
191     }
192 
193     if (0) goto skipit; // debug: just write the tree
194 
195     bool last_basement;
196     last_basement = false;
197     // verify key_range for keys that exist in the tree
198     uint64_t random_fudge;
199     random_fudge = random_keys ? rows_per_basement + nrows / 10 : 0;
200     for (uint64_t i=0; i<nrows; i++) {
201 	char key[100];
202 	snprintf(key, 100, "%08llu", (unsigned long long)2*i+1);
203 	DBT k;
204 	uint64_t less,equal,greater;
205 	int is_exact;
206 	r = db->key_range64(db, txn, dbt_init(&k, key, 1+strlen(key)), &less, &equal, &greater, &is_exact); CKERR(r);
207 	if (verbose)
208             printf("key %llu/%llu %llu %llu %llu %llu\n", (unsigned long long)2*i, (unsigned long long)2*nrows, (unsigned long long)less, (unsigned long long)equal, (unsigned long long)greater,
209                    (unsigned long long)(less+equal+greater));
210         assert(is_exact == 0);
211         assert(0 < less + equal + greater);
212         if (use_loader) {
213             assert(less + equal + greater <= nrows);
214             if (get_all || last_basement) {
215                 assert(equal == 1);
216             } else if (i < nrows - rows_per_basement * 2) {
217                 assert(equal == 0);
218             } else if (i == nrows - 1) {
219                 assert(equal == 1);
220             } else if (equal == 1) {
221                 last_basement = true;
222             }
223             assert(less <= max64(i, i + rows_per_basement/2));
224             assert(greater <= nrows - less);
225         } else {
226             assert(less + equal + greater <= nrows + nrows / 8);
227             if (get_all || last_basement) {
228                 assert(equal == 1);
229             } else if (i < nrows - rows_per_basement * 2) {
230                 assert(equal == 0);
231             } else if (i == nrows - 1) {
232                 assert(equal == 1);
233             } else if (equal == 1) {
234                 last_basement = true;
235             }
236             uint64_t est_i = i * 2 + rows_per_basement;
237             assert(less <= est_i + random_fudge);
238             assert(greater <= nrows - i + rows_per_basement + random_fudge);
239 	}
240     }
241 
242     // verify key range for keys that do not exist in the tree
243     for (uint64_t i=0; i<1+nrows; i++) {
244 	char key[100];
245 	snprintf(key, 100, "%08llu", (unsigned long long)2*i);
246 	DBT k;
247 	uint64_t less,equal,greater;
248 	int is_exact;
249 	r = db->key_range64(db, txn, dbt_init(&k, key, 1+strlen(key)), &less, &equal, &greater, &is_exact); CKERR(r);
250 	if (verbose)
251             printf("key %llu/%llu %llu %llu %llu %llu\n", (unsigned long long)2*i, (unsigned long long)2*nrows, (unsigned long long)less, (unsigned long long)equal, (unsigned long long)greater,
252                    (unsigned long long)(less+equal+greater));
253         assert(is_exact == 0);
254         assert(0 < less + equal + greater);
255         if (use_loader) {
256             assert(less + equal + greater <= nrows);
257             assert(equal == 0);
258             assert(less <= max64(i, i + rows_per_basement/2));
259             assert(greater <= nrows - less);
260         } else {
261             assert(less + equal + greater <= nrows + nrows / 8);
262             assert(equal == 0);
263             uint64_t est_i = i * 2 + rows_per_basement;
264             assert(less <= est_i + random_fudge);
265             assert(greater <= nrows - i + rows_per_basement + random_fudge);
266         }
267     }
268 
269  skipit:
270     r = txn->commit(txn, 0);    CKERR(r);
271     r = db->close(db, 0);     CKERR(r);
272     r = env->close(env, 0);   CKERR(r);
273 
274     toku_free(keys);
275 }
276 
277 static int
usage(void)278 usage(void) {
279     fprintf(stderr, "-v (verbose)\n");
280     fprintf(stderr, "-q (quiet)\n");
281     fprintf(stderr, "--nrows %" PRIu64 " (number of rows)\n", nrows);
282     fprintf(stderr, "--nrows %" PRIu64 " (number of rows)\n", nrows);
283     fprintf(stderr, "--loader %u (use the loader to load the keys)\n", use_loader);
284     fprintf(stderr, "--get %u (get all keys before keyrange)\n", get_all);
285     fprintf(stderr, "--random_keys %u\n", random_keys);
286     fprintf(stderr, "--page_size %u\n", db_page_size);
287     fprintf(stderr, "--basement_size %u\n", db_basement_size);
288     return 1;
289 }
290 
291 int
test_main(int argc,char * const argv[])292 test_main (int argc , char * const argv[]) {
293     for (int i = 1 ; i < argc; i++) {
294         if (strcmp(argv[i], "-v") == 0 || strcmp(argv[i], "--verbose") == 0) {
295             verbose++;
296             continue;
297         }
298         if (strcmp(argv[i], "-q") == 0) {
299             if (verbose > 0)
300                 verbose--;
301             continue;
302         }
303         if (strcmp(argv[i], "--nrows") == 0 && i+1 < argc) {
304             nrows = atoll(argv[++i]);
305             continue;
306         }
307         if (strcmp(argv[i], "--get") == 0 && i+1 < argc) {
308             get_all = atoi(argv[++i]) != 0;
309             continue;
310         }
311         if (strcmp(argv[i], "--loader") == 0 && i+1 < argc) {
312             use_loader = atoi(argv[++i]) != 0;
313             continue;
314         }
315         if (strcmp(argv[i], "--random_keys") == 0 && i+1 < argc) {
316             random_keys = atoi(argv[++i]) != 0;
317             continue;
318         }
319         if (strcmp(argv[i], "--page_size") == 0 && i+1 < argc) {
320             db_page_size = atoi(argv[++i]);
321             continue;
322         }
323         if (strcmp(argv[i], "--basement_size") == 0 && i+1 < argc) {
324             db_basement_size = atoi(argv[++i]);
325             continue;
326         }
327         return usage();
328     }
329 
330     toku_os_recursive_delete(envdir);
331     int r = toku_os_mkdir(envdir, S_IRWXU+S_IRWXG+S_IRWXO);       CKERR(r);
332 
333     run_test();
334 
335     return 0;
336 }
337