1 /*
2 * Runner for littlefs benchmarks
3 *
4 * Copyright (c) 2022, The littlefs authors.
5 * SPDX-License-Identifier: BSD-3-Clause
6 */
7 #ifndef _POSIX_C_SOURCE
8 #define _POSIX_C_SOURCE 199309L
9 #endif
10
11 #include "runners/bench_runner.h"
12 #include "bd/lfs_emubd.h"
13
14 #include <getopt.h>
15 #include <sys/types.h>
16 #include <errno.h>
17 #include <setjmp.h>
18 #include <fcntl.h>
19 #include <stdarg.h>
20 #include <stdio.h>
21 #include <unistd.h>
22 #include <execinfo.h>
23 #include <time.h>
24
25
26 // some helpers
27
28 // append to an array with amortized doubling
mappend(void ** p,size_t size,size_t * count,size_t * capacity)29 void *mappend(void **p,
30 size_t size,
31 size_t *count,
32 size_t *capacity) {
33 uint8_t *p_ = *p;
34 size_t count_ = *count;
35 size_t capacity_ = *capacity;
36
37 count_ += 1;
38 if (count_ > capacity_) {
39 capacity_ = (2*capacity_ < 4) ? 4 : 2*capacity_;
40
41 p_ = realloc(p_, capacity_*size);
42 if (!p_) {
43 return NULL;
44 }
45 }
46
47 *p = p_;
48 *count = count_;
49 *capacity = capacity_;
50 return &p_[(count_-1)*size];
51 }
52
53 // a quick self-terminating text-safe varint scheme
leb16_print(uintmax_t x)54 static void leb16_print(uintmax_t x) {
55 // allow 'w' to indicate negative numbers
56 if ((intmax_t)x < 0) {
57 printf("w");
58 x = -x;
59 }
60
61 while (true) {
62 char nibble = (x & 0xf) | (x > 0xf ? 0x10 : 0);
63 printf("%c", (nibble < 10) ? '0'+nibble : 'a'+nibble-10);
64 if (x <= 0xf) {
65 break;
66 }
67 x >>= 4;
68 }
69 }
70
leb16_parse(const char * s,char ** tail)71 static uintmax_t leb16_parse(const char *s, char **tail) {
72 bool neg = false;
73 uintmax_t x = 0;
74 if (tail) {
75 *tail = (char*)s;
76 }
77
78 if (s[0] == 'w') {
79 neg = true;
80 s = s+1;
81 }
82
83 size_t i = 0;
84 while (true) {
85 uintmax_t nibble = s[i];
86 if (nibble >= '0' && nibble <= '9') {
87 nibble = nibble - '0';
88 } else if (nibble >= 'a' && nibble <= 'v') {
89 nibble = nibble - 'a' + 10;
90 } else {
91 // invalid?
92 return 0;
93 }
94
95 x |= (nibble & 0xf) << (4*i);
96 i += 1;
97 if (!(nibble & 0x10)) {
98 s = s + i;
99 break;
100 }
101 }
102
103 if (tail) {
104 *tail = (char*)s;
105 }
106 return neg ? -x : x;
107 }
108
109
110
111 // bench_runner types
112
113 typedef struct bench_geometry {
114 const char *name;
115 bench_define_t defines[BENCH_GEOMETRY_DEFINE_COUNT];
116 } bench_geometry_t;
117
118 typedef struct bench_id {
119 const char *name;
120 const bench_define_t *defines;
121 size_t define_count;
122 } bench_id_t;
123
124
125 // bench suites are linked into a custom ld section
126 extern struct bench_suite __start__bench_suites;
127 extern struct bench_suite __stop__bench_suites;
128
129 const struct bench_suite *bench_suites = &__start__bench_suites;
130 #define BENCH_SUITE_COUNT \
131 ((size_t)(&__stop__bench_suites - &__start__bench_suites))
132
133
134 // bench define management
135 typedef struct bench_define_map {
136 const bench_define_t *defines;
137 size_t count;
138 } bench_define_map_t;
139
140 typedef struct bench_define_names {
141 const char *const *names;
142 size_t count;
143 } bench_define_names_t;
144
bench_define_lit(void * data)145 intmax_t bench_define_lit(void *data) {
146 return (intptr_t)data;
147 }
148
149 #define BENCH_CONST(x) {bench_define_lit, (void*)(uintptr_t)(x)}
150 #define BENCH_LIT(x) ((bench_define_t)BENCH_CONST(x))
151
152
153 #define BENCH_DEF(k, v) \
154 intmax_t bench_define_##k(void *data) { \
155 (void)data; \
156 return v; \
157 }
158
159 BENCH_IMPLICIT_DEFINES
160 #undef BENCH_DEF
161
162 #define BENCH_DEFINE_MAP_OVERRIDE 0
163 #define BENCH_DEFINE_MAP_EXPLICIT 1
164 #define BENCH_DEFINE_MAP_PERMUTATION 2
165 #define BENCH_DEFINE_MAP_GEOMETRY 3
166 #define BENCH_DEFINE_MAP_IMPLICIT 4
167 #define BENCH_DEFINE_MAP_COUNT 5
168
169 bench_define_map_t bench_define_maps[BENCH_DEFINE_MAP_COUNT] = {
170 [BENCH_DEFINE_MAP_IMPLICIT] = {
171 (const bench_define_t[BENCH_IMPLICIT_DEFINE_COUNT]) {
172 #define BENCH_DEF(k, v) \
173 [k##_i] = {bench_define_##k, NULL},
174
175 BENCH_IMPLICIT_DEFINES
176 #undef BENCH_DEF
177 },
178 BENCH_IMPLICIT_DEFINE_COUNT,
179 },
180 };
181
182 #define BENCH_DEFINE_NAMES_SUITE 0
183 #define BENCH_DEFINE_NAMES_IMPLICIT 1
184 #define BENCH_DEFINE_NAMES_COUNT 2
185
186 bench_define_names_t bench_define_names[BENCH_DEFINE_NAMES_COUNT] = {
187 [BENCH_DEFINE_NAMES_IMPLICIT] = {
188 (const char *const[BENCH_IMPLICIT_DEFINE_COUNT]){
189 #define BENCH_DEF(k, v) \
190 [k##_i] = #k,
191
192 BENCH_IMPLICIT_DEFINES
193 #undef BENCH_DEF
194 },
195 BENCH_IMPLICIT_DEFINE_COUNT,
196 },
197 };
198
199 intmax_t *bench_define_cache;
200 size_t bench_define_cache_count;
201 unsigned *bench_define_cache_mask;
202
bench_define_name(size_t define)203 const char *bench_define_name(size_t define) {
204 // lookup in our bench names
205 for (size_t i = 0; i < BENCH_DEFINE_NAMES_COUNT; i++) {
206 if (define < bench_define_names[i].count
207 && bench_define_names[i].names
208 && bench_define_names[i].names[define]) {
209 return bench_define_names[i].names[define];
210 }
211 }
212
213 return NULL;
214 }
215
bench_define_ispermutation(size_t define)216 bool bench_define_ispermutation(size_t define) {
217 // is this define specific to the permutation?
218 for (size_t i = 0; i < BENCH_DEFINE_MAP_IMPLICIT; i++) {
219 if (define < bench_define_maps[i].count
220 && bench_define_maps[i].defines[define].cb) {
221 return true;
222 }
223 }
224
225 return false;
226 }
227
bench_define(size_t define)228 intmax_t bench_define(size_t define) {
229 // is the define in our cache?
230 if (define < bench_define_cache_count
231 && (bench_define_cache_mask[define/(8*sizeof(unsigned))]
232 & (1 << (define%(8*sizeof(unsigned)))))) {
233 return bench_define_cache[define];
234 }
235
236 // lookup in our bench defines
237 for (size_t i = 0; i < BENCH_DEFINE_MAP_COUNT; i++) {
238 if (define < bench_define_maps[i].count
239 && bench_define_maps[i].defines[define].cb) {
240 intmax_t v = bench_define_maps[i].defines[define].cb(
241 bench_define_maps[i].defines[define].data);
242
243 // insert into cache!
244 bench_define_cache[define] = v;
245 bench_define_cache_mask[define / (8*sizeof(unsigned))]
246 |= 1 << (define%(8*sizeof(unsigned)));
247
248 return v;
249 }
250 }
251
252 return 0;
253
254 // not found?
255 const char *name = bench_define_name(define);
256 fprintf(stderr, "error: undefined define %s (%zd)\n",
257 name ? name : "(unknown)",
258 define);
259 assert(false);
260 exit(-1);
261 }
262
bench_define_flush(void)263 void bench_define_flush(void) {
264 // clear cache between permutations
265 memset(bench_define_cache_mask, 0,
266 sizeof(unsigned)*(
267 (bench_define_cache_count+(8*sizeof(unsigned))-1)
268 / (8*sizeof(unsigned))));
269 }
270
271 // geometry updates
272 const bench_geometry_t *bench_geometry = NULL;
273
bench_define_geometry(const bench_geometry_t * geometry)274 void bench_define_geometry(const bench_geometry_t *geometry) {
275 bench_define_maps[BENCH_DEFINE_MAP_GEOMETRY] = (bench_define_map_t){
276 geometry->defines, BENCH_GEOMETRY_DEFINE_COUNT};
277 }
278
279 // override updates
280 typedef struct bench_override {
281 const char *name;
282 const intmax_t *defines;
283 size_t permutations;
284 } bench_override_t;
285
286 const bench_override_t *bench_overrides = NULL;
287 size_t bench_override_count = 0;
288
289 bench_define_t *bench_override_defines = NULL;
290 size_t bench_override_define_count = 0;
291 size_t bench_override_define_permutations = 1;
292 size_t bench_override_define_capacity = 0;
293
294 // suite/perm updates
bench_define_suite(const struct bench_suite * suite)295 void bench_define_suite(const struct bench_suite *suite) {
296 bench_define_names[BENCH_DEFINE_NAMES_SUITE] = (bench_define_names_t){
297 suite->define_names, suite->define_count};
298
299 // make sure our cache is large enough
300 if (lfs_max(suite->define_count, BENCH_IMPLICIT_DEFINE_COUNT)
301 > bench_define_cache_count) {
302 // align to power of two to avoid any superlinear growth
303 size_t ncount = 1 << lfs_npw2(
304 lfs_max(suite->define_count, BENCH_IMPLICIT_DEFINE_COUNT));
305 bench_define_cache = realloc(bench_define_cache, ncount*sizeof(intmax_t));
306 bench_define_cache_mask = realloc(bench_define_cache_mask,
307 sizeof(unsigned)*(
308 (ncount+(8*sizeof(unsigned))-1)
309 / (8*sizeof(unsigned))));
310 bench_define_cache_count = ncount;
311 }
312
313 // map any overrides
314 if (bench_override_count > 0) {
315 // first figure out the total size of override permutations
316 size_t count = 0;
317 size_t permutations = 1;
318 for (size_t i = 0; i < bench_override_count; i++) {
319 for (size_t d = 0;
320 d < lfs_max(
321 suite->define_count,
322 BENCH_IMPLICIT_DEFINE_COUNT);
323 d++) {
324 // define name match?
325 const char *name = bench_define_name(d);
326 if (name && strcmp(name, bench_overrides[i].name) == 0) {
327 count = lfs_max(count, d+1);
328 permutations *= bench_overrides[i].permutations;
329 break;
330 }
331 }
332 }
333 bench_override_define_count = count;
334 bench_override_define_permutations = permutations;
335
336 // make sure our override arrays are big enough
337 if (count * permutations > bench_override_define_capacity) {
338 // align to power of two to avoid any superlinear growth
339 size_t ncapacity = 1 << lfs_npw2(count * permutations);
340 bench_override_defines = realloc(
341 bench_override_defines,
342 sizeof(bench_define_t)*ncapacity);
343 bench_override_define_capacity = ncapacity;
344 }
345
346 // zero unoverridden defines
347 memset(bench_override_defines, 0,
348 sizeof(bench_define_t) * count * permutations);
349
350 // compute permutations
351 size_t p = 1;
352 for (size_t i = 0; i < bench_override_count; i++) {
353 for (size_t d = 0;
354 d < lfs_max(
355 suite->define_count,
356 BENCH_IMPLICIT_DEFINE_COUNT);
357 d++) {
358 // define name match?
359 const char *name = bench_define_name(d);
360 if (name && strcmp(name, bench_overrides[i].name) == 0) {
361 // scatter the define permutations based on already
362 // seen permutations
363 for (size_t j = 0; j < permutations; j++) {
364 bench_override_defines[j*count + d] = BENCH_LIT(
365 bench_overrides[i].defines[(j/p)
366 % bench_overrides[i].permutations]);
367 }
368
369 // keep track of how many permutations we've seen so far
370 p *= bench_overrides[i].permutations;
371 break;
372 }
373 }
374 }
375 }
376 }
377
bench_define_perm(const struct bench_suite * suite,const struct bench_case * case_,size_t perm)378 void bench_define_perm(
379 const struct bench_suite *suite,
380 const struct bench_case *case_,
381 size_t perm) {
382 if (case_->defines) {
383 bench_define_maps[BENCH_DEFINE_MAP_PERMUTATION] = (bench_define_map_t){
384 case_->defines + perm*suite->define_count,
385 suite->define_count};
386 } else {
387 bench_define_maps[BENCH_DEFINE_MAP_PERMUTATION] = (bench_define_map_t){
388 NULL, 0};
389 }
390 }
391
bench_define_override(size_t perm)392 void bench_define_override(size_t perm) {
393 bench_define_maps[BENCH_DEFINE_MAP_OVERRIDE] = (bench_define_map_t){
394 bench_override_defines + perm*bench_override_define_count,
395 bench_override_define_count};
396 }
397
bench_define_explicit(const bench_define_t * defines,size_t define_count)398 void bench_define_explicit(
399 const bench_define_t *defines,
400 size_t define_count) {
401 bench_define_maps[BENCH_DEFINE_MAP_EXPLICIT] = (bench_define_map_t){
402 defines, define_count};
403 }
404
bench_define_cleanup(void)405 void bench_define_cleanup(void) {
406 // bench define management can allocate a few things
407 free(bench_define_cache);
408 free(bench_define_cache_mask);
409 free(bench_override_defines);
410 }
411
412
413
414 // bench state
415 extern const bench_geometry_t *bench_geometries;
416 extern size_t bench_geometry_count;
417
418 const bench_id_t *bench_ids = (const bench_id_t[]) {
419 {NULL, NULL, 0},
420 };
421 size_t bench_id_count = 1;
422
423 size_t bench_step_start = 0;
424 size_t bench_step_stop = -1;
425 size_t bench_step_step = 1;
426
427 const char *bench_disk_path = NULL;
428 const char *bench_trace_path = NULL;
429 bool bench_trace_backtrace = false;
430 uint32_t bench_trace_period = 0;
431 uint32_t bench_trace_freq = 0;
432 FILE *bench_trace_file = NULL;
433 uint32_t bench_trace_cycles = 0;
434 uint64_t bench_trace_time = 0;
435 uint64_t bench_trace_open_time = 0;
436 lfs_emubd_sleep_t bench_read_sleep = 0.0;
437 lfs_emubd_sleep_t bench_prog_sleep = 0.0;
438 lfs_emubd_sleep_t bench_erase_sleep = 0.0;
439
440 // this determines both the backtrace buffer and the trace printf buffer, if
441 // trace ends up interleaved or truncated this may need to be increased
442 #ifndef BENCH_TRACE_BACKTRACE_BUFFER_SIZE
443 #define BENCH_TRACE_BACKTRACE_BUFFER_SIZE 8192
444 #endif
445 void *bench_trace_backtrace_buffer[
446 BENCH_TRACE_BACKTRACE_BUFFER_SIZE / sizeof(void*)];
447
448 // trace printing
bench_trace(const char * fmt,...)449 void bench_trace(const char *fmt, ...) {
450 if (bench_trace_path) {
451 // sample at a specific period?
452 if (bench_trace_period) {
453 if (bench_trace_cycles % bench_trace_period != 0) {
454 bench_trace_cycles += 1;
455 return;
456 }
457 bench_trace_cycles += 1;
458 }
459
460 // sample at a specific frequency?
461 if (bench_trace_freq) {
462 struct timespec t;
463 clock_gettime(CLOCK_MONOTONIC, &t);
464 uint64_t now = (uint64_t)t.tv_sec*1000*1000*1000
465 + (uint64_t)t.tv_nsec;
466 if (now - bench_trace_time < (1000*1000*1000) / bench_trace_freq) {
467 return;
468 }
469 bench_trace_time = now;
470 }
471
472 if (!bench_trace_file) {
473 // Tracing output is heavy and trying to open every trace
474 // call is slow, so we only try to open the trace file every
475 // so often. Note this doesn't affect successfully opened files
476 struct timespec t;
477 clock_gettime(CLOCK_MONOTONIC, &t);
478 uint64_t now = (uint64_t)t.tv_sec*1000*1000*1000
479 + (uint64_t)t.tv_nsec;
480 if (now - bench_trace_open_time < 100*1000*1000) {
481 return;
482 }
483 bench_trace_open_time = now;
484
485 // try to open the trace file
486 int fd;
487 if (strcmp(bench_trace_path, "-") == 0) {
488 fd = dup(1);
489 if (fd < 0) {
490 return;
491 }
492 } else {
493 fd = open(
494 bench_trace_path,
495 O_WRONLY | O_CREAT | O_APPEND | O_NONBLOCK,
496 0666);
497 if (fd < 0) {
498 return;
499 }
500 int err = fcntl(fd, F_SETFL, O_WRONLY | O_CREAT | O_APPEND);
501 assert(!err);
502 }
503
504 FILE *f = fdopen(fd, "a");
505 assert(f);
506 int err = setvbuf(f, NULL, _IOFBF,
507 BENCH_TRACE_BACKTRACE_BUFFER_SIZE);
508 assert(!err);
509 bench_trace_file = f;
510 }
511
512 // print trace
513 va_list va;
514 va_start(va, fmt);
515 int res = vfprintf(bench_trace_file, fmt, va);
516 va_end(va);
517 if (res < 0) {
518 fclose(bench_trace_file);
519 bench_trace_file = NULL;
520 return;
521 }
522
523 if (bench_trace_backtrace) {
524 // print backtrace
525 size_t count = backtrace(
526 bench_trace_backtrace_buffer,
527 BENCH_TRACE_BACKTRACE_BUFFER_SIZE);
528 // note we skip our own stack frame
529 for (size_t i = 1; i < count; i++) {
530 res = fprintf(bench_trace_file, "\tat %p\n",
531 bench_trace_backtrace_buffer[i]);
532 if (res < 0) {
533 fclose(bench_trace_file);
534 bench_trace_file = NULL;
535 return;
536 }
537 }
538 }
539
540 // flush immediately
541 fflush(bench_trace_file);
542 }
543 }
544
545
546 // bench prng
bench_prng(uint32_t * state)547 uint32_t bench_prng(uint32_t *state) {
548 // A simple xorshift32 generator, easily reproducible. Keep in mind
549 // determinism is much more important than actual randomness here.
550 uint32_t x = *state;
551 x ^= x << 13;
552 x ^= x >> 17;
553 x ^= x << 5;
554 *state = x;
555 return x;
556 }
557
558
559 // bench recording state
560 static struct lfs_config *bench_cfg = NULL;
561 static lfs_emubd_io_t bench_last_readed = 0;
562 static lfs_emubd_io_t bench_last_proged = 0;
563 static lfs_emubd_io_t bench_last_erased = 0;
564 lfs_emubd_io_t bench_readed = 0;
565 lfs_emubd_io_t bench_proged = 0;
566 lfs_emubd_io_t bench_erased = 0;
567
bench_reset(void)568 void bench_reset(void) {
569 bench_readed = 0;
570 bench_proged = 0;
571 bench_erased = 0;
572 bench_last_readed = 0;
573 bench_last_proged = 0;
574 bench_last_erased = 0;
575 }
576
bench_start(void)577 void bench_start(void) {
578 assert(bench_cfg);
579 lfs_emubd_sio_t readed = lfs_emubd_readed(bench_cfg);
580 assert(readed >= 0);
581 lfs_emubd_sio_t proged = lfs_emubd_proged(bench_cfg);
582 assert(proged >= 0);
583 lfs_emubd_sio_t erased = lfs_emubd_erased(bench_cfg);
584 assert(erased >= 0);
585
586 bench_last_readed = readed;
587 bench_last_proged = proged;
588 bench_last_erased = erased;
589 }
590
bench_stop(void)591 void bench_stop(void) {
592 assert(bench_cfg);
593 lfs_emubd_sio_t readed = lfs_emubd_readed(bench_cfg);
594 assert(readed >= 0);
595 lfs_emubd_sio_t proged = lfs_emubd_proged(bench_cfg);
596 assert(proged >= 0);
597 lfs_emubd_sio_t erased = lfs_emubd_erased(bench_cfg);
598 assert(erased >= 0);
599
600 bench_readed += readed - bench_last_readed;
601 bench_proged += proged - bench_last_proged;
602 bench_erased += erased - bench_last_erased;
603 }
604
605
606 // encode our permutation into a reusable id
perm_printid(const struct bench_suite * suite,const struct bench_case * case_)607 static void perm_printid(
608 const struct bench_suite *suite,
609 const struct bench_case *case_) {
610 (void)suite;
611 // case[:permutation]
612 printf("%s:", case_->name);
613 for (size_t d = 0;
614 d < lfs_max(
615 suite->define_count,
616 BENCH_IMPLICIT_DEFINE_COUNT);
617 d++) {
618 if (bench_define_ispermutation(d)) {
619 leb16_print(d);
620 leb16_print(BENCH_DEFINE(d));
621 }
622 }
623 }
624
625 // a quick trie for keeping track of permutations we've seen
626 typedef struct bench_seen {
627 struct bench_seen_branch *branches;
628 size_t branch_count;
629 size_t branch_capacity;
630 } bench_seen_t;
631
632 struct bench_seen_branch {
633 intmax_t define;
634 struct bench_seen branch;
635 };
636
bench_seen_insert(bench_seen_t * seen,const struct bench_suite * suite,const struct bench_case * case_)637 bool bench_seen_insert(
638 bench_seen_t *seen,
639 const struct bench_suite *suite,
640 const struct bench_case *case_) {
641 (void)case_;
642 bool was_seen = true;
643
644 // use the currently set defines
645 for (size_t d = 0;
646 d < lfs_max(
647 suite->define_count,
648 BENCH_IMPLICIT_DEFINE_COUNT);
649 d++) {
650 // treat unpermuted defines the same as 0
651 intmax_t define = bench_define_ispermutation(d) ? BENCH_DEFINE(d) : 0;
652
653 // already seen?
654 struct bench_seen_branch *branch = NULL;
655 for (size_t i = 0; i < seen->branch_count; i++) {
656 if (seen->branches[i].define == define) {
657 branch = &seen->branches[i];
658 break;
659 }
660 }
661
662 // need to create a new node
663 if (!branch) {
664 was_seen = false;
665 branch = mappend(
666 (void**)&seen->branches,
667 sizeof(struct bench_seen_branch),
668 &seen->branch_count,
669 &seen->branch_capacity);
670 branch->define = define;
671 branch->branch = (bench_seen_t){NULL, 0, 0};
672 }
673
674 seen = &branch->branch;
675 }
676
677 return was_seen;
678 }
679
bench_seen_cleanup(bench_seen_t * seen)680 void bench_seen_cleanup(bench_seen_t *seen) {
681 for (size_t i = 0; i < seen->branch_count; i++) {
682 bench_seen_cleanup(&seen->branches[i].branch);
683 }
684 free(seen->branches);
685 }
686
687 // iterate through permutations in a bench case
case_forperm(const struct bench_suite * suite,const struct bench_case * case_,const bench_define_t * defines,size_t define_count,void (* cb)(void * data,const struct bench_suite * suite,const struct bench_case * case_),void * data)688 static void case_forperm(
689 const struct bench_suite *suite,
690 const struct bench_case *case_,
691 const bench_define_t *defines,
692 size_t define_count,
693 void (*cb)(
694 void *data,
695 const struct bench_suite *suite,
696 const struct bench_case *case_),
697 void *data) {
698 // explicit permutation?
699 if (defines) {
700 bench_define_explicit(defines, define_count);
701
702 for (size_t v = 0; v < bench_override_define_permutations; v++) {
703 // define override permutation
704 bench_define_override(v);
705 bench_define_flush();
706
707 cb(data, suite, case_);
708 }
709
710 return;
711 }
712
713 bench_seen_t seen = {NULL, 0, 0};
714
715 for (size_t k = 0; k < case_->permutations; k++) {
716 // define permutation
717 bench_define_perm(suite, case_, k);
718
719 for (size_t v = 0; v < bench_override_define_permutations; v++) {
720 // define override permutation
721 bench_define_override(v);
722
723 for (size_t g = 0; g < bench_geometry_count; g++) {
724 // define geometry
725 bench_define_geometry(&bench_geometries[g]);
726 bench_define_flush();
727
728 // have we seen this permutation before?
729 bool was_seen = bench_seen_insert(&seen, suite, case_);
730 if (!(k == 0 && v == 0 && g == 0) && was_seen) {
731 continue;
732 }
733
734 cb(data, suite, case_);
735 }
736 }
737 }
738
739 bench_seen_cleanup(&seen);
740 }
741
742
743 // how many permutations are there actually in a bench case
744 struct perm_count_state {
745 size_t total;
746 size_t filtered;
747 };
748
perm_count(void * data,const struct bench_suite * suite,const struct bench_case * case_)749 void perm_count(
750 void *data,
751 const struct bench_suite *suite,
752 const struct bench_case *case_) {
753 struct perm_count_state *state = data;
754 (void)suite;
755 (void)case_;
756
757 state->total += 1;
758
759 if (case_->filter && !case_->filter()) {
760 return;
761 }
762
763 state->filtered += 1;
764 }
765
766
767 // operations we can do
summary(void)768 static void summary(void) {
769 printf("%-23s %7s %7s %7s %11s\n",
770 "", "flags", "suites", "cases", "perms");
771 size_t suites = 0;
772 size_t cases = 0;
773 bench_flags_t flags = 0;
774 struct perm_count_state perms = {0, 0};
775
776 for (size_t t = 0; t < bench_id_count; t++) {
777 for (size_t i = 0; i < BENCH_SUITE_COUNT; i++) {
778 bench_define_suite(&bench_suites[i]);
779
780 for (size_t j = 0; j < bench_suites[i].case_count; j++) {
781 // does neither suite nor case name match?
782 if (bench_ids[t].name && !(
783 strcmp(bench_ids[t].name,
784 bench_suites[i].name) == 0
785 || strcmp(bench_ids[t].name,
786 bench_suites[i].cases[j].name) == 0)) {
787 continue;
788 }
789
790 cases += 1;
791 case_forperm(
792 &bench_suites[i],
793 &bench_suites[i].cases[j],
794 bench_ids[t].defines,
795 bench_ids[t].define_count,
796 perm_count,
797 &perms);
798 }
799
800 suites += 1;
801 flags |= bench_suites[i].flags;
802 }
803 }
804
805 char perm_buf[64];
806 sprintf(perm_buf, "%zu/%zu", perms.filtered, perms.total);
807 char flag_buf[64];
808 sprintf(flag_buf, "%s%s",
809 (flags & BENCH_REENTRANT) ? "r" : "",
810 (!flags) ? "-" : "");
811 printf("%-23s %7s %7zu %7zu %11s\n",
812 "TOTAL",
813 flag_buf,
814 suites,
815 cases,
816 perm_buf);
817 }
818
list_suites(void)819 static void list_suites(void) {
820 // at least size so that names fit
821 unsigned name_width = 23;
822 for (size_t i = 0; i < BENCH_SUITE_COUNT; i++) {
823 size_t len = strlen(bench_suites[i].name);
824 if (len > name_width) {
825 name_width = len;
826 }
827 }
828 name_width = 4*((name_width+1+4-1)/4)-1;
829
830 printf("%-*s %7s %7s %11s\n",
831 name_width, "suite", "flags", "cases", "perms");
832 for (size_t t = 0; t < bench_id_count; t++) {
833 for (size_t i = 0; i < BENCH_SUITE_COUNT; i++) {
834 bench_define_suite(&bench_suites[i]);
835
836 size_t cases = 0;
837 struct perm_count_state perms = {0, 0};
838
839 for (size_t j = 0; j < bench_suites[i].case_count; j++) {
840 // does neither suite nor case name match?
841 if (bench_ids[t].name && !(
842 strcmp(bench_ids[t].name,
843 bench_suites[i].name) == 0
844 || strcmp(bench_ids[t].name,
845 bench_suites[i].cases[j].name) == 0)) {
846 continue;
847 }
848
849 cases += 1;
850 case_forperm(
851 &bench_suites[i],
852 &bench_suites[i].cases[j],
853 bench_ids[t].defines,
854 bench_ids[t].define_count,
855 perm_count,
856 &perms);
857 }
858
859 // no benches found?
860 if (!cases) {
861 continue;
862 }
863
864 char perm_buf[64];
865 sprintf(perm_buf, "%zu/%zu", perms.filtered, perms.total);
866 char flag_buf[64];
867 sprintf(flag_buf, "%s%s",
868 (bench_suites[i].flags & BENCH_REENTRANT) ? "r" : "",
869 (!bench_suites[i].flags) ? "-" : "");
870 printf("%-*s %7s %7zu %11s\n",
871 name_width,
872 bench_suites[i].name,
873 flag_buf,
874 cases,
875 perm_buf);
876 }
877 }
878 }
879
list_cases(void)880 static void list_cases(void) {
881 // at least size so that names fit
882 unsigned name_width = 23;
883 for (size_t i = 0; i < BENCH_SUITE_COUNT; i++) {
884 for (size_t j = 0; j < bench_suites[i].case_count; j++) {
885 size_t len = strlen(bench_suites[i].cases[j].name);
886 if (len > name_width) {
887 name_width = len;
888 }
889 }
890 }
891 name_width = 4*((name_width+1+4-1)/4)-1;
892
893 printf("%-*s %7s %11s\n", name_width, "case", "flags", "perms");
894 for (size_t t = 0; t < bench_id_count; t++) {
895 for (size_t i = 0; i < BENCH_SUITE_COUNT; i++) {
896 bench_define_suite(&bench_suites[i]);
897
898 for (size_t j = 0; j < bench_suites[i].case_count; j++) {
899 // does neither suite nor case name match?
900 if (bench_ids[t].name && !(
901 strcmp(bench_ids[t].name,
902 bench_suites[i].name) == 0
903 || strcmp(bench_ids[t].name,
904 bench_suites[i].cases[j].name) == 0)) {
905 continue;
906 }
907
908 struct perm_count_state perms = {0, 0};
909 case_forperm(
910 &bench_suites[i],
911 &bench_suites[i].cases[j],
912 bench_ids[t].defines,
913 bench_ids[t].define_count,
914 perm_count,
915 &perms);
916
917 char perm_buf[64];
918 sprintf(perm_buf, "%zu/%zu", perms.filtered, perms.total);
919 char flag_buf[64];
920 sprintf(flag_buf, "%s%s",
921 (bench_suites[i].cases[j].flags & BENCH_REENTRANT)
922 ? "r" : "",
923 (!bench_suites[i].cases[j].flags)
924 ? "-" : "");
925 printf("%-*s %7s %11s\n",
926 name_width,
927 bench_suites[i].cases[j].name,
928 flag_buf,
929 perm_buf);
930 }
931 }
932 }
933 }
934
list_suite_paths(void)935 static void list_suite_paths(void) {
936 // at least size so that names fit
937 unsigned name_width = 23;
938 for (size_t i = 0; i < BENCH_SUITE_COUNT; i++) {
939 size_t len = strlen(bench_suites[i].name);
940 if (len > name_width) {
941 name_width = len;
942 }
943 }
944 name_width = 4*((name_width+1+4-1)/4)-1;
945
946 printf("%-*s %s\n", name_width, "suite", "path");
947 for (size_t t = 0; t < bench_id_count; t++) {
948 for (size_t i = 0; i < BENCH_SUITE_COUNT; i++) {
949 size_t cases = 0;
950
951 for (size_t j = 0; j < bench_suites[i].case_count; j++) {
952 // does neither suite nor case name match?
953 if (bench_ids[t].name && !(
954 strcmp(bench_ids[t].name,
955 bench_suites[i].name) == 0
956 || strcmp(bench_ids[t].name,
957 bench_suites[i].cases[j].name) == 0)) {
958 continue;
959
960 cases += 1;
961 }
962 }
963
964 // no benches found?
965 if (!cases) {
966 continue;
967 }
968
969 printf("%-*s %s\n",
970 name_width,
971 bench_suites[i].name,
972 bench_suites[i].path);
973 }
974 }
975 }
976
list_case_paths(void)977 static void list_case_paths(void) {
978 // at least size so that names fit
979 unsigned name_width = 23;
980 for (size_t i = 0; i < BENCH_SUITE_COUNT; i++) {
981 for (size_t j = 0; j < bench_suites[i].case_count; j++) {
982 size_t len = strlen(bench_suites[i].cases[j].name);
983 if (len > name_width) {
984 name_width = len;
985 }
986 }
987 }
988 name_width = 4*((name_width+1+4-1)/4)-1;
989
990 printf("%-*s %s\n", name_width, "case", "path");
991 for (size_t t = 0; t < bench_id_count; t++) {
992 for (size_t i = 0; i < BENCH_SUITE_COUNT; i++) {
993 for (size_t j = 0; j < bench_suites[i].case_count; j++) {
994 // does neither suite nor case name match?
995 if (bench_ids[t].name && !(
996 strcmp(bench_ids[t].name,
997 bench_suites[i].name) == 0
998 || strcmp(bench_ids[t].name,
999 bench_suites[i].cases[j].name) == 0)) {
1000 continue;
1001 }
1002
1003 printf("%-*s %s\n",
1004 name_width,
1005 bench_suites[i].cases[j].name,
1006 bench_suites[i].cases[j].path);
1007 }
1008 }
1009 }
1010 }
1011
1012 struct list_defines_define {
1013 const char *name;
1014 intmax_t *values;
1015 size_t value_count;
1016 size_t value_capacity;
1017 };
1018
1019 struct list_defines_defines {
1020 struct list_defines_define *defines;
1021 size_t define_count;
1022 size_t define_capacity;
1023 };
1024
list_defines_add(struct list_defines_defines * defines,size_t d)1025 static void list_defines_add(
1026 struct list_defines_defines *defines,
1027 size_t d) {
1028 const char *name = bench_define_name(d);
1029 intmax_t value = BENCH_DEFINE(d);
1030
1031 // define already in defines?
1032 for (size_t i = 0; i < defines->define_count; i++) {
1033 if (strcmp(defines->defines[i].name, name) == 0) {
1034 // value already in values?
1035 for (size_t j = 0; j < defines->defines[i].value_count; j++) {
1036 if (defines->defines[i].values[j] == value) {
1037 return;
1038 }
1039 }
1040
1041 *(intmax_t*)mappend(
1042 (void**)&defines->defines[i].values,
1043 sizeof(intmax_t),
1044 &defines->defines[i].value_count,
1045 &defines->defines[i].value_capacity) = value;
1046
1047 return;
1048 }
1049 }
1050
1051 // new define?
1052 struct list_defines_define *define = mappend(
1053 (void**)&defines->defines,
1054 sizeof(struct list_defines_define),
1055 &defines->define_count,
1056 &defines->define_capacity);
1057 define->name = name;
1058 define->values = malloc(sizeof(intmax_t));
1059 define->values[0] = value;
1060 define->value_count = 1;
1061 define->value_capacity = 1;
1062 }
1063
perm_list_defines(void * data,const struct bench_suite * suite,const struct bench_case * case_)1064 void perm_list_defines(
1065 void *data,
1066 const struct bench_suite *suite,
1067 const struct bench_case *case_) {
1068 struct list_defines_defines *defines = data;
1069 (void)suite;
1070 (void)case_;
1071
1072 // collect defines
1073 for (size_t d = 0;
1074 d < lfs_max(suite->define_count,
1075 BENCH_IMPLICIT_DEFINE_COUNT);
1076 d++) {
1077 if (d < BENCH_IMPLICIT_DEFINE_COUNT
1078 || bench_define_ispermutation(d)) {
1079 list_defines_add(defines, d);
1080 }
1081 }
1082 }
1083
perm_list_permutation_defines(void * data,const struct bench_suite * suite,const struct bench_case * case_)1084 void perm_list_permutation_defines(
1085 void *data,
1086 const struct bench_suite *suite,
1087 const struct bench_case *case_) {
1088 struct list_defines_defines *defines = data;
1089 (void)suite;
1090 (void)case_;
1091
1092 // collect permutation_defines
1093 for (size_t d = 0;
1094 d < lfs_max(suite->define_count,
1095 BENCH_IMPLICIT_DEFINE_COUNT);
1096 d++) {
1097 if (bench_define_ispermutation(d)) {
1098 list_defines_add(defines, d);
1099 }
1100 }
1101 }
1102
1103 extern const bench_geometry_t builtin_geometries[];
1104
list_defines(void)1105 static void list_defines(void) {
1106 struct list_defines_defines defines = {NULL, 0, 0};
1107
1108 // add defines
1109 for (size_t t = 0; t < bench_id_count; t++) {
1110 for (size_t i = 0; i < BENCH_SUITE_COUNT; i++) {
1111 bench_define_suite(&bench_suites[i]);
1112
1113 for (size_t j = 0; j < bench_suites[i].case_count; j++) {
1114 // does neither suite nor case name match?
1115 if (bench_ids[t].name && !(
1116 strcmp(bench_ids[t].name,
1117 bench_suites[i].name) == 0
1118 || strcmp(bench_ids[t].name,
1119 bench_suites[i].cases[j].name) == 0)) {
1120 continue;
1121 }
1122
1123 case_forperm(
1124 &bench_suites[i],
1125 &bench_suites[i].cases[j],
1126 bench_ids[t].defines,
1127 bench_ids[t].define_count,
1128 perm_list_defines,
1129 &defines);
1130 }
1131 }
1132 }
1133
1134 for (size_t i = 0; i < defines.define_count; i++) {
1135 printf("%s=", defines.defines[i].name);
1136 for (size_t j = 0; j < defines.defines[i].value_count; j++) {
1137 printf("%jd", defines.defines[i].values[j]);
1138 if (j != defines.defines[i].value_count-1) {
1139 printf(",");
1140 }
1141 }
1142 printf("\n");
1143 }
1144
1145 for (size_t i = 0; i < defines.define_count; i++) {
1146 free(defines.defines[i].values);
1147 }
1148 free(defines.defines);
1149 }
1150
list_permutation_defines(void)1151 static void list_permutation_defines(void) {
1152 struct list_defines_defines defines = {NULL, 0, 0};
1153
1154 // add permutation defines
1155 for (size_t t = 0; t < bench_id_count; t++) {
1156 for (size_t i = 0; i < BENCH_SUITE_COUNT; i++) {
1157 bench_define_suite(&bench_suites[i]);
1158
1159 for (size_t j = 0; j < bench_suites[i].case_count; j++) {
1160 // does neither suite nor case name match?
1161 if (bench_ids[t].name && !(
1162 strcmp(bench_ids[t].name,
1163 bench_suites[i].name) == 0
1164 || strcmp(bench_ids[t].name,
1165 bench_suites[i].cases[j].name) == 0)) {
1166 continue;
1167 }
1168
1169 case_forperm(
1170 &bench_suites[i],
1171 &bench_suites[i].cases[j],
1172 bench_ids[t].defines,
1173 bench_ids[t].define_count,
1174 perm_list_permutation_defines,
1175 &defines);
1176 }
1177 }
1178 }
1179
1180 for (size_t i = 0; i < defines.define_count; i++) {
1181 printf("%s=", defines.defines[i].name);
1182 for (size_t j = 0; j < defines.defines[i].value_count; j++) {
1183 printf("%jd", defines.defines[i].values[j]);
1184 if (j != defines.defines[i].value_count-1) {
1185 printf(",");
1186 }
1187 }
1188 printf("\n");
1189 }
1190
1191 for (size_t i = 0; i < defines.define_count; i++) {
1192 free(defines.defines[i].values);
1193 }
1194 free(defines.defines);
1195 }
1196
list_implicit_defines(void)1197 static void list_implicit_defines(void) {
1198 struct list_defines_defines defines = {NULL, 0, 0};
1199
1200 // yes we do need to define a suite, this does a bit of bookeeping
1201 // such as setting up the define cache
1202 bench_define_suite(&(const struct bench_suite){0});
1203
1204 // make sure to include builtin geometries here
1205 extern const bench_geometry_t builtin_geometries[];
1206 for (size_t g = 0; builtin_geometries[g].name; g++) {
1207 bench_define_geometry(&builtin_geometries[g]);
1208 bench_define_flush();
1209
1210 // add implicit defines
1211 for (size_t d = 0; d < BENCH_IMPLICIT_DEFINE_COUNT; d++) {
1212 list_defines_add(&defines, d);
1213 }
1214 }
1215
1216 for (size_t i = 0; i < defines.define_count; i++) {
1217 printf("%s=", defines.defines[i].name);
1218 for (size_t j = 0; j < defines.defines[i].value_count; j++) {
1219 printf("%jd", defines.defines[i].values[j]);
1220 if (j != defines.defines[i].value_count-1) {
1221 printf(",");
1222 }
1223 }
1224 printf("\n");
1225 }
1226
1227 for (size_t i = 0; i < defines.define_count; i++) {
1228 free(defines.defines[i].values);
1229 }
1230 free(defines.defines);
1231 }
1232
1233
1234
1235 // geometries to bench
1236
1237 const bench_geometry_t builtin_geometries[] = {
1238 {"default", {{0}, BENCH_CONST(16), BENCH_CONST(512), {0}}},
1239 {"eeprom", {{0}, BENCH_CONST(1), BENCH_CONST(512), {0}}},
1240 {"emmc", {{0}, {0}, BENCH_CONST(512), {0}}},
1241 {"nor", {{0}, BENCH_CONST(1), BENCH_CONST(4096), {0}}},
1242 {"nand", {{0}, BENCH_CONST(4096), BENCH_CONST(32768), {0}}},
1243 {NULL, {{0}, {0}, {0}, {0}}},
1244 };
1245
1246 const bench_geometry_t *bench_geometries = builtin_geometries;
1247 size_t bench_geometry_count = 5;
1248
list_geometries(void)1249 static void list_geometries(void) {
1250 // at least size so that names fit
1251 unsigned name_width = 23;
1252 for (size_t g = 0; builtin_geometries[g].name; g++) {
1253 size_t len = strlen(builtin_geometries[g].name);
1254 if (len > name_width) {
1255 name_width = len;
1256 }
1257 }
1258 name_width = 4*((name_width+1+4-1)/4)-1;
1259
1260 // yes we do need to define a suite, this does a bit of bookeeping
1261 // such as setting up the define cache
1262 bench_define_suite(&(const struct bench_suite){0});
1263
1264 printf("%-*s %7s %7s %7s %7s %11s\n",
1265 name_width, "geometry", "read", "prog", "erase", "count", "size");
1266 for (size_t g = 0; builtin_geometries[g].name; g++) {
1267 bench_define_geometry(&builtin_geometries[g]);
1268 bench_define_flush();
1269 printf("%-*s %7ju %7ju %7ju %7ju %11ju\n",
1270 name_width,
1271 builtin_geometries[g].name,
1272 READ_SIZE,
1273 PROG_SIZE,
1274 ERASE_SIZE,
1275 ERASE_COUNT,
1276 ERASE_SIZE*ERASE_COUNT);
1277 }
1278 }
1279
1280
1281
1282 // global bench step count
1283 size_t bench_step = 0;
1284
perm_run(void * data,const struct bench_suite * suite,const struct bench_case * case_)1285 void perm_run(
1286 void *data,
1287 const struct bench_suite *suite,
1288 const struct bench_case *case_) {
1289 (void)data;
1290
1291 // skip this step?
1292 if (!(bench_step >= bench_step_start
1293 && bench_step < bench_step_stop
1294 && (bench_step-bench_step_start) % bench_step_step == 0)) {
1295 bench_step += 1;
1296 return;
1297 }
1298 bench_step += 1;
1299
1300 // filter?
1301 if (case_->filter && !case_->filter()) {
1302 printf("skipped ");
1303 perm_printid(suite, case_);
1304 printf("\n");
1305 return;
1306 }
1307
1308 // create block device and configuration
1309 lfs_emubd_t bd;
1310
1311 struct lfs_config cfg = {
1312 .context = &bd,
1313 .read = lfs_emubd_read,
1314 .prog = lfs_emubd_prog,
1315 .erase = lfs_emubd_erase,
1316 .sync = lfs_emubd_sync,
1317 .read_size = READ_SIZE,
1318 .prog_size = PROG_SIZE,
1319 .block_size = BLOCK_SIZE,
1320 .block_count = BLOCK_COUNT,
1321 .block_cycles = BLOCK_CYCLES,
1322 .cache_size = CACHE_SIZE,
1323 .lookahead_size = LOOKAHEAD_SIZE,
1324 .compact_thresh = COMPACT_THRESH,
1325 .metadata_max = METADATA_MAX,
1326 .inline_max = INLINE_MAX,
1327 };
1328
1329 struct lfs_emubd_config bdcfg = {
1330 .read_size = READ_SIZE,
1331 .prog_size = PROG_SIZE,
1332 .erase_size = ERASE_SIZE,
1333 .erase_count = ERASE_COUNT,
1334 .erase_value = ERASE_VALUE,
1335 .erase_cycles = ERASE_CYCLES,
1336 .badblock_behavior = BADBLOCK_BEHAVIOR,
1337 .disk_path = bench_disk_path,
1338 .read_sleep = bench_read_sleep,
1339 .prog_sleep = bench_prog_sleep,
1340 .erase_sleep = bench_erase_sleep,
1341 };
1342
1343 int err = lfs_emubd_create(&cfg, &bdcfg);
1344 if (err) {
1345 fprintf(stderr, "error: could not create block device: %d\n", err);
1346 exit(-1);
1347 }
1348
1349 // run the bench
1350 bench_cfg = &cfg;
1351 bench_reset();
1352 printf("running ");
1353 perm_printid(suite, case_);
1354 printf("\n");
1355
1356 case_->run(&cfg);
1357
1358 printf("finished ");
1359 perm_printid(suite, case_);
1360 printf(" %"PRIu64" %"PRIu64" %"PRIu64,
1361 bench_readed,
1362 bench_proged,
1363 bench_erased);
1364 printf("\n");
1365
1366 // cleanup
1367 err = lfs_emubd_destroy(&cfg);
1368 if (err) {
1369 fprintf(stderr, "error: could not destroy block device: %d\n", err);
1370 exit(-1);
1371 }
1372 }
1373
run(void)1374 static void run(void) {
1375 // ignore disconnected pipes
1376 signal(SIGPIPE, SIG_IGN);
1377
1378 for (size_t t = 0; t < bench_id_count; t++) {
1379 for (size_t i = 0; i < BENCH_SUITE_COUNT; i++) {
1380 bench_define_suite(&bench_suites[i]);
1381
1382 for (size_t j = 0; j < bench_suites[i].case_count; j++) {
1383 // does neither suite nor case name match?
1384 if (bench_ids[t].name && !(
1385 strcmp(bench_ids[t].name,
1386 bench_suites[i].name) == 0
1387 || strcmp(bench_ids[t].name,
1388 bench_suites[i].cases[j].name) == 0)) {
1389 continue;
1390 }
1391
1392 case_forperm(
1393 &bench_suites[i],
1394 &bench_suites[i].cases[j],
1395 bench_ids[t].defines,
1396 bench_ids[t].define_count,
1397 perm_run,
1398 NULL);
1399 }
1400 }
1401 }
1402 }
1403
1404
1405
1406 // option handling
1407 enum opt_flags {
1408 OPT_HELP = 'h',
1409 OPT_SUMMARY = 'Y',
1410 OPT_LIST_SUITES = 'l',
1411 OPT_LIST_CASES = 'L',
1412 OPT_LIST_SUITE_PATHS = 1,
1413 OPT_LIST_CASE_PATHS = 2,
1414 OPT_LIST_DEFINES = 3,
1415 OPT_LIST_PERMUTATION_DEFINES = 4,
1416 OPT_LIST_IMPLICIT_DEFINES = 5,
1417 OPT_LIST_GEOMETRIES = 6,
1418 OPT_DEFINE = 'D',
1419 OPT_GEOMETRY = 'G',
1420 OPT_STEP = 's',
1421 OPT_DISK = 'd',
1422 OPT_TRACE = 't',
1423 OPT_TRACE_BACKTRACE = 7,
1424 OPT_TRACE_PERIOD = 8,
1425 OPT_TRACE_FREQ = 9,
1426 OPT_READ_SLEEP = 10,
1427 OPT_PROG_SLEEP = 11,
1428 OPT_ERASE_SLEEP = 12,
1429 };
1430
1431 const char *short_opts = "hYlLD:G:s:d:t:";
1432
1433 const struct option long_opts[] = {
1434 {"help", no_argument, NULL, OPT_HELP},
1435 {"summary", no_argument, NULL, OPT_SUMMARY},
1436 {"list-suites", no_argument, NULL, OPT_LIST_SUITES},
1437 {"list-cases", no_argument, NULL, OPT_LIST_CASES},
1438 {"list-suite-paths", no_argument, NULL, OPT_LIST_SUITE_PATHS},
1439 {"list-case-paths", no_argument, NULL, OPT_LIST_CASE_PATHS},
1440 {"list-defines", no_argument, NULL, OPT_LIST_DEFINES},
1441 {"list-permutation-defines",
1442 no_argument, NULL, OPT_LIST_PERMUTATION_DEFINES},
1443 {"list-implicit-defines",
1444 no_argument, NULL, OPT_LIST_IMPLICIT_DEFINES},
1445 {"list-geometries", no_argument, NULL, OPT_LIST_GEOMETRIES},
1446 {"define", required_argument, NULL, OPT_DEFINE},
1447 {"geometry", required_argument, NULL, OPT_GEOMETRY},
1448 {"step", required_argument, NULL, OPT_STEP},
1449 {"disk", required_argument, NULL, OPT_DISK},
1450 {"trace", required_argument, NULL, OPT_TRACE},
1451 {"trace-backtrace", no_argument, NULL, OPT_TRACE_BACKTRACE},
1452 {"trace-period", required_argument, NULL, OPT_TRACE_PERIOD},
1453 {"trace-freq", required_argument, NULL, OPT_TRACE_FREQ},
1454 {"read-sleep", required_argument, NULL, OPT_READ_SLEEP},
1455 {"prog-sleep", required_argument, NULL, OPT_PROG_SLEEP},
1456 {"erase-sleep", required_argument, NULL, OPT_ERASE_SLEEP},
1457 {NULL, 0, NULL, 0},
1458 };
1459
1460 const char *const help_text[] = {
1461 "Show this help message.",
1462 "Show quick summary.",
1463 "List bench suites.",
1464 "List bench cases.",
1465 "List the path for each bench suite.",
1466 "List the path and line number for each bench case.",
1467 "List all defines in this bench-runner.",
1468 "List explicit defines in this bench-runner.",
1469 "List implicit defines in this bench-runner.",
1470 "List the available disk geometries.",
1471 "Override a bench define.",
1472 "Comma-separated list of disk geometries to bench.",
1473 "Comma-separated range of bench permutations to run (start,stop,step).",
1474 "Direct block device operations to this file.",
1475 "Direct trace output to this file.",
1476 "Include a backtrace with every trace statement.",
1477 "Sample trace output at this period in cycles.",
1478 "Sample trace output at this frequency in hz.",
1479 "Artificial read delay in seconds.",
1480 "Artificial prog delay in seconds.",
1481 "Artificial erase delay in seconds.",
1482 };
1483
main(int argc,char ** argv)1484 int main(int argc, char **argv) {
1485 void (*op)(void) = run;
1486
1487 size_t bench_override_capacity = 0;
1488 size_t bench_geometry_capacity = 0;
1489 size_t bench_id_capacity = 0;
1490
1491 // parse options
1492 while (true) {
1493 int c = getopt_long(argc, argv, short_opts, long_opts, NULL);
1494 switch (c) {
1495 // generate help message
1496 case OPT_HELP: {
1497 printf("usage: %s [options] [bench_id]\n", argv[0]);
1498 printf("\n");
1499
1500 printf("options:\n");
1501 size_t i = 0;
1502 while (long_opts[i].name) {
1503 size_t indent;
1504 if (long_opts[i].has_arg == no_argument) {
1505 if (long_opts[i].val >= '0' && long_opts[i].val < 'z') {
1506 indent = printf(" -%c, --%s ",
1507 long_opts[i].val,
1508 long_opts[i].name);
1509 } else {
1510 indent = printf(" --%s ",
1511 long_opts[i].name);
1512 }
1513 } else {
1514 if (long_opts[i].val >= '0' && long_opts[i].val < 'z') {
1515 indent = printf(" -%c %s, --%s %s ",
1516 long_opts[i].val,
1517 long_opts[i].name,
1518 long_opts[i].name,
1519 long_opts[i].name);
1520 } else {
1521 indent = printf(" --%s %s ",
1522 long_opts[i].name,
1523 long_opts[i].name);
1524 }
1525 }
1526
1527 // a quick, hacky, byte-level method for text wrapping
1528 size_t len = strlen(help_text[i]);
1529 size_t j = 0;
1530 if (indent < 24) {
1531 printf("%*s %.80s\n",
1532 (int)(24-1-indent),
1533 "",
1534 &help_text[i][j]);
1535 j += 80;
1536 } else {
1537 printf("\n");
1538 }
1539
1540 while (j < len) {
1541 printf("%24s%.80s\n", "", &help_text[i][j]);
1542 j += 80;
1543 }
1544
1545 i += 1;
1546 }
1547
1548 printf("\n");
1549 exit(0);
1550 }
1551 // summary/list flags
1552 case OPT_SUMMARY:
1553 op = summary;
1554 break;
1555 case OPT_LIST_SUITES:
1556 op = list_suites;
1557 break;
1558 case OPT_LIST_CASES:
1559 op = list_cases;
1560 break;
1561 case OPT_LIST_SUITE_PATHS:
1562 op = list_suite_paths;
1563 break;
1564 case OPT_LIST_CASE_PATHS:
1565 op = list_case_paths;
1566 break;
1567 case OPT_LIST_DEFINES:
1568 op = list_defines;
1569 break;
1570 case OPT_LIST_PERMUTATION_DEFINES:
1571 op = list_permutation_defines;
1572 break;
1573 case OPT_LIST_IMPLICIT_DEFINES:
1574 op = list_implicit_defines;
1575 break;
1576 case OPT_LIST_GEOMETRIES:
1577 op = list_geometries;
1578 break;
1579 // configuration
1580 case OPT_DEFINE: {
1581 // allocate space
1582 bench_override_t *override = mappend(
1583 (void**)&bench_overrides,
1584 sizeof(bench_override_t),
1585 &bench_override_count,
1586 &bench_override_capacity);
1587
1588 // parse into string key/intmax_t value, cannibalizing the
1589 // arg in the process
1590 char *sep = strchr(optarg, '=');
1591 char *parsed = NULL;
1592 if (!sep) {
1593 goto invalid_define;
1594 }
1595 *sep = '\0';
1596 override->name = optarg;
1597 optarg = sep+1;
1598
1599 // parse comma-separated permutations
1600 {
1601 override->defines = NULL;
1602 override->permutations = 0;
1603 size_t override_capacity = 0;
1604 while (true) {
1605 optarg += strspn(optarg, " ");
1606
1607 if (strncmp(optarg, "range", strlen("range")) == 0) {
1608 // range of values
1609 optarg += strlen("range");
1610 optarg += strspn(optarg, " ");
1611 if (*optarg != '(') {
1612 goto invalid_define;
1613 }
1614 optarg += 1;
1615
1616 intmax_t start = strtoumax(optarg, &parsed, 0);
1617 intmax_t stop = -1;
1618 intmax_t step = 1;
1619 // allow empty string for start=0
1620 if (parsed == optarg) {
1621 start = 0;
1622 }
1623 optarg = parsed + strspn(parsed, " ");
1624
1625 if (*optarg != ',' && *optarg != ')') {
1626 goto invalid_define;
1627 }
1628
1629 if (*optarg == ',') {
1630 optarg += 1;
1631 stop = strtoumax(optarg, &parsed, 0);
1632 // allow empty string for stop=end
1633 if (parsed == optarg) {
1634 stop = -1;
1635 }
1636 optarg = parsed + strspn(parsed, " ");
1637
1638 if (*optarg != ',' && *optarg != ')') {
1639 goto invalid_define;
1640 }
1641
1642 if (*optarg == ',') {
1643 optarg += 1;
1644 step = strtoumax(optarg, &parsed, 0);
1645 // allow empty string for stop=1
1646 if (parsed == optarg) {
1647 step = 1;
1648 }
1649 optarg = parsed + strspn(parsed, " ");
1650
1651 if (*optarg != ')') {
1652 goto invalid_define;
1653 }
1654 }
1655 } else {
1656 // single value = stop only
1657 stop = start;
1658 start = 0;
1659 }
1660
1661 if (*optarg != ')') {
1662 goto invalid_define;
1663 }
1664 optarg += 1;
1665
1666 // calculate the range of values
1667 assert(step != 0);
1668 for (intmax_t i = start;
1669 (step < 0)
1670 ? i > stop
1671 : (uintmax_t)i < (uintmax_t)stop;
1672 i += step) {
1673 *(intmax_t*)mappend(
1674 (void**)&override->defines,
1675 sizeof(intmax_t),
1676 &override->permutations,
1677 &override_capacity) = i;
1678 }
1679 } else if (*optarg != '\0') {
1680 // single value
1681 intmax_t define = strtoimax(optarg, &parsed, 0);
1682 if (parsed == optarg) {
1683 goto invalid_define;
1684 }
1685 optarg = parsed + strspn(parsed, " ");
1686 *(intmax_t*)mappend(
1687 (void**)&override->defines,
1688 sizeof(intmax_t),
1689 &override->permutations,
1690 &override_capacity) = define;
1691 } else {
1692 break;
1693 }
1694
1695 if (*optarg == ',') {
1696 optarg += 1;
1697 }
1698 }
1699 }
1700 assert(override->permutations > 0);
1701 break;
1702
1703 invalid_define:
1704 fprintf(stderr, "error: invalid define: %s\n", optarg);
1705 exit(-1);
1706 }
1707 case OPT_GEOMETRY: {
1708 // reset our geometry scenarios
1709 if (bench_geometry_capacity > 0) {
1710 free((bench_geometry_t*)bench_geometries);
1711 }
1712 bench_geometries = NULL;
1713 bench_geometry_count = 0;
1714 bench_geometry_capacity = 0;
1715
1716 // parse the comma separated list of disk geometries
1717 while (*optarg) {
1718 // allocate space
1719 bench_geometry_t *geometry = mappend(
1720 (void**)&bench_geometries,
1721 sizeof(bench_geometry_t),
1722 &bench_geometry_count,
1723 &bench_geometry_capacity);
1724
1725 // parse the disk geometry
1726 optarg += strspn(optarg, " ");
1727
1728 // named disk geometry
1729 size_t len = strcspn(optarg, " ,");
1730 for (size_t i = 0; builtin_geometries[i].name; i++) {
1731 if (len == strlen(builtin_geometries[i].name)
1732 && memcmp(optarg,
1733 builtin_geometries[i].name,
1734 len) == 0) {
1735 *geometry = builtin_geometries[i];
1736 optarg += len;
1737 goto geometry_next;
1738 }
1739 }
1740
1741 // comma-separated read/prog/erase/count
1742 if (*optarg == '{') {
1743 lfs_size_t sizes[4];
1744 size_t count = 0;
1745
1746 char *s = optarg + 1;
1747 while (count < 4) {
1748 char *parsed = NULL;
1749 sizes[count] = strtoumax(s, &parsed, 0);
1750 count += 1;
1751
1752 s = parsed + strspn(parsed, " ");
1753 if (*s == ',') {
1754 s += 1;
1755 continue;
1756 } else if (*s == '}') {
1757 s += 1;
1758 break;
1759 } else {
1760 goto geometry_unknown;
1761 }
1762 }
1763
1764 // allow implicit r=p and p=e for common geometries
1765 memset(geometry, 0, sizeof(bench_geometry_t));
1766 if (count >= 3) {
1767 geometry->defines[READ_SIZE_i]
1768 = BENCH_LIT(sizes[0]);
1769 geometry->defines[PROG_SIZE_i]
1770 = BENCH_LIT(sizes[1]);
1771 geometry->defines[ERASE_SIZE_i]
1772 = BENCH_LIT(sizes[2]);
1773 } else if (count >= 2) {
1774 geometry->defines[PROG_SIZE_i]
1775 = BENCH_LIT(sizes[0]);
1776 geometry->defines[ERASE_SIZE_i]
1777 = BENCH_LIT(sizes[1]);
1778 } else {
1779 geometry->defines[ERASE_SIZE_i]
1780 = BENCH_LIT(sizes[0]);
1781 }
1782 if (count >= 4) {
1783 geometry->defines[ERASE_COUNT_i]
1784 = BENCH_LIT(sizes[3]);
1785 }
1786 optarg = s;
1787 goto geometry_next;
1788 }
1789
1790 // leb16-encoded read/prog/erase/count
1791 if (*optarg == ':') {
1792 lfs_size_t sizes[4];
1793 size_t count = 0;
1794
1795 char *s = optarg + 1;
1796 while (true) {
1797 char *parsed = NULL;
1798 uintmax_t x = leb16_parse(s, &parsed);
1799 if (parsed == s || count >= 4) {
1800 break;
1801 }
1802
1803 sizes[count] = x;
1804 count += 1;
1805 s = parsed;
1806 }
1807
1808 // allow implicit r=p and p=e for common geometries
1809 memset(geometry, 0, sizeof(bench_geometry_t));
1810 if (count >= 3) {
1811 geometry->defines[READ_SIZE_i]
1812 = BENCH_LIT(sizes[0]);
1813 geometry->defines[PROG_SIZE_i]
1814 = BENCH_LIT(sizes[1]);
1815 geometry->defines[ERASE_SIZE_i]
1816 = BENCH_LIT(sizes[2]);
1817 } else if (count >= 2) {
1818 geometry->defines[PROG_SIZE_i]
1819 = BENCH_LIT(sizes[0]);
1820 geometry->defines[ERASE_SIZE_i]
1821 = BENCH_LIT(sizes[1]);
1822 } else {
1823 geometry->defines[ERASE_SIZE_i]
1824 = BENCH_LIT(sizes[0]);
1825 }
1826 if (count >= 4) {
1827 geometry->defines[ERASE_COUNT_i]
1828 = BENCH_LIT(sizes[3]);
1829 }
1830 optarg = s;
1831 goto geometry_next;
1832 }
1833
1834 geometry_unknown:
1835 // unknown scenario?
1836 fprintf(stderr, "error: unknown disk geometry: %s\n",
1837 optarg);
1838 exit(-1);
1839
1840 geometry_next:
1841 optarg += strspn(optarg, " ");
1842 if (*optarg == ',') {
1843 optarg += 1;
1844 } else if (*optarg == '\0') {
1845 break;
1846 } else {
1847 goto geometry_unknown;
1848 }
1849 }
1850 break;
1851 }
1852 case OPT_STEP: {
1853 char *parsed = NULL;
1854 bench_step_start = strtoumax(optarg, &parsed, 0);
1855 bench_step_stop = -1;
1856 bench_step_step = 1;
1857 // allow empty string for start=0
1858 if (parsed == optarg) {
1859 bench_step_start = 0;
1860 }
1861 optarg = parsed + strspn(parsed, " ");
1862
1863 if (*optarg != ',' && *optarg != '\0') {
1864 goto step_unknown;
1865 }
1866
1867 if (*optarg == ',') {
1868 optarg += 1;
1869 bench_step_stop = strtoumax(optarg, &parsed, 0);
1870 // allow empty string for stop=end
1871 if (parsed == optarg) {
1872 bench_step_stop = -1;
1873 }
1874 optarg = parsed + strspn(parsed, " ");
1875
1876 if (*optarg != ',' && *optarg != '\0') {
1877 goto step_unknown;
1878 }
1879
1880 if (*optarg == ',') {
1881 optarg += 1;
1882 bench_step_step = strtoumax(optarg, &parsed, 0);
1883 // allow empty string for stop=1
1884 if (parsed == optarg) {
1885 bench_step_step = 1;
1886 }
1887 optarg = parsed + strspn(parsed, " ");
1888
1889 if (*optarg != '\0') {
1890 goto step_unknown;
1891 }
1892 }
1893 } else {
1894 // single value = stop only
1895 bench_step_stop = bench_step_start;
1896 bench_step_start = 0;
1897 }
1898
1899 break;
1900 step_unknown:
1901 fprintf(stderr, "error: invalid step: %s\n", optarg);
1902 exit(-1);
1903 }
1904 case OPT_DISK:
1905 bench_disk_path = optarg;
1906 break;
1907 case OPT_TRACE:
1908 bench_trace_path = optarg;
1909 break;
1910 case OPT_TRACE_BACKTRACE:
1911 bench_trace_backtrace = true;
1912 break;
1913 case OPT_TRACE_PERIOD: {
1914 char *parsed = NULL;
1915 bench_trace_period = strtoumax(optarg, &parsed, 0);
1916 if (parsed == optarg) {
1917 fprintf(stderr, "error: invalid trace-period: %s\n", optarg);
1918 exit(-1);
1919 }
1920 break;
1921 }
1922 case OPT_TRACE_FREQ: {
1923 char *parsed = NULL;
1924 bench_trace_freq = strtoumax(optarg, &parsed, 0);
1925 if (parsed == optarg) {
1926 fprintf(stderr, "error: invalid trace-freq: %s\n", optarg);
1927 exit(-1);
1928 }
1929 break;
1930 }
1931 case OPT_READ_SLEEP: {
1932 char *parsed = NULL;
1933 double read_sleep = strtod(optarg, &parsed);
1934 if (parsed == optarg) {
1935 fprintf(stderr, "error: invalid read-sleep: %s\n", optarg);
1936 exit(-1);
1937 }
1938 bench_read_sleep = read_sleep*1.0e9;
1939 break;
1940 }
1941 case OPT_PROG_SLEEP: {
1942 char *parsed = NULL;
1943 double prog_sleep = strtod(optarg, &parsed);
1944 if (parsed == optarg) {
1945 fprintf(stderr, "error: invalid prog-sleep: %s\n", optarg);
1946 exit(-1);
1947 }
1948 bench_prog_sleep = prog_sleep*1.0e9;
1949 break;
1950 }
1951 case OPT_ERASE_SLEEP: {
1952 char *parsed = NULL;
1953 double erase_sleep = strtod(optarg, &parsed);
1954 if (parsed == optarg) {
1955 fprintf(stderr, "error: invalid erase-sleep: %s\n", optarg);
1956 exit(-1);
1957 }
1958 bench_erase_sleep = erase_sleep*1.0e9;
1959 break;
1960 }
1961 // done parsing
1962 case -1:
1963 goto getopt_done;
1964 // unknown arg, getopt prints a message for us
1965 default:
1966 exit(-1);
1967 }
1968 }
1969 getopt_done: ;
1970
1971 if (argc > optind) {
1972 // reset our bench identifier list
1973 bench_ids = NULL;
1974 bench_id_count = 0;
1975 bench_id_capacity = 0;
1976 }
1977
1978 // parse bench identifier, if any, cannibalizing the arg in the process
1979 for (; argc > optind; optind++) {
1980 bench_define_t *defines = NULL;
1981 size_t define_count = 0;
1982
1983 // parse name, can be suite or case
1984 char *name = argv[optind];
1985 char *defines_ = strchr(name, ':');
1986 if (defines_) {
1987 *defines_ = '\0';
1988 defines_ += 1;
1989 }
1990
1991 // remove optional path and .toml suffix
1992 char *slash = strrchr(name, '/');
1993 if (slash) {
1994 name = slash+1;
1995 }
1996
1997 size_t name_len = strlen(name);
1998 if (name_len > 5 && strcmp(&name[name_len-5], ".toml") == 0) {
1999 name[name_len-5] = '\0';
2000 }
2001
2002 if (defines_) {
2003 // parse defines
2004 while (true) {
2005 char *parsed;
2006 size_t d = leb16_parse(defines_, &parsed);
2007 intmax_t v = leb16_parse(parsed, &parsed);
2008 if (parsed == defines_) {
2009 break;
2010 }
2011 defines_ = parsed;
2012
2013 if (d >= define_count) {
2014 // align to power of two to avoid any superlinear growth
2015 size_t ncount = 1 << lfs_npw2(d+1);
2016 defines = realloc(defines,
2017 ncount*sizeof(bench_define_t));
2018 memset(defines+define_count, 0,
2019 (ncount-define_count)*sizeof(bench_define_t));
2020 define_count = ncount;
2021 }
2022 defines[d] = BENCH_LIT(v);
2023 }
2024 }
2025
2026 // append to identifier list
2027 *(bench_id_t*)mappend(
2028 (void**)&bench_ids,
2029 sizeof(bench_id_t),
2030 &bench_id_count,
2031 &bench_id_capacity) = (bench_id_t){
2032 .name = name,
2033 .defines = defines,
2034 .define_count = define_count,
2035 };
2036 }
2037
2038 // do the thing
2039 op();
2040
2041 // cleanup (need to be done for valgrind benching)
2042 bench_define_cleanup();
2043 if (bench_overrides) {
2044 for (size_t i = 0; i < bench_override_count; i++) {
2045 free((void*)bench_overrides[i].defines);
2046 }
2047 free((void*)bench_overrides);
2048 }
2049 if (bench_geometry_capacity) {
2050 free((void*)bench_geometries);
2051 }
2052 if (bench_id_capacity) {
2053 for (size_t i = 0; i < bench_id_count; i++) {
2054 free((void*)bench_ids[i].defines);
2055 }
2056 free((void*)bench_ids);
2057 }
2058 }
2059