1 /*-
2 * Copyright (c) 2007, 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 "TpcbExample.h"
10
11 #define HISTORY_LEN 100
12 #define RECLEN 100
13 #define BEGID 1000000
14
15 struct Defrec {
16 u_int32_t id;
17 u_int32_t balance;
18 u_int8_t pad[RECLEN - sizeof(u_int32_t) - sizeof(u_int32_t)];
19 };
20
21 struct Histrec {
22 u_int32_t aid;
23 u_int32_t bid;
24 u_int32_t tid;
25 u_int32_t amount;
26 u_int8_t pad[RECLEN - 4 * sizeof(u_int32_t)];
27 };
28
29 const char *progname = "wce_tpcb";
30
TpcbExample()31 TpcbExample::TpcbExample()
32 : dbenv(0), accounts(ACCOUNTS), branches(BRANCHES),
33 tellers(TELLERS), history(HISTORY), fast_mode(1), verbose(0),
34 cachesize(0)
35 {
36 rand_seed = GetTickCount();
37 setHomeDir(TESTDIR);
38 }
39
createEnv(int flags)40 int TpcbExample::createEnv(int flags)
41 {
42 int ret;
43 u_int32_t local_flags;
44
45 // If the env object already exists, close and re-open
46 // don't just return immediately, since advanced options
47 // may have been altered (cachesize, NOSYNC..)
48 if (dbenv != NULL)
49 closeEnv();
50
51 srand(rand_seed);
52
53 if ((ret = db_env_create(&dbenv, 0)) != 0) {
54 _snprintf(msgString, ERR_STRING_MAX,
55 "%s: db_env_create: %s\n", progname, db_strerror(ret));
56 return (1);
57 }
58 dbenv->set_errcall(dbenv, &tpcb_errcallback);
59 dbenv->set_errpfx(dbenv, "TpcbExample");
60 dbenv->set_cachesize(dbenv, 0, cachesize == 0 ?
61 1 * 1024 * 1024 : (u_int32_t)cachesize, 0);
62
63 if (fast_mode)
64 dbenv->set_flags(dbenv, DB_TXN_NOSYNC, 1);
65
66 local_flags = DB_CREATE | DB_INIT_LOCK | DB_INIT_LOG |
67 DB_INIT_MPOOL | DB_INIT_TXN;
68 dbenv->open(dbenv, homeDirName, local_flags, 0);
69 return (0);
70 }
71
closeEnv()72 void TpcbExample::closeEnv()
73 {
74 if (dbenv != NULL) {
75 dbenv->close(dbenv, 0);
76 dbenv = 0;
77 }
78 }
79
80 //
81 // Initialize the database to the specified number of accounts, branches,
82 // history records, and tellers.
83 //
84 int
populate()85 TpcbExample::populate()
86 {
87 DB *dbp;
88
89 int err;
90 u_int32_t balance, idnum;
91 u_int32_t end_anum, end_bnum, end_tnum;
92 u_int32_t start_anum, start_bnum, start_tnum;
93
94 idnum = BEGID;
95 balance = 500000;
96
97 if ((err = db_create(&dbp, dbenv, 0)) != 0) {
98 _snprintf(msgString, ERR_STRING_MAX,
99 "db_create of accounts db failed.");
100 return (1);
101 }
102 dbp->set_h_nelem(dbp, (unsigned int)accounts);
103
104 if ((err = dbp->open(dbp, NULL, "account", NULL, DB_HASH,
105 DB_CREATE, 0644)) != 0) {
106 _snprintf(msgString, ERR_STRING_MAX,
107 "Account file create failed. error: %s.", db_strerror(err));
108 return (1);
109 }
110
111 start_anum = idnum;
112 if ((err =
113 populateTable(dbp, idnum, balance, accounts, "account")) != 0)
114 return (1);
115 idnum += accounts;
116 end_anum = idnum - 1;
117 if ((err = dbp->close(dbp, 0)) != 0) {
118 _snprintf(msgString, ERR_STRING_MAX,
119 "Account file close failed. error: %s.", db_strerror(err));
120 return (1);
121 }
122
123 if ((err = db_create(&dbp, dbenv, 0)) != 0) {
124 _snprintf(msgString, ERR_STRING_MAX,
125 "db_create of branches db failed.");
126 return (1);
127 }
128 //
129 // Since the number of branches is very small, we want to use very
130 // small pages and only 1 key per page. This is the poor-man's way
131 // of getting key locking instead of page locking.
132 //
133 dbp->set_h_ffactor(dbp, 1);
134 dbp->set_h_nelem(dbp, (unsigned int)branches);
135 dbp->set_pagesize(dbp, 512);
136
137 if ((err = dbp->open(dbp, NULL, "branch", NULL, DB_HASH,
138 DB_CREATE, 0644)) != 0) {
139 _snprintf(msgString, ERR_STRING_MAX,
140 "Branch file create failed. error: %s.", db_strerror(err));
141 return (1);
142 }
143 start_bnum = idnum;
144 if ((err = populateTable(dbp, idnum, balance, branches, "branch")) != 0)
145 return (1);
146 idnum += branches;
147 end_bnum = idnum - 1;
148 if ((err = dbp->close(dbp, 0)) != 0) {
149 _snprintf(msgString, ERR_STRING_MAX,
150 "Close of branch file failed. error: %s.",
151 db_strerror(err));
152 return (1);
153 }
154
155 if ((err = db_create(&dbp, dbenv, 0)) != 0) {
156 _snprintf(msgString, ERR_STRING_MAX,
157 "db_create of teller db failed.");
158 return (1);
159 }
160 //
161 // In the case of tellers, we also want small pages, but we'll let
162 // the fill factor dynamically adjust itself.
163 //
164 dbp->set_h_ffactor(dbp, 0);
165 dbp->set_h_nelem(dbp, (unsigned int)tellers);
166 dbp->set_pagesize(dbp, 512);
167
168 if ((err = dbp->open(dbp, NULL, "teller", NULL, DB_HASH,
169 DB_CREATE, 0644)) != 0) {
170 _snprintf(msgString, ERR_STRING_MAX,
171 "Teller file create failed. error: %s.", db_strerror(err));
172 return (1);
173 }
174
175 start_tnum = idnum;
176 if ((err = populateTable(dbp, idnum, balance, tellers, "teller")) != 0)
177 return (1);
178 idnum += tellers;
179 end_tnum = idnum - 1;
180 if ((err = dbp->close(dbp, 0)) != 0) {
181 _snprintf(msgString, ERR_STRING_MAX,
182 "Close of teller file failed. error: %s.",
183 db_strerror(err));
184 return (1);
185 }
186
187 if ((err = db_create(&dbp, dbenv, 0)) != 0) {
188 _snprintf(msgString, ERR_STRING_MAX,
189 "db_create of history db failed.");
190 return (1);
191 }
192 dbp->set_re_len(dbp, HISTORY_LEN);
193 if ((err = dbp->open(dbp, NULL, "history", NULL, DB_RECNO,
194 DB_CREATE, 0644)) != 0) {
195 _snprintf(msgString, ERR_STRING_MAX,
196 "Create of history file failed. error: %s.",
197 db_strerror(err));
198 return (1);
199 }
200
201 populateHistory(dbp, history, accounts, branches, tellers);
202 if ((err = dbp->close(dbp, 0)) != 0) {
203 _snprintf(msgString, ERR_STRING_MAX,
204 "Close of history file failed. error: %s.",
205 db_strerror(err));
206 return (1);
207 }
208
209 _snprintf(msgString, ERR_STRING_MAX, "Populated OK.");
210 return (0);
211 }
212
213 int
populateTable(DB * dbp,u_int32_t start_id,u_int32_t balance,int nrecs,const char * msg)214 TpcbExample::populateTable(DB *dbp,
215 u_int32_t start_id, u_int32_t balance,
216 int nrecs, const char *msg)
217 {
218 DBT kdbt, ddbt;
219 Defrec drec;
220 memset(&drec.pad[0], 1, sizeof(drec.pad));
221
222 memset(&kdbt, 0, sizeof(kdbt));
223 memset(&ddbt, 0, sizeof(ddbt));
224 kdbt.data = &drec.id;
225 kdbt.size = sizeof(u_int32_t);
226 ddbt.data = &drec;
227 ddbt.size = sizeof(drec);
228
229 for (int i = 0; i < nrecs; i++) {
230 drec.id = start_id + (u_int32_t)i;
231 drec.balance = balance;
232 int err;
233 if ((err =
234 dbp->put(dbp, NULL, &kdbt, &ddbt, DB_NOOVERWRITE)) != 0) {
235 _snprintf(msgString, ERR_STRING_MAX,
236 "Failure initializing %s file: %s. Likely re-initializing.",
237 msg, db_strerror(err));
238 return (1);
239 }
240 }
241 return (0);
242 }
243
244 int
populateHistory(DB * dbp,int nrecs,u_int32_t accounts,u_int32_t branches,u_int32_t tellers)245 TpcbExample::populateHistory(DB *dbp, int nrecs, u_int32_t accounts,
246 u_int32_t branches, u_int32_t tellers)
247 {
248 DBT kdbt, ddbt;
249 Histrec hrec;
250 memset(&hrec.pad[0], 1, sizeof(hrec.pad));
251 hrec.amount = 10;
252 db_recno_t key;
253
254 memset(&kdbt, 0, sizeof(kdbt));
255 memset(&ddbt, 0, sizeof(ddbt));
256 kdbt.data = &key;
257 kdbt.size = sizeof(u_int32_t);
258 ddbt.data = &hrec;
259 ddbt.size = sizeof(hrec);
260
261 for (int i = 1; i <= nrecs; i++) {
262 hrec.aid = randomId(ACCOUNT, accounts, branches, tellers);
263 hrec.bid = randomId(BRANCH, accounts, branches, tellers);
264 hrec.tid = randomId(TELLER, accounts, branches, tellers);
265
266 int err;
267 key = (db_recno_t)i;
268 if ((err = dbp->put(dbp, NULL, &kdbt, &ddbt, DB_APPEND)) != 0) {
269 _snprintf(msgString, ERR_STRING_MAX,
270 "Failure initializing history file: %s.",
271 db_strerror(err));
272 return (1);
273 }
274 }
275 return (0);
276 }
277
278 int
run(int n)279 TpcbExample::run(int n)
280 {
281 DB *adb, *bdb, *hdb, *tdb;
282 int failed, ret, txns;
283 DWORD start_time, end_time;
284 double elapsed_secs;
285
286 //
287 // Open the database files.
288 //
289
290 int err;
291 if ((err = db_create(&adb, dbenv, 0)) != 0) {
292 _snprintf(msgString, ERR_STRING_MAX,
293 "db_create of account db failed. Error: %s",
294 db_strerror(err));
295 return (1);
296 }
297 if ((err = adb->open(adb, NULL, "account", NULL, DB_UNKNOWN,
298 DB_AUTO_COMMIT, 0)) != 0) {
299 _snprintf(msgString, ERR_STRING_MAX,
300 "Open of account file failed. Error: %s", db_strerror(err));
301 return (1);
302 }
303
304 if ((err = db_create(&bdb, dbenv, 0)) != 0) {
305 _snprintf(msgString, ERR_STRING_MAX,
306 "db_create of branch db failed. Error: %s",
307 db_strerror(err));
308 return (1);
309 }
310 if ((err = bdb->open(bdb, NULL, "branch", NULL, DB_UNKNOWN,
311 DB_AUTO_COMMIT, 0)) != 0) {
312 _snprintf(msgString, ERR_STRING_MAX,
313 "Open of branch file failed. Error: %s", db_strerror(err));
314 return (1);
315 }
316
317 if ((err = db_create(&tdb, dbenv, 0)) != 0) {
318 _snprintf(msgString, ERR_STRING_MAX,
319 "db_create of teller db failed. Error: %s",
320 db_strerror(err));
321 return (1);
322 }
323 if ((err = tdb->open(tdb, NULL, "teller", NULL, DB_UNKNOWN,
324 DB_AUTO_COMMIT, 0)) != 0) {
325 _snprintf(msgString, ERR_STRING_MAX,
326 "Open of teller file failed. Error: %s", db_strerror(err));
327 return (1);
328 }
329
330 if ((err = db_create(&hdb, dbenv, 0)) != 0) {
331 _snprintf(msgString, ERR_STRING_MAX,
332 "db_create of teller db failed. Error: %s",
333 db_strerror(err));
334 return (1);
335 }
336 if ((err = hdb->open(hdb, NULL, "history", NULL, DB_UNKNOWN,
337 DB_AUTO_COMMIT, 0)) != 0) {
338 _snprintf(msgString, ERR_STRING_MAX,
339 "Open of history file failed. Error: %s", db_strerror(err));
340 return (1);
341 }
342
343 start_time = GetTickCount();
344 for (txns = n, failed = 0; n-- > 0;)
345 if ((ret = txn(adb, bdb, tdb, hdb,
346 accounts, branches, tellers)) != 0)
347 ++failed;
348 end_time = GetTickCount();
349 if (end_time == start_time)
350 ++end_time;
351 #define MILLISECS_PER_SEC 1000
352 elapsed_secs = (double)((end_time - start_time))/MILLISECS_PER_SEC;
353 _snprintf(msgString, ERR_STRING_MAX,
354 "%s: %d txns: %d failed, %.2f TPS\n", progname, txns, failed,
355 (txns - failed) / elapsed_secs);
356
357 (void)adb->close(adb, 0);
358 (void)bdb->close(bdb, 0);
359 (void)tdb->close(tdb, 0);
360 (void)hdb->close(hdb, 0);
361
362 return (0);
363 }
364
365 //
366 // XXX Figure out the appropriate way to pick out IDs.
367 //
368 int
txn(DB * adb,DB * bdb,DB * tdb,DB * hdb,int accounts,int branches,int tellers)369 TpcbExample::txn(DB *adb, DB *bdb, DB *tdb, DB *hdb,
370 int accounts, int branches, int tellers)
371 {
372 DBC *acurs, *bcurs, *tcurs;
373 DB_TXN *t;
374 DBT d_dbt, d_histdbt, k_dbt, k_histdbt;
375
376 db_recno_t key;
377 Defrec rec;
378 Histrec hrec;
379 int account, branch, teller, ret;
380
381 memset(&d_dbt, 0, sizeof(d_dbt));
382 memset(&d_histdbt, 0, sizeof(d_histdbt));
383 memset(&k_dbt, 0, sizeof(k_dbt));
384 memset(&k_histdbt, 0, sizeof(k_histdbt));
385 k_histdbt.data = &key;
386 k_histdbt.size = sizeof(key);
387
388 // !!!
389 // This is sample code -- we could move a lot of this into the driver
390 // to make it faster.
391 //
392 account = randomId(ACCOUNT, accounts, branches, tellers);
393 branch = randomId(BRANCH, accounts, branches, tellers);
394 teller = randomId(TELLER, accounts, branches, tellers);
395
396 k_dbt.size = sizeof(int);
397
398 d_dbt.flags |= DB_DBT_USERMEM;
399 d_dbt.data = &rec;
400 d_dbt.ulen = sizeof(rec);
401
402 hrec.aid = account;
403 hrec.bid = branch;
404 hrec.tid = teller;
405 hrec.amount = 10;
406 // Request 0 bytes since we're just positioning.
407 d_histdbt.flags |= DB_DBT_PARTIAL;
408
409 // START PER-TRANSACTION TIMING.
410 //
411 // Technically, TPCB requires a limit on response time, you only get
412 // to count transactions that complete within 2 seconds. That's not
413 // an issue for this sample application -- regardless, here's where
414 // the transaction begins.
415 if (dbenv->txn_begin(dbenv, NULL, &t, 0) != 0)
416 goto err;
417
418 if (adb->cursor(adb, t, &acurs, 0) != 0 ||
419 bdb->cursor(bdb, t, &bcurs, 0) != 0 ||
420 tdb->cursor(tdb, t, &tcurs, 0) != 0)
421 goto err;
422
423 // Account record
424 k_dbt.data = &account;
425 if (acurs->get(acurs, &k_dbt, &d_dbt, DB_SET) != 0)
426 goto err;
427 rec.balance += 10;
428 if (acurs->put(acurs, &k_dbt, &d_dbt, DB_CURRENT) != 0)
429 goto err;
430
431 // Branch record
432 k_dbt.data = &branch;
433 if (bcurs->get(bcurs, &k_dbt, &d_dbt, DB_SET) != 0)
434 goto err;
435 rec.balance += 10;
436 if (bcurs->put(bcurs, &k_dbt, &d_dbt, DB_CURRENT) != 0)
437 goto err;
438
439 // Teller record
440 k_dbt.data = &teller;
441 if (tcurs->get(tcurs, &k_dbt, &d_dbt, DB_SET) != 0)
442 goto err;
443 rec.balance += 10;
444 if (tcurs->put(tcurs, &k_dbt, &d_dbt, DB_CURRENT) != 0)
445 goto err;
446
447 // History record
448 d_histdbt.flags = 0;
449 d_histdbt.data = &hrec;
450 d_histdbt.ulen = sizeof(hrec);
451 if (hdb->put(hdb, t, &k_histdbt, &d_histdbt, DB_APPEND) != 0)
452 goto err;
453
454 if (acurs->close(acurs) != 0 || bcurs->close(bcurs) != 0
455 || tcurs->close(tcurs) != 0)
456 goto err;
457
458 ret = t->commit(t, 0);
459 t = NULL;
460 if (ret != 0)
461 goto err;
462
463 // END PER-TRANSACTION TIMING.
464 return (0);
465
466 err:
467 if (acurs != NULL)
468 (void)acurs->close(acurs);
469 if (bcurs != NULL)
470 (void)bcurs->close(bcurs);
471 if (tcurs != NULL)
472 (void)tcurs->close(tcurs);
473 if (t != NULL)
474 (void)t->abort(t);
475
476 return (-1);
477 }
478
479 // Utility functions
480
481 u_int32_t
randomInt(u_int32_t lo,u_int32_t hi)482 TpcbExample::randomInt(u_int32_t lo, u_int32_t hi)
483 {
484 u_int32_t ret;
485 int t;
486
487 t = rand();
488 ret = (u_int32_t)(((double)t / ((double)(RAND_MAX) + 1)) *
489 (hi - lo + 1));
490 ret += lo;
491 return (ret);
492 }
493
494 u_int32_t
randomId(FTYPE type,u_int32_t accounts,u_int32_t branches,u_int32_t tellers)495 TpcbExample::randomId(FTYPE type, u_int32_t accounts,
496 u_int32_t branches, u_int32_t tellers)
497 {
498 u_int32_t min, max, num;
499
500 max = min = BEGID;
501 num = accounts;
502 switch (type) {
503 case TELLER:
504 min += branches;
505 num = tellers;
506 // Fallthrough
507 case BRANCH:
508 if (type == BRANCH)
509 num = branches;
510 min += accounts;
511 // Fallthrough
512 case ACCOUNT:
513 max = min + num - 1;
514 }
515 return (randomInt(min, max));
516 }
517
518 char *
getHomeDir(char * path,int max)519 TpcbExample::getHomeDir(char *path, int max)
520 {
521 memcpy(path, homeDirName, min(max, MAX_PATH));
522 return path;
523 }
524
525 wchar_t *
getHomeDirW(wchar_t * path,int max)526 TpcbExample::getHomeDirW(wchar_t *path, int max)
527 {
528 memcpy(path, wHomeDirName, min(max, MAX_PATH)*sizeof(wchar_t));
529 return path;
530 }
531
532 void
setHomeDir(char * path)533 TpcbExample::setHomeDir(char *path)
534 {
535 int path_len;
536
537 path_len = strlen(path);
538 strncpy(homeDirName, path, MAX_PATH);
539 MultiByteToWideChar(CP_ACP, 0, path, path_len, wHomeDirName, MAX_PATH);
540 wHomeDirName[path_len] = L'\0';
541 }
542
543 void
setHomeDirW(wchar_t * path)544 TpcbExample::setHomeDirW(wchar_t *path)
545 {
546 int path_len;
547
548 path_len = wcslen(path);
549 wcsncpy(wHomeDirName, path, MAX_PATH);
550 WideCharToMultiByte(CP_ACP, 0, path, path_len, homeDirName,
551 MAX_PATH, 0, 0);
552 homeDirName[path_len] = '\0';
553 }
554