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