#include "iwkv.h" #include "iwutils.h" #include #include #include #include char *g_program; uint32_t _execsize(); typedef struct _BMCTX BMCTX; typedef bool (bench_method(BMCTX *bmctx)); #define RND_DATA_SZ (10*1048576) char RND_DATA[RND_DATA_SZ]; struct BM { bool initiated; int ksz; int argc; char **argv; char *param_db; int param_num; int param_num_reads; int param_value_size; uint32_t param_seed; char *param_benchmarks; void (*val_free)(void *ptr); void (*env_setup)(void); void *(*db_open)(BMCTX *ctx); bool (*db_close)(BMCTX *ctx); bool (*db_put)(BMCTX *ctx, const IWKV_val *key, const IWKV_val *val, bool sync); bool (*db_get)(BMCTX *ctx, const IWKV_val *key, IWKV_val *val, bool *found); bool (*db_cursor_to_key)(BMCTX *ctx, const IWKV_val *key, IWKV_val *val, bool *found); bool (*db_del)(BMCTX *ctx, const IWKV_val *key, bool sync); bool (*db_read_seq)(BMCTX *ctx, bool reverse); uint64_t (*db_size_bytes)(BMCTX *ctx); } bm = {0}; struct _BMCTX { bool success; bool freshdb; bool logdbsize; char *name; int num; int value_size; uint64_t start_ms; uint64_t end_ms; void *db; void *extra; bench_method *method; int rnd_data_pos; }; static void _val_dispose(IWKV_val *val) { if (bm.val_free) { if (val->data) { bm.val_free(val->data); val->size = 0; } } else { iwkv_val_dispose(val); } } static void _bmctx_dispose(BMCTX *ctx) { free(ctx->name); free(ctx); } static const char *_bmctx_rndbuf_nextptr(BMCTX *ctx, int len) { if (len > RND_DATA_SZ) { fprintf(stderr, "record value length exceeds maximum allowed: %d\n", RND_DATA_SZ); exit(1); } if (ctx->rnd_data_pos + len > RND_DATA_SZ) { ctx->rnd_data_pos = 0; } const char *ret = RND_DATA + ctx->rnd_data_pos; ctx->rnd_data_pos += len; return ret; } static bool _bm_check() { if (!bm.env_setup) { fprintf(stderr, "env_setup function is not set\n"); return false; } if (!bm.db_open) { fprintf(stderr, "db_open function is not set\n"); return false; } if (!bm.db_close) { fprintf(stderr, "db_close function is not set\n"); return false; } if (!bm.db_put) { fprintf(stderr, "db_put function is not set\n"); return false; } if (!bm.db_get) { fprintf(stderr, "db_get function is not set\n"); return false; } if (!bm.db_del) { fprintf(stderr, "db_del function is not set\n"); return false; } if (!bm.db_read_seq) { fprintf(stderr, "db_read_seq function is not set\n"); return false; } if (!bm.db_cursor_to_key) { fprintf(stderr, "db_cursor_to_key function is not set\n"); return false; } return true; } static void _bm_help() { fprintf(stderr, "\n"); fprintf(stderr, "Usage %s [options]\n\n", g_program); fprintf(stderr, "Options:\n"); fprintf(stderr, " -h Show this help\n"); fprintf(stderr, " -w Write Ahead Log (WAL) enabled\n"); fprintf(stderr, " -n Number of stored records\n"); fprintf(stderr, " -r Number of records to read (equals to number of stored records if not specified, not for readseq,readreverse)\n"); fprintf(stderr, " -vz Size of a single record value in bytes\n"); fprintf(stderr, " -kz <16|192|1024> Size of record key. Either 16 (default) or 192 or 1024 bytes\n"); fprintf(stderr, " -b \n\n"); fprintf(stderr, " -db Database file/directory\n\n"); fprintf(stderr, " -rs Random seed used for iwu random generator\n\n"); fprintf(stderr, "Available benchmarks:\n"); fprintf(stderr, " fillseq write N fixed length values in sequential key order\n"); fprintf(stderr, " fillseq2 write N random length values in sequential key order\n"); fprintf(stderr, " fillrandom write N fixed length values in random key order\n"); fprintf(stderr, " fillrandom2 write N random length values in random key order\n"); fprintf(stderr, " overwrite overwrite N values in random key order\n"); fprintf(stderr, " fillsync write N/100 values in random key order in sync mode\n"); fprintf(stderr, " fill100K write N/100 100K values in random order\n"); fprintf(stderr, " deleteseq delete N keys in sequential order\n"); fprintf(stderr, " deleterandom delete N keys in random order\n"); fprintf(stderr, " readseq read all records sequentially\n"); fprintf(stderr, " readreverse read all records sequentially in reverse order\n"); fprintf(stderr, " readrandom read N times in random order\n"); fprintf(stderr, " readmissing read N missing keys in random order\n"); fprintf(stderr, " readhot read N times in random order from 1%% section of DB\n"); fprintf(stderr, " seekrandom N random seeks\n"); fprintf(stderr, "\n"); } static bool _bm_init(int argc, char *argv[]) { if (bm.initiated) { fprintf(stderr, "Benchmark already initialized\n"); return false; } if (iw_init()) { return false; } bm.argc = argc; bm.argv = argv; bm.param_num = 2000000; // 2M records bm.param_num_reads = -1; // Same as param_num bm.param_value_size = 400; bm.param_benchmarks = "fillrandom2," "readrandom," "deleterandom," "fillseq2," "readrandom," "deleteseq," "fillseq," "overwrite," "readrandom," "readseq," "readreverse," "readhot," "readmissing," "deleteseq," "fill100K"; #ifndef NDEBUG fprintf(stderr, "WARNING: Assertions are enabled, benchmarks can be slow\n"); #endif for (int i = 1; i < argc; ++i) { if (!strcmp(argv[i], "-h")) { _bm_help(); return false; } if (!strcmp(argv[i], "-n")) { if (++i >= argc) { fprintf(stderr, "'-n ' option has no value\n"); return false; } bm.param_num = atoi(argv[i]); if (bm.param_num < 1) { fprintf(stderr, "'-n ' invalid option value\n"); return false; } } else if (!strcmp(argv[i], "-r")) { if (++i >= argc) { fprintf(stderr, "'-r ' option has no value\n"); return false; } bm.param_num_reads = atoi(argv[i]); } else if (!strcmp(argv[i], "-vz")) { if (++i >= argc) { fprintf(stderr, "'-vz ' option has not value\n"); return false; } bm.param_value_size = atoi(argv[i]); if (bm.param_value_size <= 0) { fprintf(stderr, "'-vz ' invalid option value\n"); return false; } } else if (!strcmp(argv[i], "-b")) { if (++i >= argc) { fprintf(stderr, "'-b ' options has no value\n"); return false; } bm.param_benchmarks = argv[i]; } else if (!strcmp(argv[i], "-db")) { if (++i >= argc) { fprintf(stderr, "'-db ' options has no value\n"); return false; } bm.param_db = argv[i]; } else if (!strcmp(argv[i], "-rs")) { if (++i >= argc) { fprintf(stderr, "'-rs ' options has no value\n"); return false; } bm.param_seed = atoll(argv[i]); } else if (!strcmp(argv[i], "-kz")) { if (++i >= argc) { fprintf(stderr, "'-kz 16|192|1024' options has no value\n"); return false; } int ksz = atoi(argv[i]); if (ksz == 192) { bm.ksz = 192; } else if (ksz == 1024) { bm.ksz = 1024; } else if (ksz != 16) { fprintf(stderr, "'-kz 16|192|1024' options has invalid value %d\n", ksz); return false; } } } if (bm.ksz == 0) { bm.ksz = 16; } if (bm.param_num_reads < 0) { bm.param_num_reads = bm.param_num; } if (!_bm_check()) { fprintf(stderr, "Benchmark `bm` structure is not configured properly\n"); return false; } if (!bm.param_seed) { uint64_t ts; iwp_current_time_ms(&ts, false); ts = IW_SWAB64(ts); ts >>= 32; bm.param_seed = ts; } iwu_rand_seed(bm.param_seed); srandom(bm.param_seed); fprintf(stderr, "\n exec size: %u" "\n random seed: %u" "\n num records: %d\n read num records: %d\n value size: %d\n benchmarks: %s" "\n key size: %d \n\n", _execsize(), bm.param_seed, bm.param_num, bm.param_num_reads, bm.param_value_size, bm.param_benchmarks, bm.ksz); // Fill up random data array for (int i = 0; i < RND_DATA_SZ; ++i) { RND_DATA[i] = ' ' + iwu_rand_range(95); // ascii space ... ~ } return true; } static void _logdbsize(BMCTX *ctx) { if (bm.db_size_bytes) { uint64_t dbz = bm.db_size_bytes(ctx); fprintf(stderr, " db size: %lu (%lu MB)\n", dbz, (dbz / 1024 / 1024)); } } uint32_t _execsize() { const char *path = g_program; IWP_FILE_STAT fst; iwrc rc = iwp_fstat(path, &fst); if (rc) { iwlog_ecode_error3(rc); return 0; } return fst.size; } static void _bm_run(BMCTX *ctx) { assert(ctx->method); uint64_t llv; ctx->db = bm.db_open(ctx); if (!ctx->db) { ctx->success = false; return; } iwp_current_time_ms(&llv, false); ctx->start_ms = llv; ctx->success = ctx->method(ctx); if (!bm.db_close(ctx)) { ctx->success = false; } iwp_current_time_ms(&llv, false); ctx->end_ms = llv; } #define FILLKEY() snprintf(key.data, sizeof(kbuf), bm.ksz == 16 ? "%016d" : bm.ksz == 192 ? "%0192d" : "%01024d", k) static bool _do_write(BMCTX *ctx, bool seq, bool sync, bool rvlen) { char kbuf[1025]; IWKV_val key, val; key.data = kbuf; int value_size = ctx->value_size; for (int i = 0; i < ctx->num; ++i) { if (rvlen) { value_size = iwu_rand_range(ctx->value_size + 1); if (value_size == 0) { value_size = 1; } } const int k = seq ? i : iwu_rand_range(bm.param_num); FILLKEY(); key.size = strlen(key.data); val.data = (void *) _bmctx_rndbuf_nextptr(ctx, value_size); val.size = value_size; if (!bm.db_put(ctx, &key, &val, sync)) { return false; } } return true; } static bool _do_delete(BMCTX *ctx, bool sync, bool seq) { char kbuf[1025]; IWKV_val key; key.data = kbuf; for (int i = 0; i < ctx->num; ++i) { const int k = seq ? i : iwu_rand_range(bm.param_num); FILLKEY(); key.size = strlen(key.data); if (!bm.db_del(ctx, &key, sync)) { return false; } } return true; } static bool _do_read_seq(BMCTX *ctx) { return bm.db_read_seq(ctx, false); } static bool _do_read_reverse(BMCTX *ctx) { return bm.db_read_seq(ctx, true); } static bool _do_read_random(BMCTX *ctx) { char kbuf[1025]; IWKV_val key, val; bool found; key.data = kbuf; for (int i = 0; i < bm.param_num_reads; ++i) { const int k = iwu_rand_range(bm.param_num); FILLKEY(); key.size = strlen(key.data); val.data = 0; val.size = 0; if (!bm.db_get(ctx, &key, &val, &found)) { _val_dispose(&val); return false; } _val_dispose(&val); } return true; } static bool _do_read_missing(BMCTX *ctx) { char kbuf[1025]; IWKV_val key, val; bool found; key.data = kbuf; for (int i = 0; i < bm.param_num_reads; ++i) { const int k = iwu_rand_range(bm.param_num); snprintf(key.data, sizeof(kbuf), bm.ksz == 16 ? "%016d." : bm.ksz == 192 ? "%0192d." : "%01024d.", k); key.size = strlen(key.data); val.data = 0; val.size = 0; if (!bm.db_get(ctx, &key, &val, &found)) { _val_dispose(&val); return false; } _val_dispose(&val); if (found) { return false; } } return true; } static bool _do_read_hot(BMCTX *ctx) { char kbuf[1025]; IWKV_val key, val; bool found; key.data = kbuf; const int range = (bm.param_num + 99) / 100; for (int i = 0; i < bm.param_num_reads; ++i) { const int k = iwu_rand_range(range); FILLKEY(); key.size = strlen(key.data); val.data = 0; val.size = 0; if (!bm.db_get(ctx, &key, &val, &found)) { _val_dispose(&val); return false; } _val_dispose(&val); } return true; } static bool _do_seek_random(BMCTX *ctx) { char kbuf[1025]; IWKV_val key, val; bool found; key.data = kbuf; for (int i = 0; i < bm.param_num_reads; ++i) { const int k = iwu_rand_range(bm.param_num); FILLKEY(); key.size = strlen(key.data); val.data = 0; val.size = 0; if (!bm.db_cursor_to_key(ctx, &key, &val, &found)) { _val_dispose(&val); return false; } _val_dispose(&val); } return true; } static bool _bm_fillseq(BMCTX *ctx) { if (!ctx->freshdb) return false; return _do_write(ctx, true, false, false); } static bool _bm_fillseq2(BMCTX *ctx) { if (!ctx->freshdb) return false; return _do_write(ctx, true, false, true); } static bool _bm_fillrandom(BMCTX *ctx) { if (!ctx->freshdb) return false; return _do_write(ctx, false, false, false); } static bool _bm_fillrandom2(BMCTX *ctx) { if (!ctx->freshdb) return false; return _do_write(ctx, false, false, true); } static bool _bm_overwrite(BMCTX *ctx) { if (ctx->freshdb) return false; return _do_write(ctx, false, false, false); } static bool _bm_fillsync(BMCTX *ctx) { if (!ctx->freshdb) return false; ctx->num /= 100; if (ctx->num < 1) ctx->num = 1; fprintf(stderr, "\tfillsync num records: %d\n", ctx->num); return _do_write(ctx, false, true, false); } static bool _bm_fill100K(BMCTX *ctx) { if (!ctx->freshdb) return false; ctx->num /= 100; if (ctx->num < 1) ctx->num = 1; fprintf(stderr, "\tfill100K num records: %d\n", ctx->num); ctx->value_size = 100 * 1000; return _do_write(ctx, false, false, false); } static bool _bm_deleteseq(BMCTX *ctx) { if (ctx->freshdb) return false; return _do_delete(ctx, false, true); } static bool _bm_deleterandom(BMCTX *ctx) { if (ctx->freshdb) return false; return _do_delete(ctx, false, false); } static bool _bm_readseq(BMCTX *ctx) { if (ctx->freshdb) return false; return _do_read_seq(ctx); } static bool _bm_readreverse(BMCTX *ctx) { if (ctx->freshdb) return false; return _do_read_reverse(ctx); } static bool _bm_readrandom(BMCTX *ctx) { if (ctx->freshdb) return false; return _do_read_random(ctx); } static bool _bm_readmissing(BMCTX *ctx) { if (ctx->freshdb) return false; return _do_read_missing(ctx); } static bool _bm_readhot(BMCTX *ctx) { if (ctx->freshdb) return false; return _do_read_hot(ctx); } static bool _bm_seekrandom(BMCTX *ctx) { if (ctx->freshdb) return false; return _do_seek_random(ctx); } static BMCTX *_bmctx_create(const char *name) { bench_method *method = 0; bool logdbsize = false; bool freshdb = false; if (!strcmp(name, "fillseq")) { freshdb = true; logdbsize = true; method = _bm_fillseq; } else if (!strcmp(name, "fillseq2")) { freshdb = true; logdbsize = true; method = _bm_fillseq2; } else if (!strcmp(name, "fillrandom")) { freshdb = true; logdbsize = true; method = _bm_fillrandom; } else if (!strcmp(name, "fillrandom2")) { freshdb = true; logdbsize = true; method = _bm_fillrandom2; } else if (!strcmp(name, "overwrite")) { logdbsize = true; method = _bm_overwrite; } else if (!strcmp(name, "fillsync")) { freshdb = true; logdbsize = true; method = _bm_fillsync; } else if (!strcmp(name, "fill100K")) { freshdb = true; logdbsize = true; method = _bm_fill100K; } else if (!strcmp(name, "deleteseq")) { logdbsize = true; method = _bm_deleteseq; } else if (!strcmp(name, "deleterandom")) { logdbsize = true; method = _bm_deleterandom; } else if (!strcmp(name, "readseq")) { method = _bm_readseq; } else if (!strcmp(name, "readreverse")) { method = _bm_readreverse; } else if (!strcmp(name, "readrandom")) { method = _bm_readrandom; } else if (!strcmp(name, "readmissing")) { method = _bm_readmissing; } else if (!strcmp(name, "readhot")) { method = _bm_readhot; } else if (!strcmp(name, "seekrandom")) { method = _bm_seekrandom; } else { fprintf(stderr, "Unknown benchmark: '%s'\n", name); return 0; } BMCTX *bmctx = calloc(1, sizeof(*bmctx)); bmctx->name = strdup(name); bmctx->method = method; bmctx->num = bm.param_num; bmctx->value_size = bm.param_value_size; bmctx->freshdb = freshdb; bmctx->logdbsize = logdbsize; return bmctx; } static bool bm_bench_run(int argc, char *argv[]) { if (!_bm_init(argc, argv)) { fprintf(stderr, "Benchmark cannot be initialized\n"); exit(1); } bm.env_setup(); int c = 0; const char *ptr = bm.param_benchmarks; char bname[100]; bool bmres = true; while (bmres) { if (*ptr == ',' || *ptr == '\0' || c >= 99) { bname[c] = '\0'; BMCTX *ctx = _bmctx_create(bname); c = 0; if (ctx) { fprintf(stderr, " benchmark: %s\n", bname); _bm_run(ctx); bmres = ctx->success; if (!bmres) { fprintf(stderr, "Failed to run benchmark: %s\n", bname); } else { fprintf(stderr, " done: %s in %lu\n", bname, (ctx->end_ms - ctx->start_ms)); } if (ctx->logdbsize) { _logdbsize(ctx); } _bmctx_dispose(ctx); } if (*ptr == '\0' || !bmres) { break; } } else if (!isspace(*ptr)) { bname[c++] = *ptr; } ptr += 1; } return bmres; }