Эх сурвалжийг харах

Several tweaks to script flags

- Changed multi-field flags to action=append instead of comma-separated.
- Dropped short-names for geometries/powerlosses
- Renamed -Pexponential -> -Plog
- Allowed omitting the 0 for -W0/-H0/-n0 and made -j0 consistent
- Better handling of --xlim/--ylim
Christopher Haster 3 жил өмнө
parent
commit
9507e6243c

+ 4 - 4
Makefile

@@ -170,10 +170,10 @@ coverage: $(GCDA)
 .PHONY: summary sizes
 .PHONY: summary sizes
 summary sizes: $(BUILDDIR)lfs.csv
 summary sizes: $(BUILDDIR)lfs.csv
 	$(strip ./scripts/summary.py -Y $^ \
 	$(strip ./scripts/summary.py -Y $^ \
-		-fcode=code_size,$\
-			data=data_size,$\
-			stack=stack_limit,$\
-			struct=struct_size \
+		-fcode=code_size \
+		-fdata=data_size \
+		-fstack=stack_limit \
+		-fstruct=struct_size \
 		--max=stack \
 		--max=stack \
 		$(SUMMARYFLAGS))
 		$(SUMMARYFLAGS))
 
 

+ 19 - 25
runners/bench_runner.c

@@ -90,9 +90,7 @@ static uintmax_t leb16_parse(const char *s, char **tail) {
 // bench_runner types
 // bench_runner types
 
 
 typedef struct bench_geometry {
 typedef struct bench_geometry {
-    char short_name;
-    const char *long_name;
-
+    const char *name;
     bench_define_t defines[BENCH_GEOMETRY_DEFINE_COUNT];
     bench_define_t defines[BENCH_GEOMETRY_DEFINE_COUNT];
 } bench_geometry_t;
 } bench_geometry_t;
 
 
@@ -1057,7 +1055,7 @@ static void list_implicit_defines(void) {
 
 
     // make sure to include builtin geometries here
     // make sure to include builtin geometries here
     extern const bench_geometry_t builtin_geometries[];
     extern const bench_geometry_t builtin_geometries[];
-    for (size_t g = 0; builtin_geometries[g].long_name; g++) {
+    for (size_t g = 0; builtin_geometries[g].name; g++) {
         bench_define_geometry(&builtin_geometries[g]);
         bench_define_geometry(&builtin_geometries[g]);
         bench_define_flush();
         bench_define_flush();
 
 
@@ -1089,12 +1087,12 @@ static void list_implicit_defines(void) {
 // geometries to bench
 // geometries to bench
 
 
 const bench_geometry_t builtin_geometries[] = {
 const bench_geometry_t builtin_geometries[] = {
-    {'d', "default", {{NULL}, BENCH_CONST(16),   BENCH_CONST(512),   {NULL}}},
-    {'e', "eeprom",  {{NULL}, BENCH_CONST(1),    BENCH_CONST(512),   {NULL}}},
-    {'E', "emmc",    {{NULL}, {NULL},            BENCH_CONST(512),   {NULL}}},
-    {'n', "nor",     {{NULL}, BENCH_CONST(1),    BENCH_CONST(4096),  {NULL}}},
-    {'N', "nand",    {{NULL}, BENCH_CONST(4096), BENCH_CONST(32768), {NULL}}},
-    {0, NULL, {{NULL}, {NULL}, {NULL}, {NULL}}},
+    {"default", {{NULL}, BENCH_CONST(16),   BENCH_CONST(512),   {NULL}}},
+    {"eeprom",  {{NULL}, BENCH_CONST(1),    BENCH_CONST(512),   {NULL}}},
+    {"emmc",    {{NULL}, {NULL},            BENCH_CONST(512),   {NULL}}},
+    {"nor",     {{NULL}, BENCH_CONST(1),    BENCH_CONST(4096),  {NULL}}},
+    {"nand",    {{NULL}, BENCH_CONST(4096), BENCH_CONST(32768), {NULL}}},
+    {NULL, {{NULL}, {NULL}, {NULL}, {NULL}}},
 };
 };
 
 
 const bench_geometry_t *bench_geometries = builtin_geometries;
 const bench_geometry_t *bench_geometries = builtin_geometries;
@@ -1107,12 +1105,11 @@ static void list_geometries(void) {
 
 
     printf("%-24s %7s %7s %7s %7s %11s\n",
     printf("%-24s %7s %7s %7s %7s %11s\n",
             "geometry", "read", "prog", "erase", "count", "size");
             "geometry", "read", "prog", "erase", "count", "size");
-    for (size_t g = 0; builtin_geometries[g].long_name; g++) {
+    for (size_t g = 0; builtin_geometries[g].name; g++) {
         bench_define_geometry(&builtin_geometries[g]);
         bench_define_geometry(&builtin_geometries[g]);
         bench_define_flush();
         bench_define_flush();
-        printf("%c,%-22s %7ju %7ju %7ju %7ju %11ju\n",
-                builtin_geometries[g].short_name,
-                builtin_geometries[g].long_name,
+        printf("%-24s %7ju %7ju %7ju %7ju %11ju\n",
+                builtin_geometries[g].name,
                 READ_SIZE,
                 READ_SIZE,
                 PROG_SIZE,
                 PROG_SIZE,
                 BLOCK_SIZE,
                 BLOCK_SIZE,
@@ -1253,7 +1250,7 @@ enum opt_flags {
     OPT_LIST_IMPLICIT_DEFINES    = 5,
     OPT_LIST_IMPLICIT_DEFINES    = 5,
     OPT_LIST_GEOMETRIES          = 6,
     OPT_LIST_GEOMETRIES          = 6,
     OPT_DEFINE                   = 'D',
     OPT_DEFINE                   = 'D',
-    OPT_GEOMETRY                 = 'g',
+    OPT_GEOMETRY                 = 'G',
     OPT_STEP                     = 's',
     OPT_STEP                     = 's',
     OPT_DISK                     = 'd',
     OPT_DISK                     = 'd',
     OPT_TRACE                    = 't',
     OPT_TRACE                    = 't',
@@ -1262,7 +1259,7 @@ enum opt_flags {
     OPT_ERASE_SLEEP              = 9,
     OPT_ERASE_SLEEP              = 9,
 };
 };
 
 
-const char *short_opts = "hYlLD:g:s:d:t:";
+const char *short_opts = "hYlLD:G:s:d:t:";
 
 
 const struct option long_opts[] = {
 const struct option long_opts[] = {
     {"help",             no_argument,       NULL, OPT_HELP},
     {"help",             no_argument,       NULL, OPT_HELP},
@@ -1300,7 +1297,7 @@ const char *const help_text[] = {
     "List implicit defines in this bench-runner.",
     "List implicit defines in this bench-runner.",
     "List the available disk geometries.",
     "List the available disk geometries.",
     "Override a bench define.",
     "Override a bench define.",
-    "Comma-separated list of disk geometries to bench. Defaults to d,e,E,n,N.",
+    "Comma-separated list of disk geometries to bench.",
     "Comma-separated range of bench permutations to run (start,stop,step).",
     "Comma-separated range of bench permutations to run (start,stop,step).",
     "Redirect block device operations to this file.",
     "Redirect block device operations to this file.",
     "Redirect trace output to this file.",
     "Redirect trace output to this file.",
@@ -1555,14 +1552,11 @@ invalid_define:
 
 
                     // named disk geometry
                     // named disk geometry
                     size_t len = strcspn(optarg, " ,");
                     size_t len = strcspn(optarg, " ,");
-                    for (size_t i = 0; builtin_geometries[i].long_name; i++) {
-                        if ((len == 1
-                                && *optarg == builtin_geometries[i].short_name)
-                                || (len == strlen(
-                                        builtin_geometries[i].long_name)
-                                    && memcmp(optarg,
-                                        builtin_geometries[i].long_name,
-                                        len) == 0))  {
+                    for (size_t i = 0; builtin_geometries[i].name; i++) {
+                        if (len == strlen(builtin_geometries[i].name)
+                                && memcmp(optarg,
+                                    builtin_geometries[i].name,
+                                    len) == 0) {
                             *geometry = builtin_geometries[i];
                             *geometry = builtin_geometries[i];
                             optarg += len;
                             optarg += len;
                             goto geometry_next;
                             goto geometry_next;

+ 44 - 51
runners/test_runner.c

@@ -90,16 +90,12 @@ static uintmax_t leb16_parse(const char *s, char **tail) {
 // test_runner types
 // test_runner types
 
 
 typedef struct test_geometry {
 typedef struct test_geometry {
-    char short_name;
-    const char *long_name;
-
+    const char *name;
     test_define_t defines[TEST_GEOMETRY_DEFINE_COUNT];
     test_define_t defines[TEST_GEOMETRY_DEFINE_COUNT];
 } test_geometry_t;
 } test_geometry_t;
 
 
 typedef struct test_powerloss {
 typedef struct test_powerloss {
-    char short_name;
-    const char *long_name;
-
+    const char *name;
     void (*run)(
     void (*run)(
             const lfs_emubd_powercycles_t *cycles,
             const lfs_emubd_powercycles_t *cycles,
             size_t cycle_count,
             size_t cycle_count,
@@ -574,6 +570,11 @@ void test_seen_cleanup(test_seen_t *seen) {
     free(seen->branches);
     free(seen->branches);
 }
 }
 
 
+static void run_powerloss_none(
+        const lfs_emubd_powercycles_t *cycles,
+        size_t cycle_count,
+        const struct test_suite *suite,
+        const struct test_case *case_);
 static void run_powerloss_cycles(
 static void run_powerloss_cycles(
         const lfs_emubd_powercycles_t *cycles,
         const lfs_emubd_powercycles_t *cycles,
         size_t cycle_count,
         size_t cycle_count,
@@ -606,7 +607,7 @@ static void case_forperm(
         } else {
         } else {
             for (size_t p = 0; p < test_powerloss_count; p++) {
             for (size_t p = 0; p < test_powerloss_count; p++) {
                 // skip non-reentrant tests when powerloss testing
                 // skip non-reentrant tests when powerloss testing
-                if (test_powerlosses[p].short_name != '0'
+                if (test_powerlosses[p].run != run_powerloss_none
                         && !(case_->flags & TEST_REENTRANT)) {
                         && !(case_->flags & TEST_REENTRANT)) {
                     continue;
                     continue;
                 }
                 }
@@ -646,7 +647,7 @@ static void case_forperm(
                 } else {
                 } else {
                     for (size_t p = 0; p < test_powerloss_count; p++) {
                     for (size_t p = 0; p < test_powerloss_count; p++) {
                         // skip non-reentrant tests when powerloss testing
                         // skip non-reentrant tests when powerloss testing
-                        if (test_powerlosses[p].short_name != '0'
+                        if (test_powerlosses[p].run != run_powerloss_none
                                 && !(case_->flags & TEST_REENTRANT)) {
                                 && !(case_->flags & TEST_REENTRANT)) {
                             continue;
                             continue;
                         }
                         }
@@ -1094,7 +1095,7 @@ static void list_implicit_defines(void) {
 
 
     // make sure to include builtin geometries here
     // make sure to include builtin geometries here
     extern const test_geometry_t builtin_geometries[];
     extern const test_geometry_t builtin_geometries[];
-    for (size_t g = 0; builtin_geometries[g].long_name; g++) {
+    for (size_t g = 0; builtin_geometries[g].name; g++) {
         test_define_geometry(&builtin_geometries[g]);
         test_define_geometry(&builtin_geometries[g]);
         test_define_flush();
         test_define_flush();
 
 
@@ -1126,12 +1127,12 @@ static void list_implicit_defines(void) {
 // geometries to test
 // geometries to test
 
 
 const test_geometry_t builtin_geometries[] = {
 const test_geometry_t builtin_geometries[] = {
-    {'d', "default", {{NULL}, TEST_CONST(16),   TEST_CONST(512),   {NULL}}},
-    {'e', "eeprom",  {{NULL}, TEST_CONST(1),    TEST_CONST(512),   {NULL}}},
-    {'E', "emmc",    {{NULL}, {NULL},           TEST_CONST(512),   {NULL}}},
-    {'n', "nor",     {{NULL}, TEST_CONST(1),    TEST_CONST(4096),  {NULL}}},
-    {'N', "nand",    {{NULL}, TEST_CONST(4096), TEST_CONST(32768), {NULL}}},
-    {0, NULL, {{NULL}, {NULL}, {NULL}, {NULL}}},
+    {"default", {{NULL}, TEST_CONST(16),   TEST_CONST(512),   {NULL}}},
+    {"eeprom",  {{NULL}, TEST_CONST(1),    TEST_CONST(512),   {NULL}}},
+    {"emmc",    {{NULL}, {NULL},           TEST_CONST(512),   {NULL}}},
+    {"nor",     {{NULL}, TEST_CONST(1),    TEST_CONST(4096),  {NULL}}},
+    {"nand",    {{NULL}, TEST_CONST(4096), TEST_CONST(32768), {NULL}}},
+    {NULL, {{NULL}, {NULL}, {NULL}, {NULL}}},
 };
 };
 
 
 const test_geometry_t *test_geometries = builtin_geometries;
 const test_geometry_t *test_geometries = builtin_geometries;
@@ -1144,12 +1145,11 @@ static void list_geometries(void) {
 
 
     printf("%-24s %7s %7s %7s %7s %11s\n",
     printf("%-24s %7s %7s %7s %7s %11s\n",
             "geometry", "read", "prog", "erase", "count", "size");
             "geometry", "read", "prog", "erase", "count", "size");
-    for (size_t g = 0; builtin_geometries[g].long_name; g++) {
+    for (size_t g = 0; builtin_geometries[g].name; g++) {
         test_define_geometry(&builtin_geometries[g]);
         test_define_geometry(&builtin_geometries[g]);
         test_define_flush();
         test_define_flush();
-        printf("%c,%-22s %7ju %7ju %7ju %7ju %11ju\n",
-                builtin_geometries[g].short_name,
-                builtin_geometries[g].long_name,
+        printf("%-24s %7ju %7ju %7ju %7ju %11ju\n",
+                builtin_geometries[g].name,
                 READ_SIZE,
                 READ_SIZE,
                 PROG_SIZE,
                 PROG_SIZE,
                 BLOCK_SIZE,
                 BLOCK_SIZE,
@@ -1314,7 +1314,7 @@ static void run_powerloss_linear(
     }
     }
 }
 }
 
 
-static void run_powerloss_exponential(
+static void run_powerloss_log(
         const lfs_emubd_powercycles_t *cycles,
         const lfs_emubd_powercycles_t *cycles,
         size_t cycle_count,
         size_t cycle_count,
         const struct test_suite *suite,
         const struct test_suite *suite,
@@ -1646,11 +1646,11 @@ static void run_powerloss_exhaustive(
 
 
 
 
 const test_powerloss_t builtin_powerlosses[] = {
 const test_powerloss_t builtin_powerlosses[] = {
-    {'0', "none",        run_powerloss_none,        NULL, 0},
-    {'e', "exponential", run_powerloss_exponential, NULL, 0},
-    {'l', "linear",      run_powerloss_linear,      NULL, 0},
-    {'x', "exhaustive",  run_powerloss_exhaustive,  NULL, SIZE_MAX},
-    {0, NULL, NULL, NULL, 0},
+    {"none",       run_powerloss_none,       NULL, 0},
+    {"log",        run_powerloss_log,        NULL, 0},
+    {"linear",     run_powerloss_linear,     NULL, 0},
+    {"exhaustive", run_powerloss_exhaustive, NULL, SIZE_MAX},
+    {NULL, NULL, NULL, 0},
 };
 };
 
 
 const char *const builtin_powerlosses_help[] = {
 const char *const builtin_powerlosses_help[] = {
@@ -1664,17 +1664,16 @@ const char *const builtin_powerlosses_help[] = {
 };
 };
 
 
 const test_powerloss_t *test_powerlosses = (const test_powerloss_t[]){
 const test_powerloss_t *test_powerlosses = (const test_powerloss_t[]){
-    {'0', "none", run_powerloss_none, NULL, 0},
+    {"none", run_powerloss_none, NULL, 0},
 };
 };
 size_t test_powerloss_count = 1;
 size_t test_powerloss_count = 1;
 
 
 static void list_powerlosses(void) {
 static void list_powerlosses(void) {
     printf("%-24s %s\n", "scenario", "description");
     printf("%-24s %s\n", "scenario", "description");
     size_t i = 0;
     size_t i = 0;
-    for (; builtin_powerlosses[i].long_name; i++) {
-        printf("%c,%-22s %s\n",
-                builtin_powerlosses[i].short_name,
-                builtin_powerlosses[i].long_name,
+    for (; builtin_powerlosses[i].name; i++) {
+        printf("%-24s %s\n",
+                builtin_powerlosses[i].name,
                 builtin_powerlosses_help[i]);
                 builtin_powerlosses_help[i]);
     }
     }
 
 
@@ -1765,8 +1764,8 @@ enum opt_flags {
     OPT_LIST_GEOMETRIES          = 6,
     OPT_LIST_GEOMETRIES          = 6,
     OPT_LIST_POWERLOSSES         = 7,
     OPT_LIST_POWERLOSSES         = 7,
     OPT_DEFINE                   = 'D',
     OPT_DEFINE                   = 'D',
-    OPT_GEOMETRY                 = 'g',
-    OPT_POWERLOSS                = 'p',
+    OPT_GEOMETRY                 = 'G',
+    OPT_POWERLOSS                = 'P',
     OPT_STEP                     = 's',
     OPT_STEP                     = 's',
     OPT_DISK                     = 'd',
     OPT_DISK                     = 'd',
     OPT_TRACE                    = 't',
     OPT_TRACE                    = 't',
@@ -1775,7 +1774,7 @@ enum opt_flags {
     OPT_ERASE_SLEEP              = 10,
     OPT_ERASE_SLEEP              = 10,
 };
 };
 
 
-const char *short_opts = "hYlLD:g:p:s:d:t:";
+const char *short_opts = "hYlLD:G:P:s:d:t:";
 
 
 const struct option long_opts[] = {
 const struct option long_opts[] = {
     {"help",             no_argument,       NULL, OPT_HELP},
     {"help",             no_argument,       NULL, OPT_HELP},
@@ -1816,8 +1815,8 @@ const char *const help_text[] = {
     "List the available disk geometries.",
     "List the available disk geometries.",
     "List the available power-loss scenarios.",
     "List the available power-loss scenarios.",
     "Override a test define.",
     "Override a test define.",
-    "Comma-separated list of disk geometries to test. Defaults to d,e,E,n,N.",
-    "Comma-separated list of power-loss scenarios to test. Defaults to 0,l.",
+    "Comma-separated list of disk geometries to test.",
+    "Comma-separated list of power-loss scenarios to test.",
     "Comma-separated range of test permutations to run (start,stop,step).",
     "Comma-separated range of test permutations to run (start,stop,step).",
     "Redirect block device operations to this file.",
     "Redirect block device operations to this file.",
     "Redirect trace output to this file.",
     "Redirect trace output to this file.",
@@ -2076,14 +2075,11 @@ invalid_define:
 
 
                     // named disk geometry
                     // named disk geometry
                     size_t len = strcspn(optarg, " ,");
                     size_t len = strcspn(optarg, " ,");
-                    for (size_t i = 0; builtin_geometries[i].long_name; i++) {
-                        if ((len == 1
-                                && *optarg == builtin_geometries[i].short_name)
-                                || (len == strlen(
-                                        builtin_geometries[i].long_name)
-                                    && memcmp(optarg,
-                                        builtin_geometries[i].long_name,
-                                        len) == 0))  {
+                    for (size_t i = 0; builtin_geometries[i].name; i++) {
+                        if (len == strlen(builtin_geometries[i].name)
+                                && memcmp(optarg,
+                                    builtin_geometries[i].name,
+                                    len) == 0)  {
                             *geometry = builtin_geometries[i];
                             *geometry = builtin_geometries[i];
                             optarg += len;
                             optarg += len;
                             goto geometry_next;
                             goto geometry_next;
@@ -2224,14 +2220,11 @@ geometry_next:
 
 
                     // named power-loss scenario
                     // named power-loss scenario
                     size_t len = strcspn(optarg, " ,");
                     size_t len = strcspn(optarg, " ,");
-                    for (size_t i = 0; builtin_powerlosses[i].long_name; i++) {
-                        if ((len == 1
-                                && *optarg == builtin_powerlosses[i].short_name)
-                                || (len == strlen(
-                                        builtin_powerlosses[i].long_name)
-                                    && memcmp(optarg,
-                                        builtin_powerlosses[i].long_name,
-                                        len) == 0))  {
+                    for (size_t i = 0; builtin_powerlosses[i].name; i++) {
+                        if (len == strlen(builtin_powerlosses[i].name)
+                                && memcmp(optarg,
+                                    builtin_powerlosses[i].name,
+                                    len) == 0) {
                             *powerloss = builtin_powerlosses[i];
                             *powerloss = builtin_powerlosses[i];
                             optarg += len;
                             optarg += len;
                             goto powerloss_next;
                             goto powerloss_next;

+ 9 - 6
scripts/bench.py

@@ -511,7 +511,7 @@ def find_runner(runner, **args):
 
 
     # other context
     # other context
     if args.get('geometry'):
     if args.get('geometry'):
-        cmd.append('-g%s' % args['geometry'])
+        cmd.append('-G%s' % args['geometry'])
     if args.get('disk'):
     if args.get('disk'):
         cmd.append('-d%s' % args['disk'])
         cmd.append('-d%s' % args['disk'])
     if args.get('trace'):
     if args.get('trace'):
@@ -1003,6 +1003,10 @@ def run(runner, bench_ids=[], **args):
         total_perms))
         total_perms))
     print()
     print()
 
 
+    # automatic job detection?
+    if args.get('jobs') == 0:
+        args['jobs'] = len(os.sched_getaffinity(0))
+
     # truncate and open logs here so they aren't disconnected between benches
     # truncate and open logs here so they aren't disconnected between benches
     stdout = None
     stdout = None
     if args.get('stdout'):
     if args.get('stdout'):
@@ -1246,9 +1250,8 @@ if __name__ == "__main__":
         action='append',
         action='append',
         help="Override a bench define.")
         help="Override a bench define.")
     bench_parser.add_argument(
     bench_parser.add_argument(
-        '-g', '--geometry',
-        help="Comma-separated list of disk geometries to bench. "
-            "Defaults to d,e,E,n,N.")
+        '-G', '--geometry',
+        help="Comma-separated list of disk geometries to bench.")
     bench_parser.add_argument(
     bench_parser.add_argument(
         '-d', '--disk',
         '-d', '--disk',
         help="Direct block device operations to this file.")
         help="Direct block device operations to this file.")
@@ -1274,8 +1277,8 @@ if __name__ == "__main__":
         '-j', '--jobs',
         '-j', '--jobs',
         nargs='?',
         nargs='?',
         type=lambda x: int(x, 0),
         type=lambda x: int(x, 0),
-        const=len(os.sched_getaffinity(0)),
-        help="Number of parallel runners to run.")
+        const=0,
+        help="Number of parallel runners to run. 0 runs one runner per core.")
     bench_parser.add_argument(
     bench_parser.add_argument(
         '-k', '--keep-going',
         '-k', '--keep-going',
         action='store_true',
         action='store_true',

+ 31 - 25
scripts/plot.py

@@ -479,8 +479,8 @@ def main(csv_paths, *,
         x=None,
         x=None,
         y=None,
         y=None,
         define=[],
         define=[],
-        xlim=None,
-        ylim=None,
+        xlim=(None,None),
+        ylim=(None,None),
         width=None,
         width=None,
         height=17,
         height=17,
         cat=False,
         cat=False,
@@ -489,7 +489,7 @@ def main(csv_paths, *,
         colors=None,
         colors=None,
         chars=None,
         chars=None,
         line_chars=None,
         line_chars=None,
-        no_lines=False,
+        points=False,
         legend=None,
         legend=None,
         keep_open=False,
         keep_open=False,
         sleep=None,
         sleep=None,
@@ -503,9 +503,9 @@ def main(csv_paths, *,
         color = False
         color = False
 
 
     # allow shortened ranges
     # allow shortened ranges
-    if xlim is not None and len(xlim) == 1:
+    if len(xlim) == 1:
         xlim = (0, xlim[0])
         xlim = (0, xlim[0])
-    if ylim is not None and len(ylim) == 1:
+    if len(ylim) == 1:
         ylim = (0, ylim[0])
         ylim = (0, ylim[0])
 
 
     # separate out renames
     # separate out renames
@@ -544,7 +544,7 @@ def main(csv_paths, *,
 
 
         if line_chars is not None:
         if line_chars is not None:
             line_chars_ = line_chars
             line_chars_ = line_chars
-        elif not no_lines:
+        elif not points:
             line_chars_ = [True]
             line_chars_ = [True]
         else:
         else:
             line_chars_ = [False]
             line_chars_ = [False]
@@ -567,28 +567,26 @@ def main(csv_paths, *,
                     legend_width = max(legend_width, len(label)+1)
                     legend_width = max(legend_width, len(label)+1)
 
 
         # find xlim/ylim
         # find xlim/ylim
-        if xlim is not None:
-            xlim_ = xlim
-        else:
-            xlim_ = (
-                min(it.chain([0], (k
+        xlim_ = (
+            xlim[0] if xlim[0] is not None
+                else min(it.chain([0], (k
                     for r in datasets_.values()
                     for r in datasets_.values()
                     for k, v in r.items()
                     for k, v in r.items()
                     if v is not None))),
                     if v is not None))),
-                max(it.chain([0], (k
+            xlim[1] if xlim[1] is not None
+                else max(it.chain([0], (k
                     for r in datasets_.values()
                     for r in datasets_.values()
                     for k, v in r.items()
                     for k, v in r.items()
                     if v is not None))))
                     if v is not None))))
 
 
-        if ylim is not None:
-            ylim_ = ylim
-        else:
-            ylim_ = (
-                min(it.chain([0], (v
+        ylim_ = (
+            ylim[0] if ylim[0] is not None
+                else min(it.chain([0], (v
                     for r in datasets_.values()
                     for r in datasets_.values()
                     for _, v in r.items()
                     for _, v in r.items()
                     if v is not None))),
                     if v is not None))),
-                max(it.chain([0], (v
+            ylim[1] if ylim[1] is not None
+                else max(it.chain([0], (v
                     for r in datasets_.values()
                     for r in datasets_.values()
                     for _, v in r.items()
                     for _, v in r.items()
                     if v is not None))))
                     if v is not None))))
@@ -740,17 +738,17 @@ if __name__ == "__main__":
             "or list of paths. Defaults to %r." % CSV_PATHS)
             "or list of paths. Defaults to %r." % CSV_PATHS)
     parser.add_argument(
     parser.add_argument(
         '-b', '--by',
         '-b', '--by',
-        type=lambda x: [x.strip() for x in x.split(',')],
+        action='append',
         help="Fields to render as separate plots. All other fields will be "
         help="Fields to render as separate plots. All other fields will be "
             "summed as needed. Can rename fields with new_name=old_name.")
             "summed as needed. Can rename fields with new_name=old_name.")
     parser.add_argument(
     parser.add_argument(
         '-x',
         '-x',
-        type=lambda x: [x.strip() for x in x.split(',')],
+        action='append',
         help="Fields to use for the x-axis. Can rename fields with "
         help="Fields to use for the x-axis. Can rename fields with "
             "new_name=old_name.")
             "new_name=old_name.")
     parser.add_argument(
     parser.add_argument(
         '-y',
         '-y',
-        type=lambda x: [x.strip() for x in x.split(',')],
+        action='append',
         help="Fields to use for the y-axis. Can rename fields with "
         help="Fields to use for the y-axis. Can rename fields with "
             "new_name=old_name.")
             "new_name=old_name.")
     parser.add_argument(
     parser.add_argument(
@@ -771,7 +769,7 @@ if __name__ == "__main__":
             "sometimes suffer from inconsistent widths.")
             "sometimes suffer from inconsistent widths.")
     parser.add_argument(
     parser.add_argument(
         '--colors',
         '--colors',
-        type=lambda x: x.split(','),
+        type=lambda x: [x.strip() for x in x.split(',')],
         help="Colors to use.")
         help="Colors to use.")
     parser.add_argument(
     parser.add_argument(
         '--chars',
         '--chars',
@@ -780,17 +778,21 @@ if __name__ == "__main__":
         '--line-chars',
         '--line-chars',
         help="Characters to use for lines.")
         help="Characters to use for lines.")
     parser.add_argument(
     parser.add_argument(
-        '-L', '--no-lines',
+        '-.', '--points',
         action='store_true',
         action='store_true',
         help="Only draw the data points.")
         help="Only draw the data points.")
     parser.add_argument(
     parser.add_argument(
         '-W', '--width',
         '-W', '--width',
+        nargs='?',
         type=lambda x: int(x, 0),
         type=lambda x: int(x, 0),
+        const=0,
         help="Width in columns. 0 uses the terminal width. Defaults to "
         help="Width in columns. 0 uses the terminal width. Defaults to "
             "min(terminal, 80).")
             "min(terminal, 80).")
     parser.add_argument(
     parser.add_argument(
         '-H', '--height',
         '-H', '--height',
+        nargs='?',
         type=lambda x: int(x, 0),
         type=lambda x: int(x, 0),
+        const=0,
         help="Height in rows. 0 uses the terminal height. Defaults to 17.")
         help="Height in rows. 0 uses the terminal height. Defaults to 17.")
     parser.add_argument(
     parser.add_argument(
         '-z', '--cat',
         '-z', '--cat',
@@ -798,11 +800,15 @@ if __name__ == "__main__":
         help="Pipe directly to stdout.")
         help="Pipe directly to stdout.")
     parser.add_argument(
     parser.add_argument(
         '-X', '--xlim',
         '-X', '--xlim',
-        type=lambda x: tuple(dat(x) if x else None for x in x.split(',')),
+        type=lambda x: tuple(
+            dat(x) if x.strip() else None
+            for x in x.split(',')),
         help="Range for the x-axis.")
         help="Range for the x-axis.")
     parser.add_argument(
     parser.add_argument(
         '-Y', '--ylim',
         '-Y', '--ylim',
-        type=lambda x: tuple(dat(x) if x else None for x in x.split(',')),
+        type=lambda x: tuple(
+            dat(x) if x.strip() else None
+            for x in x.split(',')),
         help="Range for the y-axis.")
         help="Range for the y-axis.")
     parser.add_argument(
     parser.add_argument(
         '--xlog',
         '--xlog',

+ 10 - 10
scripts/summary.py

@@ -671,46 +671,46 @@ if __name__ == "__main__":
         help="Only show percentage change, not a full diff.")
         help="Only show percentage change, not a full diff.")
     parser.add_argument(
     parser.add_argument(
         '-b', '--by',
         '-b', '--by',
-        type=lambda x: [x.strip() for x in x.split(',')],
+        action='append',
         help="Group by these fields. All other fields will be merged as "
         help="Group by these fields. All other fields will be merged as "
             "needed. Can rename fields with new_name=old_name.")
             "needed. Can rename fields with new_name=old_name.")
     parser.add_argument(
     parser.add_argument(
         '-f', '--fields',
         '-f', '--fields',
-        type=lambda x: [x.strip() for x in x.split(',')],
+        action='append',
         help="Use these fields. Can rename fields with new_name=old_name.")
         help="Use these fields. Can rename fields with new_name=old_name.")
     parser.add_argument(
     parser.add_argument(
         '-D', '--define',
         '-D', '--define',
-        type=lambda x: (lambda k,v: (k, set(v.split(','))))(*x.split('=', 1)),
         action='append',
         action='append',
+        type=lambda x: (lambda k,v: (k, set(v.split(','))))(*x.split('=', 1)),
         help="Only include rows where this field is this value. May include "
         help="Only include rows where this field is this value. May include "
             "comma-separated options.")
             "comma-separated options.")
     parser.add_argument(
     parser.add_argument(
         '--add',
         '--add',
-        type=lambda x: [x.strip() for x in x.split(',')],
+        action='append',
         help="Add these fields (the default).")
         help="Add these fields (the default).")
     parser.add_argument(
     parser.add_argument(
         '--mul',
         '--mul',
-        type=lambda x: [x.strip() for x in x.split(',')],
+        action='append',
         help="Multiply these fields.")
         help="Multiply these fields.")
     parser.add_argument(
     parser.add_argument(
         '--min',
         '--min',
-        type=lambda x: [x.strip() for x in x.split(',')],
+        action='append',
         help="Take the minimum of these fields.")
         help="Take the minimum of these fields.")
     parser.add_argument(
     parser.add_argument(
         '--max',
         '--max',
-        type=lambda x: [x.strip() for x in x.split(',')],
+        action='append',
         help="Take the maximum of these fields.")
         help="Take the maximum of these fields.")
     parser.add_argument(
     parser.add_argument(
         '--avg',
         '--avg',
-        type=lambda x: [x.strip() for x in x.split(',')],
+        action='append',
         help="Average these fields.")
         help="Average these fields.")
     parser.add_argument(
     parser.add_argument(
         '-s', '--sort',
         '-s', '--sort',
-        type=lambda x: [x.strip() for x in x.split(',')],
+        action='append',
         help="Sort by these fields.")
         help="Sort by these fields.")
     parser.add_argument(
     parser.add_argument(
         '-S', '--reverse-sort',
         '-S', '--reverse-sort',
-        type=lambda x: [x.strip() for x in x.split(',')],
+        action='append',
         help="Sort by these fields, but backwards.")
         help="Sort by these fields, but backwards.")
     parser.add_argument(
     parser.add_argument(
         '-Y', '--summary',
         '-Y', '--summary',

+ 3 - 2
scripts/tailpipe.py

@@ -121,9 +121,10 @@ if __name__ == "__main__":
         nargs='?',
         nargs='?',
         help="Path to read from.")
         help="Path to read from.")
     parser.add_argument(
     parser.add_argument(
-        '-n',
-        '--lines',
+        '-n', '--lines',
+        nargs='?',
         type=lambda x: int(x, 0),
         type=lambda x: int(x, 0),
+        const=0,
         help="Show this many lines of history. 0 uses the terminal height. "
         help="Show this many lines of history. 0 uses the terminal height. "
             "Defaults to 5.")
             "Defaults to 5.")
     parser.add_argument(
     parser.add_argument(

+ 12 - 10
scripts/test.py

@@ -525,9 +525,9 @@ def find_runner(runner, **args):
 
 
     # other context
     # other context
     if args.get('geometry'):
     if args.get('geometry'):
-        cmd.append('-g%s' % args['geometry'])
+        cmd.append('-G%s' % args['geometry'])
     if args.get('powerloss'):
     if args.get('powerloss'):
-        cmd.append('-p%s' % args['powerloss'])
+        cmd.append('-P%s' % args['powerloss'])
     if args.get('disk'):
     if args.get('disk'):
         cmd.append('-d%s' % args['disk'])
         cmd.append('-d%s' % args['disk'])
     if args.get('trace'):
     if args.get('trace'):
@@ -1009,6 +1009,10 @@ def run(runner, test_ids=[], **args):
         total_perms))
         total_perms))
     print()
     print()
 
 
+    # automatic job detection?
+    if args.get('jobs') == 0:
+        args['jobs'] = len(os.sched_getaffinity(0))
+
     # truncate and open logs here so they aren't disconnected between tests
     # truncate and open logs here so they aren't disconnected between tests
     stdout = None
     stdout = None
     if args.get('stdout'):
     if args.get('stdout'):
@@ -1251,13 +1255,11 @@ if __name__ == "__main__":
         action='append',
         action='append',
         help="Override a test define.")
         help="Override a test define.")
     test_parser.add_argument(
     test_parser.add_argument(
-        '-g', '--geometry',
-        help="Comma-separated list of disk geometries to test. "
-            "Defaults to d,e,E,n,N.")
+        '-G', '--geometry',
+        help="Comma-separated list of disk geometries to test.")
     test_parser.add_argument(
     test_parser.add_argument(
-        '-p', '--powerloss',
-        help="Comma-separated list of power-loss scenarios to test. "
-            "Defaults to 0,l.")
+        '-P', '--powerloss',
+        help="Comma-separated list of power-loss scenarios to test.")
     test_parser.add_argument(
     test_parser.add_argument(
         '-d', '--disk',
         '-d', '--disk',
         help="Direct block device operations to this file.")
         help="Direct block device operations to this file.")
@@ -1283,8 +1285,8 @@ if __name__ == "__main__":
         '-j', '--jobs',
         '-j', '--jobs',
         nargs='?',
         nargs='?',
         type=lambda x: int(x, 0),
         type=lambda x: int(x, 0),
-        const=len(os.sched_getaffinity(0)),
-        help="Number of parallel runners to run.")
+        const=0,
+        help="Number of parallel runners to run. 0 runs one runner per core.")
     test_parser.add_argument(
     test_parser.add_argument(
         '-k', '--keep-going',
         '-k', '--keep-going',
         action='store_true',
         action='store_true',

+ 14 - 4
scripts/tracebd.py

@@ -853,11 +853,15 @@ if __name__ == "__main__":
         help="Render wear.")
         help="Render wear.")
     parser.add_argument(
     parser.add_argument(
         '-b', '--block',
         '-b', '--block',
-        type=lambda x: tuple(int(x,0) if x else None for x in x.split(',',1)),
+        type=lambda x: tuple(
+            int(x, 0) if x.strip() else None
+            for x in x.split(',')),
         help="Show a specific block or range of blocks.")
         help="Show a specific block or range of blocks.")
     parser.add_argument(
     parser.add_argument(
         '-i', '--off',
         '-i', '--off',
-        type=lambda x: tuple(int(x,0) if x else None for x in x.split(',',1)),
+        type=lambda x: tuple(
+            int(x, 0) if x.strip() else None
+            for x in x.split(',')),
         help="Show a specific offset or range of offsets.")
         help="Show a specific offset or range of offsets.")
     parser.add_argument(
     parser.add_argument(
         '-B', '--block-size',
         '-B', '--block-size',
@@ -901,24 +905,30 @@ if __name__ == "__main__":
         help="Characters to use for showing wear.")
         help="Characters to use for showing wear.")
     parser.add_argument(
     parser.add_argument(
         '--colors',
         '--colors',
-        type=lambda x: x.split(','),
+        type=lambda x: [x.strip() for x in x.split(',')],
         help="Colors to use for read, prog, erase, noop operations.")
         help="Colors to use for read, prog, erase, noop operations.")
     parser.add_argument(
     parser.add_argument(
         '--wear-colors',
         '--wear-colors',
-        type=lambda x: x.split(','),
+        type=lambda x: [x.strip() for x in x.split(',')],
         help="Colors to use for showing wear.")
         help="Colors to use for showing wear.")
     parser.add_argument(
     parser.add_argument(
         '-W', '--width',
         '-W', '--width',
+        nargs='?',
         type=lambda x: int(x, 0),
         type=lambda x: int(x, 0),
+        const=0,
         help="Width in columns. 0 uses the terminal width. Defaults to "
         help="Width in columns. 0 uses the terminal width. Defaults to "
             "min(terminal, 80).")
             "min(terminal, 80).")
     parser.add_argument(
     parser.add_argument(
         '-H', '--height',
         '-H', '--height',
+        nargs='?',
         type=lambda x: int(x, 0),
         type=lambda x: int(x, 0),
+        const=0,
         help="Height in rows. 0 uses the terminal height. Defaults to 1.")
         help="Height in rows. 0 uses the terminal height. Defaults to 1.")
     parser.add_argument(
     parser.add_argument(
         '-n', '--lines',
         '-n', '--lines',
+        nargs='?',
         type=lambda x: int(x, 0),
         type=lambda x: int(x, 0),
+        const=0,
         help="Show this many lines of history. 0 uses the terminal height. "
         help="Show this many lines of history. 0 uses the terminal height. "
             "Defaults to 5.")
             "Defaults to 5.")
     parser.add_argument(
     parser.add_argument(