1/*- 2 * See the file LICENSE for redistribution information. 3 * 4 * Copyright (c) 2007-2009 Oracle. All rights reserved. 5 * 6 * $Id$ 7 */ 8 9#include "TpcbExample.h" 10 11#define HISTORY_LEN 100 12#define RECLEN 100 13#define BEGID 1000000 14 15struct 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 21struct 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 29const char *progname = "wce_tpcb"; 30 31TpcbExample::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 40int 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 72void 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// 84int 85TpcbExample::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 213int 214TpcbExample::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 244int 245TpcbExample::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 278int 279TpcbExample::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// 368int 369TpcbExample::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 466err: 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 481u_int32_t 482TpcbExample::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 494u_int32_t 495TpcbExample::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 518char * 519TpcbExample::getHomeDir(char *path, int max) 520{ 521 memcpy(path, homeDirName, min(max, MAX_PATH)); 522 return path; 523} 524 525wchar_t * 526TpcbExample::getHomeDirW(wchar_t *path, int max) 527{ 528 memcpy(path, wHomeDirName, min(max, MAX_PATH)*sizeof(wchar_t)); 529 return path; 530} 531 532void 533TpcbExample::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 543void 544TpcbExample::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