1 /*
2  * See the file LICENSE for redistribution information.
3  *
4  * Copyright (c) 2005, 2013 Oracle and/or its affiliates.  All rights reserved.
5  *
6  * $Id$
7  */
8 
9 #include "bench.h"
10 #include "b_workload.h"
11 
12 static int   b_workload_dump_verbose_stats __P((DB *, CONFIG *));
13 static int   b_workload_is_del_workload __P((int));
14 static int   b_workload_is_get_workload __P((int));
15 static int   b_workload_is_put_workload __P((int));
16 static int   b_workload_run_mixed_workload __P((DB *, CONFIG *));
17 static int   b_workload_run_std_workload __P((DB *, CONFIG *));
18 static int   b_workload_usage __P((void));
19 static char *b_workload_workload_str __P((int));
20 
21 /*
22  * General TODO list:
23  * * The workload type. Might work better as a bitmask than the current enum.
24  * * Improve the verbose stats, so they can be easily parsed.
25  * * Think about doing automatic btree/hash comparison in here.
26  */
27 int
b_workload(argc,argv)28 b_workload(argc, argv)
29 	int argc;
30 	char *argv[];
31 {
32 	extern char *optarg;
33 	extern int optind, __db_getopt_reset;
34 	CONFIG conf;
35 	DB *dbp;
36 	DB_ENV *dbenv;
37 	int ch, ffactor, ksz;
38 
39 	dbenv = NULL;
40 	memset(&conf, 0, sizeof(conf));
41 	conf.seed = 124087;
42 	srand(conf.seed);
43 
44 	conf.pcount = 100000;
45 	conf.ts = "Btree";
46 	conf.type = DB_BTREE;
47 	conf.dsize = 20;
48 	conf.presize = 0;
49 	conf.workload = T_PUT_GET_DELETE;
50 
51 	__db_getopt_reset = 1;
52 	while ((ch = getopt(argc, argv, "b:c:d:e:g:ik:m:op:r:t:vw:")) != EOF)
53 		switch (ch) {
54 		case 'b':
55 			conf.cachesz = atoi(optarg);
56 			break;
57 		case 'c':
58 			conf.pcount = atoi(optarg);
59 			break;
60 		case 'd':
61 			conf.dsize = atoi(optarg);
62 			break;
63 		case 'e':
64 			conf.cursor_del = atoi(optarg);
65 			break;
66 		case 'g':
67 			conf.gcount = atoi(optarg);
68 			break;
69 		case 'i':
70 			conf.presize = 1;
71 			break;
72 		case 'k':
73 			conf.ksize = atoi(optarg);
74 			break;
75 		case 'm':
76 			conf.message = optarg;
77 			break;
78 		case 'o':
79 			conf.orderedkeys = 1;
80 			break;
81 		case 'p':
82 			conf.pagesz = atoi(optarg);
83 			break;
84 		case 'r':
85 			conf.num_dups = atoi(optarg);
86 			break;
87 		case 't':
88 			switch (optarg[0]) {
89 			case 'B': case 'b':
90 				conf.ts = "Btree";
91 				conf.type = DB_BTREE;
92 				break;
93 			case 'H': case 'h':
94 				if (b_util_have_hash())
95 					return (0);
96 				conf.ts = "Hash";
97 				conf.type = DB_HASH;
98 				break;
99 			default:
100 				return (b_workload_usage());
101 			}
102 			break;
103 		case 'v':
104 			conf.verbose = 1;
105 			break;
106 		case 'w':
107 			switch (optarg[0]) {
108 			case 'A':
109 				conf.workload = T_PUT_GET_DELETE;
110 				break;
111 			case 'B':
112 				conf.workload = T_GET;
113 				break;
114 			case 'C':
115 				conf.workload = T_PUT;
116 				break;
117 			case 'D':
118 				conf.workload = T_DELETE;
119 				break;
120 			case 'E':
121 				conf.workload = T_PUT_GET;
122 				break;
123 			case 'F':
124 				conf.workload = T_PUT_DELETE;
125 				break;
126 			case 'G':
127 				conf.workload = T_GET_DELETE;
128 				break;
129 			case 'H':
130 				conf.workload = T_MIXED;
131 				break;
132 			default:
133 				return (b_workload_usage());
134 			}
135 			break;
136 		case '?':
137 		default:
138 			fprintf(stderr, "Invalid option: %c\n", ch);
139 			return (b_workload_usage());
140 		}
141 	argc -= optind;
142 	argv += optind;
143 	if (argc != 0)
144 		return (b_workload_usage());
145 
146 	/*
147 	 * Validate the input parameters if specified.
148 	 */
149 	if (conf.pagesz != 0)
150 		DB_BENCH_ASSERT(conf.pagesz >= 512 && conf.pagesz <= 65536 &&
151 		   ((conf.pagesz & (conf.pagesz - 1)) == 0));
152 
153 	if (conf.cachesz != 0)
154 		DB_BENCH_ASSERT(conf.cachesz > 20480);
155 	DB_BENCH_ASSERT(conf.ksize == 0 || conf.orderedkeys == 0);
156 
157 	/* Create the environment. */
158 	DB_BENCH_ASSERT(db_env_create(&dbenv, 0) == 0);
159 	dbenv->set_errfile(dbenv, stderr);
160 	if (conf.cachesz != 0)
161 		DB_BENCH_ASSERT(
162 		    dbenv->set_cachesize(dbenv, 0, conf.cachesz, 0) == 0);
163 
164 #if DB_VERSION_MAJOR == 3 && DB_VERSION_MINOR < 1
165 	DB_BENCH_ASSERT(dbenv->open(dbenv, "TESTDIR",
166 	    NULL, DB_CREATE | DB_INIT_MPOOL | DB_PRIVATE, 0666) == 0);
167 #else
168 	DB_BENCH_ASSERT(dbenv->open(dbenv, "TESTDIR",
169 	    DB_CREATE | DB_INIT_MPOOL | DB_PRIVATE, 0666) == 0);
170 #endif
171 
172 	DB_BENCH_ASSERT(db_create(&dbp, dbenv, 0) == 0);
173 	if (conf.pagesz != 0)
174 		DB_BENCH_ASSERT(
175 		    dbp->set_pagesize(dbp, conf.pagesz) == 0);
176 	if (conf.presize != 0 && conf.type == DB_HASH) {
177 		ksz = (conf.orderedkeys != 0) ? sizeof(u_int32_t) : conf.ksize;
178 		if (ksz == 0)
179 			ksz = 10;
180 		ffactor = (conf.pagesz - 32)/(ksz + conf.dsize + 8);
181 		fprintf(stderr, "ffactor: %d\n", ffactor);
182 		DB_BENCH_ASSERT(
183 		    dbp->set_h_ffactor(dbp, ffactor) == 0);
184 		DB_BENCH_ASSERT(
185 		    dbp->set_h_nelem(dbp, conf.pcount*10) == 0);
186 	}
187 #if DB_VERSION_MAJOR > 4 || (DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR >= 1)
188 	DB_BENCH_ASSERT(dbp->open(
189 	    dbp, NULL, TESTFILE, NULL, conf.type, DB_CREATE, 0666) == 0);
190 #else
191 	DB_BENCH_ASSERT(dbp->open(
192 	    dbp, TESTFILE, NULL, conf.type, DB_CREATE, 0666) == 0);
193 #endif
194 
195 	if (conf.workload == T_MIXED)
196 		 b_workload_run_mixed_workload(dbp, &conf);
197 	else
198 		b_workload_run_std_workload(dbp, &conf);
199 
200 	if (b_workload_is_put_workload(conf.workload) == 0)
201 		timespecadd(&conf.tot_time, &conf.put_time);
202 	if (b_workload_is_get_workload(conf.workload) == 0)
203 		timespecadd(&conf.tot_time, &conf.get_time);
204 	if (b_workload_is_del_workload(conf.workload) == 0)
205 		timespecadd(&conf.tot_time, &conf.del_time);
206 
207 	/* Ensure data is flushed for following measurements. */
208 	DB_BENCH_ASSERT(dbp->sync(dbp, 0) == 0);
209 
210 	if (conf.verbose != 0)
211 		b_workload_dump_verbose_stats(dbp, &conf);
212 
213 	DB_BENCH_ASSERT(dbp->close(dbp, 0) == 0);
214 	DB_BENCH_ASSERT(dbenv->close(dbenv, 0) == 0);
215 
216 	/*
217 	 * Construct a string for benchmark output.
218 	 *
219 	 * Insert HTML in-line to make the output prettier -- ugly, but easy.
220 	 */
221 	printf("# workload test: %s: %s<br>%lu ops",
222 	    conf.ts, b_workload_workload_str(conf.workload), (u_long)conf.pcount);
223 	if (conf.ksize != 0)
224 		printf(", key size: %lu", (u_long)conf.ksize);
225 	if (conf.dsize != 0)
226 		printf(", data size: %lu", (u_long)conf.dsize);
227 	if (conf.pagesz != 0)
228 		printf(", page size: %lu", (u_long)conf.pagesz);
229 	else
230 		printf(", page size: default");
231 	if (conf.cachesz != 0)
232 		printf(", cache size: %lu", (u_long)conf.cachesz);
233 	else
234 		printf(", cache size: default");
235 	printf(", %s keys", conf.orderedkeys == 1 ? "ordered" : "unordered");
236 	printf(", num dups: %lu", (u_long)conf.num_dups);
237 	printf("\n");
238 
239 	if (conf.workload != T_MIXED) {
240 		if (conf.message != NULL)
241 			printf("%s %s ", conf.message, conf.ts);
242 		TIME_DISPLAY(conf.pcount, conf.tot_time);
243 	} else
244 		TIMER_DISPLAY(conf.pcount);
245 
246 	return (0);
247 }
248 
249 /*
250  * The mixed workload is designed to simulate a somewhat real
251  * usage scenario.
252  * NOTES: * rand is used to decide on the current operation. This will
253  *        be repeatable, since the same seed is always used.
254  *        * All added keys are stored in a FIFO queue, this is not very
255  *        space efficient, but is the best way I could come up with to
256  *        insert random key values, and be able to retrieve/delete them.
257  *        * TODO: the workload will currently only work with unordered
258  *        fixed length keys.
259  */
260 #define	GET_PROPORTION 90
261 #define	PUT_PROPORTION 7
262 #define	DEL_PROPORTION 3
263 
264 static int
b_workload_run_mixed_workload(dbp,config)265 b_workload_run_mixed_workload(dbp, config)
266 	DB *dbp;
267 	CONFIG *config;
268 {
269 	DBT key, data;
270 	size_t next_op, i, ioff, inscount;
271 	char kbuf[KBUF_LEN];
272 	struct bench_q operation_queue;
273 
274 	/* Having ordered insertion does not make sense here */
275 	DB_BENCH_ASSERT(config->orderedkeys == 0);
276 
277 	srand(config->seed);
278 	memset(&operation_queue, 0, sizeof(struct bench_q));
279 
280 	ioff = 0;
281 	INIT_KEY(key, config);
282 	memset(&data, 0, sizeof(data));
283 	DB_BENCH_ASSERT(
284 	    (data.data = malloc(data.size = config->dsize)) != NULL);
285 
286 	/*
287 	 * Add an initial sample set of data to the DB.
288 	 * This should add some stability, and reduce the likelihood
289 	 * of deleting all of the entries in the DB.
290 	 */
291 	inscount = 2 * config->pcount;
292 	if (inscount > 100000)
293 		inscount = 100000;
294 
295 	for (i = 0; i < inscount; ++i) {
296 		GET_KEY_NEXT(key, config, kbuf, i);
297 		BENCH_Q_TAIL_INSERT(operation_queue, kbuf);
298 		DB_BENCH_ASSERT(dbp->put(dbp, NULL, &key, &data, 0) == 0);
299 	}
300 
301 	TIMER_START;
302 	for (i = 0; i < config->pcount; ++i) {
303 		next_op = rand()%100;
304 
305 		if (next_op < GET_PROPORTION ) {
306 			BENCH_Q_POP_PUSH(operation_queue, kbuf);
307 			key.data = kbuf;
308 			key.size = sizeof(kbuf);
309 			dbp->get(dbp, NULL, &key, &data, 0);
310 		} else if (next_op < GET_PROPORTION+PUT_PROPORTION) {
311 			GET_KEY_NEXT(key, config, kbuf, i);
312 			BENCH_Q_TAIL_INSERT(operation_queue, kbuf);
313 			dbp->put(dbp, NULL, &key, &data, 0);
314 		} else {
315 			BENCH_Q_POP(operation_queue, kbuf);
316 			key.data = kbuf;
317 			key.size = sizeof(kbuf);
318 			dbp->del(dbp, NULL, &key, 0);
319 		}
320 	}
321 	TIMER_STOP;
322 	TIMER_GET(config->tot_time);
323 
324 	return (0);
325 }
326 
327 static int
b_workload_run_std_workload(dbp,config)328 b_workload_run_std_workload(dbp, config)
329 	DB *dbp;
330 	CONFIG *config;
331 {
332 	DBT key, data;
333 	DBC *dbc;
334 	u_int32_t i;
335 	int ret;
336 	char kbuf[KBUF_LEN];
337 
338 	/* Setup a key/data pair. */
339 	INIT_KEY(key, config);
340 	memset(&data, 0, sizeof(data));
341 	DB_BENCH_ASSERT(
342 	    (data.data = malloc(data.size = config->dsize)) != NULL);
343 
344 	/* Store the key/data pair count times. */
345 	TIMER_START;
346 	for (i = 0; i < config->pcount; ++i) {
347 		GET_KEY_NEXT(key, config, kbuf, i);
348 		DB_BENCH_ASSERT(dbp->put(dbp, NULL, &key, &data, 0) == 0);
349 	}
350 	TIMER_STOP;
351 	TIMER_GET(config->put_time);
352 
353 	if (b_workload_is_get_workload(config->workload) == 0) {
354 		TIMER_START;
355 		for (i = 0; i <= config->gcount; ++i) {
356 			DB_BENCH_ASSERT(dbp->cursor(dbp, NULL, &dbc, 0) == 0);
357 			while ((dbc->c_get(dbc, &key, &data, DB_NEXT)) == 0);
358 			DB_BENCH_ASSERT(dbc->c_close(dbc) == 0);
359 		}
360 		TIMER_STOP;
361 		TIMER_GET(config->get_time);
362 	}
363 
364 	if (b_workload_is_del_workload(config->workload) == 0) {
365 		/* reset rand to reproduce key sequence. */
366 		srand(config->seed);
367 
368 		TIMER_START;
369 		if (config->cursor_del != 0) {
370 			DB_BENCH_ASSERT(dbp->cursor(dbp, NULL, &dbc, 0) == 0);
371 			while (
372 			    (ret = dbc->c_get(dbc, &key, &data, DB_NEXT)) == 0)
373 				DB_BENCH_ASSERT(dbc->c_del(dbc, 0) == 0);
374 			DB_BENCH_ASSERT (ret == DB_NOTFOUND);
375 		} else {
376 			INIT_KEY(key, config);
377 			for (i = 0; i < config->pcount; ++i) {
378 				GET_KEY_NEXT(key, config, kbuf, i);
379 
380 				ret = dbp->del(dbp, NULL, &key, 0);
381 				/*
382 				 * Random key generation can cause dups,
383 				 * so NOTFOUND result is OK.
384 				 */
385 				if (config->ksize == 0)
386 					DB_BENCH_ASSERT
387 					    (ret == 0 || ret == DB_NOTFOUND);
388 				else
389 					DB_BENCH_ASSERT(ret == 0);
390 			}
391 		}
392 		TIMER_STOP;
393 		TIMER_GET(config->del_time);
394 	}
395 	return (0);
396 }
397 
398 static int
b_workload_dump_verbose_stats(dbp,config)399 b_workload_dump_verbose_stats(dbp, config)
400 	DB *dbp;
401 	CONFIG *config;
402 {
403 /*
404  * It would be nice to be able to define stat as _stat on
405  * Windows, but that substitutes _stat for the db call as well.
406  */
407 #ifdef DB_WIN32
408 	struct _stat fstat;
409 #else
410 	struct stat fstat;
411 #endif
412 	DB_HASH_STAT *hstat;
413 	DB_BTREE_STAT *bstat;
414 	double free_prop;
415 	char path[1024];
416 
417 #ifdef DB_BENCH_INCLUDE_CONFIG_SUMMARY
418 	printf("Completed workload benchmark.\n");
419 	printf("Configuration summary:\n");
420 	printf("\tworkload type: %d\n", (int)config->workload);
421 	printf("\tdatabase type: %s\n", config->ts);
422 	if (config->cachesz != 0)
423 		printf("\tcache size: %lu\n", (u_long)config->cachesz);
424 	if (config->pagesz != 0)
425 		printf("\tdatabase page size: %lu\n", (u_long)config->pagesz);
426 	printf("\tput element count: %lu\n", (u_long)config->pcount);
427 	if ( b_workload_is_get_workload(config->workload) == 0)
428 		printf("\tget element count: %lu\n", (u_long)config->gcount);
429 	if (config->orderedkeys)
430 		printf("\tInserting items in order\n");
431 	else if (config->ksize == 0)
432 		printf("\tInserting keys with size 10\n");
433 	else
434 		printf(
435 		    "\tInserting keys with size: %lu\n", (u_long)config->ksize);
436 
437 	printf("\tInserting data elements size: %lu\n", (u_long)config->dsize);
438 
439 	if (b_workload_is_del_workload(config->workload) == 0) {
440 		if (config->cursor_del)
441 			printf("\tDeleting items using a cursor\n");
442 		else
443 			printf("\tDeleting items without a cursor\n");
444 	}
445 #endif /* DB_BENCH_INCLUDE_CONFIG_SUMMARY */
446 
447 	if (b_workload_is_put_workload(config->workload) == 0)
448 		printf("%s Time spent inserting (%lu) (%s) items: %lu/%lu\n",
449 		    config->message[0] == '\0' ? "" : config->message,
450 		    (u_long)config->pcount, config->ts,
451 		    (u_long)config->put_time.tv_sec, config->put_time.tv_nsec);
452 
453 	if (b_workload_is_get_workload(config->workload) == 0)
454 		printf("%s Time spent getting (%lu) (%s) items: %lu/%lu\n",
455 		    config->message[0] == '\0' ? "" : config->message,
456 		    (u_long)config->pcount * ((config->gcount == 0) ?
457 		    1 : config->gcount), config->ts,
458 		    (u_long)config->get_time.tv_sec, config->get_time.tv_nsec);
459 
460 	if (b_workload_is_del_workload(config->workload) == 0)
461 		printf("%s Time spent deleting (%lu) (%s) items: %lu/%lu\n",
462 		    config->message[0] == '\0' ? "" : config->message,
463 		    (u_long)config->pcount, config->ts,
464 		    (u_long)config->del_time.tv_sec, config->del_time.tv_nsec);
465 
466 	(void)snprintf(path, sizeof(path),
467 	    "%s%c%s", TESTDIR, PATH_SEPARATOR[0], TESTFILE);
468 #ifdef DB_WIN32
469 	if (_stat(path, &fstat) == 0) {
470 #else
471 	if (stat(path, &fstat) == 0) {
472 #endif
473 		printf("%s Size of db file (%s): %lu K\n",
474 		    config->message[0] == '\0' ? "" : config->message,
475 		    config->ts, (u_long)fstat.st_size/1024);
476 	}
477 
478 	if (config->type == DB_HASH) {
479 #if DB_VERSION_MAJOR < 3 || DB_VERSION_MAJOR == 3 && DB_VERSION_MINOR <= 2
480 		DB_BENCH_ASSERT(dbp->stat(dbp, &hstat, NULL, 0) == 0);
481 #elif DB_VERSION_MAJOR < 4 || DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR <= 2
482 		DB_BENCH_ASSERT(dbp->stat(dbp, &hstat, 0) == 0);
483 #else
484 		DB_BENCH_ASSERT(dbp->stat(dbp, NULL, &hstat, 0) == 0);
485 #endif
486 		/*
487 		 * Hash fill factor is a bit tricky. Want to include
488 		 * both bucket and overflow buckets (not offpage).
489 		 */
490 		free_prop = hstat->hash_pagesize*hstat->hash_buckets;
491 		free_prop += hstat->hash_pagesize*hstat->hash_overflows;
492 		free_prop =
493 		    (free_prop - hstat->hash_bfree - hstat->hash_ovfl_free)/
494 		    free_prop;
495 		printf("%s db fill factor (%s): %.2f%%\n",
496 		    config->message[0] == '\0' ? "" : config->message,
497 		    config->ts, free_prop*100);
498 		free(hstat);
499 	} else { /* Btree */
500 #if DB_VERSION_MAJOR < 3 || DB_VERSION_MAJOR == 3 && DB_VERSION_MINOR <= 2
501 		DB_BENCH_ASSERT(dbp->stat(dbp, &bstat, NULL, 0) == 0);
502 #elif DB_VERSION_MAJOR < 4 || DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR <= 2
503 		DB_BENCH_ASSERT(dbp->stat(dbp, &bstat, 0) == 0);
504 #else
505 		DB_BENCH_ASSERT(dbp->stat(dbp, NULL, &bstat, 0) == 0);
506 #endif
507 		free_prop = bstat->bt_pagesize*bstat->bt_leaf_pg;
508 		free_prop = (free_prop-bstat->bt_leaf_pgfree)/free_prop;
509 		printf("%s db fill factor (%s): %.2f%%\n",
510 		    config->message[0] == '\0' ? "" : config->message,
511 		    config->ts, free_prop*100);
512 		free(bstat);
513 	}
514 	return (0);
515 }
516 
517 static char *
b_workload_workload_str(workload)518 b_workload_workload_str(workload)
519 	int workload;
520 {
521 	static char buf[128];
522 
523 	switch (workload) {
524 	case T_PUT_GET_DELETE:
525 		return ("PUT/GET/DELETE");
526 		/* NOTREACHED */
527 	case T_GET:
528 		return ("GET");
529 		/* NOTREACHED */
530 	case T_PUT:
531 		return ("PUT");
532 		/* NOTREACHED */
533 	case T_DELETE:
534 		return ("DELETE");
535 		/* NOTREACHED */
536 	case T_PUT_GET:
537 		return ("PUT/GET");
538 		/* NOTREACHED */
539 	case T_PUT_DELETE:
540 		return ("PUT/DELETE");
541 		/* NOTREACHED */
542 	case T_GET_DELETE:
543 		return ("GET/DELETE");
544 		/* NOTREACHED */
545 	case T_MIXED:
546 		snprintf(buf, sizeof(buf), "MIXED (get: %d, put: %d, del: %d)",
547 		    (int)GET_PROPORTION,
548 		    (int)PUT_PROPORTION, (int)DEL_PROPORTION);
549 		return (buf);
550 	default:
551 		break;
552 	}
553 
554 	exit(b_workload_usage());
555 	/* NOTREACHED */
556 }
557 
558 static int
b_workload_is_get_workload(workload)559 b_workload_is_get_workload(workload)
560 	int workload;
561 {
562 	switch (workload) {
563 	case T_GET:
564 	case T_PUT_GET:
565 	case T_PUT_GET_DELETE:
566 	case T_GET_DELETE:
567 		return 0;
568 	}
569 	return 1;
570 }
571 
572 static int
b_workload_is_put_workload(workload)573 b_workload_is_put_workload(workload)
574 	int workload;
575 {
576 	switch (workload) {
577 	case T_PUT:
578 	case T_PUT_GET:
579 	case T_PUT_GET_DELETE:
580 	case T_PUT_DELETE:
581 		return 0;
582 	}
583 	return 1;
584 }
585 
586 static int
b_workload_is_del_workload(workload)587 b_workload_is_del_workload(workload)
588 	int workload;
589 {
590 	switch (workload) {
591 	case T_DELETE:
592 	case T_PUT_DELETE:
593 	case T_PUT_GET_DELETE:
594 	case T_GET_DELETE:
595 		return 0;
596 	}
597 	return 1;
598 }
599 
600 static int
b_workload_usage()601 b_workload_usage()
602 {
603 	(void)fprintf(stderr,
604 	    "usage: b_workload [-b cachesz] [-c count] [-d bytes] [-e]\n");
605 	(void)fprintf(stderr,
606 	    "\t[-g getitrs] [-i] [-k keysize] [-m message] [-o] [-p pagesz]\n");
607 	(void)fprintf(stderr, "\t[-r dup_count] [-t type] [-w type]\n");
608 
609 	(void)fprintf(stderr, "Where:\n");
610 	(void)fprintf(stderr, "\t-b the size of the DB cache.\n");
611 	(void)fprintf(stderr, "\t-c the number of elements to be measured.\n");
612 	(void)fprintf(stderr, "\t-d the size of each data element.\n");
613 	(void)fprintf(stderr, "\t-e delete entries using a cursor.\n");
614 	(void)fprintf(stderr, "\t-g number of get cursor traverses.\n");
615 	(void)fprintf(stderr, "\t-i Pre-init hash DB bucket count.\n");
616 	(void)fprintf(stderr, "\t-k the size of each key inserted.\n");
617 	(void)fprintf(stderr, "\t-m message pre-pended to log output.\n");
618 	(void)fprintf(stderr, "\t-o keys should be ordered for insert.\n");
619 	(void)fprintf(stderr, "\t-p the page size for the database.\n");
620 	(void)fprintf(stderr, "\t-r the number of duplicates to insert\n");
621 	(void)fprintf(stderr, "\t-t type of the underlying database.\n");
622 	(void)fprintf(stderr, "\t-w the workload to measure, available:\n");
623 	(void)fprintf(stderr, "\t\tA - PUT_GET_DELETE\n");
624 	(void)fprintf(stderr, "\t\tB - GET\n");
625 	(void)fprintf(stderr, "\t\tC - PUT\n");
626 	(void)fprintf(stderr, "\t\tD - DELETE\n");
627 	(void)fprintf(stderr, "\t\tE - PUT_GET\n");
628 	(void)fprintf(stderr, "\t\tF - PUT_DELETE\n");
629 	(void)fprintf(stderr, "\t\tG - GET_DELETE\n");
630 	(void)fprintf(stderr, "\t\tH - MIXED\n");
631 	return (EXIT_FAILURE);
632 }
633