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