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     };
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 
run(void)1371 static 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
1404 enum 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 
1428 const char *short_opts = "hYlLD:G:s:d:t:";
1429 
1430 const 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 
1457 const 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 
main(int argc,char ** argv)1481 int 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 
1700 invalid_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 
1831 geometry_unknown:
1832                     // unknown scenario?
1833                     fprintf(stderr, "error: unknown disk geometry: %s\n",
1834                             optarg);
1835                     exit(-1);
1836 
1837 geometry_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;
1897 step_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     }
1966 getopt_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