1 /*
2  * See the file LICENSE for redistribution information.
3  *
4  * Copyright (c) 2011, 2013 Oracle and/or its affiliates.  All rights reserved.
5  *
6  * $Id$
7  *
8  * This program demonstrates:
9  *  1. Usage of heap access method.
10  *  2. Differences between the heap and btree access methods.
11  *
12  * The application initially populates a database, and then proceeds to
13  * move into a process of adding and removing data. Keeping a fairly
14  * constant amount of data in the database. The heap access method will
15  * maintain a constant database size if the heap size is configured properly,
16  * while the btree database will continue to grow.
17  */
18 
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <string.h>
22 
23 #include "db.h"
24 
25 #ifndef lint
26 static const char copyright[] =
27     "Copyright (c) 2011, 2013 Oracle and/or its affiliates.  All rights reserved.\n";
28 #endif
29 
30 #define	BUFFER_LEN		30     /* Buffer size to hold data */
31 #define	NS_PER_MS		1000000/* Nanoseconds in a millisecond*/
32 #define	NS_PER_US		1000   /* Nanoseconds in a microsecond*/
33 #define	DEF_INIT_RECS		10000  /* Default initial records */
34 #define	DEF_RECS_PER_REP	10000  /* Default records per repeat. */
35 #define	DEF_REPEATS		1      /* Default repetition value */
36 
37 /*
38  * Average space each record needs, based on the data generated.
39  * The ideal heap size for this example should be set as (bytes):
40  * AVG_SPACE_PER_RECORD * (DEF_INIT_RECS + records inserted each repetition)
41  */
42 #define	AVG_SPACE_PER_RECORD	36
43 
44 #ifdef _WIN32
45 #include <sys/timeb.h>
46 #include <time.h>
47 
48 extern int getopt(int, char * const *, const char *);
49 
50 /* Implement a basic high resource timer with a POSIX interface for Windows.*/
51 struct timeval {
52 	time_t tv_sec;
53 	long tv_usec;
54 };
55 
gettimeofday(struct timeval * tv,struct timezone * tz)56 int gettimeofday(struct timeval *tv, struct timezone *tz)
57 {
58 	struct _timeb now;
59 	_ftime(&now);
60 	tv->tv_sec = now.time;
61 	tv->tv_usec = now.millitm * NS_PER_US;
62 	return (0);
63 }
64 #else
65 #include <sys/time.h>
66 #include <unistd.h>
67 #endif
68 
69 int	compare_int(DB *, const DBT *, const DBT *);
70 int	delete_recs __P((DB *, DB_ENV *, int));
71 int	file_size __P((DB *, DBTYPE, int *));
72 int	generate_data __P((char [], int, int));
73 int	insert_btree __P((DB *, DB_ENV *, int, int, int));
74 int	insert_heap __P((DB *, DB_ENV *, int, int, int));
75 int	open_db __P((
76     DB **, DB_ENV *, DBTYPE, char *, u_int32_t, u_int32_t, u_int32_t));
77 int	open_env __P((DB_ENV **, char *, u_int32_t));
78 int	run_workload __P((DB *, int, int, int));
79 void	usage __P((void));
80 
81 const char *progname = "ex_heap";
82 
83 int
main(argc,argv)84 main(argc, argv)
85 	int argc;
86 	char *argv[];
87 {
88 	extern char *optarg;
89 	DB_ENV *dbenv;
90 	DB *dbp;
91 	u_int32_t cachesize, ghpsize, hpsize, pgsize;
92 	char *home;
93 	int ch, ret, ret_t, set_ghpsize, set_hpsize, test_btree, test_var_data;
94 	int recs_per_rep, repeats;
95 
96 	dbenv = NULL;
97 	dbp = NULL;
98 	cachesize = 0;
99 	ret = ret_t = set_ghpsize = set_hpsize = test_btree = 0;
100 	home = NULL;
101 
102 	recs_per_rep = DEF_RECS_PER_REP;
103 	ghpsize = hpsize = pgsize = 0;
104 	repeats = DEF_REPEATS;
105 	test_var_data = 0; /* Default as fix-length data. */
106 
107 	while ((ch = getopt(argc, argv, "bc:dh:n:p:r:S:s:")) != EOF)
108 		switch (ch) {
109 		case 'b':
110 			test_btree = 1;
111 			break;
112 		case 'c':
113 			cachesize = atoi(optarg);
114 			break;
115 		case 'd':
116 			test_var_data = 1;
117 			break;
118 		case 'h':
119 			home = optarg;
120 			break;
121 		case 'n':
122 			recs_per_rep = atoi(optarg);
123 			break;
124 		case 'p':
125 			pgsize = atoi(optarg);
126 			break;
127 		case 'r':
128 			repeats = atoi(optarg);
129 			break;
130 		case 's':
131 			set_hpsize = 1;
132 			hpsize = atoi(optarg);
133 			break;
134 		case 'S':
135 			set_ghpsize = 1;
136 			ghpsize = atoi(optarg);
137 			break;
138 		default:
139 			usage();
140 		}
141 
142 	if (!home)
143 		usage();
144 
145 	srand((int)time(NULL));
146 
147 	/*
148 	 * If heap size is not specified, then use our default configuration
149 	 * as follows.
150 	 */
151 	if (!set_hpsize && !set_ghpsize)
152 		hpsize = AVG_SPACE_PER_RECORD * (DEF_INIT_RECS + recs_per_rep);
153 
154 	if ((ret = open_env(&dbenv, home, cachesize)) != 0) {
155 		fprintf(stderr, "%s: open_env: %s", progname, db_strerror(ret));
156 		goto err;
157 	}
158 
159 	if ((ret = open_db(&dbp, dbenv, DB_HEAP, home,
160 	    ghpsize, hpsize, pgsize)) != 0) {
161 		dbenv->err(dbenv, ret, "Failed to open heap database.");
162 		goto err;
163 	}
164 
165 	/*
166 	 * Perform requested rounds of insert/delete operations
167 	 * using heap database.
168 	 */
169 	if ((ret =
170 	    run_workload(dbp, repeats, recs_per_rep, test_var_data)) != 0) {
171 		dbenv->err(dbenv, ret,
172 		    "Failed to perform operations on heap database.");
173 		goto err;
174 	}
175 
176 	if (test_btree) {
177 		/* Close the DB handle for heap. */
178 		if ((ret = dbp->close(dbp, 0)) != 0) {
179 			dbenv->err(dbenv, ret, "DB->close");
180 			goto err;
181 		}
182 		dbp = NULL;
183 
184 		if ((ret =
185 		    open_db(&dbp, dbenv, DB_BTREE, home, 0, 0, pgsize)) != 0) {
186 			dbenv->err(dbenv, ret,
187 			    "Failed to open btree database.");
188 			goto err;
189 		}
190 
191 		/*
192 		 * Perform requested rounds of insert/delete operations
193 		 * using btree database.
194 		 */
195 		if ((ret = run_workload(dbp,
196 		    repeats, recs_per_rep, test_var_data)) != 0) {
197 			dbenv->err(dbenv, ret,
198 			    "Failed to perform operations on btree database.");
199 			goto err;
200 		}
201 	}
202 err:
203 	if (dbp != NULL && (ret_t = dbp->close(dbp, 0)) != 0) {
204 		dbenv->err(dbenv, ret_t, "DB->close");
205 		ret = (ret == 0 ? ret_t : ret);
206 	}
207 
208 	if (dbenv != NULL && (ret_t = dbenv->close(dbenv, 0)) != 0) {
209 		fprintf(stderr, "%s: dbenv->close: %s", progname,
210 		    db_strerror(ret_t));
211 		ret = (ret == 0 ? ret_t : ret);
212 	}
213 
214 	return (ret);
215 }
216 
217 int
run_workload(dbp,repeats,recs_per_rep,test_var)218 run_workload(dbp, repeats, recs_per_rep, test_var)
219 	DB *dbp;
220 	int repeats, recs_per_rep, test_var;
221 {
222 	DB_ENV *dbenv;
223 	DBTYPE dbtype;
224 	u_int32_t ghpsize, hpsize;
225 	struct timeval end_time, start_time;
226 	double *time_secs;
227 	int *db_file_sizes, fsize, i, ret;
228 
229 	dbenv = dbp->dbenv;
230 	fsize = 0;
231 	time_secs = NULL;
232 	db_file_sizes = NULL;
233 
234 	if ((ret = dbp->get_type(dbp, &dbtype)) != 0) {
235 		dbenv->err(dbenv, ret, "DB->get_type");
236 		goto err;
237 	}
238 
239 	if (dbtype == DB_HEAP &&
240 	    (ret = dbp->get_heapsize(dbp, &ghpsize, &hpsize)) != 0) {
241 		dbenv->err(dbenv, ret, "DB->get_heapsize");
242 		goto err;
243 	}
244 
245 	/* An array to record the physical database file size. */
246 	if ((db_file_sizes =
247 	    (int *)malloc((repeats + 1) * sizeof(int))) == NULL) {
248 		fprintf(stderr,
249 		    "%s: Unable to allocate space for array db_file_sizes.\n",
250 		    progname);
251 		goto err;
252 	}
253 	memset(db_file_sizes, 0, (repeats + 1) * sizeof(int));
254 
255 	/* An array to record the running time for each repetition. */
256 	if ((time_secs =
257 	    (double *)malloc((repeats + 1) * sizeof(double))) == NULL) {
258 		fprintf(stderr,
259 		    "%s: Unable to allocate space for array time_secs.\n",
260 		    progname);
261 		goto err;
262 	}
263 	memset(time_secs, 0, (repeats + 1) * sizeof(double));
264 
265 	printf("\n\n======================================================");
266 	printf("\nAbout to enter the insert phase.");
267 	printf("\n\tDatabase type: %s \t",
268 	    dbtype == DB_HEAP ? "Heap" : "Btree");
269 	if (dbtype == DB_HEAP)
270 		printf("with configured heapsize = %d gbytes and %d bytes.",
271 		    ghpsize, hpsize);
272 	printf("\n\tPagesize: %d", dbp->pgsize);
273 	printf("\n\tInitial records number: %d", DEF_INIT_RECS);
274 	printf("\n\tNumber of repetitions: %d", repeats);
275 	printf("\n\tNumber of inserts per repetition: %d\n", recs_per_rep);
276 
277 	/*
278 	 * Insert records to the database and delete the same number from
279 	 * the database, then check the change of the physical database file.
280 	 *
281 	 * Don't delete after the first insertion to leave some data
282 	 * in the tables for subsequent iterations.
283 	 */
284 	for (i = 0; i <= repeats; i++) {
285 		/* Time for each loop. */
286 		(void)gettimeofday(&start_time, NULL);
287 
288 		if ((dbtype == DB_HEAP) && (ret = insert_heap(dbp, dbenv,
289 		    i == 0 ? DEF_INIT_RECS : recs_per_rep,
290 		    i == 0 ? 0 : (DEF_INIT_RECS + (i - 1) * recs_per_rep),
291 		    test_var)) != 0) {
292 			dbenv->err(dbenv, ret,
293 			    "Failed to insert records to heap database.");
294 			goto err;
295 		}
296 
297 		if ((dbtype == DB_BTREE) && (ret = insert_btree(dbp, dbenv,
298 		    i == 0 ? DEF_INIT_RECS : recs_per_rep,
299 		    i == 0 ? 0 : (DEF_INIT_RECS + (i - 1) * recs_per_rep),
300 		    test_var)) != 0) {
301 			dbenv->err(dbenv, ret,
302 			    "Failed to insert records to btree database.");
303 			goto err;
304 		}
305 
306 		if (i > 0 &&
307 		    (ret = delete_recs(dbp, dbenv, recs_per_rep)) != 0) {
308 			dbenv->err(dbenv, ret, "Failed to delete records.");
309 			goto err;
310 		}
311 
312 		(void)gettimeofday(&end_time, NULL);
313 		time_secs[i] =
314 		    (((double)end_time.tv_sec * NS_PER_MS +
315 		    end_time.tv_usec) -
316 		    ((double)start_time.tv_sec * NS_PER_MS +
317 		    start_time.tv_usec)) / NS_PER_MS;
318 
319 		/* Calculate the physical file size for each repetition. */
320 		if ((ret = file_size(dbp, dbtype, &fsize)) != 0) {
321 			dbenv->err(dbenv, ret, "Failed to calculate "
322 			    "the file size on repeat %d.\n", i);
323 			goto err;
324 		}
325 		db_file_sizes[i] = fsize;
326 	}
327 	printf("\n------------------------------------------------------\n");
328 	printf("%5s \t %10s \t %10s\n", "repetition", "physical file size",
329 	    "running time");
330 	for (i = 0; i <= repeats; i++)
331 		printf("%5d \t\t %10d \t\t %.2f seconds\n",
332 		    i, db_file_sizes[i], time_secs[i]);
333 
334 err:
335 	if (db_file_sizes != NULL)
336 		free(db_file_sizes);
337 	if (time_secs != NULL)
338 		free(time_secs);
339 
340 	return (ret);
341 }
342 
343 /* Calculate the size of the given database. */
344 int
file_size(dbp,dbtype,fsize)345 file_size(dbp, dbtype, fsize)
346 	DB *dbp;
347 	DBTYPE dbtype;
348 	int *fsize;
349 {
350 	DB_ENV *dbenv;
351 	u_int32_t pgcnt, pgsize;
352 	int ret, size;
353 	void *statp;
354 
355 	dbenv = dbp->dbenv;
356 	pgsize = dbp->pgsize;
357 	ret = size = 0;
358 
359 	if ((ret = dbp->stat(dbp, NULL, &statp, DB_FAST_STAT)) != 0) {
360 		dbenv->err(dbenv, ret, "DB->stat");
361 		return (ret);
362 	}
363 
364 	pgcnt = (dbtype == DB_HEAP ? ((DB_HEAP_STAT *)statp)->heap_pagecnt :
365 	    ((DB_BTREE_STAT *)statp)->bt_pagecnt);
366 
367 	size = pgcnt * pgsize;
368 	*fsize = size;
369 
370 	free(statp);
371 
372 	return (ret);
373 }
374 
375 /*
376  * Insert an certain number of records to heap database,
377  * with the key beginning with a specified value.
378  */
379 int
insert_heap(dbp,dbenv,numrecs,start,test_var)380 insert_heap(dbp, dbenv, numrecs, start, test_var)
381 	DB *dbp;
382 	DB_ENV *dbenv;
383 	int numrecs, start, test_var;
384 {
385 	DB_HEAP_RID rid;
386 	DBT key, data;
387 	char buf[BUFFER_LEN];
388 	int cnt, ret;
389 
390 	memset(&rid, 0, sizeof(DB_HEAP_RID));
391 	memset(&key, 0, sizeof(DBT));
392 	memset(&data, 0, sizeof(DBT));
393 
394 	ret = 0;
395 
396 	key.data = &rid;
397 	key.size = key.ulen = sizeof(DB_HEAP_RID);
398 	key.flags = DB_DBT_USERMEM;
399 	data.data = buf;
400 	data.flags = DB_DBT_USERMEM;
401 
402 	for (cnt = start; cnt < (numrecs + start) &&
403 	    (ret = generate_data(buf, cnt, test_var)) == 0; ++cnt) {
404 		data.size = data.ulen = (u_int32_t)strlen(buf) + 1;
405 
406 		/* Require DB_APPEND flag to add new data to the database.*/
407 		if ((ret = dbp->put(dbp, NULL, &key, &data, DB_APPEND)) != 0) {
408 			dbenv->err(dbenv, ret, "insert_heap:DB->put");
409 			break;
410 		}
411 	}
412 
413 	return (ret);
414 }
415 
416 /*
417  * Insert an certain number of records to btree database,
418  * with the key beginning with a specified value.
419  */
420 int
insert_btree(dbp,dbenv,numrecs,start,test_var)421 insert_btree(dbp, dbenv, numrecs, start, test_var)
422 	DB *dbp;
423 	DB_ENV *dbenv;
424 	int numrecs, start, test_var;
425 {
426 	DBT key, data;
427 	char buf[BUFFER_LEN];
428 	int cnt, ret;
429 
430 	memset(&key, 0, sizeof(DBT));
431 	memset(&data, 0, sizeof(DBT));
432 
433 	ret = 0;
434 
435 	key.data = &cnt;
436 	key.size = key.ulen = sizeof(int);
437 	key.flags = DB_DBT_USERMEM;
438 	data.data = buf;
439 	data.flags = DB_DBT_USERMEM;
440 
441 	for (cnt = start; cnt < (numrecs + start) &&
442 	    (ret = generate_data(buf, cnt, test_var)) == 0; ++cnt) {
443 		data.size = data.ulen = (u_int32_t)strlen(buf) + 1;
444 
445 		if ((ret = dbp->put(dbp, NULL, &key, &data, 0)) != 0) {
446 			dbenv->err(dbenv, ret, "insert_btree:DB->put");
447 			break;
448 		}
449 	}
450 
451 	return (ret);
452 }
453 
454 /* Generate the data for the specified record. */
455 int
generate_data(buf,rec_no,test_var)456 generate_data(buf, rec_no, test_var)
457 	char *buf;
458 	int rec_no, test_var;
459 {
460 	const char *str = "abcdefghijklmnopqrst";
461 	int len = (int)strlen(str);
462 
463 	/*
464 	 * Default use the fix-length data,
465 	 * if required then use variable-length data.
466 	 */
467 	if (test_var == 1)
468 		len = rand() % (len - 2) + 1;
469 
470 	(void)sprintf(buf, "%04d_%*s", rec_no, len, str);
471 
472 	return (0);
473 }
474 
475 /* Delete an certain number of records. */
476 int
delete_recs(dbp,dbenv,numrecs)477 delete_recs(dbp, dbenv, numrecs)
478 	DB *dbp;
479 	DB_ENV *dbenv;
480 	int numrecs;
481 {
482 	DBC *dbcp;
483 	DBT key, data;
484 	int cnt, ret;
485 
486 	memset(&key, 0, sizeof(DBT));
487 	memset(&data, 0, sizeof(DBT));
488 
489 	dbcp = NULL;
490 	cnt = ret = 0;
491 
492 	/*
493 	 * Delete from the first entry, get the first entry using
494 	 * the DBcursor, then delete it using the DB handle.
495 	 */
496 	if ((ret = dbp->cursor(dbp, NULL, &dbcp, 0)) != 0) {
497 		dbenv->err(dbenv, ret, "delete_recs:DB->cursor");
498 		goto err;
499 	}
500 
501 	while ((ret = dbcp->get(dbcp, &key, &data, DB_NEXT)) == 0 &&
502 	    cnt < numrecs) {
503 		if ((ret = dbcp->del(dbcp, 0)) != 0) {
504 			dbenv->err(dbenv, ret, "delete_recs:DBCursor->del");
505 			break;
506 		} else
507 			++cnt;
508 	}
509 
510 err:
511 	if (dbcp != NULL && (ret = dbcp->close(dbcp)) != 0)
512 		dbenv->err(dbenv, ret, "delete_recs:DBCursor->close");
513 
514 	return (ret);
515 }
516 
517 void
usage()518 usage()
519 {
520 	fprintf(stderr, "usage: %s:\n%s \n %s\n", progname,
521 	    "\t[-b][-c cachesize][-d] -h home [-n recs_per_rep]",
522 	    "\t[-p pgsize][-r repeats][-S ghpsize][-s hpsize]");
523 
524 	fprintf(stderr, "-b: run sample application using a btree database.\n");
525 	fprintf(stderr, "-c: specify the cache size for the environment.\n");
526 	fprintf(stderr, "-d: test on variable-length data "
527 	    "(default: fix-length).\n");
528 	fprintf(stderr, "-h: specify the home directory for "
529 	    "the environment (required).\n");
530 	fprintf(stderr, "-n: specify the num. of records "
531 	    "per repetition (default: %d).\n", DEF_RECS_PER_REP);
532 	fprintf(stderr, "-p: specify the pgsize of database.\n");
533 	fprintf(stderr, "-r: number of repetition (a pair of "
534 	    "insertion and deletion (default: %d)).\n", DEF_REPEATS);
535 	fprintf(stderr,
536 	    "-S: specify the heap size (gbytes) for the heap database.\n");
537 	fprintf(stderr,
538 	    "-s: specify the heap size (bytes) for the heap database.\n");
539 
540 	exit(EXIT_FAILURE);
541 }
542 
543 int
open_env(dbenvp,home,cachesize)544 open_env(dbenvp, home, cachesize)
545 	DB_ENV **dbenvp;
546 	char *home;
547 	u_int32_t cachesize;
548 {
549 	DB_ENV *dbenv;
550 	int ret = 0;
551 
552 	/* Create an environment handle and open an environment. */
553 	if ((ret = db_env_create(&dbenv, 0)) != 0) {
554 		fprintf(stderr, "%s: db_env_create: %s\n",
555 		    progname, db_strerror(ret));
556 		return (ret);
557 	}
558 
559 	*dbenvp = dbenv;
560 
561 	dbenv->set_errfile(dbenv, stderr);
562 	dbenv->set_errpfx(dbenv, progname);
563 
564 	if ((cachesize > 0) && (ret =
565 	    dbenv->set_cachesize(dbenv, (u_int32_t)0, cachesize, 1)) != 0) {
566 		dbenv->err(dbenv, ret, "DB_ENV->set_cachesize");
567 		return (ret);
568 	}
569 
570 	if ((ret = dbenv->open(dbenv, home, DB_CREATE | DB_INIT_MPOOL, 0)) != 0)
571 		dbenv->err(dbenv, ret, "DB_ENV->open");
572 
573 	return (ret);
574 }
575 
576 int
open_db(dbpp,dbenv,dbtype,home,ghpsize,hpsize,pgsize)577 open_db(dbpp, dbenv, dbtype, home, ghpsize, hpsize, pgsize)
578 	DB **dbpp;
579 	DB_ENV *dbenv;
580 	DBTYPE dbtype;
581 	char *home;
582 	u_int32_t ghpsize, hpsize, pgsize;
583 {
584 	DB *dbp;
585 	u_int32_t dbflags = 0;
586 	char *dbname;
587 	int ret = 0;
588 
589 	dbname = (dbtype == DB_HEAP) ? "heap.db" : "btree.db";
590 
591 	/* Create a database handle and open a database. */
592 	if ((ret = db_create(&dbp, dbenv, 0)) != 0) {
593 		dbenv->err(dbenv, ret, "db_create : %s", dbname);
594 		goto err;
595 	}
596 
597 	*dbpp = dbp;
598 
599 	if ((dbtype == DB_BTREE) &&
600 	    (ret = dbp->set_bt_compare(dbp, compare_int)) != 0) {
601 		dbp->err(dbp, ret, "DB->set_bt_compare");
602 		goto err;
603 	}
604 
605 	if ((dbtype == DB_HEAP) && (ghpsize > 0 || hpsize > 0) &&
606 	    (ret = dbp->set_heapsize(dbp, ghpsize, hpsize, 0)) != 0) {
607 		dbenv->err(dbenv, ret, "DB->set_heapsize");
608 		return (ret);
609 	}
610 
611 	if ((pgsize > 0) && (ret = dbp->set_pagesize(dbp, pgsize)) != 0) {
612 		dbenv->err(dbenv, ret, "DB->set_pagesize");
613 		return (ret);
614 	}
615 
616 	if ((ret =
617 	    dbp->open(dbp, NULL, dbname, NULL, dbtype, DB_CREATE, 0)) != 0)
618 		dbenv->err(dbenv, ret, "DB->open");
619 err:
620 
621 	return (ret);
622 }
623 
624 int
compare_int(dbp,a,b)625 compare_int(dbp, a, b)
626 	DB *dbp;
627 	const DBT *a, *b;
628 {
629 	int ai, bi;
630 
631 	dbp = NULL;
632 
633 	/*
634 	 * Returns:
635 	 *	< 0 if a < b
636 	 *	= 0 if a = b
637 	 *	> 0 if a > b
638 	 */
639 	memcpy(&ai, a->data, sizeof(int));
640 	memcpy(&bi, b->data, sizeof(int));
641 	return (ai - bi);
642 }
643