diff options
author | Alex Gorrod <alexg@wiredtiger.com> | 2014-02-26 16:09:21 +1100 |
---|---|---|
committer | Alex Gorrod <alexg@wiredtiger.com> | 2014-02-26 16:09:21 +1100 |
commit | e95fe42a0b9509244d2e9c5ab6138ba02c416a90 (patch) | |
tree | 0f1f04a4e6e490ce63fedcd47b9c1a02631d07b6 | |
parent | 6bb1d7c51208f8d99bdca4d194d661360952cb77 (diff) | |
parent | af1793bf189527f7bc6d6133f065a0007cdd2fb9 (diff) | |
download | mongo-e95fe42a0b9509244d2e9c5ab6138ba02c416a90.tar.gz |
Merge branch 'config-parse-api' into wtperf-multiple-databases
Conflicts:
bench/wtperf/wtperf.c
bench/wtperf/wtperf.h
35 files changed, 623 insertions, 538 deletions
diff --git a/bench/wtperf/config.c b/bench/wtperf/config.c index 63944f802bc..800c760f72d 100644 --- a/bench/wtperf/config.c +++ b/bench/wtperf/config.c @@ -69,8 +69,8 @@ config_assign(CONFIG *dest, const CONFIG *src) dest->popthreads = NULL; dest->workers = NULL; - if (src->uri != NULL) - dest->uri = strdup(src->uri); + if (src->base_uri != NULL) + dest->base_uri = strdup(src->base_uri); if (src->workload != NULL) { dest->workload = calloc(WORKLOAD_MAX, sizeof(WORKLOAD)); memcpy(dest->workload, @@ -121,7 +121,7 @@ config_free(CONFIG *cfg) free(cfg->ckptthreads); free(cfg->popthreads); - free(cfg->uri); + free(cfg->base_uri); free(cfg->workers); free(cfg->workload); } @@ -168,9 +168,10 @@ config_threads(CONFIG *cfg, const char *config, size_t len) { WORKLOAD *workp; WT_CONFIG_ITEM groupk, groupv, k, v; - WT_CONFIG_SCAN *group, *scan; + WT_CONFIG_PARSER *group, *scan; int ret; + group = scan = NULL; /* Allocate the workload array. */ if ((cfg->workload = calloc(WORKLOAD_MAX, sizeof(WORKLOAD))) == NULL) return (enomem(cfg)); @@ -185,11 +186,10 @@ config_threads(CONFIG *cfg, const char *config, size_t len) * returned from the original string. */ if ((ret = - wiredtiger_config_scan_begin(NULL, config, len, &group)) != 0) + wiredtiger_config_parser_open(NULL, config, len, &group)) != 0) goto err; - while ((ret = - wiredtiger_config_scan_next(NULL, group, &groupk, &groupv)) == 0) { - if ((ret = wiredtiger_config_scan_begin( + while ((ret = group->next(group, &groupk, &groupv)) == 0) { + if ((ret = wiredtiger_config_parser_open( NULL, groupk.str, groupk.len, &scan)) != 0) goto err; @@ -203,8 +203,7 @@ config_threads(CONFIG *cfg, const char *config, size_t len) } workp = &cfg->workload[cfg->workload_cnt++]; - while ((ret = - wiredtiger_config_scan_next(NULL, scan, &k, &v)) == 0) { + while ((ret = scan->next(scan, &k, &v)) == 0) { if (STRING_MATCH("count", k.str, k.len)) { if ((workp->threads = v.val) <= 0) goto err; @@ -234,8 +233,10 @@ config_threads(CONFIG *cfg, const char *config, size_t len) ret = 0; if (ret != 0 ) goto err; - if ((ret = wiredtiger_config_scan_end(NULL, scan)) != 0) + if ((ret = scan->close(scan)) != 0) { + scan = NULL; goto err; + } if (workp->insert == 0 && workp->read == 0 && workp->update == 0) @@ -243,12 +244,19 @@ config_threads(CONFIG *cfg, const char *config, size_t len) cfg->workers_cnt += (u_int)workp->threads; } - if ((ret = wiredtiger_config_scan_end(NULL, group)) != 0) + if ((ret = group->close(group)) != 0) { + group = NULL; goto err; + } return (0); -err: fprintf(stderr, +err: if (group != NULL) + (void)group->close(group); + if (scan != NULL) + (void)scan->close(scan); + + fprintf(stderr, "invalid thread configuration or scan error: %.*s\n", (int)len, config); return (EINVAL); @@ -461,19 +469,17 @@ int config_opt_line(CONFIG *cfg, const char *optstr) { WT_CONFIG_ITEM k, v; - WT_CONFIG_SCAN *scan; + WT_CONFIG_PARSER *scan; int ret, t_ret; - - if ((ret = wiredtiger_config_scan_begin( + if ((ret = wiredtiger_config_parser_open( NULL, optstr, strlen(optstr), &scan)) != 0) { lprintf(cfg, ret, 0, "Error in config_scan_begin"); return (ret); } while (ret == 0) { - if ((ret = - wiredtiger_config_scan_next(NULL, scan, &k, &v)) != 0) { + if ((ret = scan->next(scan, &k, &v)) != 0) { /* Any parse error has already been reported. */ if (ret == WT_NOTFOUND) ret = 0; @@ -481,7 +487,7 @@ config_opt_line(CONFIG *cfg, const char *optstr) } ret = config_opt(cfg, &k, &v); } - if ((t_ret = wiredtiger_config_scan_end(NULL, scan)) != 0) { + if ((t_ret = scan->close(scan)) != 0) { lprintf(cfg, ret, 0, "Error in config_scan_end"); if (ret == 0) ret = t_ret; diff --git a/bench/wtperf/runners/test1-500m-lsm.wtperf b/bench/wtperf/runners/test1-500m-lsm.wtperf index 0e79bdddb12..7f85b8c13e5 100644 --- a/bench/wtperf/runners/test1-500m-lsm.wtperf +++ b/bench/wtperf/runners/test1-500m-lsm.wtperf @@ -5,8 +5,9 @@ #conn_config="cache_size=21G,checkpoint_sync=false,mmap=false,session_max=1024,statistics=(fast,clear),statistics_log=(wait=60)" conn_config="cache_size=21G,checkpoint_sync=false,mmap=false,session_max=1024" compact=true +compression="snappy" sess_config="isolation=snapshot" -table_config="internal_page_max=128K,lsm=(bloom_config=(leaf_page_max=8MB),bloom_bit_count=28,bloom_hash_count=19,bloom_oldest=true,chunk_max=100GB,chunk_size=100MB,merge_threads=3),type=lsm,leaf_page_max=16K" +table_config="internal_page_max=128K,lsm=(bloom_config=(leaf_page_max=8MB),bloom_bit_count=28,bloom_hash_count=19,bloom_oldest=true,chunk_max=5GB,chunk_size=100MB,merge_threads=3),type=lsm,leaf_page_max=16K" icount=500000000 key_sz=40 value_sz=1000 diff --git a/bench/wtperf/runners/test2-500m-lsm.wtperf b/bench/wtperf/runners/test2-500m-lsm.wtperf index f305ad621d0..db1ee9d469c 100644 --- a/bench/wtperf/runners/test2-500m-lsm.wtperf +++ b/bench/wtperf/runners/test2-500m-lsm.wtperf @@ -6,8 +6,9 @@ #conn_config="cache_size=21G,checkpoint_sync=false,mmap=false,session_max=1024,statistics=(fast,clear),statistics_log=(wait=60)" conn_config="cache_size=21G,checkpoint_sync=false,mmap=false,session_max=1024" create=false -sess_config="isolation=snapshot" -table_config="internal_page_max=128K,lsm=(bloom_config=(leaf_page_max=8MB),bloom_bit_count=28,bloom_hash_count=19,bloom_oldest=true,chunk_max=100GB,chunk_size=100MB,merge_threads=3),type=lsm,leaf_page_max=16K" +compression="snappy" +sess_config="isolation=snapshot +table_config="internal_page_max=128K,lsm=(bloom_config=(leaf_page_max=8MB),bloom_bit_count=28,bloom_hash_count=19,bloom_oldest=true,chunk_max=5GB,chunk_size=100MB,merge_threads=3),type=lsm,leaf_page_max=16K" key_sz=40 value_sz=1000 report_interval=10 diff --git a/bench/wtperf/runners/test3-500m-lsm.wtperf b/bench/wtperf/runners/test3-500m-lsm.wtperf index 4960d52b869..446309c32c8 100644 --- a/bench/wtperf/runners/test3-500m-lsm.wtperf +++ b/bench/wtperf/runners/test3-500m-lsm.wtperf @@ -6,8 +6,9 @@ #conn_config="cache_size=21G,checkpoint_sync=false,mmap=false,session_max=1024,statistics=(fast,clear),statistics_log=(wait=60)" conn_config="cache_size=21G,checkpoint_sync=false,mmap=false,session_max=1024" create=false +compression="snappy" sess_config="isolation=snapshot" -table_config="internal_page_max=128K,lsm=(bloom_config=(leaf_page_max=8MB),bloom_bit_count=28,bloom_hash_count=19,bloom_oldest=true,chunk_max=100GB,chunk_size=100MB,merge_threads=3),type=lsm,leaf_page_max=16K" +table_config="internal_page_max=128K,lsm=(bloom_config=(leaf_page_max=8MB),bloom_bit_count=28,bloom_hash_count=19,bloom_oldest=true,chunk_max=5GB,chunk_size=100MB,merge_threads=3),type=lsm,leaf_page_max=16K" key_sz=40 value_sz=1000 pareto=true diff --git a/bench/wtperf/runners/test4-500m-lsm.wtperf b/bench/wtperf/runners/test4-500m-lsm.wtperf index 624241bb934..a959ec9057e 100644 --- a/bench/wtperf/runners/test4-500m-lsm.wtperf +++ b/bench/wtperf/runners/test4-500m-lsm.wtperf @@ -6,8 +6,9 @@ #conn_config="cache_size=21G,checkpoint_sync=false,mmap=false,session_max=1024,statistics=(fast,clear),statistics_log=(wait=60)" conn_config="cache_size=21G,checkpoint_sync=false,mmap=false,session_max=1024" create=false +compression="snappy" sess_config="isolation=snapshot" -table_config="internal_page_max=128K,lsm=(bloom_config=(leaf_page_max=8MB),bloom_bit_count=28,bloom_hash_count=19,bloom_oldest=true,chunk_max=100GB,chunk_size=100MB,merge_threads=3),type=lsm,leaf_page_max=16K" +table_config="internal_page_max=128K,lsm=(bloom_config=(leaf_page_max=8MB),bloom_bit_count=28,bloom_hash_count=19,bloom_oldest=true,chunk_max=5GB,chunk_size=100MB,merge_threads=3),type=lsm,leaf_page_max=16K" key_sz=40 value_sz=1000 report_interval=10 diff --git a/bench/wtperf/wtperf.c b/bench/wtperf/wtperf.c index 94afcf01e85..df78bca021f 100644 --- a/bench/wtperf/wtperf.c +++ b/bench/wtperf/wtperf.c @@ -31,7 +31,7 @@ static const CONFIG default_cfg = { "WT_TEST", /* home */ "WT_TEST", /* monitor dir */ - NULL, /* uri */ + NULL, /* base_uri */ NULL, /* uris */ NULL, /* helium_mount */ NULL, /* conn */ @@ -212,28 +212,21 @@ worker(void *arg) lprintf(cfg, ret, 0, "worker: WT_CONNECTION.open_session"); goto err; } - if (cfg->table_count > 1) { - cursors = (WT_CURSOR **)calloc( - cfg->table_count, sizeof(WT_CURSOR *)); - if (cursors == NULL) { - lprintf(cfg, ENOMEM, 0, - "worker: couldn't allocate cursor array"); + cursors = (WT_CURSOR **)calloc( + cfg->table_count, sizeof(WT_CURSOR *)); + if (cursors == NULL) { + lprintf(cfg, ENOMEM, 0, + "worker: couldn't allocate cursor array"); + goto err; + } + for (i = 0; i < cfg->table_count; i++) { + if ((ret = session->open_cursor(session, + cfg->uris[i], NULL, NULL, &cursors[i])) != 0) { + lprintf(cfg, ret, 0, + "worker: WT_SESSION.open_cursor: %s", + cfg->uris[i]); goto err; } - for (i = 0; i < cfg->table_count; i++) { - if ((ret = session->open_cursor(session, - cfg->uris[i], NULL, NULL, &cursors[i])) != 0) { - lprintf(cfg, ret, 0, - "worker: WT_SESSION.open_cursor: %s", - cfg->uris[i]); - goto err; - } - } - } else if ((ret = session->open_cursor( - session, cfg->uri, NULL, NULL, &cursor)) != 0) { - lprintf(cfg, - ret, 0, "worker: WT_SESSION.open_cursor: %s", cfg->uri); - goto err; } key_buf = thread->key_buf; @@ -243,11 +236,6 @@ worker(void *arg) op_end = op + sizeof(thread->workload->ops); while (!cfg->stop) { - /* Pick a cursor if there are multiple tables. */ - if (cfg->table_count > 1) - cursor = cursors[ - __wt_random() % (cfg->table_count - 1)]; - /* * Generate the next key and setup operation specific * statistics tracking objects. @@ -282,7 +270,18 @@ worker(void *arg) } sprintf(key_buf, "%0*" PRIu64, cfg->key_sz, next_val); - measure_latency = cfg->sample_interval != 0 && ( + + /* + * Spread the data out around the multiple databases. + */ + cursor = cursors[next_val % cfg->table_count]; + + /* + * Skip the first time we do an operation, when trk->ops + * is 0, to avoid first time latency spikes. + */ + measure_latency = + cfg->sample_interval != 0 && trk->ops != 0 && ( trk->ops % cfg->sample_rate == 0); if (measure_latency && (ret = __wt_epoch(NULL, &start)) != 0) { @@ -529,30 +528,23 @@ populate_thread(void *arg) /* Do bulk loads if populate is single-threaded. */ cursor_config = cfg->populate_threads == 1 ? "bulk" : NULL; - /* Create the cursor or cursors if there are multiple tables. */ - if (cfg->table_count > 1) { - cursors = (WT_CURSOR **)calloc( - cfg->table_count, sizeof(WT_CURSOR *)); - if (cursors == NULL) { - lprintf(cfg, ENOMEM, 0, - "worker: couldn't allocate cursor array"); + /* Create the cursors. */ + cursors = (WT_CURSOR **)calloc( + cfg->table_count, sizeof(WT_CURSOR *)); + if (cursors == NULL) { + lprintf(cfg, ENOMEM, 0, + "worker: couldn't allocate cursor array"); + goto err; + } + for (i = 0; i < cfg->table_count; i++) { + if ((ret = session->open_cursor( + session, cfg->uris[i], NULL, + cursor_config, &cursors[i])) != 0) { + lprintf(cfg, ret, 0, + "populate: WT_SESSION.open_cursor: %s", + cfg->uris[i]); goto err; } - for (i = 0; i < cfg->table_count; i++) { - if ((ret = session->open_cursor( - session, cfg->uris[i], NULL, - cursor_config, &cursors[i])) != 0) { - lprintf(cfg, ret, 0, - "populate: WT_SESSION.open_cursor: %s", - cfg->uris[i]); - goto err; - } - } - } else if ((ret = session->open_cursor( - session, cfg->uri, NULL, cursor_config, &cursor)) != 0) { - lprintf(cfg, - ret, 0, "populate: WT_SESSION.open_cursor: %s", cfg->uri); - goto err; } /* Populate the databases. */ @@ -570,38 +562,44 @@ populate_thread(void *arg) } intxn = 1; } + /* + * Figure out which table this op belongs to. + */ + cursor = cursors[op % cfg->table_count]; sprintf(key_buf, "%0*" PRIu64, cfg->key_sz, op); - measure_latency = cfg->sample_interval != 0 && ( + measure_latency = + cfg->sample_interval != 0 && trk->ops != 0 && ( trk->ops % cfg->sample_rate == 0); if (measure_latency && (ret = __wt_epoch(NULL, &start)) != 0) { lprintf(cfg, ret, 0, "Get time call failed"); goto err; } - for (i = 0; i < cfg->table_count; i++) { - if (cfg->table_count > 1) - cursor = cursors[i]; - cursor->set_key(cursor, key_buf); - if (cfg->random_value) - randomize_value(cfg, value_buf); - cursor->set_value(cursor, value_buf); - if ((ret = cursor->insert(cursor)) != 0) { - lprintf(cfg, ret, 0, "Failed inserting"); + cursor->set_key(cursor, key_buf); + if (cfg->random_value) + randomize_value(cfg, value_buf); + cursor->set_value(cursor, value_buf); + if ((ret = cursor->insert(cursor)) != 0) { + lprintf(cfg, ret, 0, "Failed inserting"); + goto err; + } + /* + * Gather statistics. + * We measure the latency of inserting a single key. If there + * are multiple tables, it is the time for insertion into all + * of them. + */ + if (measure_latency) { + if ((ret = __wt_epoch(NULL, &stop)) != 0) { + lprintf(cfg, ret, 0, + "Get time call failed"); goto err; } - /* Gather statistics */ - if (measure_latency) { - if ((ret = __wt_epoch(NULL, &stop)) != 0) { - lprintf(cfg, ret, 0, - "Get time call failed"); - goto err; - } - ++trk->latency_ops; - usecs = ns_to_us(WT_TIMEDIFF(stop, start)); - track_operation(trk, usecs); - } - ++thread->insert.ops; /* Same as trk->ops */ + ++trk->latency_ops; + usecs = ns_to_us(WT_TIMEDIFF(stop, start)); + track_operation(trk, usecs); } + ++thread->insert.ops; /* Same as trk->ops */ if (cfg->populate_ops_per_txn != 0) { if (++opcount < cfg->populate_ops_per_txn) @@ -833,6 +831,7 @@ execute_populate(CONFIG *cfg) WT_SESSION *session; struct timespec start, stop; double secs; + size_t i; uint64_t last_ops; uint32_t interval; int elapsed, ret, t_ret; @@ -927,12 +926,17 @@ execute_populate(CONFIG *cfg) lprintf(cfg, ret, 0, "Get time failed in populate."); goto err; } - if ((ret = session->compact( - session, cfg->uri, "timeout=0")) != 0) { - lprintf(cfg, ret, 0, - "execute_populate: WT_SESSION.compact"); - goto err; - } + /* + * We measure how long it takes to compact all tables for this + * workload. + */ + for (i = 0; i < cfg->table_count; i++) + if ((ret = session->compact( + session, cfg->uris[i], "timeout=0")) != 0) { + lprintf(cfg, ret, 0, + "execute_populate: WT_SESSION.compact"); + goto err; + } if ((ret = __wt_epoch(NULL, &stop)) != 0) { lprintf(cfg, ret, 0, "Get time failed in populate."); goto err; @@ -1085,47 +1089,54 @@ find_table_count(CONFIG *cfg) WT_CURSOR *cursor; WT_SESSION *session; char *key; + uint32_t i, max_icount, table_icount; int ret, t_ret; conn = cfg->conn; + max_icount = 0; if ((ret = conn->open_session( conn, NULL, cfg->sess_config, &session)) != 0) { lprintf(cfg, ret, 0, - "open_session failed finding existing table count"); - goto err; - } - if ((ret = session->open_cursor(session, cfg->uri, - NULL, NULL, &cursor)) != 0) { - lprintf(cfg, ret, 0, - "open_cursor failed finding existing table count"); - goto err; - } - if ((ret = cursor->prev(cursor)) != 0) { - lprintf(cfg, ret, 0, - "cursor prev failed finding existing table count"); - goto err; - } - if ((ret = cursor->get_key(cursor, &key)) != 0) { - lprintf(cfg, ret, 0, - "cursor get_key failed finding existing table count"); - goto err; + "find_table_count: open_session failed"); + goto out; } - cfg->icount = (uint32_t)atoi(key); + for (i = 0; i < cfg->table_count; i++) { + if ((ret = session->open_cursor(session, cfg->uris[i], + NULL, NULL, &cursor)) != 0) { + lprintf(cfg, ret, 0, + "find_table_count: open_cursor failed"); + goto err; + } + if ((ret = cursor->prev(cursor)) != 0) { + lprintf(cfg, ret, 0, + "find_table_count: cursor prev failed"); + goto err; + } + if ((ret = cursor->get_key(cursor, &key)) != 0) { + lprintf(cfg, ret, 0, + "find_table_count: cursor get_key failed"); + goto err; + } + table_icount = (uint32_t)atoi(key); + if (table_icount > max_icount) + max_icount = table_icount; -err: if ((t_ret = session->close(session, NULL)) != 0) { - if (ret == 0) - ret = t_ret; - lprintf(cfg, ret, 0, - "session close failed finding existing table count"); +err: if ((t_ret = session->close(session, NULL)) != 0) { + if (ret == 0) + ret = t_ret; + lprintf(cfg, ret, 0, + "find_table_count: session close failed"); + } } - return (ret); + cfg->icount = max_icount; +out: return (ret); } /* * Populate the uri array if more than one table is being used. */ -int +static int create_uris(CONFIG *cfg) { char *uri; @@ -1134,12 +1145,7 @@ create_uris(CONFIG *cfg) uint32_t i; ret = 0; - if (cfg->table_count < 2) { - cfg->uris = NULL; - return (0); - } - - base_uri_len = strlen(cfg->uri); + base_uri_len = strlen(cfg->base_uri); cfg->uris = (char **)calloc(cfg->table_count, sizeof(char *)); if (cfg->uris == NULL) { ret = ENOMEM; @@ -1151,10 +1157,15 @@ create_uris(CONFIG *cfg) ret = ENOMEM; goto err; } - memcpy(uri, cfg->uri, base_uri_len); - uri[base_uri_len] = uri[base_uri_len + 1] = '0'; - uri[base_uri_len] = '0' + (i / 10); - uri[base_uri_len + 1] = '0' + (i % 10); + memcpy(uri, cfg->base_uri, base_uri_len); + /* + * If there is only one table, just use base name. + */ + if (cfg->table_count > 1) { + uri[base_uri_len] = uri[base_uri_len + 1] = '0'; + uri[base_uri_len] = '0' + (i / 10); + uri[base_uri_len + 1] = '0' + (i % 10); + } } err: if (ret != 0 && cfg->uris != NULL) { for (i = 0; i < cfg->table_count; i++) @@ -1165,7 +1176,7 @@ err: if (ret != 0 && cfg->uris != NULL) { return (ret); } -int +static int create_tables(CONFIG *cfg) { WT_SESSION *session; @@ -1177,7 +1188,7 @@ create_tables(CONFIG *cfg) if (cfg->create == 0) return (0); - uri = cfg->uri; + uri = cfg->base_uri; if ((ret = cfg->conn->open_session( cfg->conn, NULL, cfg->sess_config, &session)) != 0) { lprintf(cfg, ret, 0, @@ -1185,12 +1196,11 @@ create_tables(CONFIG *cfg) return (ret); } for (i = 0; i < cfg->table_count; i++) { - if (cfg->table_count > 1) - uri = cfg->uris[i]; + uri = cfg->uris[i]; if ((ret = session->create( session, uri, cfg->table_config)) != 0) { lprintf(cfg, ret, 0, - "Error creating table %s", cfg->uri); + "Error creating table %s", cfg->uris[i]); return (ret); } } @@ -1530,11 +1540,11 @@ main(int argc, char *argv[]) /* Build the URI from the table name. */ req_len = strlen("table:") + strlen(HELIUM_NAME) + strlen(cfg->table_name) + 2; - if ((cfg->uri = calloc(req_len, 1)) == NULL) { + if ((cfg->base_uri = calloc(req_len, 1)) == NULL) { ret = enomem(cfg); goto err; } - snprintf(cfg->uri, req_len, "table:%s%s%s", + snprintf(cfg->base_uri, req_len, "table:%s%s%s", cfg->helium_mount == NULL ? "" : HELIUM_NAME, cfg->helium_mount == NULL ? "" : "/", cfg->table_name); diff --git a/bench/wtperf/wtperf.h b/bench/wtperf/wtperf.h index 15d296d00a0..c94ec10d8fd 100644 --- a/bench/wtperf/wtperf.h +++ b/bench/wtperf/wtperf.h @@ -83,7 +83,7 @@ typedef struct { struct __config { /* Configuration struction */ const char *home; /* WiredTiger home */ const char *monitor_dir; /* Monitor output dir */ - char *uri; /* Object URI */ + char *base_uri; /* Object URI */ char **uris; /* URIs if multiple tables */ const char *helium_mount; /* Optional Helium mount point */ diff --git a/bench/wtperf/wtperf_opt.i b/bench/wtperf/wtperf_opt.i index af50e7e9bf3..0fff7a14416 100644 --- a/bench/wtperf/wtperf_opt.i +++ b/bench/wtperf/wtperf_opt.i @@ -123,8 +123,8 @@ DEF_OPT_AS_CONFIG_STRING(table_config, "leaf_page_max=4kb,internal_page_max=64kb,allocation_size=4kb,", "table configuration string") DEF_OPT_AS_UINT32(table_count, 1, - "number of tables to run operations over. Operations are spread evenly " - "over the tables amongst all threads. Default 1, maximum 99.") + "number of tables to run operations over. Keys are divided evenly " + "over the tables. Default 1, maximum 99.") DEF_OPT_AS_STRING(threads, "", "workload configuration: each 'count' " "entry is the total number of threads, and the 'insert', 'read' and " "'update' entries are the ratios of insert, read and update operations " diff --git a/dist/s_funcs.list b/dist/s_funcs.list index b34564adda7..6ffb956f59e 100644 --- a/dist/s_funcs.list +++ b/dist/s_funcs.list @@ -22,6 +22,7 @@ __wt_log_scan __wt_nlpo2 __wt_nlpo2_round __wt_print_huffman_code +wiredtiger_config_parser_open wiredtiger_pack_int wiredtiger_pack_item wiredtiger_pack_str diff --git a/dist/s_string.ok b/dist/s_string.ok index a71cca62a8c..5f0d331e105 100644 --- a/dist/s_string.ok +++ b/dist/s_string.ok @@ -688,6 +688,7 @@ openfile os ovfl packv +parserp patchp pathname pathnames diff --git a/dist/s_symbols.list b/dist/s_symbols.list index a66af1f8994..d3803bc3afa 100644 --- a/dist/s_symbols.list +++ b/dist/s_symbols.list @@ -1,4 +1,5 @@ # List of OK external symbols. +wiredtiger_config_parser_open wiredtiger_open wiredtiger_pack_close wiredtiger_pack_int diff --git a/examples/c/Makefile.am b/examples/c/Makefile.am index bd281df7130..5b43dcc2285 100644 --- a/examples/c/Makefile.am +++ b/examples/c/Makefile.am @@ -6,6 +6,7 @@ noinst_PROGRAMS = \ ex_all \ ex_call_center \ ex_config \ + ex_config_parse \ ex_cursor \ ex_data_source \ ex_extending \ diff --git a/examples/c/ex_config_parse.c b/examples/c/ex_config_parse.c new file mode 100644 index 00000000000..c6adc327c78 --- /dev/null +++ b/examples/c/ex_config_parse.c @@ -0,0 +1,166 @@ +/*- + * Public Domain 2008-2014 WiredTiger, Inc. + * + * This is free and unencumbered software released into the public domain. + * + * Anyone is free to copy, modify, publish, use, compile, sell, or + * distribute this software, either in source code form or as a compiled + * binary, for any purpose, commercial or non-commercial, and by any + * means. + * + * In jurisdictions that recognize copyright laws, the author or authors + * of this software dedicate any and all copyright interest in the + * software to the public domain. We make this dedication for the benefit + * of the public at large and to the detriment of our heirs and + * successors. We intend this dedication to be an overt act of + * relinquishment in perpetuity of all present and future rights to this + * software under copyright law. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * ex_config_parse.c + * This is an example demonstrating how to parse WiredTiger compatible + * configuration strings. + */ + +#include <stdio.h> +#include <string.h> + +#include <wiredtiger.h> + +const char *home = NULL; + +int main(void) +{ + int ret; + + /*! [Create a configuration parser] */ + WT_CONFIG_ITEM k, v; + WT_CONFIG_PARSER *parser; + const char *config_string = + "path=/dev/loop,page_size=1024,log=(archive=true,file_max=20MB)"; + + if ((ret = wiredtiger_config_parser_open( + NULL, config_string, strlen(config_string), &parser)) != 0) { + fprintf(stderr, "Error creating configuration parser: %s\n", + wiredtiger_strerror(ret)); + return (ret); + } + if ((ret = parser->close(parser)) != 0) { + fprintf(stderr, "Error closing configuration parser: %s\n", + wiredtiger_strerror(ret)); + return (ret); + } + /*! [Create a configuration parser] */ + + if ((ret = wiredtiger_config_parser_open( + NULL, config_string, strlen(config_string), &parser)) != 0) { + fprintf(stderr, "Error creating configuration parser: %s\n", + wiredtiger_strerror(ret)); + return (ret); + } + + { + /*! [get] */ + int64_t my_page_size; + /* + * Retrieve the value of the integer configuration string "page_size". + */ + if ((ret = parser->get(parser, "page_size", &v)) != 0) { + fprintf(stderr, + "page_size configuration: %s", wiredtiger_strerror(ret)); + return (ret); + } + my_page_size = v.val; + /*! [get] */ + + ret = parser->close(parser); + + (void)my_page_size; + } + + { + if ((ret = wiredtiger_config_parser_open( + NULL, config_string, strlen(config_string), &parser)) != 0) { + fprintf(stderr, "Error creating configuration parser: %s\n", + wiredtiger_strerror(ret)); + return (ret); + } + /*! [next] */ + /* + * Retrieve and print the values of the configuration strings. + */ + while ((ret = parser->next(parser, &k, &v)) == 0) { + printf("%.*s:", (int)k.len, k.str); + if (v.type == WT_CONFIG_ITEM_NUM) + printf("%d\n", (int)v.val); + else + printf("%.*s\n", (int)v.len, v.str); + } + /*! [next] */ + ret = parser->close(parser); + } + + if ((ret = wiredtiger_config_parser_open( + NULL, config_string, strlen(config_string), &parser)) != 0) { + fprintf(stderr, "Error creating configuration parser: %s\n", + wiredtiger_strerror(ret)); + return (ret); + } + + /*! [nested get] */ + /* + * Retrieve the value of the nested log file_max configuration string + * using dot shorthand. Utilize the configuration parsing automatic + * conversion of value strings into an integer. + */ + v.type = WT_CONFIG_ITEM_NUM; + if ((ret = parser->get(parser, "log.file_max", &v)) != 0) { + fprintf(stderr, + "log.file_max configuration: %s", wiredtiger_strerror(ret)); + return (ret); + } + printf("log file max: %d\n", (int)v.val); + /*! [nested get] */ + ret = parser->close(parser); + + if ((ret = wiredtiger_config_parser_open( + NULL, config_string, strlen(config_string), &parser)) != 0) { + fprintf(stderr, "Error creating configuration parser: %s\n", + wiredtiger_strerror(ret)); + return (ret); + } + /*! [nested traverse] */ + { + WT_CONFIG_PARSER *sub_parser; + while ((ret = parser->next(parser, &k, &v)) == 0) { + if (v.type == WT_CONFIG_ITEM_STRUCT) { + printf("Found nested configuration: %.*s\n", + (int)k.len, k.str); + if ((ret = wiredtiger_config_parser_open( + NULL, v.str, v.len, &sub_parser)) != 0) { + fprintf(stderr, + "Error creating nested configuration " + "parser: %s\n", + wiredtiger_strerror(ret)); + parser->close(parser); + return (ret); + } + while ((ret = sub_parser->next( + sub_parser, &k, &v)) == 0) + printf("\t%.*s\n", (int)k.len, k.str); + sub_parser->close(sub_parser); + } + } + /*! [nested traverse] */ + parser->close(parser); + } + + return (0); +} diff --git a/examples/c/ex_data_source.c b/examples/c/ex_data_source.c index daedce04075..953bb799340 100644 --- a/examples/c/ex_data_source.c +++ b/examples/c/ex_data_source.c @@ -339,30 +339,6 @@ my_open_cursor(WT_DATA_SOURCE *dsrc, WT_SESSION *session, } { - /*! [WT_EXTENSION config_strget] */ - WT_CONFIG_ITEM v; - int64_t my_data_source_page_size; - - /* - * Retrieve the value of the integer type configuration string - * "page_size" from a local string (as opposed to the provided - * WT_CONFIG_ARG reference). - */ - const char *config_string = "path=/dev/loop,page_size=1024"; - - if ((ret = wt_api->config_strget( - wt_api, session, config_string, "page_size", &v)) != 0) { - (void)wt_api->err_printf(wt_api, session, - "page_size configuration: %s", wiredtiger_strerror(ret)); - return (ret); - } - my_data_source_page_size = v.val; - /*! [WT_EXTENSION config_strget] */ - - (void)my_data_source_page_size; - } - - { /*! [WT_EXTENSION config_get] */ WT_CONFIG_ITEM v; const char *my_data_source_key; @@ -392,31 +368,6 @@ my_open_cursor(WT_DATA_SOURCE *dsrc, WT_SESSION *session, } { - /*! [WT_EXTENSION config scan] */ - WT_CONFIG_ITEM k, v; - WT_CONFIG_SCAN *scan; - - /* - * Retrieve the value of the list type configuration string "paths". - */ - if ((ret = wt_api->config_get( - wt_api, session, config, "paths", &v)) != 0) { - (void)wt_api->err_printf(wt_api, session, - "paths configuration: %s", wiredtiger_strerror(ret)); - return (ret); - } - - /* - * Step through the list of entries. - */ - ret = wt_api->config_scan_begin(wt_api, session, v.str, v.len, &scan); - while ((ret = wt_api->config_scan_next(wt_api, scan, &k, &v)) == 0) - printf("%.*s\n", (int)k.len, k.str); - ret = wt_api->config_scan_end(wt_api, scan); - /*! [WT_EXTENSION config scan] */ - } - - { /*! [WT_EXTENSION collator config] */ /* * Configure the appropriate collator. diff --git a/ext/datasources/helium/helium.c b/ext/datasources/helium/helium.c index 1239c88befa..c8a6fe99bb0 100644 --- a/ext/datasources/helium/helium.c +++ b/ext/datasources/helium/helium.c @@ -2098,19 +2098,22 @@ helium_session_open_cursor(WT_DATA_SOURCE *wtds, WT_SESSION *session, CURSOR *cursor; DATA_SOURCE *ds; WT_CONFIG_ITEM v; + WT_CONFIG_PARSER *config_parser; WT_CURSOR *wtcursor; WT_EXTENSION_API *wtext; WT_SOURCE *ws; - int locked, ret = 0; + int locked, ret, t_ret; const char *value; *new_cursor = NULL; + config_parser = NULL; cursor = NULL; ds = (DATA_SOURCE *)wtds; wtext = ds->wtext; ws = NULL; locked = 0; + ret = t_ret = 0; value = NULL; /* Allocate and initialize a cursor. */ @@ -2164,23 +2167,28 @@ helium_session_open_cursor(WT_DATA_SOURCE *wtds, WT_SESSION *session, if ((ret = master_uri_get(wtds, session, uri, &value)) != 0) goto err; - if ((ret = wtext->config_strget( - wtext, session, value, "key_format", &v)) != 0) + if ((ret = wtext->config_parser_open(wtext, + session, value, strlen(value), &config_parser)) != 0) + EMSG_ERR(wtext, session, ret, + "Configuration string parser: %s", + wtext->strerror(ret)); + if ((ret = config_parser->get( + config_parser, "key_format", &v)) != 0) EMSG_ERR(wtext, session, ret, "key_format configuration: %s", wtext->strerror(ret)); ws->config_recno = v.len == 1 && v.str[0] == 'r'; - if ((ret = wtext->config_strget( - wtext, session, value, "value_format", &v)) != 0) + if ((ret = config_parser->get( + config_parser, "value_format", &v)) != 0) EMSG_ERR(wtext, session, ret, "value_format configuration: %s", wtext->strerror(ret)); ws->config_bitfield = v.len == 2 && isdigit(v.str[0]) && v.str[1] == 't'; - if ((ret = wtext->config_strget( - wtext, session, value, "helium_o_compress", &v)) != 0) + if ((ret = config_parser->get( + config_parser, "helium_o_compress", &v)) != 0) EMSG_ERR(wtext, session, ret, "helium_o_compress configuration: %s", wtext->strerror(ret)); @@ -2219,6 +2227,9 @@ err: if (ws != NULL && locked) ESET(unlock(wtext, session, &ws->lock)); cursor_destroy(cursor); } + if (config_parser != NULL && (t_ret = + (void)config_parser->close(config_parser)) != 0 && ret == 0) + ret = t_ret; free((void *)value); return (ret); } @@ -2882,19 +2893,19 @@ helium_config_read(WT_EXTENSION_API *wtext, WT_CONFIG_ITEM *config, char **devicep, HE_ENV *envp, int *env_setp, int *flagsp) { WT_CONFIG_ITEM k, v; - WT_CONFIG_SCAN *scan; + WT_CONFIG_PARSER *config_parser; int ret = 0, tret; *env_setp = 0; *flagsp = 0; - /* Set up the scan of the configuration arguments list. */ - if ((ret = wtext->config_scan_begin( - wtext, NULL, config->str, config->len, &scan)) != 0) + /* Traverse the configuration arguments list. */ + if ((ret = wtext->config_parser_open( + wtext, NULL, config->str, config->len, &config_parser)) != 0) ERET(wtext, NULL, ret, - "WT_EXTENSION_API.config_scan_begin: %s", + "WT_EXTENSION_API.config_parser_open: %s", wtext->strerror(ret)); - while ((ret = wtext->config_scan_next(wtext, scan, &k, &v)) == 0) { + while ((ret = config_parser->next(config_parser, &k, &v)) == 0) { if (string_match("helium_devices", k.str, k.len)) { if ((*devicep = calloc(1, v.len + 1)) == NULL) return (os_errno()); @@ -2924,13 +2935,11 @@ helium_config_read(WT_EXTENSION_API *wtext, WT_CONFIG_ITEM *config, ret = 0; if (ret != 0) EMSG_ERR(wtext, NULL, ret, - "WT_EXTENSION_API.config_scan_next: %s", - wtext->strerror(ret)); + "WT_CONFIG_PARSER.next: %s", wtext->strerror(ret)); -err: if ((tret = wtext->config_scan_end(wtext, scan)) != 0) +err: if ((tret = config_parser->close(config_parser)) != 0) EMSG(wtext, NULL, tret, - "WT_EXTENSION_API.config_scan_end: %s", - wtext->strerror(tret)); + "WT_CONFIG_PARSER.close: %s", wtext->strerror(tret)); return (ret); } @@ -3320,11 +3329,12 @@ wiredtiger_extension_init(WT_CONNECTION *connection, WT_CONFIG_ARG *config) DATA_SOURCE *ds; HELIUM_SOURCE *hs; WT_CONFIG_ITEM k, v; - WT_CONFIG_SCAN *scan; + WT_CONFIG_PARSER *config_parser; WT_EXTENSION_API *wtext; int vmajor, vminor, ret = 0; const char **p; + config_parser = NULL; ds = NULL; wtext = connection->get_extension_api(connection); @@ -3357,12 +3367,12 @@ wiredtiger_extension_init(WT_CONNECTION *connection, WT_CONFIG_ARG *config) wtext->strerror(ret)); /* Step through the list of Helium sources, opening each one. */ - if ((ret = - wtext->config_scan_begin(wtext, NULL, v.str, v.len, &scan)) != 0) + if ((ret = wtext->config_parser_open( + wtext, NULL, v.str, v.len, &config_parser)) != 0) EMSG_ERR(wtext, NULL, ret, - "WT_EXTENSION_API.config_scan_begin: config: %s", + "WT_EXTENSION_API.config_parser_open: config: %s", wtext->strerror(ret)); - while ((ret = wtext->config_scan_next(wtext, scan, &k, &v)) == 0) { + while ((ret = config_parser->next(config_parser, &k, &v)) == 0) { if (string_match("helium_verbose", k.str, k.len)) { verbose = v.val == 0 ? 0 : 1; continue; @@ -3372,12 +3382,13 @@ wiredtiger_extension_init(WT_CONNECTION *connection, WT_CONFIG_ARG *config) } if (ret != WT_NOTFOUND) EMSG_ERR(wtext, NULL, ret, - "WT_EXTENSION_API.config_scan_next: config: %s", + "WT_CONFIG_PARSER.next: config: %s", wtext->strerror(ret)); - if ((ret = wtext->config_scan_end(wtext, scan)) != 0) + if ((ret = config_parser->close(config_parser)) != 0) EMSG_ERR(wtext, NULL, ret, - "WT_EXTENSION_API.config_scan_end: config: %s", + "WT_CONFIG_PARSER.close: config: %s", wtext->strerror(ret)); + config_parser = NULL; /* Find and open the database transaction store. */ if ((ret = helium_source_open_txn(ds)) != 0) @@ -3414,6 +3425,8 @@ wiredtiger_extension_init(WT_CONNECTION *connection, WT_CONFIG_ARG *config) err: if (ds != NULL) ESET(helium_terminate((WT_DATA_SOURCE *)ds, NULL)); + if (config_parser != NULL) + (void)config_parser->close(config_parser); return (ret); } diff --git a/lang/java/java_doc.i b/lang/java/java_doc.i index fcb580ddbd4..83404b45508 100644 --- a/lang/java/java_doc.i +++ b/lang/java/java_doc.i @@ -40,3 +40,6 @@ COPYDOC(__wt_connection, WT_CONNECTION, add_data_source) COPYDOC(__wt_connection, WT_CONNECTION, add_collator) COPYDOC(__wt_connection, WT_CONNECTION, add_compressor) COPYDOC(__wt_connection, WT_CONNECTION, add_extractor) +COPYDOC(__wt_config_parser, WT_CONFIG_PARSER, close) +COPYDOC(__wt_config_parser, WT_CONFIG_PARSER, next) +COPYDOC(__wt_config_parser, WT_CONFIG_PARSER, get) diff --git a/src/btree/bt_discard.c b/src/btree/bt_discard.c index 568e07f7fd1..c0c7a245a9f 100644 --- a/src/btree/bt_discard.c +++ b/src/btree/bt_discard.c @@ -317,6 +317,10 @@ __free_update_list(WT_SESSION_IMPL *session, WT_UPDATE *upd) do { next = upd->next; + /* Everything we free should be visible to everyone. */ + WT_ASSERT(session, + upd->txnid == WT_TXN_ABORTED || + __wt_txn_visible_all(session, upd->txnid)); __wt_free(session, upd); } while ((upd = next) != NULL); } diff --git a/src/btree/bt_evict.c b/src/btree/bt_evict.c index d57162c06a9..fed52963de4 100644 --- a/src/btree/bt_evict.c +++ b/src/btree/bt_evict.c @@ -436,6 +436,9 @@ __wt_evict_file(WT_SESSION_IMPL *session, int syncop) */ __wt_evict_file_exclusive_on(session); + /* Make sure the oldest transaction ID is up-to-date. */ + __wt_txn_update_oldest(session); + /* * We can't evict the page just returned to us, it marks our place in * the tree. So, always walk one page ahead of the page being evicted. diff --git a/src/config/config.c b/src/config/config.c index c30c816de85..c0eb672015f 100644 --- a/src/config/config.c +++ b/src/config/config.c @@ -738,10 +738,8 @@ __wt_config_subgetraw(WT_SESSION_IMPL *session, __wt_config_subgets(WT_SESSION_IMPL *session, WT_CONFIG_ITEM *cfg, const char *key, WT_CONFIG_ITEM *value) { - WT_CONFIG_ITEM key_item; - - key_item.str = key; - key_item.len = strlen(key); + WT_CONFIG_ITEM key_item = + { key, strlen(key), 0, WT_CONFIG_ITEM_STRING }; return (__wt_config_subgetraw(session, cfg, &key_item, value)); } diff --git a/src/config/config_api.c b/src/config/config_api.c index a5781a20c61..42f4c117b81 100644 --- a/src/config/config_api.c +++ b/src/config/config_api.c @@ -8,89 +8,98 @@ #include "wt_internal.h" /* - * wiredtiger_config_get -- - * Given a NULL-terminated list of configuration strings, find the final - * value for a given string key (external API version). + * __config_parser_close -- + * WT_CONFIG_PARSER->close method. */ -int -wiredtiger_config_get(WT_SESSION *wt_session, - WT_CONFIG_ARG *cfg_arg, const char *key, WT_CONFIG_ITEM *cval) +static int +__config_parser_close(WT_CONFIG_PARSER *wt_config_parser) { - WT_SESSION_IMPL *session; - const char **cfg; + WT_CONFIG_PARSER_IMPL *config_parser; - session = (WT_SESSION_IMPL *)wt_session; + config_parser = (WT_CONFIG_PARSER_IMPL *)wt_config_parser; - if ((cfg = (const char **)cfg_arg) == NULL) - return (WT_NOTFOUND); - return (__wt_config_gets(session, cfg, key, cval)); + if (config_parser == NULL) + return (EINVAL); + + __wt_free(config_parser->session, config_parser); + return (0); } /* - * wiredtiger_config_strget -- - * Given a single configuration string, find the final value for a given - * string key (external API version). + * __config_parser_get -- + * WT_CONFIG_PARSER->search method. */ -int -wiredtiger_config_strget(WT_SESSION *wt_session, - const char *config, const char *key, WT_CONFIG_ITEM *cval) +static int +__config_parser_get(WT_CONFIG_PARSER *wt_config_parser, + const char *key, WT_CONFIG_ITEM *cval) { - const char *cfg_arg[] = { config, NULL }; + WT_CONFIG_PARSER_IMPL *config_parser; - return (wiredtiger_config_get( - wt_session, (WT_CONFIG_ARG *)cfg_arg, key, cval)); + config_parser = (WT_CONFIG_PARSER_IMPL *)wt_config_parser; + + if (config_parser == NULL) + return (EINVAL); + + return (__wt_config_subgets(config_parser->session, + &config_parser->config_item, key, cval)); } /* - * wiredtiger_config_scan_begin -- - * Start a scan of a config string. + * __config_parser_next -- + * WT_CONFIG_PARSER->next method. */ -int -wiredtiger_config_scan_begin(WT_SESSION *wt_session, - const char *str, size_t len, WT_CONFIG_SCAN **scanp) +static int +__config_parser_next(WT_CONFIG_PARSER *wt_config_parser, + WT_CONFIG_ITEM *key, WT_CONFIG_ITEM *cval) { - WT_CONFIG config, *scan; - WT_SESSION_IMPL *session; + WT_CONFIG_PARSER_IMPL *config_parser; - session = (WT_SESSION_IMPL *)wt_session; + config_parser = (WT_CONFIG_PARSER_IMPL *)wt_config_parser; - /* Note: allocate memory last to avoid cleanup. */ - WT_CLEAR(config); - WT_RET(__wt_config_initn(session, &config, str, len)); - WT_RET(__wt_calloc_def(session, 1, &scan)); - *scan = config; - *scanp = (WT_CONFIG_SCAN *)scan; - return (0); + if (config_parser == NULL) + return (EINVAL); + + return (__wt_config_next(&config_parser->config, key, cval)); } /* - * wiredtiger_config_scan_end -- - * End a scan of a config string. + * wiredtiger_config_parser_open -- + * Create a configuration parser. */ int -wiredtiger_config_scan_end(WT_SESSION *wt_session, WT_CONFIG_SCAN *scan) +wiredtiger_config_parser_open(WT_SESSION *wt_session, + const char *config, size_t len, WT_CONFIG_PARSER **config_parserp) { - WT_CONFIG *conf; + static const WT_CONFIG_PARSER stds = { + __config_parser_close, + __config_parser_next, + __config_parser_get + }; + WT_CONFIG_ITEM config_item = + { config, len, 0, WT_CONFIG_ITEM_STRING }; + WT_CONFIG_PARSER_IMPL *config_parser; + WT_DECL_RET; + WT_SESSION_IMPL *session; - WT_UNUSED(wt_session); + *config_parserp = NULL; + session = (WT_SESSION_IMPL *)wt_session; - conf = (WT_CONFIG *)scan; - __wt_free(conf->session, scan); - return (0); -} + WT_RET(__wt_calloc_def(session, 1, &config_parser)); + config_parser->iface = stds; + config_parser->session = session; -/* - * wiredtiger_config_scan_next -- - * Get the next key/value pair from a config scan. - */ -int -wiredtiger_config_scan_next(WT_SESSION *wt_session, - WT_CONFIG_SCAN *scan, WT_CONFIG_ITEM *key, WT_CONFIG_ITEM *value) -{ - WT_CONFIG *conf; + /* + * Setup a WT_CONFIG_ITEM to be used for get calls and a WT_CONFIG + * structure for iterations through the configuration string. + */ + memcpy(&config_parser->config_item, &config_item, sizeof(config_item)); + WT_ERR(__wt_config_initn( + session, &config_parser->config, config, len)); - WT_UNUSED(wt_session); + if (ret == 0) + *config_parserp = (WT_CONFIG_PARSER *)config_parser; + else +err: __wt_free(session, config_parser); - conf = (WT_CONFIG *)scan; - return (__wt_config_next(conf, key, value)); + return (ret); } diff --git a/src/config/config_ext.c b/src/config/config_ext.c index 7dd5445cc3c..26b3799d61c 100644 --- a/src/config/config_ext.c +++ b/src/config/config_ext.c @@ -8,6 +8,19 @@ #include "wt_internal.h" /* + * __wt_ext_config_parser_open -- + * WT_EXTENSION_API->config_parser_open implementation + */ +int +__wt_ext_config_parser_open(WT_EXTENSION_API *wt_ext, WT_SESSION *wt_session, + const char *config, size_t len, WT_CONFIG_PARSER **config_parserp) +{ + WT_UNUSED(wt_ext); + return (wiredtiger_config_parser_open( + wt_session, config, len, config_parserp)); +} + +/* * __wt_ext_config_get -- * Given a NULL-terminated list of configuration strings, find the final * value for a given string key (external API version). @@ -29,81 +42,3 @@ __wt_ext_config_get(WT_EXTENSION_API *wt_api, return (WT_NOTFOUND); return (__wt_config_gets(session, cfg, key, cval)); } - -/* - * __wt_ext_config_strget -- - * Given a single configuration string, find the final value for a given - * string key (external API version). - */ -int -__wt_ext_config_strget(WT_EXTENSION_API *wt_api, - WT_SESSION *wt_session, const char *config, const char *key, - WT_CONFIG_ITEM *cval) -{ - const char *cfg_arg[] = { config, NULL }; - - return (__wt_ext_config_get( - wt_api, wt_session, (WT_CONFIG_ARG *)cfg_arg, key, cval)); -} - -/* - * __wt_ext_config_scan_begin -- - * Start a scan of a config string. - * (external API only). - */ -int -__wt_ext_config_scan_begin( - WT_EXTENSION_API *wt_api, WT_SESSION *wt_session, - const char *str, size_t len, WT_CONFIG_SCAN **scanp) -{ - WT_CONFIG config, *scan; - WT_CONNECTION_IMPL *conn; - WT_SESSION_IMPL *session; - - conn = (WT_CONNECTION_IMPL *)wt_api->conn; - if ((session = (WT_SESSION_IMPL *)wt_session) == NULL) - session = conn->default_session; - - /* Note: allocate memory last to avoid cleanup. */ - WT_CLEAR(config); - WT_RET(__wt_config_initn(session, &config, str, len)); - WT_RET(__wt_calloc_def(session, 1, &scan)); - *scan = config; - *scanp = (WT_CONFIG_SCAN *)scan; - return (0); -} - -/* - * __wt_ext_config_scan_end -- - * End a scan of a config string. - * (external API only). - */ -int -__wt_ext_config_scan_end(WT_EXTENSION_API *wt_api, WT_CONFIG_SCAN *scan) -{ - WT_CONFIG *conf; - - WT_UNUSED(wt_api); - - conf = (WT_CONFIG *)scan; - __wt_free(conf->session, scan); - return (0); -} - -/* - * __wt_ext_config_scan_next -- - * Get the next key/value pair from a config scan. - * (external API only). - */ -int -__wt_ext_config_scan_next( - WT_EXTENSION_API *wt_api, WT_CONFIG_SCAN *scan, - WT_CONFIG_ITEM *key, WT_CONFIG_ITEM *value) -{ - WT_CONFIG *conf; - - WT_UNUSED(wt_api); - - conf = (WT_CONFIG *)scan; - return (__wt_config_next(conf, key, value)); -} diff --git a/src/conn/conn_api.c b/src/conn/conn_api.c index 780ae6f6be5..bcbb8e3ceb8 100644 --- a/src/conn/conn_api.c +++ b/src/conn/conn_api.c @@ -105,11 +105,8 @@ __conn_get_extension_api(WT_CONNECTION *wt_conn) conn->extension_api.scr_free = __wt_ext_scr_free; conn->extension_api.collator_config = ext_collator_config; conn->extension_api.collate = ext_collate; + conn->extension_api.config_parser_open = __wt_ext_config_parser_open; conn->extension_api.config_get = __wt_ext_config_get; - conn->extension_api.config_strget = __wt_ext_config_strget; - conn->extension_api.config_scan_begin = __wt_ext_config_scan_begin; - conn->extension_api.config_scan_end = __wt_ext_config_scan_end; - conn->extension_api.config_scan_next = __wt_ext_config_scan_next; conn->extension_api.metadata_insert = __wt_ext_metadata_insert; conn->extension_api.metadata_remove = __wt_ext_metadata_remove; conn->extension_api.metadata_search = __wt_ext_metadata_search; @@ -123,6 +120,7 @@ __conn_get_extension_api(WT_CONNECTION *wt_conn) conn->extension_api.transaction_notify = __wt_ext_transaction_notify; conn->extension_api.transaction_oldest = __wt_ext_transaction_oldest; conn->extension_api.transaction_visible = __wt_ext_transaction_visible; + conn->extension_api.version = wiredtiger_version; return (&conn->extension_api); } @@ -498,6 +496,20 @@ __conn_close(WT_CONNECTION *wt_conn, const char *config) CONNECTION_API_CALL(conn, session, close, config, cfg); WT_UNUSED(cfg); + /* + * Rollback all running transactions. + * We do this as a separate pass because an active transaction in one + * session could cause trouble when closing a file, even if that + * session never referenced that file. + */ + for (s = conn->sessions, i = 0; i < conn->session_cnt; ++s, ++i) + if (s->active && !F_ISSET(s, WT_SESSION_INTERNAL) && + F_ISSET(&s->txn, TXN_RUNNING)) { + wt_session = &s->iface; + WT_TRET(wt_session->rollback_transaction( + wt_session, NULL)); + } + /* Close open, external sessions. */ for (s = conn->sessions, i = 0; i < conn->session_cnt; ++s, ++i) if (s->active && !F_ISSET(s, WT_SESSION_INTERNAL)) { diff --git a/src/docs/Doxyfile b/src/docs/Doxyfile index 5f7e5016892..d8f753b269b 100644 --- a/src/docs/Doxyfile +++ b/src/docs/Doxyfile @@ -1576,6 +1576,7 @@ PREDEFINED = DOXYGEN \ __wt_compressor:=WT_COMPRESSOR \ __wt_config_arg:=WT_CONFIG_ARG \ __wt_config_item:=WT_CONFIG_ITEM \ + __wt_config_parser:=WT_CONFIG_PARSER \ __wt_config_scan:=WT_CONFIG_SCAN \ __wt_connection:=WT_CONNECTION \ __wt_cursor:=WT_CURSOR \ diff --git a/src/docs/command-line.dox b/src/docs/command-line.dox index 52be68a1074..04daa4050cd 100644 --- a/src/docs/command-line.dox +++ b/src/docs/command-line.dox @@ -291,9 +291,6 @@ engine, or, if specified, for the URI on the command-line. <code>wt [-Vv] [-C config] [-h directory] stat [-a] [uri]</code> @subsection util_stat_options Options -The \c stat command has no command-specific options. - -@subsection util_stat_options Options The following are command-specific options for the \c stat command: @par <code>-a</code> diff --git a/src/docs/custom_data.dox b/src/docs/custom_data.dox index b1ee8dfbd9e..22dd75dcc26 100644 --- a/src/docs/custom_data.dox +++ b/src/docs/custom_data.dox @@ -165,11 +165,6 @@ of a configuration string as follows: @snippet ex_data_source.c WT_EXTENSION config_get -The WT_DATA_SOURCE::open_cursor method might retrieve the list value -of a configuration string as follows: - -@snippet ex_data_source.c WT_EXTENSION config scan - @subsection custom_ds_config_add Creating data-specific configuration strings Applications can add their own configuration strings to WiredTiger diff --git a/src/docs/helium.dox b/src/docs/helium.dox index cd6b47fb968..35e3886a8d6 100644 --- a/src/docs/helium.dox +++ b/src/docs/helium.dox @@ -103,7 +103,7 @@ WT_SESSION *session; /* Create and truncate the access table. */ ret = session->create(session, "table:dev1/access", - "key_format=S,value_format=S,type=helium,helium_open_o_truncate=1"); + "key_format=S,value_format=S,type=helium,helium_o_truncate=1"); @endcode @section helium_notes Helium notes diff --git a/src/include/cache.h b/src/include/cache.h index 055041b7e6c..abbc2189f9d 100644 --- a/src/include/cache.h +++ b/src/include/cache.h @@ -9,7 +9,7 @@ * Tuning constants: I hesitate to call this tuning, but we want to review some * number of pages from each file's in-memory tree for each page we evict. */ -#define WT_EVICT_INT_SKEW (1<<12) /* Prefer leaf pages over internal +#define WT_EVICT_INT_SKEW (1<<20) /* Prefer leaf pages over internal pages by this many increments of the read generation. */ #define WT_EVICT_WALK_PER_FILE 10 /* Pages to visit per file */ diff --git a/src/include/config.h b/src/include/config.h index d8837f0f368..c83d96c8a5e 100644 --- a/src/include/config.h +++ b/src/include/config.h @@ -33,6 +33,14 @@ struct __wt_config_entry { const WT_CONFIG_CHECK *checks; /* check array */ }; +struct __wt_config_parser_impl { + WT_CONFIG_PARSER iface; + + WT_SESSION_IMPL *session; + WT_CONFIG config; + WT_CONFIG_ITEM config_item; +}; + /* * DO NOT EDIT: automatically built by dist/api_config.py. * configuration section: BEGIN diff --git a/src/include/extern.h b/src/include/extern.h index d1662717345..bd24cb02d61 100644 --- a/src/include/extern.h +++ b/src/include/extern.h @@ -529,27 +529,16 @@ extern int __wt_config_concat( WT_SESSION_IMPL *session, const char **config_ret); extern int __wt_conn_config_init(WT_SESSION_IMPL *session); extern void __wt_conn_config_discard(WT_SESSION_IMPL *session); +extern int __wt_ext_config_parser_open(WT_EXTENSION_API *wt_ext, + WT_SESSION *wt_session, + const char *config, + size_t len, + WT_CONFIG_PARSER **config_parserp); extern int __wt_ext_config_get(WT_EXTENSION_API *wt_api, WT_SESSION *wt_session, WT_CONFIG_ARG *cfg_arg, const char *key, WT_CONFIG_ITEM *cval); -extern int __wt_ext_config_strget(WT_EXTENSION_API *wt_api, - WT_SESSION *wt_session, - const char *config, - const char *key, - WT_CONFIG_ITEM *cval); -extern int __wt_ext_config_scan_begin( WT_EXTENSION_API *wt_api, - WT_SESSION *wt_session, - const char *str, - size_t len, - WT_CONFIG_SCAN **scanp); -extern int __wt_ext_config_scan_end(WT_EXTENSION_API *wt_api, - WT_CONFIG_SCAN *scan); -extern int __wt_ext_config_scan_next( WT_EXTENSION_API *wt_api, - WT_CONFIG_SCAN *scan, - WT_CONFIG_ITEM *key, - WT_CONFIG_ITEM *value); extern int __wt_collator_config( WT_SESSION_IMPL *session, const char **cfg, WT_COLLATOR **collatorp); diff --git a/src/include/txn.i b/src/include/txn.i index cdfe697ee51..8ec1f52bff4 100644 --- a/src/include/txn.i +++ b/src/include/txn.i @@ -167,19 +167,23 @@ __wt_txn_visible(WT_SESSION_IMPL *session, uint64_t id) /* * __wt_txn_read_skip -- * Get the first visible update in a list (or NULL if none are visible), - * and report whether uncommitted changes were skipped. + * and report whether there are an uncommitted changes in the list. */ static inline WT_UPDATE * __wt_txn_read_skip(WT_SESSION_IMPL *session, WT_UPDATE *upd, int *skipp) { + WT_UPDATE *first_upd; + *skipp = 0; - while (upd != NULL && !__wt_txn_visible(session, upd->txnid)) { - if (upd->txnid != WT_TXN_ABORTED) - *skipp = 1; - upd = upd->next; - } + for (first_upd = NULL; upd != NULL; upd = upd->next) + if (upd->txnid != WT_TXN_ABORTED) { + if (!__wt_txn_visible(session, upd->txnid)) + *skipp = 1; + else if (first_upd == NULL) + first_upd = upd; + } - return (upd); + return (first_upd); } /* diff --git a/src/include/wiredtiger.in b/src/include/wiredtiger.in index ac776e31ecd..c08b4d37206 100644 --- a/src/include/wiredtiger.in +++ b/src/include/wiredtiger.in @@ -71,9 +71,9 @@ extern "C" { *******************************************/ struct __wt_collator; typedef struct __wt_collator WT_COLLATOR; struct __wt_compressor; typedef struct __wt_compressor WT_COMPRESSOR; -struct __wt_config_arg; typedef struct __wt_config_arg WT_CONFIG_ARG; struct __wt_config_item; typedef struct __wt_config_item WT_CONFIG_ITEM; -struct __wt_config_scan; typedef struct __wt_config_scan WT_CONFIG_SCAN; +struct __wt_config_parser; + typedef struct __wt_config_parser WT_CONFIG_PARSER; struct __wt_connection; typedef struct __wt_connection WT_CONNECTION; struct __wt_cursor; typedef struct __wt_cursor WT_CURSOR; struct __wt_data_source; typedef struct __wt_data_source WT_DATA_SOURCE; @@ -1910,7 +1910,7 @@ struct __wt_config_item { * list or string), the \c str field will reference the value of the * configuration string. * - * The bytes referenced by \c str are <b>not</b> be nul-terminated, + * The bytes referenced by \c str are <b>not</b> nul-terminated, * use the \c len field instead of a terminating nul byte. */ const char *str; @@ -1951,93 +1951,94 @@ struct __wt_config_item { }; /*! - * @typedef WT_CONFIG_ARG - * - * A configuration object passed to some extension interfaces. This is an - * opaque type: configuration values can be queried using - * WT_EXTENSION_API::config_get and wiredtiger_config_get. - */ - -/*! - * @typedef WT_CONFIG_SCAN - * - * A handle for a scan through a configuration string. - * This is an opaque type returned by WT_EXTENSION_API::config_scan_begin and - * wiredtiger_config_scan_begin. Configuration values can be queried using - * WT_EXTENSION_API::config_scan_next or wiredtiger_config_scan_next. Call - * WT_EXTENSION_API::config_scan_end or wiredtiger_config_scan_end when - * finished to release resources. + * Create a handle that can be used to parse or create configuration strings + * compatible with WiredTiger APIs. + * This API is outside the scope of a WiredTiger connection handle, since + * applications may need to generate configuration strings prior to calling + * ::wiredtiger_open. + * @param session the session handle (or NULL if none available), used for + * error reporting if provided + * @param config the configuration string being parsed. The string must + * remain valid for the lifetime of the parser handle. + * @param len the number of valid bytes in \c config + * @param[out] config_parserp A pointer to the newly opened handle + * @errors */ +int wiredtiger_config_parser_open(WT_SESSION *session, + const char *config, size_t len, WT_CONFIG_PARSER **config_parserp); /*! - * Return the value of a configuration string. + * A handle that can be used to search and traverse configuration strings + * compatible with WiredTiger APIs. + * To parse the contents of a list or nested configuration string use a new + * configuration parser handle based on the content of the ::WT_CONFIG_ITEM + * retrieved from the parent configuration string. * - * @param session the session handle (or NULL if none available) - * @param key configuration key string - * @param config the configuration information passed to an application - * @param value the returned value - * @errors + * @section config_parse_examples Configuration String Parsing examples * - */ -int wiredtiger_config_get(WT_SESSION *session, - WT_CONFIG_ARG *config, const char *key, WT_CONFIG_ITEM *value); - -/*! - * Return the value of a configuration string. + * This could be used in C to create a configuration parser as follows: * - * @param session the session handle (or NULL if none available) - * @param config a configuration string - * @param key configuration key string - * @param value the returned value - * @errors + * @snippet ex_config_parse.c Create a configuration parser * - */ -int wiredtiger_config_strget(WT_SESSION *session, - const char *config, const char *key, WT_CONFIG_ITEM *value); - -/*! - * Return the list entries of a configuration string value. - * This method steps through the entries found in the last returned - * value from wiredtiger_config_get. The last returned value - * should be of type "list". - * - * @param session the session handle (or NULL if none available) - * @param str the configuration string to scan - * @param len the number of valid bytes in \c str - * @param[out] scanp a handle used to scan the config string - * @errors + * Once the parser has been created the content can be queried directly: * - */ -int wiredtiger_config_scan_begin(WT_SESSION *session, - const char *str, size_t len, WT_CONFIG_SCAN **scanp); - -/*! - * Release any resources allocated by - * wiredtiger_config_scan_begin. + * @snippet ex_config_parse.c get * - * @param session the session handle (or NULL if none available) - * @param scan the configuration scanner, invalid after this call - * @errors + * Or the content can be traversed linearly: * - */ -int wiredtiger_config_scan_end(WT_SESSION *session, WT_CONFIG_SCAN *scan); - -/*! - * Return the next key/value pair from a config string scan. + * @snippet ex_config_parse.c next * - * If the string contains a list of items with no assigned value, the - * items will be returned in \c key and the \c value will be set to the - * boolean \c "true" value. + * Nested configuration values can be queried using a shorthand notation: * - * @param session the session handle (or NULL if none available) - * @param scan the configuration scanner - * @param key the returned key - * @param value the returned value - * @errors + * @snippet ex_config_parse.c nested get * + * Nested configuration values can be traversed using multiple + * ::WT_CONFIG_PARSER handles: + * + * @snippet ex_config_parse.c nested traverse */ -int wiredtiger_config_scan_next(WT_SESSION *session, - WT_CONFIG_SCAN *scan, WT_CONFIG_ITEM *key, WT_CONFIG_ITEM *value); +struct __wt_config_parser { + + /*! + * Close the configuration scanner releasing any resources. + * + * @param config_parser the configuration parser handle + * @errors + * + */ + int __F(close)(WT_CONFIG_PARSER *config_parser); + + /*! + * Return the next key/value pair. + * + * When iteration would pass the end of the configuration string + * ::WT_NOTFOUND will be returned. + * + * If an item has no explicitly assigned value, the item will be + * returned in \c key and the \c value will be set to the boolean + * \c "true" value. + * + * @param config_parser the configuration parser handle + * @param key the returned key + * @param value the returned value + * @errors + * + */ + int __F(next)(WT_CONFIG_PARSER *config_parser, + WT_CONFIG_ITEM *key, WT_CONFIG_ITEM *value); + + /*! + * Return the value of an item in the configuration string. + * + * @param config_parser the configuration parser handle + * @param key configuration key string + * @param value the returned value + * @errors + * + */ + int __F(get)(WT_CONFIG_PARSER *config_parser, + const char *key, WT_CONFIG_ITEM *value); +}; #endif /* !defined(SWIG) */ /*! @@ -2134,6 +2135,7 @@ const char *wiredtiger_version(int *majorp, int *minorp, int *patchp); /******************************************* * Forward structure declarations for the extension API *******************************************/ +struct __wt_config_arg; typedef struct __wt_config_arg WT_CONFIG_ARG; /*! * The interface implemented by applications to provide custom ordering of diff --git a/src/include/wiredtiger_ext.h b/src/include/wiredtiger_ext.h index 1f66c421027..acf7efad3d9 100644 --- a/src/include/wiredtiger_ext.h +++ b/src/include/wiredtiger_ext.h @@ -184,6 +184,12 @@ struct __wt_extension_api { WT_ITEM *first, WT_ITEM *second, int *cmp); /*! + * @copydoc wiredtiger_config_parser_open + */ + int (*config_parser_open)(WT_EXTENSION_API *wt_api, WT_SESSION *session, + const char *config, size_t len, WT_CONFIG_PARSER **config_parserp); + + /*! * Return the value of a configuration string. * * @param wt_api the extension handle @@ -199,69 +205,6 @@ struct __wt_extension_api { WT_CONFIG_ARG *config, const char *key, WT_CONFIG_ITEM *value); /*! - * Return the value of a configuration string. - * - * @param wt_api the extension handle - * @param session the session handle (or NULL if none available) - * @param config a configuration string - * @param key configuration key string - * @param value the returned value - * @errors - * - * @snippet ex_data_source.c WT_EXTENSION config_strget - */ - int (*config_strget)(WT_EXTENSION_API *wt_api, WT_SESSION *session, - const char *config, const char *key, WT_CONFIG_ITEM *value); - - /*! - * Return the list entries of a configuration string value. - * This method steps through the entries found in the last returned - * value from WT_EXTENSION_API::config_get. The last returned value - * should be of type "list". - * - * @param wt_api the extension handle - * @param session the session handle (or NULL if none available) - * @param str the configuration string to scan - * @param len the number of valid bytes in \c str - * @param[out] scanp a handle used to scan the config string - * @errors - * - * @snippet ex_data_source.c WT_EXTENSION config scan - */ - int (*config_scan_begin)(WT_EXTENSION_API *wt_api, WT_SESSION *session, - const char *str, size_t len, WT_CONFIG_SCAN **scanp); - - /*! - * Release any resources allocated by - * WT_EXTENSION_API::config_scan_begin. - * - * @param wt_api the extension handle - * @param scan the configuration scanner, invalid after this call - * @errors - * - * @snippet ex_data_source.c WT_EXTENSION config scan - */ - int (*config_scan_end)(WT_EXTENSION_API *wt_api, WT_CONFIG_SCAN *scan); - - /*! - * Return the next key/value pair from a config string scan. - * - * If the string contains a list of items with no assigned value, the - * items will be returned in \c key and the \c value will be set to the - * boolean \c "true" value. - * - * @param wt_api the extension handle - * @param scan the configuration scanner - * @param key the returned key - * @param value the returned value - * @errors - * - * @snippet ex_data_source.c WT_EXTENSION config scan - */ - int (*config_scan_next)(WT_EXTENSION_API *wt_api, - WT_CONFIG_SCAN *scan, WT_CONFIG_ITEM *key, WT_CONFIG_ITEM *value); - - /*! * Insert a row into the metadata if it does not already exist. * * @param wt_api the extension handle @@ -428,8 +371,21 @@ struct __wt_extension_api { */ int (*transaction_visible)(WT_EXTENSION_API *wt_api, WT_SESSION *session, uint64_t transaction_id); + + /*! + * @copydoc wiredtiger_version + */ + const char *(*version)(int *majorp, int *minorp, int *patchp); }; +/*! + * @typedef WT_CONFIG_ARG + * + * A configuration object passed to some extension interfaces. This is an + * opaque type: configuration values can be queried using + * WT_EXTENSION_API::config_get + */ + /*! @} */ #endif /* SWIG */ diff --git a/src/include/wt_internal.h b/src/include/wt_internal.h index 3ae89fc211e..19a805aa25f 100644 --- a/src/include/wt_internal.h +++ b/src/include/wt_internal.h @@ -93,6 +93,8 @@ struct __wt_config_check; typedef struct __wt_config_check WT_CONFIG_CHECK; struct __wt_config_entry; typedef struct __wt_config_entry WT_CONFIG_ENTRY; +struct __wt_config_parser_impl; + typedef struct __wt_config_parser_impl WT_CONFIG_PARSER_IMPL; struct __wt_connection_impl; typedef struct __wt_connection_impl WT_CONNECTION_IMPL; struct __wt_connection_stats; diff --git a/src/lsm/lsm_cursor.c b/src/lsm/lsm_cursor.c index b51c9deeade..5076343bc4b 100644 --- a/src/lsm/lsm_cursor.c +++ b/src/lsm/lsm_cursor.c @@ -385,7 +385,7 @@ retry: if (F_ISSET(clsm, WT_CLSM_MERGE)) { * generation number and retry if it has changed under us. */ if (clsm->cursors != NULL && (ngood < clsm->nchunks || - (F_ISSET(clsm, WT_CLSM_OPEN_READ) && nupdates > 0))) { + (!F_ISSET(clsm, WT_CLSM_OPEN_READ) && nupdates > 0))) { saved_gen = lsm_tree->dsk_gen; locked = 0; WT_ERR(__wt_lsm_tree_unlock(session, lsm_tree)); diff --git a/tools/wtperf_stats.py b/tools/wtperf_stats.py index 388eb9fe24b..d9743055fff 100644 --- a/tools/wtperf_stats.py +++ b/tools/wtperf_stats.py @@ -76,6 +76,19 @@ def munge_dict(values_dict, abstime): next_val[title] = value ret.append(next_val) + # After building the series, eliminate constants + d0 = ret[0] + for t0, v0 in d0.items(): + skip = True + for d in ret: + v = d[t0] + if v != v0: + skip = False + break + if skip: + for dicts in ret: + del dicts[t0] + return ret def addPlotsToChart(chart, graph_data, wtstat_chart = False): |