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