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
29void *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
54static 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
71static 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
113typedef struct bench_geometry {
114    const char *name;
115    bench_define_t defines[BENCH_GEOMETRY_DEFINE_COUNT];
116} bench_geometry_t;
117
118typedef 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
126extern struct bench_suite __start__bench_suites;
127extern struct bench_suite __stop__bench_suites;
128
129const 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
135typedef struct bench_define_map {
136    const bench_define_t *defines;
137    size_t count;
138} bench_define_map_t;
139
140typedef struct bench_define_names {
141    const char *const *names;
142    size_t count;
143} bench_define_names_t;
144
145intmax_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
169bench_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
186bench_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
199intmax_t *bench_define_cache;
200size_t bench_define_cache_count;
201unsigned *bench_define_cache_mask;
202
203const 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
216bool 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
228intmax_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
263void 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
272const bench_geometry_t *bench_geometry = NULL;
273
274void 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
280typedef struct bench_override {
281    const char *name;
282    const intmax_t *defines;
283    size_t permutations;
284} bench_override_t;
285
286const bench_override_t *bench_overrides = NULL;
287size_t bench_override_count = 0;
288
289bench_define_t *bench_override_defines = NULL;
290size_t bench_override_define_count = 0;
291size_t bench_override_define_permutations = 1;
292size_t bench_override_define_capacity = 0;
293
294// suite/perm updates
295void 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
378void 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
392void 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
398void 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
405void 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
415extern const bench_geometry_t *bench_geometries;
416extern size_t bench_geometry_count;
417
418const bench_id_t *bench_ids = (const bench_id_t[]) {
419    {NULL, NULL, 0},
420};
421size_t bench_id_count = 1;
422
423size_t bench_step_start = 0;
424size_t bench_step_stop = -1;
425size_t bench_step_step = 1;
426
427const char *bench_disk_path = NULL;
428const char *bench_trace_path = NULL;
429bool bench_trace_backtrace = false;
430uint32_t bench_trace_period = 0;
431uint32_t bench_trace_freq = 0;
432FILE *bench_trace_file = NULL;
433uint32_t bench_trace_cycles = 0;
434uint64_t bench_trace_time = 0;
435uint64_t bench_trace_open_time = 0;
436lfs_emubd_sleep_t bench_read_sleep = 0.0;
437lfs_emubd_sleep_t bench_prog_sleep = 0.0;
438lfs_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
445void *bench_trace_backtrace_buffer[
446    BENCH_TRACE_BACKTRACE_BUFFER_SIZE / sizeof(void*)];
447
448// trace printing
449void 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
547uint32_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
560static struct lfs_config *bench_cfg = NULL;
561static lfs_emubd_io_t bench_last_readed = 0;
562static lfs_emubd_io_t bench_last_proged = 0;
563static lfs_emubd_io_t bench_last_erased = 0;
564lfs_emubd_io_t bench_readed = 0;
565lfs_emubd_io_t bench_proged = 0;
566lfs_emubd_io_t bench_erased = 0;
567
568void 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
577void 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
591void 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
607static 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
626typedef struct bench_seen {
627    struct bench_seen_branch *branches;
628    size_t branch_count;
629    size_t branch_capacity;
630} bench_seen_t;
631
632struct bench_seen_branch {
633    intmax_t define;
634    struct bench_seen branch;
635};
636
637bool 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
680void 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
688static 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
744struct perm_count_state {
745    size_t total;
746    size_t filtered;
747};
748
749void 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
768static 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
819static 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
880static 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
935static 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
977static 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
1012struct list_defines_define {
1013    const char *name;
1014    intmax_t *values;
1015    size_t value_count;
1016    size_t value_capacity;
1017};
1018
1019struct list_defines_defines {
1020    struct list_defines_define *defines;
1021    size_t define_count;
1022    size_t define_capacity;
1023};
1024
1025static 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
1064void 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
1084void 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
1103extern const bench_geometry_t builtin_geometries[];
1104
1105static 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
1151static 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
1197static 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
1237const 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
1246const bench_geometry_t *bench_geometries = builtin_geometries;
1247size_t bench_geometry_count = 5;
1248
1249static 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
1283size_t bench_step = 0;
1284
1285void 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    };
1325
1326    struct lfs_emubd_config bdcfg = {
1327        .read_size          = READ_SIZE,
1328        .prog_size          = PROG_SIZE,
1329        .erase_size         = ERASE_SIZE,
1330        .erase_count        = ERASE_COUNT,
1331        .erase_value        = ERASE_VALUE,
1332        .erase_cycles       = ERASE_CYCLES,
1333        .badblock_behavior  = BADBLOCK_BEHAVIOR,
1334        .disk_path          = bench_disk_path,
1335        .read_sleep         = bench_read_sleep,
1336        .prog_sleep         = bench_prog_sleep,
1337        .erase_sleep        = bench_erase_sleep,
1338    };
1339
1340    int err = lfs_emubd_create(&cfg, &bdcfg);
1341    if (err) {
1342        fprintf(stderr, "error: could not create block device: %d\n", err);
1343        exit(-1);
1344    }
1345
1346    // run the bench
1347    bench_cfg = &cfg;
1348    bench_reset();
1349    printf("running ");
1350    perm_printid(suite, case_);
1351    printf("\n");
1352
1353    case_->run(&cfg);
1354
1355    printf("finished ");
1356    perm_printid(suite, case_);
1357    printf(" %"PRIu64" %"PRIu64" %"PRIu64,
1358        bench_readed,
1359        bench_proged,
1360        bench_erased);
1361    printf("\n");
1362
1363    // cleanup
1364    err = lfs_emubd_destroy(&cfg);
1365    if (err) {
1366        fprintf(stderr, "error: could not destroy block device: %d\n", err);
1367        exit(-1);
1368    }
1369}
1370
1371static void run(void) {
1372    // ignore disconnected pipes
1373    signal(SIGPIPE, SIG_IGN);
1374
1375    for (size_t t = 0; t < bench_id_count; t++) {
1376        for (size_t i = 0; i < BENCH_SUITE_COUNT; i++) {
1377            bench_define_suite(&bench_suites[i]);
1378
1379            for (size_t j = 0; j < bench_suites[i].case_count; j++) {
1380                // does neither suite nor case name match?
1381                if (bench_ids[t].name && !(
1382                        strcmp(bench_ids[t].name,
1383                            bench_suites[i].name) == 0
1384                        || strcmp(bench_ids[t].name,
1385                            bench_suites[i].cases[j].name) == 0)) {
1386                    continue;
1387                }
1388
1389                case_forperm(
1390                        &bench_suites[i],
1391                        &bench_suites[i].cases[j],
1392                        bench_ids[t].defines,
1393                        bench_ids[t].define_count,
1394                        perm_run,
1395                        NULL);
1396            }
1397        }
1398    }
1399}
1400
1401
1402
1403// option handling
1404enum opt_flags {
1405    OPT_HELP                     = 'h',
1406    OPT_SUMMARY                  = 'Y',
1407    OPT_LIST_SUITES              = 'l',
1408    OPT_LIST_CASES               = 'L',
1409    OPT_LIST_SUITE_PATHS         = 1,
1410    OPT_LIST_CASE_PATHS          = 2,
1411    OPT_LIST_DEFINES             = 3,
1412    OPT_LIST_PERMUTATION_DEFINES = 4,
1413    OPT_LIST_IMPLICIT_DEFINES    = 5,
1414    OPT_LIST_GEOMETRIES          = 6,
1415    OPT_DEFINE                   = 'D',
1416    OPT_GEOMETRY                 = 'G',
1417    OPT_STEP                     = 's',
1418    OPT_DISK                     = 'd',
1419    OPT_TRACE                    = 't',
1420    OPT_TRACE_BACKTRACE          = 7,
1421    OPT_TRACE_PERIOD             = 8,
1422    OPT_TRACE_FREQ               = 9,
1423    OPT_READ_SLEEP               = 10,
1424    OPT_PROG_SLEEP               = 11,
1425    OPT_ERASE_SLEEP              = 12,
1426};
1427
1428const char *short_opts = "hYlLD:G:s:d:t:";
1429
1430const struct option long_opts[] = {
1431    {"help",             no_argument,       NULL, OPT_HELP},
1432    {"summary",          no_argument,       NULL, OPT_SUMMARY},
1433    {"list-suites",      no_argument,       NULL, OPT_LIST_SUITES},
1434    {"list-cases",       no_argument,       NULL, OPT_LIST_CASES},
1435    {"list-suite-paths", no_argument,       NULL, OPT_LIST_SUITE_PATHS},
1436    {"list-case-paths",  no_argument,       NULL, OPT_LIST_CASE_PATHS},
1437    {"list-defines",     no_argument,       NULL, OPT_LIST_DEFINES},
1438    {"list-permutation-defines",
1439                         no_argument,       NULL, OPT_LIST_PERMUTATION_DEFINES},
1440    {"list-implicit-defines",
1441                         no_argument,       NULL, OPT_LIST_IMPLICIT_DEFINES},
1442    {"list-geometries",  no_argument,       NULL, OPT_LIST_GEOMETRIES},
1443    {"define",           required_argument, NULL, OPT_DEFINE},
1444    {"geometry",         required_argument, NULL, OPT_GEOMETRY},
1445    {"step",             required_argument, NULL, OPT_STEP},
1446    {"disk",             required_argument, NULL, OPT_DISK},
1447    {"trace",            required_argument, NULL, OPT_TRACE},
1448    {"trace-backtrace",  no_argument,       NULL, OPT_TRACE_BACKTRACE},
1449    {"trace-period",     required_argument, NULL, OPT_TRACE_PERIOD},
1450    {"trace-freq",       required_argument, NULL, OPT_TRACE_FREQ},
1451    {"read-sleep",       required_argument, NULL, OPT_READ_SLEEP},
1452    {"prog-sleep",       required_argument, NULL, OPT_PROG_SLEEP},
1453    {"erase-sleep",      required_argument, NULL, OPT_ERASE_SLEEP},
1454    {NULL, 0, NULL, 0},
1455};
1456
1457const char *const help_text[] = {
1458    "Show this help message.",
1459    "Show quick summary.",
1460    "List bench suites.",
1461    "List bench cases.",
1462    "List the path for each bench suite.",
1463    "List the path and line number for each bench case.",
1464    "List all defines in this bench-runner.",
1465    "List explicit defines in this bench-runner.",
1466    "List implicit defines in this bench-runner.",
1467    "List the available disk geometries.",
1468    "Override a bench define.",
1469    "Comma-separated list of disk geometries to bench.",
1470    "Comma-separated range of bench permutations to run (start,stop,step).",
1471    "Direct block device operations to this file.",
1472    "Direct trace output to this file.",
1473    "Include a backtrace with every trace statement.",
1474    "Sample trace output at this period in cycles.",
1475    "Sample trace output at this frequency in hz.",
1476    "Artificial read delay in seconds.",
1477    "Artificial prog delay in seconds.",
1478    "Artificial erase delay in seconds.",
1479};
1480
1481int main(int argc, char **argv) {
1482    void (*op)(void) = run;
1483
1484    size_t bench_override_capacity = 0;
1485    size_t bench_geometry_capacity = 0;
1486    size_t bench_id_capacity = 0;
1487
1488    // parse options
1489    while (true) {
1490        int c = getopt_long(argc, argv, short_opts, long_opts, NULL);
1491        switch (c) {
1492            // generate help message
1493            case OPT_HELP: {
1494                printf("usage: %s [options] [bench_id]\n", argv[0]);
1495                printf("\n");
1496
1497                printf("options:\n");
1498                size_t i = 0;
1499                while (long_opts[i].name) {
1500                    size_t indent;
1501                    if (long_opts[i].has_arg == no_argument) {
1502                        if (long_opts[i].val >= '0' && long_opts[i].val < 'z') {
1503                            indent = printf("  -%c, --%s ",
1504                                    long_opts[i].val,
1505                                    long_opts[i].name);
1506                        } else {
1507                            indent = printf("  --%s ",
1508                                    long_opts[i].name);
1509                        }
1510                    } else {
1511                        if (long_opts[i].val >= '0' && long_opts[i].val < 'z') {
1512                            indent = printf("  -%c %s, --%s %s ",
1513                                    long_opts[i].val,
1514                                    long_opts[i].name,
1515                                    long_opts[i].name,
1516                                    long_opts[i].name);
1517                        } else {
1518                            indent = printf("  --%s %s ",
1519                                    long_opts[i].name,
1520                                    long_opts[i].name);
1521                        }
1522                    }
1523
1524                    // a quick, hacky, byte-level method for text wrapping
1525                    size_t len = strlen(help_text[i]);
1526                    size_t j = 0;
1527                    if (indent < 24) {
1528                        printf("%*s %.80s\n",
1529                                (int)(24-1-indent),
1530                                "",
1531                                &help_text[i][j]);
1532                        j += 80;
1533                    } else {
1534                        printf("\n");
1535                    }
1536
1537                    while (j < len) {
1538                        printf("%24s%.80s\n", "", &help_text[i][j]);
1539                        j += 80;
1540                    }
1541
1542                    i += 1;
1543                }
1544
1545                printf("\n");
1546                exit(0);
1547            }
1548            // summary/list flags
1549            case OPT_SUMMARY:
1550                op = summary;
1551                break;
1552            case OPT_LIST_SUITES:
1553                op = list_suites;
1554                break;
1555            case OPT_LIST_CASES:
1556                op = list_cases;
1557                break;
1558            case OPT_LIST_SUITE_PATHS:
1559                op = list_suite_paths;
1560                break;
1561            case OPT_LIST_CASE_PATHS:
1562                op = list_case_paths;
1563                break;
1564            case OPT_LIST_DEFINES:
1565                op = list_defines;
1566                break;
1567            case OPT_LIST_PERMUTATION_DEFINES:
1568                op = list_permutation_defines;
1569                break;
1570            case OPT_LIST_IMPLICIT_DEFINES:
1571                op = list_implicit_defines;
1572                break;
1573            case OPT_LIST_GEOMETRIES:
1574                op = list_geometries;
1575                break;
1576            // configuration
1577            case OPT_DEFINE: {
1578                // allocate space
1579                bench_override_t *override = mappend(
1580                        (void**)&bench_overrides,
1581                        sizeof(bench_override_t),
1582                        &bench_override_count,
1583                        &bench_override_capacity);
1584
1585                // parse into string key/intmax_t value, cannibalizing the
1586                // arg in the process
1587                char *sep = strchr(optarg, '=');
1588                char *parsed = NULL;
1589                if (!sep) {
1590                    goto invalid_define;
1591                }
1592                *sep = '\0';
1593                override->name = optarg;
1594                optarg = sep+1;
1595
1596                // parse comma-separated permutations
1597                {
1598                    override->defines = NULL;
1599                    override->permutations = 0;
1600                    size_t override_capacity = 0;
1601                    while (true) {
1602                        optarg += strspn(optarg, " ");
1603
1604                        if (strncmp(optarg, "range", strlen("range")) == 0) {
1605                            // range of values
1606                            optarg += strlen("range");
1607                            optarg += strspn(optarg, " ");
1608                            if (*optarg != '(') {
1609                                goto invalid_define;
1610                            }
1611                            optarg += 1;
1612
1613                            intmax_t start = strtoumax(optarg, &parsed, 0);
1614                            intmax_t stop = -1;
1615                            intmax_t step = 1;
1616                            // allow empty string for start=0
1617                            if (parsed == optarg) {
1618                                start = 0;
1619                            }
1620                            optarg = parsed + strspn(parsed, " ");
1621
1622                            if (*optarg != ',' && *optarg != ')') {
1623                                goto invalid_define;
1624                            }
1625
1626                            if (*optarg == ',') {
1627                                optarg += 1;
1628                                stop = strtoumax(optarg, &parsed, 0);
1629                                // allow empty string for stop=end
1630                                if (parsed == optarg) {
1631                                    stop = -1;
1632                                }
1633                                optarg = parsed + strspn(parsed, " ");
1634
1635                                if (*optarg != ',' && *optarg != ')') {
1636                                    goto invalid_define;
1637                                }
1638
1639                                if (*optarg == ',') {
1640                                    optarg += 1;
1641                                    step = strtoumax(optarg, &parsed, 0);
1642                                    // allow empty string for stop=1
1643                                    if (parsed == optarg) {
1644                                        step = 1;
1645                                    }
1646                                    optarg = parsed + strspn(parsed, " ");
1647
1648                                    if (*optarg != ')') {
1649                                        goto invalid_define;
1650                                    }
1651                                }
1652                            } else {
1653                                // single value = stop only
1654                                stop = start;
1655                                start = 0;
1656                            }
1657
1658                            if (*optarg != ')') {
1659                                goto invalid_define;
1660                            }
1661                            optarg += 1;
1662
1663                            // calculate the range of values
1664                            assert(step != 0);
1665                            for (intmax_t i = start;
1666                                    (step < 0)
1667                                        ? i > stop
1668                                        : (uintmax_t)i < (uintmax_t)stop;
1669                                    i += step) {
1670                                *(intmax_t*)mappend(
1671                                        (void**)&override->defines,
1672                                        sizeof(intmax_t),
1673                                        &override->permutations,
1674                                        &override_capacity) = i;
1675                            }
1676                        } else if (*optarg != '\0') {
1677                            // single value
1678                            intmax_t define = strtoimax(optarg, &parsed, 0);
1679                            if (parsed == optarg) {
1680                                goto invalid_define;
1681                            }
1682                            optarg = parsed + strspn(parsed, " ");
1683                            *(intmax_t*)mappend(
1684                                    (void**)&override->defines,
1685                                    sizeof(intmax_t),
1686                                    &override->permutations,
1687                                    &override_capacity) = define;
1688                        } else {
1689                            break;
1690                        }
1691
1692                        if (*optarg == ',') {
1693                            optarg += 1;
1694                        }
1695                    }
1696                }
1697                assert(override->permutations > 0);
1698                break;
1699
1700invalid_define:
1701                fprintf(stderr, "error: invalid define: %s\n", optarg);
1702                exit(-1);
1703            }
1704            case OPT_GEOMETRY: {
1705                // reset our geometry scenarios
1706                if (bench_geometry_capacity > 0) {
1707                    free((bench_geometry_t*)bench_geometries);
1708                }
1709                bench_geometries = NULL;
1710                bench_geometry_count = 0;
1711                bench_geometry_capacity = 0;
1712
1713                // parse the comma separated list of disk geometries
1714                while (*optarg) {
1715                    // allocate space
1716                    bench_geometry_t *geometry = mappend(
1717                            (void**)&bench_geometries,
1718                            sizeof(bench_geometry_t),
1719                            &bench_geometry_count,
1720                            &bench_geometry_capacity);
1721
1722                    // parse the disk geometry
1723                    optarg += strspn(optarg, " ");
1724
1725                    // named disk geometry
1726                    size_t len = strcspn(optarg, " ,");
1727                    for (size_t i = 0; builtin_geometries[i].name; i++) {
1728                        if (len == strlen(builtin_geometries[i].name)
1729                                && memcmp(optarg,
1730                                    builtin_geometries[i].name,
1731                                    len) == 0) {
1732                            *geometry = builtin_geometries[i];
1733                            optarg += len;
1734                            goto geometry_next;
1735                        }
1736                    }
1737
1738                    // comma-separated read/prog/erase/count
1739                    if (*optarg == '{') {
1740                        lfs_size_t sizes[4];
1741                        size_t count = 0;
1742
1743                        char *s = optarg + 1;
1744                        while (count < 4) {
1745                            char *parsed = NULL;
1746                            sizes[count] = strtoumax(s, &parsed, 0);
1747                            count += 1;
1748
1749                            s = parsed + strspn(parsed, " ");
1750                            if (*s == ',') {
1751                                s += 1;
1752                                continue;
1753                            } else if (*s == '}') {
1754                                s += 1;
1755                                break;
1756                            } else {
1757                                goto geometry_unknown;
1758                            }
1759                        }
1760
1761                        // allow implicit r=p and p=e for common geometries
1762                        memset(geometry, 0, sizeof(bench_geometry_t));
1763                        if (count >= 3) {
1764                            geometry->defines[READ_SIZE_i]
1765                                    = BENCH_LIT(sizes[0]);
1766                            geometry->defines[PROG_SIZE_i]
1767                                    = BENCH_LIT(sizes[1]);
1768                            geometry->defines[ERASE_SIZE_i]
1769                                    = BENCH_LIT(sizes[2]);
1770                        } else if (count >= 2) {
1771                            geometry->defines[PROG_SIZE_i]
1772                                    = BENCH_LIT(sizes[0]);
1773                            geometry->defines[ERASE_SIZE_i]
1774                                    = BENCH_LIT(sizes[1]);
1775                        } else {
1776                            geometry->defines[ERASE_SIZE_i]
1777                                    = BENCH_LIT(sizes[0]);
1778                        }
1779                        if (count >= 4) {
1780                            geometry->defines[ERASE_COUNT_i]
1781                                    = BENCH_LIT(sizes[3]);
1782                        }
1783                        optarg = s;
1784                        goto geometry_next;
1785                    }
1786
1787                    // leb16-encoded read/prog/erase/count
1788                    if (*optarg == ':') {
1789                        lfs_size_t sizes[4];
1790                        size_t count = 0;
1791
1792                        char *s = optarg + 1;
1793                        while (true) {
1794                            char *parsed = NULL;
1795                            uintmax_t x = leb16_parse(s, &parsed);
1796                            if (parsed == s || count >= 4) {
1797                                break;
1798                            }
1799
1800                            sizes[count] = x;
1801                            count += 1;
1802                            s = parsed;
1803                        }
1804
1805                        // allow implicit r=p and p=e for common geometries
1806                        memset(geometry, 0, sizeof(bench_geometry_t));
1807                        if (count >= 3) {
1808                            geometry->defines[READ_SIZE_i]
1809                                    = BENCH_LIT(sizes[0]);
1810                            geometry->defines[PROG_SIZE_i]
1811                                    = BENCH_LIT(sizes[1]);
1812                            geometry->defines[ERASE_SIZE_i]
1813                                    = BENCH_LIT(sizes[2]);
1814                        } else if (count >= 2) {
1815                            geometry->defines[PROG_SIZE_i]
1816                                    = BENCH_LIT(sizes[0]);
1817                            geometry->defines[ERASE_SIZE_i]
1818                                    = BENCH_LIT(sizes[1]);
1819                        } else {
1820                            geometry->defines[ERASE_SIZE_i]
1821                                    = BENCH_LIT(sizes[0]);
1822                        }
1823                        if (count >= 4) {
1824                            geometry->defines[ERASE_COUNT_i]
1825                                    = BENCH_LIT(sizes[3]);
1826                        }
1827                        optarg = s;
1828                        goto geometry_next;
1829                    }
1830
1831geometry_unknown:
1832                    // unknown scenario?
1833                    fprintf(stderr, "error: unknown disk geometry: %s\n",
1834                            optarg);
1835                    exit(-1);
1836
1837geometry_next:
1838                    optarg += strspn(optarg, " ");
1839                    if (*optarg == ',') {
1840                        optarg += 1;
1841                    } else if (*optarg == '\0') {
1842                        break;
1843                    } else {
1844                        goto geometry_unknown;
1845                    }
1846                }
1847                break;
1848            }
1849            case OPT_STEP: {
1850                char *parsed = NULL;
1851                bench_step_start = strtoumax(optarg, &parsed, 0);
1852                bench_step_stop = -1;
1853                bench_step_step = 1;
1854                // allow empty string for start=0
1855                if (parsed == optarg) {
1856                    bench_step_start = 0;
1857                }
1858                optarg = parsed + strspn(parsed, " ");
1859
1860                if (*optarg != ',' && *optarg != '\0') {
1861                    goto step_unknown;
1862                }
1863
1864                if (*optarg == ',') {
1865                    optarg += 1;
1866                    bench_step_stop = strtoumax(optarg, &parsed, 0);
1867                    // allow empty string for stop=end
1868                    if (parsed == optarg) {
1869                        bench_step_stop = -1;
1870                    }
1871                    optarg = parsed + strspn(parsed, " ");
1872
1873                    if (*optarg != ',' && *optarg != '\0') {
1874                        goto step_unknown;
1875                    }
1876
1877                    if (*optarg == ',') {
1878                        optarg += 1;
1879                        bench_step_step = strtoumax(optarg, &parsed, 0);
1880                        // allow empty string for stop=1
1881                        if (parsed == optarg) {
1882                            bench_step_step = 1;
1883                        }
1884                        optarg = parsed + strspn(parsed, " ");
1885
1886                        if (*optarg != '\0') {
1887                            goto step_unknown;
1888                        }
1889                    }
1890                } else {
1891                    // single value = stop only
1892                    bench_step_stop = bench_step_start;
1893                    bench_step_start = 0;
1894                }
1895
1896                break;
1897step_unknown:
1898                fprintf(stderr, "error: invalid step: %s\n", optarg);
1899                exit(-1);
1900            }
1901            case OPT_DISK:
1902                bench_disk_path = optarg;
1903                break;
1904            case OPT_TRACE:
1905                bench_trace_path = optarg;
1906                break;
1907            case OPT_TRACE_BACKTRACE:
1908                bench_trace_backtrace = true;
1909                break;
1910            case OPT_TRACE_PERIOD: {
1911                char *parsed = NULL;
1912                bench_trace_period = strtoumax(optarg, &parsed, 0);
1913                if (parsed == optarg) {
1914                    fprintf(stderr, "error: invalid trace-period: %s\n", optarg);
1915                    exit(-1);
1916                }
1917                break;
1918            }
1919            case OPT_TRACE_FREQ: {
1920                char *parsed = NULL;
1921                bench_trace_freq = strtoumax(optarg, &parsed, 0);
1922                if (parsed == optarg) {
1923                    fprintf(stderr, "error: invalid trace-freq: %s\n", optarg);
1924                    exit(-1);
1925                }
1926                break;
1927            }
1928            case OPT_READ_SLEEP: {
1929                char *parsed = NULL;
1930                double read_sleep = strtod(optarg, &parsed);
1931                if (parsed == optarg) {
1932                    fprintf(stderr, "error: invalid read-sleep: %s\n", optarg);
1933                    exit(-1);
1934                }
1935                bench_read_sleep = read_sleep*1.0e9;
1936                break;
1937            }
1938            case OPT_PROG_SLEEP: {
1939                char *parsed = NULL;
1940                double prog_sleep = strtod(optarg, &parsed);
1941                if (parsed == optarg) {
1942                    fprintf(stderr, "error: invalid prog-sleep: %s\n", optarg);
1943                    exit(-1);
1944                }
1945                bench_prog_sleep = prog_sleep*1.0e9;
1946                break;
1947            }
1948            case OPT_ERASE_SLEEP: {
1949                char *parsed = NULL;
1950                double erase_sleep = strtod(optarg, &parsed);
1951                if (parsed == optarg) {
1952                    fprintf(stderr, "error: invalid erase-sleep: %s\n", optarg);
1953                    exit(-1);
1954                }
1955                bench_erase_sleep = erase_sleep*1.0e9;
1956                break;
1957            }
1958            // done parsing
1959            case -1:
1960                goto getopt_done;
1961            // unknown arg, getopt prints a message for us
1962            default:
1963                exit(-1);
1964        }
1965    }
1966getopt_done: ;
1967
1968    if (argc > optind) {
1969        // reset our bench identifier list
1970        bench_ids = NULL;
1971        bench_id_count = 0;
1972        bench_id_capacity = 0;
1973    }
1974
1975    // parse bench identifier, if any, cannibalizing the arg in the process
1976    for (; argc > optind; optind++) {
1977        bench_define_t *defines = NULL;
1978        size_t define_count = 0;
1979
1980        // parse name, can be suite or case
1981        char *name = argv[optind];
1982        char *defines_ = strchr(name, ':');
1983        if (defines_) {
1984            *defines_ = '\0';
1985            defines_ += 1;
1986        }
1987
1988        // remove optional path and .toml suffix
1989        char *slash = strrchr(name, '/');
1990        if (slash) {
1991            name = slash+1;
1992        }
1993
1994        size_t name_len = strlen(name);
1995        if (name_len > 5 && strcmp(&name[name_len-5], ".toml") == 0) {
1996            name[name_len-5] = '\0';
1997        }
1998
1999        if (defines_) {
2000            // parse defines
2001            while (true) {
2002                char *parsed;
2003                size_t d = leb16_parse(defines_, &parsed);
2004                intmax_t v = leb16_parse(parsed, &parsed);
2005                if (parsed == defines_) {
2006                    break;
2007                }
2008                defines_ = parsed;
2009
2010                if (d >= define_count) {
2011                    // align to power of two to avoid any superlinear growth
2012                    size_t ncount = 1 << lfs_npw2(d+1);
2013                    defines = realloc(defines,
2014                            ncount*sizeof(bench_define_t));
2015                    memset(defines+define_count, 0,
2016                            (ncount-define_count)*sizeof(bench_define_t));
2017                    define_count = ncount;
2018                }
2019                defines[d] = BENCH_LIT(v);
2020            }
2021        }
2022
2023        // append to identifier list
2024        *(bench_id_t*)mappend(
2025                (void**)&bench_ids,
2026                sizeof(bench_id_t),
2027                &bench_id_count,
2028                &bench_id_capacity) = (bench_id_t){
2029            .name = name,
2030            .defines = defines,
2031            .define_count = define_count,
2032        };
2033    }
2034
2035    // do the thing
2036    op();
2037
2038    // cleanup (need to be done for valgrind benching)
2039    bench_define_cleanup();
2040    if (bench_overrides) {
2041        for (size_t i = 0; i < bench_override_count; i++) {
2042            free((void*)bench_overrides[i].defines);
2043        }
2044        free((void*)bench_overrides);
2045    }
2046    if (bench_geometry_capacity) {
2047        free((void*)bench_geometries);
2048    }
2049    if (bench_id_capacity) {
2050        for (size_t i = 0; i < bench_id_count; i++) {
2051            free((void*)bench_ids[i].defines);
2052        }
2053        free((void*)bench_ids);
2054    }
2055}
2056