1 /*-
2  * See the file LICENSE for redistribution information.
3  *
4  * Copyright (c) 1997, 2013 Oracle and/or its affiliates.  All rights reserved.
5  *
6  * $Id$
7  */
8 
9 #include <sys/types.h>
10 
11 #include <errno.h>
12 #include <stdlib.h>
13 #include <string.h>
14 #include <time.h>
15 
16 #include <iostream>
17 #include <iomanip>
18 #include <db_cxx.h>
19 
20 using std::cout;
21 using std::cerr;
22 
23 typedef enum { ACCOUNT, BRANCH, TELLER } FTYPE;
24 
25 static int	  invarg(int, char *);
26 u_int32_t random_id(FTYPE, u_int32_t, u_int32_t, u_int32_t);
27 u_int32_t random_int(u_int32_t, u_int32_t);
28 static int	  usage(void);
29 
30 int verbose;
31 const char *progname = "TpcbExample";                       // Program name.
32 
33 class TpcbExample : public DbEnv
34 {
35 public:
36 	void populate(int, int, int, int);
37 	void run(int, int, int, int);
38 	int txn(Db *, Db *, Db *, Db *,
39 		int, int, int);
40 	void populateHistory(Db *, int, u_int32_t, u_int32_t, u_int32_t);
41 	void populateTable(Db *, u_int32_t, u_int32_t, int, const char *);
42 
43 	// Note: the constructor creates a DbEnv(), which is
44 	// not fully initialized until the DbEnv::open() method
45 	// is called.
46 	//
47 	TpcbExample(const char *home, int cachesize, int flags);
48 
49 private:
50 	static const char FileName[];
51 
52 	// no need for copy and assignment
53 	TpcbExample(const TpcbExample &);
54 	void operator = (const TpcbExample &);
55 };
56 
57 //
58 // This program implements a basic TPC/B driver program.  To create the
59 // TPC/B database, run with the -i (init) flag.  The number of records
60 // with which to populate the account, history, branch, and teller tables
61 // is specified by the a, s, b, and t flags respectively.  To run a TPC/B
62 // test, use the n flag to indicate a number of transactions to run (note
63 // that you can run many of these processes in parallel to simulate a
64 // multiuser test run).
65 //
66 #define	TELLERS_PER_BRANCH      100
67 #define	ACCOUNTS_PER_TELLER     1000
68 #define	HISTORY_PER_BRANCH	2592000
69 
70 /*
71  * The default configuration that adheres to TPCB scaling rules requires
72  * nearly 3 GB of space.  To avoid requiring that much space for testing,
73  * we set the parameters much lower.  If you want to run a valid 10 TPS
74  * configuration, define VALID_SCALING.
75  */
76 #ifdef	VALID_SCALING
77 #define	ACCOUNTS	 1000000
78 #define	BRANCHES	      10
79 #define	TELLERS		     100
80 #define	HISTORY		25920000
81 #endif
82 
83 #ifdef	TINY
84 #define	ACCOUNTS	    1000
85 #define	BRANCHES	      10
86 #define	TELLERS		     100
87 #define	HISTORY		   10000
88 #endif
89 
90 #if !defined(VALID_SCALING) && !defined(TINY)
91 #define	ACCOUNTS	  100000
92 #define	BRANCHES	      10
93 #define	TELLERS		     100
94 #define	HISTORY		  259200
95 #endif
96 
97 #define	HISTORY_LEN	    100
98 #define	RECLEN		    100
99 #define	BEGID		1000000
100 
101 struct Defrec {
102 	u_int32_t   id;
103 	u_int32_t   balance;
104 	u_int8_t    pad[RECLEN - sizeof(u_int32_t) - sizeof(u_int32_t)];
105 };
106 
107 struct Histrec {
108 	u_int32_t   aid;
109 	u_int32_t   bid;
110 	u_int32_t   tid;
111 	u_int32_t   amount;
112 	u_int8_t    pad[RECLEN - 4 * sizeof(u_int32_t)];
113 };
114 
115 int
main(int argc,char * argv[])116 main(int argc, char *argv[])
117 {
118 	unsigned long seed;
119 	int accounts, branches, tellers, history;
120 	int iflag, mpool, ntxns, txn_no_sync;
121 	const char *home;
122 	char *endarg;
123 
124 	home = "TESTDIR";
125 	accounts = branches = history = tellers = 0;
126 	txn_no_sync = 0;
127 	mpool = ntxns = 0;
128 	verbose = 0;
129 	iflag = 0;
130 	seed = (unsigned long)time(NULL);
131 
132 	for (int i = 1; i < argc; ++i) {
133 
134 		if (strcmp(argv[i], "-a") == 0) {
135 			// Number of account records
136 			if ((accounts = atoi(argv[++i])) <= 0)
137 				return (invarg('a', argv[i]));
138 		}
139 		else if (strcmp(argv[i], "-b") == 0) {
140 			// Number of branch records
141 			if ((branches = atoi(argv[++i])) <= 0)
142 				return (invarg('b', argv[i]));
143 		}
144 		else if (strcmp(argv[i], "-c") == 0) {
145 			// Cachesize in bytes
146 			if ((mpool = atoi(argv[++i])) <= 0)
147 				return (invarg('c', argv[i]));
148 		}
149 		else if (strcmp(argv[i], "-f") == 0) {
150 			// Fast mode: no txn sync.
151 			txn_no_sync = 1;
152 		}
153 		else if (strcmp(argv[i], "-h") == 0) {
154 			// DB  home.
155 			home = argv[++i];
156 		}
157 		else if (strcmp(argv[i], "-i") == 0) {
158 			// Initialize the test.
159 			iflag = 1;
160 		}
161 		else if (strcmp(argv[i], "-n") == 0) {
162 			// Number of transactions
163 			if ((ntxns = atoi(argv[++i])) <= 0)
164 				return (invarg('n', argv[i]));
165 		}
166 		else if (strcmp(argv[i], "-S") == 0) {
167 			// Random number seed.
168 			seed = strtoul(argv[++i], &endarg, 0);
169 			if (*endarg != '\0')
170 				return (invarg('S', argv[i]));
171 		}
172 		else if (strcmp(argv[i], "-s") == 0) {
173 			// Number of history records
174 			if ((history = atoi(argv[++i])) <= 0)
175 				return (invarg('s', argv[i]));
176 		}
177 		else if (strcmp(argv[i], "-t") == 0) {
178 			// Number of teller records
179 			if ((tellers = atoi(argv[++i])) <= 0)
180 				return (invarg('t', argv[i]));
181 		}
182 		else if (strcmp(argv[i], "-v") == 0) {
183 			// Verbose option.
184 			verbose = 1;
185 		}
186 		else {
187 			return (usage());
188 		}
189 	}
190 
191 	srand((unsigned int)seed);
192 
193 	accounts = accounts == 0 ? ACCOUNTS : accounts;
194 	branches = branches == 0 ? BRANCHES : branches;
195 	tellers = tellers == 0 ? TELLERS : tellers;
196 	history = history == 0 ? HISTORY : history;
197 
198 	if (verbose)
199 		cout << (long)accounts << " Accounts, "
200 		     << (long)branches << " Branches, "
201 		     << (long)tellers << " Tellers, "
202 		     << (long)history << " History\n";
203 
204 	try {
205 		// Initialize the database environment.
206 		// Must be done in within a try block, unless you
207 		// change the error model in the environment options.
208 		//
209 		TpcbExample app(home, mpool, txn_no_sync ? DB_TXN_NOSYNC : 0);
210 
211 		if (iflag) {
212 			if (ntxns != 0)
213 				return (usage());
214 			app.populate(accounts, branches, history, tellers);
215 		}
216 		else {
217 			if (ntxns == 0)
218 				return (usage());
219 			app.run(ntxns, accounts, branches, tellers);
220 		}
221 
222 		app.close(0);
223 		return (EXIT_SUCCESS);
224 	}
225 	catch (DbException &dbe) {
226 		cerr << "TpcbExample: " << dbe.what() << "\n";
227 		return (EXIT_FAILURE);
228 	}
229 }
230 
231 static int
invarg(int arg,char * str)232 invarg(int arg, char *str)
233 {
234 	cerr << "TpcbExample: invalid argument for -"
235 	     << (char)arg << ": " << str << "\n";
236 	return (EXIT_FAILURE);
237 }
238 
239 static int
usage()240 usage()
241 {
242 	cerr << "usage: TpcbExample [-fiv] [-a accounts] [-b branches]\n"
243 	     << "                   [-c cachesize] [-h home] [-n transactions ]\n"
244 	     << "                   [-S seed] [-s history] [-t tellers]\n";
245 	return (EXIT_FAILURE);
246 }
247 
TpcbExample(const char * home,int cachesize,int flags)248 TpcbExample::TpcbExample(const char *home, int cachesize, int flags)
249 :	DbEnv(0)
250 {
251 	u_int32_t local_flags;
252 
253 	set_error_stream(&cerr);
254 	set_errpfx("TpcbExample");
255 	(void)set_lk_detect(DB_LOCK_DEFAULT);
256 	(void)set_cachesize(0, cachesize == 0 ?
257 			    4 * 1024 * 1024 : (u_int32_t)cachesize, 0);
258 
259 	if (flags & (DB_TXN_NOSYNC))
260 		set_flags(DB_TXN_NOSYNC, 1);
261 	flags &= ~(DB_TXN_NOSYNC);
262 
263 	local_flags = flags | DB_CREATE | DB_INIT_LOCK | DB_INIT_LOG |
264 	    DB_INIT_MPOOL | DB_INIT_TXN;
265 	open(home, local_flags, 0);
266 }
267 
268 //
269 // Initialize the database to the specified number of accounts, branches,
270 // history records, and tellers.
271 //
272 void
populate(int accounts,int branches,int history,int tellers)273 TpcbExample::populate(int accounts, int branches, int history, int tellers)
274 {
275 	Db *dbp;
276 
277 	int err;
278 	u_int32_t balance, idnum;
279 	u_int32_t end_anum, end_bnum, end_tnum;
280 	u_int32_t start_anum, start_bnum, start_tnum;
281 
282 	idnum = BEGID;
283 	balance = 500000;
284 
285 	dbp = new Db(this, 0);
286 	dbp->set_h_nelem((unsigned int)accounts);
287 
288 	if ((err = dbp->open(NULL, "account", NULL, DB_HASH,
289 			     DB_CREATE, 0644)) != 0) {
290 		DbException except("Account file create failed", err);
291 		throw except;
292 	}
293 
294 	start_anum = idnum;
295 	populateTable(dbp, idnum, balance, accounts, "account");
296 	idnum += accounts;
297 	end_anum = idnum - 1;
298 	if ((err = dbp->close(0)) != 0) {
299 		DbException except("Account file close failed", err);
300 		throw except;
301 	}
302 	delete dbp;
303 	if (verbose)
304 		cout << "Populated accounts: "
305 		     << (long)start_anum << " - " << (long)end_anum << "\n";
306 
307 	dbp = new Db(this, 0);
308 	//
309 	// Since the number of branches is very small, we want to use very
310 	// small pages and only 1 key per page.  This is the poor-man's way
311 	// of getting key locking instead of page locking.
312 	//
313 	dbp->set_h_ffactor(1);
314 	dbp->set_h_nelem((unsigned int)branches);
315 	dbp->set_pagesize(512);
316 
317 	if ((err = dbp->open(NULL, "branch", NULL, DB_HASH,
318 			     DB_CREATE, 0644)) != 0) {
319 		DbException except("Branch file create failed", err);
320 		throw except;
321 	}
322 	start_bnum = idnum;
323 	populateTable(dbp, idnum, balance, branches, "branch");
324 	idnum += branches;
325 	end_bnum = idnum - 1;
326 	if ((err = dbp->close(0)) != 0) {
327 		DbException except("Close of branch file failed", err);
328 		throw except;
329 	}
330 	delete dbp;
331 
332 	if (verbose)
333 		cout << "Populated branches: "
334 		     << (long)start_bnum << " - " << (long)end_bnum << "\n";
335 
336 	dbp = new Db(this, 0);
337 	//
338 	// In the case of tellers, we also want small pages, but we'll let
339 	// the fill factor dynamically adjust itself.
340 	//
341 	dbp->set_h_ffactor(0);
342 	dbp->set_h_nelem((unsigned int)tellers);
343 	dbp->set_pagesize(512);
344 
345 	if ((err = dbp->open(NULL, "teller", NULL, DB_HASH,
346 			     DB_CREATE, 0644)) != 0) {
347 		DbException except("Teller file create failed", err);
348 		throw except;
349 	}
350 
351 	start_tnum = idnum;
352 	populateTable(dbp, idnum, balance, tellers, "teller");
353 	idnum += tellers;
354 	end_tnum = idnum - 1;
355 	if ((err = dbp->close(0)) != 0) {
356 		DbException except("Close of teller file failed", err);
357 		throw except;
358 	}
359 	delete dbp;
360 	if (verbose)
361 		cout << "Populated tellers: "
362 		     << (long)start_tnum << " - " << (long)end_tnum << "\n";
363 
364 	dbp = new Db(this, 0);
365 	dbp->set_re_len(HISTORY_LEN);
366 	if ((err = dbp->open(NULL, "history", NULL, DB_RECNO,
367 			     DB_CREATE, 0644)) != 0) {
368 		DbException except("Create of history file failed", err);
369 		throw except;
370 	}
371 
372 	populateHistory(dbp, history, accounts, branches, tellers);
373 	if ((err = dbp->close(0)) != 0) {
374 		DbException except("Close of history file failed", err);
375 		throw except;
376 	}
377 	delete dbp;
378 }
379 
380 void
populateTable(Db * dbp,u_int32_t start_id,u_int32_t balance,int nrecs,const char * msg)381 TpcbExample::populateTable(Db *dbp,
382 			   u_int32_t start_id, u_int32_t balance,
383 			   int nrecs, const char *msg)
384 {
385 	Defrec drec;
386 	memset(&drec.pad[0], 1, sizeof(drec.pad));
387 
388 	Dbt kdbt(&drec.id, sizeof(u_int32_t));
389 	Dbt ddbt(&drec, sizeof(drec));
390 
391 	for (int i = 0; i < nrecs; i++) {
392 		drec.id = start_id + (u_int32_t)i;
393 		drec.balance = balance;
394 		int err;
395 		if ((err =
396 		     dbp->put(NULL, &kdbt, &ddbt, DB_NOOVERWRITE)) != 0) {
397 			cerr << "Failure initializing " << msg << " file: "
398 			     << strerror(err) << "\n";
399 			DbException except("failure initializing file", err);
400 			throw except;
401 		}
402 	}
403 }
404 
405 void
populateHistory(Db * dbp,int nrecs,u_int32_t accounts,u_int32_t branches,u_int32_t tellers)406 TpcbExample::populateHistory(Db *dbp, int nrecs, u_int32_t accounts,
407 			     u_int32_t branches, u_int32_t tellers)
408 {
409 	Histrec hrec;
410 	memset(&hrec.pad[0], 1, sizeof(hrec.pad));
411 	hrec.amount = 10;
412 	db_recno_t key;
413 
414 	Dbt kdbt(&key, sizeof(u_int32_t));
415 	Dbt ddbt(&hrec, sizeof(hrec));
416 
417 	for (int i = 1; i <= nrecs; i++) {
418 		hrec.aid = random_id(ACCOUNT, accounts, branches, tellers);
419 		hrec.bid = random_id(BRANCH, accounts, branches, tellers);
420 		hrec.tid = random_id(TELLER, accounts, branches, tellers);
421 
422 		int err;
423 		key = (db_recno_t)i;
424 		if ((err = dbp->put(NULL, &kdbt, &ddbt, DB_APPEND)) != 0) {
425 			DbException except("failure initializing history file",
426 					   err);
427 			throw except;
428 		}
429 	}
430 }
431 
432 u_int32_t
random_int(u_int32_t lo,u_int32_t hi)433 random_int(u_int32_t lo, u_int32_t hi)
434 {
435 	u_int32_t ret;
436 	int t;
437 
438 	t = rand();
439 	ret = (u_int32_t)(((double)t / ((double)(RAND_MAX) + 1)) *
440 			  (hi - lo + 1));
441 	ret += lo;
442 	return (ret);
443 }
444 
445 u_int32_t
random_id(FTYPE type,u_int32_t accounts,u_int32_t branches,u_int32_t tellers)446 random_id(FTYPE type, u_int32_t accounts, u_int32_t branches, u_int32_t tellers)
447 {
448 	u_int32_t min, max, num;
449 
450 	max = min = BEGID;
451 	num = accounts;
452 	switch (type) {
453 	case TELLER:
454 		min += branches;
455 		num = tellers;
456 		// Fallthrough
457 	case BRANCH:
458 		if (type == BRANCH)
459 			num = branches;
460 		min += accounts;
461 		// Fallthrough
462 	case ACCOUNT:
463 		max = min + num - 1;
464 	}
465 	return (random_int(min, max));
466 }
467 
468 void
run(int n,int accounts,int branches,int tellers)469 TpcbExample::run(int n, int accounts, int branches, int tellers)
470 {
471 	Db *adb, *bdb, *hdb, *tdb;
472 	int failed, ret, txns;
473 	time_t start_time, end_time;
474 
475 	//
476 	// Open the database files.
477 	//
478 
479 	int err;
480 	adb = new Db(this, 0);
481 	if ((err = adb->open(NULL, "account", NULL, DB_UNKNOWN,
482 			     DB_AUTO_COMMIT, 0)) != 0) {
483 		DbException except("Open of account file failed", err);
484 		throw except;
485 	}
486 
487 	bdb = new Db(this, 0);
488 	if ((err = bdb->open(NULL, "branch", NULL, DB_UNKNOWN,
489 			     DB_AUTO_COMMIT, 0)) != 0) {
490 		DbException except("Open of branch file failed", err);
491 		throw except;
492 	}
493 
494 	tdb = new Db(this, 0);
495 	if ((err = tdb->open(NULL, "teller", NULL, DB_UNKNOWN,
496 			     DB_AUTO_COMMIT, 0)) != 0) {
497 		DbException except("Open of teller file failed", err);
498 		throw except;
499 	}
500 
501 	hdb = new Db(this, 0);
502 	if ((err = hdb->open(NULL, "history", NULL, DB_UNKNOWN,
503 			     DB_AUTO_COMMIT, 0)) != 0) {
504 		DbException except("Open of history file failed", err);
505 		throw except;
506 	}
507 
508 	(void)time(&start_time);
509 	for (txns = n, failed = 0; n-- > 0;)
510 		if ((ret = txn(adb, bdb, tdb, hdb,
511 		    accounts, branches, tellers)) != 0)
512 			++failed;
513 	(void)time(&end_time);
514 	if (end_time == start_time)
515 		++end_time;
516 	// We use printf because it provides much simpler
517 	// formatting than iostreams.
518 	//
519 	printf("%s: %d txns: %d failed, %.2f TPS\n", progname, txns, failed,
520 	    (txns - failed) / (double)(end_time - start_time));
521 
522 	(void)adb->close(0);
523 	(void)bdb->close(0);
524 	(void)tdb->close(0);
525 	(void)hdb->close(0);
526 }
527 
528 //
529 // XXX Figure out the appropriate way to pick out IDs.
530 //
531 int
txn(Db * adb,Db * bdb,Db * tdb,Db * hdb,int accounts,int branches,int tellers)532 TpcbExample::txn(Db *adb, Db *bdb, Db *tdb, Db *hdb,
533 		 int accounts, int branches, int tellers)
534 {
535 	Dbc *acurs = NULL;
536 	Dbc *bcurs = NULL;
537 	Dbc *tcurs = NULL;
538 	DbTxn *t = NULL;
539 
540 	db_recno_t key;
541 	Defrec rec;
542 	Histrec hrec;
543 	int account, branch, teller, ret;
544 
545 	Dbt d_dbt;
546 	Dbt d_histdbt;
547 	Dbt k_dbt;
548 	Dbt k_histdbt(&key, sizeof(key));
549 
550 	// !!!
551 	// This is sample code -- we could move a lot of this into the driver
552 	// to make it faster.
553 	//
554 	account = random_id(ACCOUNT, accounts, branches, tellers);
555 	branch = random_id(BRANCH, accounts, branches, tellers);
556 	teller = random_id(TELLER, accounts, branches, tellers);
557 
558 	k_dbt.set_size(sizeof(int));
559 
560 	d_dbt.set_flags(DB_DBT_USERMEM);
561 	d_dbt.set_data(&rec);
562 	d_dbt.set_ulen(sizeof(rec));
563 
564 	hrec.aid = account;
565 	hrec.bid = branch;
566 	hrec.tid = teller;
567 	hrec.amount = 10;
568 	// Request 0 bytes since we're just positioning.
569 	d_histdbt.set_flags(DB_DBT_PARTIAL);
570 
571 	// START PER-TRANSACTION TIMING.
572 	//
573 	// Technically, TPCB requires a limit on response time, you only get
574 	// to count transactions that complete within 2 seconds.  That's not
575 	// an issue for this sample application -- regardless, here's where
576 	// the transaction begins.
577 	if (txn_begin(NULL, &t, 0) != 0)
578 		goto err;
579 
580 	if (adb->cursor(t, &acurs, 0) != 0 ||
581 	    bdb->cursor(t, &bcurs, 0) != 0 ||
582 	    tdb->cursor(t, &tcurs, 0) != 0)
583 		goto err;
584 
585 	try {
586 		// Account record
587 		k_dbt.set_data(&account);
588 		if (acurs->get(&k_dbt, &d_dbt, DB_SET) != 0)
589 			goto err;
590 		rec.balance += 10;
591 		if (acurs->put(&k_dbt, &d_dbt, DB_CURRENT) != 0)
592 			goto err;
593 
594 		// Branch record
595 		k_dbt.set_data(&branch);
596 		if (bcurs->get(&k_dbt, &d_dbt, DB_SET) != 0)
597 			goto err;
598 		rec.balance += 10;
599 		if (bcurs->put(&k_dbt, &d_dbt, DB_CURRENT) != 0)
600 			goto err;
601 
602 		// Teller record
603 		k_dbt.set_data(&teller);
604 		if (tcurs->get(&k_dbt, &d_dbt, DB_SET) != 0)
605 			goto err;
606 		rec.balance += 10;
607 		if (tcurs->put(&k_dbt, &d_dbt, DB_CURRENT) != 0)
608 			goto err;
609 
610 		// History record
611 		d_histdbt.set_flags(0);
612 		d_histdbt.set_data(&hrec);
613 		d_histdbt.set_ulen(sizeof(hrec));
614 		if (hdb->put(t, &k_histdbt, &d_histdbt, DB_APPEND) != 0)
615 			goto err;
616 	} catch (DbDeadlockException e) {
617 		goto err;
618 	}
619 
620 	if (acurs->close() != 0 || bcurs->close() != 0 || tcurs->close() != 0)
621 		goto err;
622 
623 	ret = t->commit(0);
624 	t = NULL;
625 	if (ret != 0)
626 		goto err;
627 
628 	// END PER-TRANSACTION TIMING.
629 	return (0);
630 
631 err:
632 	if (acurs != NULL)
633 		(void)acurs->close();
634 	if (bcurs != NULL)
635 		(void)bcurs->close();
636 	if (tcurs != NULL)
637 		(void)tcurs->close();
638 	if (t != NULL)
639 		(void)t->abort();
640 
641 	if (verbose)
642 		cout << "Transaction A=" << (long)account
643 		     << " B=" << (long)branch
644 		     << " T=" << (long)teller << " failed\n";
645 	return (-1);
646 }
647