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
323 #define FILLKEY() snprintf(key.data, sizeof(kbuf), bm.ksz == 16 ? "%016d" : bm.ksz == 192 ? "%0192d" : "%01024d", k)
324
325
_do_write(BMCTX * ctx,bool seq,bool sync,bool rvlen)326 static bool _do_write(BMCTX *ctx, bool seq, bool sync, bool rvlen) {
327 char kbuf[1025];
328 IWKV_val key, val;
329 key.data = kbuf;
330 int value_size = ctx->value_size;
331 for (int i = 0; i < ctx->num; ++i) {
332 if (rvlen) {
333 value_size = iwu_rand_range(ctx->value_size + 1);
334 if (value_size == 0) {
335 value_size = 1;
336 }
337 }
338 const int k = seq ? i : iwu_rand_range(bm.param_num);
339 FILLKEY();
340
341 key.size = strlen(key.data);
342 val.data = (void *) _bmctx_rndbuf_nextptr(ctx, value_size);
343 val.size = value_size;
344 if (!bm.db_put(ctx, &key, &val, sync)) {
345 return false;
346 }
347 }
348 return true;
349 }
350
_do_delete(BMCTX * ctx,bool sync,bool seq)351 static bool _do_delete(BMCTX *ctx, bool sync, bool seq) {
352 char kbuf[1025];
353 IWKV_val key;
354 key.data = kbuf;
355 for (int i = 0; i < ctx->num; ++i) {
356 const int k = seq ? i : iwu_rand_range(bm.param_num);
357 FILLKEY();
358 key.size = strlen(key.data);
359 if (!bm.db_del(ctx, &key, sync)) {
360 return false;
361 }
362 }
363 return true;
364 }
365
_do_read_seq(BMCTX * ctx)366 static bool _do_read_seq(BMCTX *ctx) {
367 return bm.db_read_seq(ctx, false);
368 }
369
_do_read_reverse(BMCTX * ctx)370 static bool _do_read_reverse(BMCTX *ctx) {
371 return bm.db_read_seq(ctx, true);
372 }
373
_do_read_random(BMCTX * ctx)374 static bool _do_read_random(BMCTX *ctx) {
375 char kbuf[1025];
376 IWKV_val key, val;
377 bool found;
378 key.data = kbuf;
379 for (int i = 0; i < bm.param_num_reads; ++i) {
380 const int k = iwu_rand_range(bm.param_num);
381 FILLKEY();
382 key.size = strlen(key.data);
383 val.data = 0;
384 val.size = 0;
385 if (!bm.db_get(ctx, &key, &val, &found)) {
386 _val_dispose(&val);
387 return false;
388 }
389 _val_dispose(&val);
390 }
391 return true;
392 }
393
_do_read_missing(BMCTX * ctx)394 static bool _do_read_missing(BMCTX *ctx) {
395 char kbuf[1025];
396 IWKV_val key, val;
397 bool found;
398 key.data = kbuf;
399 for (int i = 0; i < bm.param_num_reads; ++i) {
400 const int k = iwu_rand_range(bm.param_num);
401 snprintf(key.data, sizeof(kbuf),
402 bm.ksz == 16 ? "%016d." : bm.ksz == 192 ? "%0192d." : "%01024d.", k);
403 key.size = strlen(key.data);
404 val.data = 0;
405 val.size = 0;
406 if (!bm.db_get(ctx, &key, &val, &found)) {
407 _val_dispose(&val);
408 return false;
409 }
410 _val_dispose(&val);
411 if (found) {
412 return false;
413 }
414 }
415 return true;
416 }
417
_do_read_hot(BMCTX * ctx)418 static bool _do_read_hot(BMCTX *ctx) {
419 char kbuf[1025];
420 IWKV_val key, val;
421 bool found;
422 key.data = kbuf;
423 const int range = (bm.param_num + 99) / 100;
424 for (int i = 0; i < bm.param_num_reads; ++i) {
425 const int k = iwu_rand_range(range);
426 FILLKEY();
427 key.size = strlen(key.data);
428 val.data = 0;
429 val.size = 0;
430 if (!bm.db_get(ctx, &key, &val, &found)) {
431 _val_dispose(&val);
432 return false;
433 }
434 _val_dispose(&val);
435 }
436 return true;
437 }
438
_do_seek_random(BMCTX * ctx)439 static bool _do_seek_random(BMCTX *ctx) {
440 char kbuf[1025];
441 IWKV_val key, val;
442 bool found;
443 key.data = kbuf;
444 for (int i = 0; i < bm.param_num_reads; ++i) {
445 const int k = iwu_rand_range(bm.param_num);
446 FILLKEY();
447 key.size = strlen(key.data);
448 val.data = 0;
449 val.size = 0;
450 if (!bm.db_cursor_to_key(ctx, &key, &val, &found)) {
451 _val_dispose(&val);
452 return false;
453 }
454 _val_dispose(&val);
455 }
456 return true;
457 }
458
_bm_fillseq(BMCTX * ctx)459 static bool _bm_fillseq(BMCTX *ctx) {
460 if (!ctx->freshdb) return false;
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) return false;
466 return _do_write(ctx, true, false, true);
467 }
468
_bm_fillrandom(BMCTX * ctx)469 static bool _bm_fillrandom(BMCTX *ctx) {
470 if (!ctx->freshdb) return false;
471 return _do_write(ctx, false, false, false);
472 }
473
_bm_fillrandom2(BMCTX * ctx)474 static bool _bm_fillrandom2(BMCTX *ctx) {
475 if (!ctx->freshdb) return false;
476 return _do_write(ctx, false, false, true);
477 }
478
_bm_overwrite(BMCTX * ctx)479 static bool _bm_overwrite(BMCTX *ctx) {
480 if (ctx->freshdb) return false;
481 return _do_write(ctx, false, false, false);
482 }
483
_bm_fillsync(BMCTX * ctx)484 static bool _bm_fillsync(BMCTX *ctx) {
485 if (!ctx->freshdb) return false;
486 ctx->num /= 100;
487 if (ctx->num < 1) ctx->num = 1;
488 fprintf(stderr, "\tfillsync num records: %d\n", ctx->num);
489 return _do_write(ctx, false, true, false);
490 }
491
_bm_fill100K(BMCTX * ctx)492 static bool _bm_fill100K(BMCTX *ctx) {
493 if (!ctx->freshdb) return false;
494 ctx->num /= 100;
495 if (ctx->num < 1) ctx->num = 1;
496 fprintf(stderr, "\tfill100K num records: %d\n", ctx->num);
497 ctx->value_size = 100 * 1000;
498 return _do_write(ctx, false, false, false);
499 }
500
_bm_deleteseq(BMCTX * ctx)501 static bool _bm_deleteseq(BMCTX *ctx) {
502 if (ctx->freshdb) return false;
503 return _do_delete(ctx, false, true);
504 }
505
_bm_deleterandom(BMCTX * ctx)506 static bool _bm_deleterandom(BMCTX *ctx) {
507 if (ctx->freshdb) return false;
508 return _do_delete(ctx, false, false);
509 }
510
_bm_readseq(BMCTX * ctx)511 static bool _bm_readseq(BMCTX *ctx) {
512 if (ctx->freshdb) return false;
513 return _do_read_seq(ctx);
514 }
515
_bm_readreverse(BMCTX * ctx)516 static bool _bm_readreverse(BMCTX *ctx) {
517 if (ctx->freshdb) return false;
518 return _do_read_reverse(ctx);
519 }
520
_bm_readrandom(BMCTX * ctx)521 static bool _bm_readrandom(BMCTX *ctx) {
522 if (ctx->freshdb) return false;
523 return _do_read_random(ctx);
524 }
525
_bm_readmissing(BMCTX * ctx)526 static bool _bm_readmissing(BMCTX *ctx) {
527 if (ctx->freshdb) return false;
528 return _do_read_missing(ctx);
529 }
530
_bm_readhot(BMCTX * ctx)531 static bool _bm_readhot(BMCTX *ctx) {
532 if (ctx->freshdb) return false;
533 return _do_read_hot(ctx);
534 }
535
_bm_seekrandom(BMCTX * ctx)536 static bool _bm_seekrandom(BMCTX *ctx) {
537 if (ctx->freshdb) return false;
538 return _do_seek_random(ctx);
539 }
540
_bmctx_create(const char * name)541 static BMCTX *_bmctx_create(const char *name) {
542 bench_method *method = 0;
543 bool logdbsize = false;
544 bool freshdb = false;
545 if (!strcmp(name, "fillseq")) {
546 freshdb = true;
547 logdbsize = true;
548 method = _bm_fillseq;
549 } else if (!strcmp(name, "fillseq2")) {
550 freshdb = true;
551 logdbsize = true;
552 method = _bm_fillseq2;
553 } else if (!strcmp(name, "fillrandom")) {
554 freshdb = true;
555 logdbsize = true;
556 method = _bm_fillrandom;
557 } else if (!strcmp(name, "fillrandom2")) {
558 freshdb = true;
559 logdbsize = true;
560 method = _bm_fillrandom2;
561 } else if (!strcmp(name, "overwrite")) {
562 logdbsize = true;
563 method = _bm_overwrite;
564 } else if (!strcmp(name, "fillsync")) {
565 freshdb = true;
566 logdbsize = true;
567 method = _bm_fillsync;
568 } else if (!strcmp(name, "fill100K")) {
569 freshdb = true;
570 logdbsize = true;
571 method = _bm_fill100K;
572 } else if (!strcmp(name, "deleteseq")) {
573 logdbsize = true;
574 method = _bm_deleteseq;
575 } else if (!strcmp(name, "deleterandom")) {
576 logdbsize = true;
577 method = _bm_deleterandom;
578 } else if (!strcmp(name, "readseq")) {
579 method = _bm_readseq;
580 } else if (!strcmp(name, "readreverse")) {
581 method = _bm_readreverse;
582 } else if (!strcmp(name, "readrandom")) {
583 method = _bm_readrandom;
584 } else if (!strcmp(name, "readmissing")) {
585 method = _bm_readmissing;
586 } else if (!strcmp(name, "readhot")) {
587 method = _bm_readhot;
588 } else if (!strcmp(name, "seekrandom")) {
589 method = _bm_seekrandom;
590 } else {
591 fprintf(stderr, "Unknown benchmark: '%s'\n", name);
592 return 0;
593 }
594 BMCTX *bmctx = calloc(1, sizeof(*bmctx));
595 bmctx->name = strdup(name);
596 bmctx->method = method;
597 bmctx->num = bm.param_num;
598 bmctx->value_size = bm.param_value_size;
599 bmctx->freshdb = freshdb;
600 bmctx->logdbsize = logdbsize;
601 return bmctx;
602 }
603
bm_bench_run(int argc,char * argv[])604 static bool bm_bench_run(int argc, char *argv[]) {
605 if (!_bm_init(argc, argv)) {
606 fprintf(stderr, "Benchmark cannot be initialized\n");
607 exit(1);
608 }
609 bm.env_setup();
610 int c = 0;
611 const char *ptr = bm.param_benchmarks;
612 char bname[100];
613 bool bmres = true;
614 while (bmres) {
615 if (*ptr == ',' || *ptr == '\0' || c >= 99) {
616 bname[c] = '\0';
617 BMCTX *ctx = _bmctx_create(bname);
618 c = 0;
619 if (ctx) {
620 fprintf(stderr, " benchmark: %s\n", bname);
621 _bm_run(ctx);
622 bmres = ctx->success;
623 if (!bmres) {
624 fprintf(stderr, "Failed to run benchmark: %s\n", bname);
625 } else {
626 fprintf(stderr, " done: %s in %lu\n", bname, (ctx->end_ms - ctx->start_ms));
627 }
628 if (ctx->logdbsize) {
629 _logdbsize(ctx);
630 }
631 _bmctx_dispose(ctx);
632 }
633 if (*ptr == '\0' || !bmres) {
634 break;
635 }
636 } else if (!isspace(*ptr)) {
637 bname[c++] = *ptr;
638 }
639 ptr += 1;
640 }
641 return bmres;
642 }
643