/* * start_workers -- * Setup the configuration for the tables being populated, then start * the worker thread(s) and wait for them to finish. */ int start_workers(table_type type) { WT_SESSION *session; struct timeval start, stop; double seconds; pthread_t *tids; int i, ret; void *thread_ret; ret = 0; /* Create statistics and thread structures. */ if ((tids = calloc((size_t)(g.nworkers), sizeof(*tids))) == NULL) return (log_print_err("calloc", errno, 1)); if ((ret = g.conn->open_session(g.conn, NULL, NULL, &session)) != 0) { (void)log_print_err("conn.open_session", ret, 1); goto err; } /* Setup the cookies */ for (i = 0; i < g.ntables; ++i) { g.cookies[i].id = i; if (type == MIX) g.cookies[i].type = (table_type)((i % MAX_TABLE_TYPE) + 1); else g.cookies[i].type = type; (void)snprintf(g.cookies[i].uri, 128, "%s%04d", URI_BASE, g.cookies[i].id); /* Should probably be atomic to avoid races. */ if ((ret = create_table(session, &g.cookies[i])) != 0) goto err; } (void)gettimeofday(&start, NULL); /* Create threads. */ for (i = 0; i < g.nworkers; ++i) { if ((ret = pthread_create( &tids[i], NULL, worker, &g.cookies[i])) != 0) { (void)log_print_err("pthread_create", ret, 1); goto err; } } /* Wait for the threads. */ for (i = 0; i < g.nworkers; ++i) (void)pthread_join(tids[i], &thread_ret); (void)gettimeofday(&stop, NULL); seconds = (stop.tv_sec - start.tv_sec) + (stop.tv_usec - start.tv_usec) * 1e-6; printf("Ran workers for: %f seconds\n", seconds); err: free(tids); return (ret); }
/* * wt_connect -- * Configure the WiredTiger connection. */ static int wt_connect(const char *config_open) { static WT_EVENT_HANDLER event_handler = { handle_error, handle_message, NULL, NULL /* Close handler. */ }; int ret; char config[128]; testutil_make_work_dir(g.home); snprintf(config, sizeof(config), "create,statistics=(fast),error_prefix=\"%s\",cache_size=1GB%s%s", g.progname, config_open == NULL ? "" : ",", config_open == NULL ? "" : config_open); if ((ret = wiredtiger_open( g.home, &event_handler, config, &g.conn)) != 0) return (log_print_err("wiredtiger_open", ret, 1)); return (0); }
/* * real_checkpointer -- * Do the work of creating checkpoints and then verifying them. Also * responsible for finishing in a timely fashion. */ static int real_checkpointer(void) { WT_SESSION *session; char *checkpoint_config, _buf[128]; int ret; if (g.running == 0) return (log_print_err( "Checkpoint thread started stopped\n", EINVAL, 1)); while (g.ntables > g.ntables_created) sched_yield(); if ((ret = g.conn->open_session(g.conn, NULL, NULL, &session)) != 0) return (log_print_err("conn.open_session", ret, 1)); if (strncmp(g.checkpoint_name, "WiredTigerCheckpoint", strlen("WiredTigerCheckpoint")) == 0) checkpoint_config = NULL; else { checkpoint_config = _buf; snprintf(checkpoint_config, 128, "name=%s", g.checkpoint_name); } while (g.running) { /* Execute a checkpoint */ if ((ret = session->checkpoint( session, checkpoint_config)) != 0) return (log_print_err("session.checkpoint", ret, 1)); printf("Finished a checkpoint\n"); if (!g.running) goto done; /* Verify the content of the checkpoint. */ if ((ret = verify_checkpoint(session)) != 0) return (log_print_err("verify_checkpoint", ret, 1)); } done: if ((ret = session->close(session, NULL)) != 0) return (log_print_err("session.close", ret, 1)); return (0); }
/* * start_checkpoints -- * Responsible for creating the checkpoint thread. */ int start_checkpoints(void) { int ret; if ((ret = pthread_create( &g.checkpoint_thread, NULL, checkpointer, NULL)) != 0) return (log_print_err("pthread_create", ret, 1)); return (0); }
/* * compare_cursors -- * Compare the key/value pairs from two cursors. */ static int compare_cursors( WT_CURSOR *cursor1, const char *type1, WT_CURSOR *cursor2, const char *type2) { uint64_t key1, key2; char *val1, *val2; char buf[128]; memset(buf, 0, 128); if (cursor1->get_key(cursor1, &key1) != 0 || cursor2->get_key(cursor2, &key2) != 0) return (log_print_err("Error getting keys", EINVAL, 1)); if (key1 != key2) { printf("Key mismatch %" PRIu64 " from a %s table " "is not %" PRIu64 " from a %s table\n", key1, type1, key2, type2); return (ERR_KEY_MISMATCH); } /* Now check the values. */ if (cursor1->get_value(cursor1, &val1) != 0 || cursor2->get_value(cursor2, &val2) != 0) return (log_print_err("Error getting values", EINVAL, 1)); if (g.logfp != NULL) fprintf(g.logfp, "k1: %" PRIu64 " k2: %" PRIu64 " val1: %s val2: %s \n", key1, key2, val1, val2); if (strlen(val1) != strlen(val2) || strcmp(val1, val2) != 0) { printf("Value mismatch for key %" PRIu64 ", %s from a %s table is not %s from a %s table\n", key1, val1, type1, val2, type2); return (ERR_DATA_MISMATCH); } return (0); }
/* * compare_cursors -- * Compare the key/value pairs from two cursors. */ static int compare_cursors( WT_CURSOR *cursor1, const char *type1, WT_CURSOR *cursor2, const char *type2) { uint64_t key1, key2; char *val1, *val2, buf[128]; int ret; ret = 0; memset(buf, 0, 128); if (cursor1->get_key(cursor1, &key1) != 0 || cursor2->get_key(cursor2, &key2) != 0) return (log_print_err("Error getting keys", EINVAL, 1)); if (cursor1->get_value(cursor1, &val1) != 0 || cursor2->get_value(cursor2, &val2) != 0) return (log_print_err("Error getting values", EINVAL, 1)); if (g.logfp != NULL) fprintf(g.logfp, "k1: %" PRIu64 " k2: %" PRIu64 " val1: %s val2: %s \n", key1, key2, val1, val2); if (key1 != key2) ret = ERR_KEY_MISMATCH; else if (strlen(val1) != strlen(val2) || strcmp(val1, val2) != 0) ret = ERR_DATA_MISMATCH; else return (0); printf("Key/value mismatch: %" PRIu64 "/%s from a %s table is not %" PRIu64 "/%s from a %s table\n", key1, val1, type1, key2, val2, type2); return (ret); }
/* * wt_shutdown -- * Shut down the WiredTiger connection. */ static int wt_shutdown(void) { int ret; if (g.conn == NULL) return (0); printf("Closing connection\n"); ret = g.conn->close(g.conn, NULL); g.conn = NULL; if (ret != 0) return (log_print_err("conn.close", ret, 1)); return (0); }
/* * create_table -- * Create a WiredTiger table of the configured type for this cookie. */ static int create_table(WT_SESSION *session, COOKIE *cookie) { int ret; char config[128]; testutil_check(__wt_snprintf(config, sizeof(config), "key_format=%s,value_format=S,%s", cookie->type == COL ? "r" : "q", cookie->type == LSM ? ",type=lsm" : "")); if ((ret = session->create(session, cookie->uri, config)) != 0) if (ret != EEXIST) return (log_print_err("session.create", ret, 1)); ++g.ntables_created; return (0); }
/* * worker_op -- * Write operation. */ static inline int worker_op(WT_CURSOR *cursor, uint64_t keyno, u_int new_val) { int ret; char valuebuf[64]; cursor->set_key(cursor, keyno); testutil_check(__wt_snprintf( valuebuf, sizeof(valuebuf), "%037u", new_val)); cursor->set_value(cursor, valuebuf); if ((ret = cursor->insert(cursor)) != 0) { if (ret == WT_ROLLBACK) return (WT_ROLLBACK); return (log_print_err("cursor.insert", ret, 1)); } return (0); }
/* * create_table -- * Create a WiredTiger table of the configured type for this cookie. */ static int create_table(WT_SESSION *session, COOKIE *cookie) { int ret; char *p, *end, config[128]; p = config; end = config + sizeof(config); p += snprintf(p, (size_t)(end - p), "key_format=%s,value_format=S", cookie->type == COL ? "r" : "q"); if (cookie->type == LSM) (void)snprintf(p, (size_t)(end - p), ",type=lsm"); if ((ret = session->create(session, cookie->uri, config)) != 0) if (ret != EEXIST) return (log_print_err("session.create", ret, 1)); ++g.ntables_created; return (0); }
/* * diagnose_key_error -- * Dig a bit deeper on failure. Continue after some failures here to * extract as much information as we can. */ static int diagnose_key_error( WT_CURSOR *cursor1, int index1, WT_CURSOR *cursor2, int index2) { WT_CURSOR *c; WT_SESSION *session; uint64_t key1, key1_orig, key2, key2_orig; char next_uri[128], ckpt[128]; int ret; /* Hack to avoid passing session as parameter. */ session = cursor1->session; key1_orig = key2_orig = 0; snprintf(ckpt, 128, "checkpoint=%s", g.checkpoint_name); /* Save the failed keys. */ if (cursor1->get_key(cursor1, &key1_orig) != 0 || cursor2->get_key(cursor2, &key2_orig) != 0) { (void)log_print_err("Error retrieving key.", EINVAL, 0); goto live_check; } if (key1_orig == key2_orig) goto live_check; /* See if previous values are still valid. */ if (cursor1->prev(cursor1) != 0 || cursor2->prev(cursor2) != 0) return (1); if (cursor1->get_key(cursor1, &key1) != 0 || cursor2->get_key(cursor2, &key2) != 0) (void)log_print_err("Error decoding key", EINVAL, 1); else if (key1 != key2) (void)log_print_err("Now previous keys don't match", EINVAL, 0); if (cursor1->next(cursor1) != 0 || cursor2->next(cursor2) != 0) return (1); if (cursor1->get_key(cursor1, &key1) != 0 || cursor2->get_key(cursor2, &key2) != 0) (void)log_print_err("Error decoding key", EINVAL, 1); else if (key1 == key2) (void)log_print_err("After prev/next keys match", EINVAL, 0); if (cursor1->next(cursor1) != 0 || cursor2->next(cursor2) != 0) return (1); if (cursor1->get_key(cursor1, &key1) != 0 || cursor2->get_key(cursor2, &key2) != 0) (void)log_print_err("Error decoding key", EINVAL, 1); else if (key1 == key2) (void)log_print_err( "After prev/next/next keys match", EINVAL, 0); /* * Now try opening new cursors on the checkpoints and see if we * get the same missing key via searching. */ snprintf(next_uri, 128, "table:__wt%04d", index1); if (session->open_cursor(session, next_uri, NULL, ckpt, &c) != 0) return (1); c->set_key(c, key1_orig); if ((ret = c->search(c)) != 0) (void)log_print_err("1st cursor didn't find 1st key\n", ret, 0); c->set_key(c, key2_orig); if ((ret = c->search(c)) != 0) (void)log_print_err("1st cursor didn't find 2nd key\n", ret, 0); if (c->close(c) != 0) return (1); snprintf(next_uri, 128, "table:__wt%04d", index2); if (session->open_cursor(session, next_uri, NULL, ckpt, &c) != 0) return (1); c->set_key(c, key1_orig); if ((ret = c->search(c)) != 0) (void)log_print_err("2nd cursor didn't find 1st key\n", ret, 0); c->set_key(c, key2_orig); if ((ret = c->search(c)) != 0) (void)log_print_err("2nd cursor didn't find 2nd key\n", ret, 0); if (c->close(c) != 0) return (1); live_check: /* * Now try opening cursors on the live checkpoint to see if we get the * same missing key via searching. */ snprintf(next_uri, 128, "table:__wt%04d", index1); if (session->open_cursor(session, next_uri, NULL, NULL, &c) != 0) return (1); c->set_key(c, key1_orig); if ((ret = c->search(c)) != 0) (void)log_print_err("1st cursor didn't find 1st key\n", ret, 0); if (c->close(c) != 0) return (1); snprintf(next_uri, 128, "table:__wt%04d", index2); if (session->open_cursor(session, next_uri, NULL, NULL, &c) != 0) return (1); c->set_key(c, key2_orig); if ((ret = c->search(c)) != 0) (void)log_print_err("2nd cursor didn't find 2nd key\n", ret, 0); if (c->close(c) != 0) return (1); return (0); }
/* * verify_checkpoint -- * Open a cursor on each table at the last checkpoint and walk through * the tables in parallel. The key/values should match across all * tables. */ static int verify_checkpoint(WT_SESSION *session) { WT_CURSOR **cursors; const char *type0, *typei; char next_uri[128], ckpt[128]; int i, ret, t_ret; uint64_t key_count; ret = t_ret = 0; key_count = 0; snprintf(ckpt, 128, "checkpoint=%s", g.checkpoint_name); cursors = calloc((size_t)g.ntables, sizeof(*cursors)); if (cursors == NULL) return (log_print_err("verify_checkpoint", ENOMEM, 1)); for (i = 0; i < g.ntables; i++) { /* * TODO: LSM doesn't currently support reading from * checkpoints. */ if (g.cookies[i].type == LSM) continue; snprintf(next_uri, 128, "table:__wt%04d", i); if ((ret = session->open_cursor( session, next_uri, NULL, ckpt, &cursors[i])) != 0) { (void)log_print_err( "verify_checkpoint:session.open_cursor", ret, 1); goto err; } } while (ret == 0) { ret = cursors[0]->next(cursors[0]); if (ret == 0) ++key_count; else if (ret != WT_NOTFOUND) { (void)log_print_err("cursor->next", ret, 1); goto err; } /* * Check to see that all remaining cursors have the * same key/value pair. */ for (i = 1; i < g.ntables; i++) { /* * TODO: LSM doesn't currently support reading from * checkpoints. */ if (g.cookies[i].type == LSM) continue; t_ret = cursors[i]->next(cursors[i]); if (t_ret != 0 && t_ret != WT_NOTFOUND) { (void)log_print_err("cursor->next", ret, 1); goto err; } if (ret == WT_NOTFOUND && t_ret == WT_NOTFOUND) continue; else if (ret == WT_NOTFOUND || t_ret == WT_NOTFOUND) { (void)log_print_err( "verify_checkpoint tables with different" " amount of data", EFAULT, 1); goto err; } type0 = type_to_string(g.cookies[0].type); typei = type_to_string(g.cookies[i].type); if ((ret = compare_cursors( cursors[0], type0, cursors[i], typei)) != 0) { (void)diagnose_key_error( cursors[0], 0, cursors[i], i); (void)log_print_err( "verify_checkpoint - mismatching data", EFAULT, 1); goto err; } } } printf("Finished verifying a checkpoint with %d tables and %" PRIu64 " keys\n", g.ntables, key_count); err: for (i = 0; i < g.ntables; i++) { if (cursors[i] != NULL && (ret = cursors[i]->close(cursors[i])) != 0) (void)log_print_err( "verify_checkpoint:cursor close", ret, 1); } free(cursors); return (0); }
int main(int argc, char *argv[]) { table_type ttype; int ch, cnt, ret, runs; char *working_dir; const char *config_open; if ((g.progname = strrchr(argv[0], DIR_DELIM)) == NULL) g.progname = argv[0]; else ++g.progname; config_open = NULL; ret = 0; working_dir = NULL; ttype = MIX; g.checkpoint_name = "WiredTigerCheckpoint"; if ((g.home = malloc(512)) == NULL) testutil_die(ENOMEM, "Unable to allocate memory"); g.nkeys = 10000; g.nops = 100000; g.ntables = 3; g.nworkers = 1; runs = 1; while ((ch = __wt_getopt( g.progname, argc, argv, "c:C:h:k:l:n:r:t:T:W:")) != EOF) switch (ch) { case 'c': g.checkpoint_name = __wt_optarg; break; case 'C': /* wiredtiger_open config */ config_open = __wt_optarg; break; case 'h': /* wiredtiger_open config */ working_dir = __wt_optarg; break; case 'k': /* rows */ g.nkeys = (u_int)atoi(__wt_optarg); break; case 'l': /* log */ if ((g.logfp = fopen(__wt_optarg, "w")) == NULL) { fprintf(stderr, "%s: %s\n", __wt_optarg, strerror(errno)); return (EXIT_FAILURE); } break; case 'n': /* operations */ g.nops = (u_int)atoi(__wt_optarg); break; case 'r': /* runs */ runs = atoi(__wt_optarg); break; case 't': switch (__wt_optarg[0]) { case 'c': ttype = COL; break; case 'l': ttype = LSM; break; case 'm': ttype = MIX; break; case 'r': ttype = ROW; break; default: return (usage()); } break; case 'T': g.ntables = atoi(__wt_optarg); break; case 'W': g.nworkers = atoi(__wt_optarg); break; default: return (usage()); } argc -= __wt_optind; if (argc != 0) return (usage()); /* Clean up on signal. */ (void)signal(SIGINT, onint); testutil_work_dir_from_path(g.home, 512, working_dir); printf("%s: process %" PRIu64 "\n", g.progname, (uint64_t)getpid()); for (cnt = 1; (runs == 0 || cnt <= runs) && g.status == 0; ++cnt) { printf(" %d: %u workers, %u tables\n", cnt, g.nworkers, g.ntables); (void)cleanup(); /* Clean up previous runs */ /* Setup a fresh set of cookies in the global array. */ if ((g.cookies = calloc( (size_t)(g.ntables), sizeof(COOKIE))) == NULL) { (void)log_print_err("No memory", ENOMEM, 1); break; } g.running = 1; if ((ret = wt_connect(config_open)) != 0) { (void)log_print_err("Connection failed", ret, 1); break; } if ((ret = start_checkpoints()) != 0) { (void)log_print_err("Start checkpoints failed", ret, 1); break; } if ((ret = start_workers(ttype)) != 0) { (void)log_print_err("Start workers failed", ret, 1); break; } g.running = 0; if ((ret = end_checkpoints()) != 0) { (void)log_print_err("Start workers failed", ret, 1); break; } free(g.cookies); g.cookies = NULL; if ((ret = wt_shutdown()) != 0) { (void)log_print_err("Start workers failed", ret, 1); break; } } if (g.logfp != NULL) (void)fclose(g.logfp); /* Ensure that cleanup is done on error. */ (void)wt_shutdown(); free(g.cookies); return (g.status); }
/* * real_worker -- * A single worker thread that transactionally updates all tables with * consistent values. */ static int real_worker(void) { WT_CURSOR **cursors; WT_RAND_STATE rnd; WT_SESSION *session; u_int i, keyno; int j, ret, t_ret; ret = t_ret = 0; __wt_random_init(&rnd); if ((cursors = calloc( (size_t)(g.ntables), sizeof(WT_CURSOR *))) == NULL) return (log_print_err("malloc", ENOMEM, 1)); if ((ret = g.conn->open_session( g.conn, NULL, "isolation=snapshot", &session)) != 0) { (void)log_print_err("conn.open_session", ret, 1); goto err; } for (j = 0; j < g.ntables; j++) if ((ret = session->open_cursor(session, g.cookies[j].uri, NULL, NULL, &cursors[j])) != 0) { (void)log_print_err("session.open_cursor", ret, 1); goto err; } for (i = 0; i < g.nops && g.running; ++i, __wt_yield()) { if ((ret = session->begin_transaction(session, NULL)) != 0) { (void)log_print_err( "real_worker:begin_transaction", ret, 1); goto err; } keyno = __wt_random(&rnd) % g.nkeys + 1; for (j = 0; j < g.ntables; j++) { if ((ret = worker_op(cursors[j], keyno, i)) != 0) break; } if (ret == 0) { if ((ret = session->commit_transaction( session, NULL)) != 0) { (void)log_print_err( "real_worker:commit_transaction", ret, 1); goto err; } } else if (ret == WT_ROLLBACK) { if ((ret = session->rollback_transaction( session, NULL)) != 0) { (void)log_print_err( "real_worker:rollback_transaction", ret, 1); goto err; } } else { (void)log_print_err("worker op failed", ret, 1); goto err; } } err: if ((t_ret = session->close(session, NULL)) != 0 && ret == 0) { ret = t_ret; (void)log_print_err("session.close", ret, 1); } free(cursors); return (ret); }