1 #include "iwkv.h"
2 #include "iwutils.h"
3 #include <stdio.h>
4 #include <stdlib.h>
5 #include <string.h>
6 #include <ctype.h>
7
8 char *g_program;
9
10 uint32_t _execsize();
11
12 typedef struct _BMCTX BMCTX;
13
14 typedef bool(bench_method (BMCTX *bmctx));
15
16 #define RND_DATA_SZ (10 * 1048576)
17 char RND_DATA[RND_DATA_SZ];
18
19 struct BM {
20 bool initiated;
21 int ksz;
22 int argc;
23 char **argv;
24 char *param_db;
25 int param_num;
26 int param_num_reads;
27 int param_value_size;
28 uint32_t param_seed;
29 char *param_benchmarks;
30 void (*val_free)(void *ptr);
31 void (*env_setup)(void);
32 void * (*db_open)(BMCTX *ctx);
33 bool (*db_close)(BMCTX *ctx);
34 bool (*db_put)(BMCTX *ctx, const IWKV_val *key, const IWKV_val *val, bool sync);
35 bool (*db_get)(BMCTX *ctx, const IWKV_val *key, IWKV_val *val, bool *found);
36 bool (*db_cursor_to_key)(BMCTX *ctx, const IWKV_val *key, IWKV_val *val, bool *found);
37 bool (*db_del)(BMCTX *ctx, const IWKV_val *key, bool sync);
38 bool (*db_read_seq)(BMCTX *ctx, bool reverse);
39 uint64_t (*db_size_bytes)(BMCTX *ctx);
40 } bm = { 0 };
41
42 struct _BMCTX {
43 bool success;
44 bool freshdb;
45 bool logdbsize;
46 char *name;
47 int num;
48 int value_size;
49 uint64_t start_ms;
50 uint64_t end_ms;
51 void *db;
52 void *extra;
53 bench_method *method;
54 int rnd_data_pos;
55 };
56
_val_dispose(IWKV_val * val)57 static void _val_dispose(IWKV_val *val) {
58 if (bm.val_free) {
59 if (val->data) {
60 bm.val_free(val->data);
61 val->size = 0;
62 }
63 } else {
64 iwkv_val_dispose(val);
65 }
66 }
67
_bmctx_dispose(BMCTX * ctx)68 static void _bmctx_dispose(BMCTX *ctx) {
69 free(ctx->name);
70 free(ctx);
71 }
72
_bmctx_rndbuf_nextptr(BMCTX * ctx,int len)73 static const char *_bmctx_rndbuf_nextptr(BMCTX *ctx, int len) {
74 if (len > RND_DATA_SZ) {
75 fprintf(stderr, "record value length exceeds maximum allowed: %d\n", RND_DATA_SZ);
76 exit(1);
77 }
78 if (ctx->rnd_data_pos + len > RND_DATA_SZ) {
79 ctx->rnd_data_pos = 0;
80 }
81 const char *ret = RND_DATA + ctx->rnd_data_pos;
82 ctx->rnd_data_pos += len;
83 return ret;
84 }
85
_bm_check()86 static bool _bm_check() {
87 if (!bm.env_setup) {
88 fprintf(stderr, "env_setup function is not set\n");
89 return false;
90 }
91 if (!bm.db_open) {
92 fprintf(stderr, "db_open function is not set\n");
93 return false;
94 }
95 if (!bm.db_close) {
96 fprintf(stderr, "db_close function is not set\n");
97 return false;
98 }
99 if (!bm.db_put) {
100 fprintf(stderr, "db_put function is not set\n");
101 return false;
102 }
103 if (!bm.db_get) {
104 fprintf(stderr, "db_get function is not set\n");
105 return false;
106 }
107 if (!bm.db_del) {
108 fprintf(stderr, "db_del function is not set\n");
109 return false;
110 }
111 if (!bm.db_read_seq) {
112 fprintf(stderr, "db_read_seq function is not set\n");
113 return false;
114 }
115 if (!bm.db_cursor_to_key) {
116 fprintf(stderr, "db_cursor_to_key function is not set\n");
117 return false;
118 }
119 return true;
120 }
121
_bm_help()122 static void _bm_help() {
123 fprintf(stderr, "\n");
124 fprintf(stderr, "Usage %s [options]\n\n", g_program);
125 fprintf(stderr, "Options:\n");
126 fprintf(stderr, " -h Show this help\n");
127 fprintf(stderr, " -w Write Ahead Log (WAL) enabled\n");
128 fprintf(stderr, " -n <num> Number of stored records\n");
129 fprintf(stderr,
130 " -r <num> Number of records to read (equals to number of stored records if not specified, not for readseq,readreverse)\n");
131 fprintf(stderr, " -vz <size> Size of a single record value in bytes\n");
132 fprintf(stderr, " -kz <16|192|1024> Size of record key. Either 16 (default) or 192 or 1024 bytes\n");
133 fprintf(stderr, " -b <comma separated benchmarks to run>\n\n");
134 fprintf(stderr, " -db <file/dir> Database file/directory\n\n");
135 fprintf(stderr, " -rs <random seed> Random seed used for iwu random generator\n\n");
136 fprintf(stderr, "Available benchmarks:\n");
137 fprintf(stderr, " fillseq write N fixed length values in sequential key order\n");
138 fprintf(stderr, " fillseq2 write N random length values in sequential key order\n");
139 fprintf(stderr, " fillrandom write N fixed length values in random key order\n");
140 fprintf(stderr, " fillrandom2 write N random length values in random key order\n");
141 fprintf(stderr, " overwrite overwrite N values in random key order\n");
142 fprintf(stderr, " fillsync write N/100 values in random key order in sync mode\n");
143 fprintf(stderr, " fill100K write N/100 100K values in random order\n");
144 fprintf(stderr, " deleteseq delete N keys in sequential order\n");
145 fprintf(stderr, " deleterandom delete N keys in random order\n");
146 fprintf(stderr, " readseq read all records sequentially\n");
147 fprintf(stderr, " readreverse read all records sequentially in reverse order\n");
148 fprintf(stderr, " readrandom read N times in random order\n");
149 fprintf(stderr, " readmissing read N missing keys in random order\n");
150 fprintf(stderr, " readhot read N times in random order from 1%% section of DB\n");
151 fprintf(stderr, " seekrandom N random seeks\n");
152 fprintf(stderr, "\n");
153 }
154
_bm_init(int argc,char * argv[])155 static bool _bm_init(int argc, char *argv[]) {
156 if (bm.initiated) {
157 fprintf(stderr, "Benchmark already initialized\n");
158 return false;
159 }
160 if (iw_init()) {
161 return false;
162 }
163 bm.argc = argc;
164 bm.argv = argv;
165 bm.param_num = 2000000; // 2M records
166 bm.param_num_reads = -1; // Same as param_num
167 bm.param_value_size = 400;
168 bm.param_benchmarks = "fillrandom2,"
169 "readrandom,"
170 "deleterandom,"
171 "fillseq2,"
172 "readrandom,"
173 "deleteseq,"
174 "fillseq,"
175 "overwrite,"
176 "readrandom,"
177 "readseq,"
178 "readreverse,"
179 "readhot,"
180 "readmissing,"
181 "deleteseq,"
182 "fill100K";
183 #ifndef NDEBUG
184 fprintf(stderr, "WARNING: Assertions are enabled, benchmarks can be slow\n");
185 #endif
186 for (int i = 1; i < argc; ++i) {
187 if (!strcmp(argv[i], "-h")) {
188 _bm_help();
189 return false;
190 }
191 if (!strcmp(argv[i], "-n")) {
192 if (++i >= argc) {
193 fprintf(stderr, "'-n <num records>' option has no value\n");
194 return false;
195 }
196 bm.param_num = atoi(argv[i]);
197 if (bm.param_num < 1) {
198 fprintf(stderr, "'-n <num records>' invalid option value\n");
199 return false;
200 }
201 } else if (!strcmp(argv[i], "-r")) {
202 if (++i >= argc) {
203 fprintf(stderr, "'-r <num read records>' option has no value\n");
204 return false;
205 }
206 bm.param_num_reads = atoi(argv[i]);
207 } else if (!strcmp(argv[i], "-vz")) {
208 if (++i >= argc) {
209 fprintf(stderr, "'-vz <value size>' option has not value\n");
210 return false;
211 }
212 bm.param_value_size = atoi(argv[i]);
213 if (bm.param_value_size <= 0) {
214 fprintf(stderr, "'-vz <value size>' invalid option value\n");
215 return false;
216 }
217 } else if (!strcmp(argv[i], "-b")) {
218 if (++i >= argc) {
219 fprintf(stderr, "'-b <benchmarks>' options has no value\n");
220 return false;
221 }
222 bm.param_benchmarks = argv[i];
223 } else if (!strcmp(argv[i], "-db")) {
224 if (++i >= argc) {
225 fprintf(stderr, "'-db <db file>' options has no value\n");
226 return false;
227 }
228 bm.param_db = argv[i];
229 } else if (!strcmp(argv[i], "-rs")) {
230 if (++i >= argc) {
231 fprintf(stderr, "'-rs <random seed>' options has no value\n");
232 return false;
233 }
234 bm.param_seed = atoll(argv[i]);
235 } else if (!strcmp(argv[i], "-kz")) {
236 if (++i >= argc) {
237 fprintf(stderr, "'-kz 16|192|1024' options has no value\n");
238 return false;
239 }
240 int ksz = atoi(argv[i]);
241 if (ksz == 192) {
242 bm.ksz = 192;
243 } else if (ksz == 1024) {
244 bm.ksz = 1024;
245 } else if (ksz != 16) {
246 fprintf(stderr, "'-kz 16|192|1024' options has invalid value %d\n", ksz);
247 return false;
248 }
249 }
250 }
251 if (bm.ksz == 0) {
252 bm.ksz = 16;
253 }
254 if (bm.param_num_reads < 0) {
255 bm.param_num_reads = bm.param_num;
256 }
257 if (!_bm_check()) {
258 fprintf(stderr, "Benchmark `bm` structure is not configured properly\n");
259 return false;
260 }
261 if (!bm.param_seed) {
262 uint64_t ts;
263 iwp_current_time_ms(&ts, false);
264 ts = IW_SWAB64(ts);
265 ts >>= 32;
266 bm.param_seed = ts;
267 }
268 iwu_rand_seed(bm.param_seed);
269 srandom(bm.param_seed);
270 fprintf(stderr,
271 "\n exec size: %u"
272 "\n random seed: %u"
273 "\n num records: %d\n read num records: %d\n value size: %d\n benchmarks: %s"
274 "\n key size: %d \n\n",
275 _execsize(),
276 bm.param_seed, bm.param_num, bm.param_num_reads, bm.param_value_size, bm.param_benchmarks,
277 bm.ksz);
278
279 // Fill up random data array
280 for (int i = 0; i < RND_DATA_SZ; ++i) {
281 RND_DATA[i] = ' ' + iwu_rand_range(95); // ascii space ... ~
282 }
283 return true;
284 }
285
_logdbsize(BMCTX * ctx)286 static void _logdbsize(BMCTX *ctx) {
287 if (bm.db_size_bytes) {
288 uint64_t dbz = bm.db_size_bytes(ctx);
289 fprintf(stderr, " db size: %lu (%lu MB)\n", dbz, (dbz / 1024 / 1024));
290 }
291 }
292
_execsize()293 uint32_t _execsize() {
294 const char *path = g_program;
295 IWP_FILE_STAT fst;
296 iwrc rc = iwp_fstat(path, &fst);
297 if (rc) {
298 iwlog_ecode_error3(rc);
299 return 0;
300 }
301 return fst.size;
302 }
303
_bm_run(BMCTX * ctx)304 static void _bm_run(BMCTX *ctx) {
305 assert(ctx->method);
306 uint64_t llv;
307 ctx->db = bm.db_open(ctx);
308 if (!ctx->db) {
309 ctx->success = false;
310 return;
311 }
312 iwp_current_time_ms(&llv, false);
313 ctx->start_ms = llv;
314 ctx->success = ctx->method(ctx);
315 if (!bm.db_close(ctx)) {
316 ctx->success = false;
317 }
318 iwp_current_time_ms(&llv, false);
319 ctx->end_ms = llv;
320 }
321
322 #define FILLKEY() snprintf(key.data, sizeof(kbuf), bm.ksz == 16 ? "%016d" : bm.ksz == 192 ? "%0192d" : "%01024d", k)
323
_do_write(BMCTX * ctx,bool seq,bool sync,bool rvlen)324 static bool _do_write(BMCTX *ctx, bool seq, bool sync, bool rvlen) {
325 char kbuf[1025];
326 IWKV_val key, val;
327 key.data = kbuf;
328 int value_size = ctx->value_size;
329 for (int i = 0; i < ctx->num; ++i) {
330 if (rvlen) {
331 value_size = iwu_rand_range(ctx->value_size + 1);
332 if (value_size == 0) {
333 value_size = 1;
334 }
335 }
336 const int k = seq ? i : iwu_rand_range(bm.param_num);
337 FILLKEY();
338
339 key.size = strlen(key.data);
340 val.data = (void*) _bmctx_rndbuf_nextptr(ctx, value_size);
341 val.size = value_size;
342 if (!bm.db_put(ctx, &key, &val, sync)) {
343 return false;
344 }
345 }
346 return true;
347 }
348
_do_delete(BMCTX * ctx,bool sync,bool seq)349 static bool _do_delete(BMCTX *ctx, bool sync, bool seq) {
350 char kbuf[1025];
351 IWKV_val key;
352 key.data = kbuf;
353 for (int i = 0; i < ctx->num; ++i) {
354 const int k = seq ? i : iwu_rand_range(bm.param_num);
355 FILLKEY();
356 key.size = strlen(key.data);
357 if (!bm.db_del(ctx, &key, sync)) {
358 return false;
359 }
360 }
361 return true;
362 }
363
_do_read_seq(BMCTX * ctx)364 static bool _do_read_seq(BMCTX *ctx) {
365 return bm.db_read_seq(ctx, false);
366 }
367
_do_read_reverse(BMCTX * ctx)368 static bool _do_read_reverse(BMCTX *ctx) {
369 return bm.db_read_seq(ctx, true);
370 }
371
_do_read_random(BMCTX * ctx)372 static bool _do_read_random(BMCTX *ctx) {
373 char kbuf[1025];
374 IWKV_val key, val;
375 bool found;
376 key.data = kbuf;
377 for (int i = 0; i < bm.param_num_reads; ++i) {
378 const int k = iwu_rand_range(bm.param_num);
379 FILLKEY();
380 key.size = strlen(key.data);
381 val.data = 0;
382 val.size = 0;
383 if (!bm.db_get(ctx, &key, &val, &found)) {
384 _val_dispose(&val);
385 return false;
386 }
387 _val_dispose(&val);
388 }
389 return true;
390 }
391
_do_read_missing(BMCTX * ctx)392 static bool _do_read_missing(BMCTX *ctx) {
393 char kbuf[1025];
394 IWKV_val key, val;
395 bool found;
396 key.data = kbuf;
397 for (int i = 0; i < bm.param_num_reads; ++i) {
398 const int k = iwu_rand_range(bm.param_num);
399 snprintf(key.data, sizeof(kbuf),
400 bm.ksz == 16 ? "%016d." : bm.ksz == 192 ? "%0192d." : "%01024d.", k);
401 key.size = strlen(key.data);
402 val.data = 0;
403 val.size = 0;
404 if (!bm.db_get(ctx, &key, &val, &found)) {
405 _val_dispose(&val);
406 return false;
407 }
408 _val_dispose(&val);
409 if (found) {
410 return false;
411 }
412 }
413 return true;
414 }
415
_do_read_hot(BMCTX * ctx)416 static bool _do_read_hot(BMCTX *ctx) {
417 char kbuf[1025];
418 IWKV_val key, val;
419 bool found;
420 key.data = kbuf;
421 const int range = (bm.param_num + 99) / 100;
422 for (int i = 0; i < bm.param_num_reads; ++i) {
423 const int k = iwu_rand_range(range);
424 FILLKEY();
425 key.size = strlen(key.data);
426 val.data = 0;
427 val.size = 0;
428 if (!bm.db_get(ctx, &key, &val, &found)) {
429 _val_dispose(&val);
430 return false;
431 }
432 _val_dispose(&val);
433 }
434 return true;
435 }
436
_do_seek_random(BMCTX * ctx)437 static bool _do_seek_random(BMCTX *ctx) {
438 char kbuf[1025];
439 IWKV_val key, val;
440 bool found;
441 key.data = kbuf;
442 for (int i = 0; i < bm.param_num_reads; ++i) {
443 const int k = iwu_rand_range(bm.param_num);
444 FILLKEY();
445 key.size = strlen(key.data);
446 val.data = 0;
447 val.size = 0;
448 if (!bm.db_cursor_to_key(ctx, &key, &val, &found)) {
449 _val_dispose(&val);
450 return false;
451 }
452 _val_dispose(&val);
453 }
454 return true;
455 }
456
_bm_fillseq(BMCTX * ctx)457 static bool _bm_fillseq(BMCTX *ctx) {
458 if (!ctx->freshdb) {
459 return false;
460 }
461 return _do_write(ctx, true, false, false);
462 }
463
_bm_fillseq2(BMCTX * ctx)464 static bool _bm_fillseq2(BMCTX *ctx) {
465 if (!ctx->freshdb) {
466 return false;
467 }
468 return _do_write(ctx, true, false, true);
469 }
470
_bm_fillrandom(BMCTX * ctx)471 static bool _bm_fillrandom(BMCTX *ctx) {
472 if (!ctx->freshdb) {
473 return false;
474 }
475 return _do_write(ctx, false, false, false);
476 }
477
_bm_fillrandom2(BMCTX * ctx)478 static bool _bm_fillrandom2(BMCTX *ctx) {
479 if (!ctx->freshdb) {
480 return false;
481 }
482 return _do_write(ctx, false, false, true);
483 }
484
_bm_overwrite(BMCTX * ctx)485 static bool _bm_overwrite(BMCTX *ctx) {
486 if (ctx->freshdb) {
487 return false;
488 }
489 return _do_write(ctx, false, false, false);
490 }
491
_bm_fillsync(BMCTX * ctx)492 static bool _bm_fillsync(BMCTX *ctx) {
493 if (!ctx->freshdb) {
494 return false;
495 }
496 ctx->num /= 100;
497 if (ctx->num < 1) {
498 ctx->num = 1;
499 }
500 fprintf(stderr, "\tfillsync num records: %d\n", ctx->num);
501 return _do_write(ctx, false, true, false);
502 }
503
_bm_fill100K(BMCTX * ctx)504 static bool _bm_fill100K(BMCTX *ctx) {
505 if (!ctx->freshdb) {
506 return false;
507 }
508 ctx->num /= 100;
509 if (ctx->num < 1) {
510 ctx->num = 1;
511 }
512 fprintf(stderr, "\tfill100K num records: %d\n", ctx->num);
513 ctx->value_size = 100 * 1000;
514 return _do_write(ctx, false, false, false);
515 }
516
_bm_deleteseq(BMCTX * ctx)517 static bool _bm_deleteseq(BMCTX *ctx) {
518 if (ctx->freshdb) {
519 return false;
520 }
521 return _do_delete(ctx, false, true);
522 }
523
_bm_deleterandom(BMCTX * ctx)524 static bool _bm_deleterandom(BMCTX *ctx) {
525 if (ctx->freshdb) {
526 return false;
527 }
528 return _do_delete(ctx, false, false);
529 }
530
_bm_readseq(BMCTX * ctx)531 static bool _bm_readseq(BMCTX *ctx) {
532 if (ctx->freshdb) {
533 return false;
534 }
535 return _do_read_seq(ctx);
536 }
537
_bm_readreverse(BMCTX * ctx)538 static bool _bm_readreverse(BMCTX *ctx) {
539 if (ctx->freshdb) {
540 return false;
541 }
542 return _do_read_reverse(ctx);
543 }
544
_bm_readrandom(BMCTX * ctx)545 static bool _bm_readrandom(BMCTX *ctx) {
546 if (ctx->freshdb) {
547 return false;
548 }
549 return _do_read_random(ctx);
550 }
551
_bm_readmissing(BMCTX * ctx)552 static bool _bm_readmissing(BMCTX *ctx) {
553 if (ctx->freshdb) {
554 return false;
555 }
556 return _do_read_missing(ctx);
557 }
558
_bm_readhot(BMCTX * ctx)559 static bool _bm_readhot(BMCTX *ctx) {
560 if (ctx->freshdb) {
561 return false;
562 }
563 return _do_read_hot(ctx);
564 }
565
_bm_seekrandom(BMCTX * ctx)566 static bool _bm_seekrandom(BMCTX *ctx) {
567 if (ctx->freshdb) {
568 return false;
569 }
570 return _do_seek_random(ctx);
571 }
572
_bmctx_create(const char * name)573 static BMCTX *_bmctx_create(const char *name) {
574 bench_method *method = 0;
575 bool logdbsize = false;
576 bool freshdb = false;
577 if (!strcmp(name, "fillseq")) {
578 freshdb = true;
579 logdbsize = true;
580 method = _bm_fillseq;
581 } else if (!strcmp(name, "fillseq2")) {
582 freshdb = true;
583 logdbsize = true;
584 method = _bm_fillseq2;
585 } else if (!strcmp(name, "fillrandom")) {
586 freshdb = true;
587 logdbsize = true;
588 method = _bm_fillrandom;
589 } else if (!strcmp(name, "fillrandom2")) {
590 freshdb = true;
591 logdbsize = true;
592 method = _bm_fillrandom2;
593 } else if (!strcmp(name, "overwrite")) {
594 logdbsize = true;
595 method = _bm_overwrite;
596 } else if (!strcmp(name, "fillsync")) {
597 freshdb = true;
598 logdbsize = true;
599 method = _bm_fillsync;
600 } else if (!strcmp(name, "fill100K")) {
601 freshdb = true;
602 logdbsize = true;
603 method = _bm_fill100K;
604 } else if (!strcmp(name, "deleteseq")) {
605 logdbsize = true;
606 method = _bm_deleteseq;
607 } else if (!strcmp(name, "deleterandom")) {
608 logdbsize = true;
609 method = _bm_deleterandom;
610 } else if (!strcmp(name, "readseq")) {
611 method = _bm_readseq;
612 } else if (!strcmp(name, "readreverse")) {
613 method = _bm_readreverse;
614 } else if (!strcmp(name, "readrandom")) {
615 method = _bm_readrandom;
616 } else if (!strcmp(name, "readmissing")) {
617 method = _bm_readmissing;
618 } else if (!strcmp(name, "readhot")) {
619 method = _bm_readhot;
620 } else if (!strcmp(name, "seekrandom")) {
621 method = _bm_seekrandom;
622 } else {
623 fprintf(stderr, "Unknown benchmark: '%s'\n", name);
624 return 0;
625 }
626 BMCTX *bmctx = calloc(1, sizeof(*bmctx));
627 bmctx->name = strdup(name);
628 bmctx->method = method;
629 bmctx->num = bm.param_num;
630 bmctx->value_size = bm.param_value_size;
631 bmctx->freshdb = freshdb;
632 bmctx->logdbsize = logdbsize;
633 return bmctx;
634 }
635
bm_bench_run(int argc,char * argv[])636 static bool bm_bench_run(int argc, char *argv[]) {
637 if (!_bm_init(argc, argv)) {
638 fprintf(stderr, "Benchmark cannot be initialized\n");
639 exit(1);
640 }
641 bm.env_setup();
642 int c = 0;
643 const char *ptr = bm.param_benchmarks;
644 char bname[100];
645 bool bmres = true;
646 while (bmres) {
647 if ((*ptr == ',') || (*ptr == '\0') || (c >= 99)) {
648 bname[c] = '\0';
649 BMCTX *ctx = _bmctx_create(bname);
650 c = 0;
651 if (ctx) {
652 fprintf(stderr, " benchmark: %s\n", bname);
653 _bm_run(ctx);
654 bmres = ctx->success;
655 if (!bmres) {
656 fprintf(stderr, "Failed to run benchmark: %s\n", bname);
657 } else {
658 fprintf(stderr, " done: %s in %lu\n", bname, (ctx->end_ms - ctx->start_ms));
659 }
660 if (ctx->logdbsize) {
661 _logdbsize(ctx);
662 }
663 _bmctx_dispose(ctx);
664 }
665 if ((*ptr == '\0') || !bmres) {
666 break;
667 }
668 } else if (!isspace(*ptr)) {
669 bname[c++] = *ptr;
670 }
671 ptr += 1;
672 }
673 return bmres;
674 }
675