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