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