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