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