selftests/bpf: add CSV output mode for veristat
authorAndrii Nakryiko <andrii@kernel.org>
Wed, 21 Sep 2022 16:42:52 +0000 (09:42 -0700)
committerAlexei Starovoitov <ast@kernel.org>
Thu, 22 Sep 2022 02:41:37 +0000 (19:41 -0700)
Teach veristat to output results as CSV table for easier programmatic
processing. Change what was --output/-o argument to now be --emit/-e.
And then use --output-format/-o <fmt> to specify output format.
Currently "table" and "csv" is supported, table being default.

For CSV output mode veristat is using spec identifiers as column names.
E.g., instead of "Total states" veristat uses "total_states" as a CSV
header name.

Internally veristat recognizes three formats, one of them
(RESFMT_TABLE_CALCLEN) is a special format instructing veristat to
calculate column widths for table output. This felt a bit cleaner and
more uniform than either creating separate functions just for this.

Signed-off-by: Andrii Nakryiko <andrii@kernel.org>
Link: https://lore.kernel.org/r/20220921164254.3630690-3-andrii@kernel.org
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
tools/testing/selftests/bpf/veristat.c

index c0c8a65..0472bfa 100644 (file)
@@ -46,10 +46,17 @@ struct stat_specs {
        int lens[ALL_STATS_CNT];
 };
 
+enum resfmt {
+       RESFMT_TABLE,
+       RESFMT_TABLE_CALCLEN, /* fake format to pre-calculate table's column widths */
+       RESFMT_CSV,
+};
+
 static struct env {
        char **filenames;
        int filename_cnt;
        bool verbose;
+       enum resfmt out_fmt;
 
        struct verif_stats *prog_stats;
        int prog_stat_cnt;
@@ -78,8 +85,9 @@ const char argp_program_doc[] =
 static const struct argp_option opts[] = {
        { NULL, 'h', NULL, OPTION_HIDDEN, "Show the full help" },
        { "verbose", 'v', NULL, 0, "Verbose mode" },
-       { "output", 'o', "SPEC", 0, "Specify output stats" },
+       { "emit", 'e', "SPEC", 0, "Specify stats to be emitted" },
        { "sort", 's', "SPEC", 0, "Specify sort order" },
+       { "output-format", 'o', "FMT", 0, "Result output format (table, csv), default is table." },
        {},
 };
 
@@ -97,7 +105,7 @@ static error_t parse_arg(int key, char *arg, struct argp_state *state)
        case 'v':
                env.verbose = true;
                break;
-       case 'o':
+       case 'e':
                err = parse_stats(arg, &env.output_spec);
                if (err)
                        return err;
@@ -107,6 +115,16 @@ static error_t parse_arg(int key, char *arg, struct argp_state *state)
                if (err)
                        return err;
                break;
+       case 'o':
+               if (strcmp(arg, "table") == 0) {
+                       env.out_fmt = RESFMT_TABLE;
+               } else if (strcmp(arg, "csv") == 0) {
+                       env.out_fmt = RESFMT_CSV;
+               } else {
+                       fprintf(stderr, "Unrecognized output format '%s'\n", arg);
+                       return -EINVAL;
+               }
+               break;
        case ARGP_KEY_ARG:
                tmp = realloc(env.filenames, (env.filename_cnt + 1) * sizeof(*env.filenames));
                if (!tmp)
@@ -147,7 +165,7 @@ static struct stat_def {
        [FILE_NAME] = { "File", {"file_name", "filename", "file"}, true /* asc */ },
        [PROG_NAME] = { "Program", {"prog_name", "progname", "prog"}, true /* asc */ },
        [VERDICT] = { "Verdict", {"verdict"}, true /* asc: failure, success */ },
-       [DURATION] = { "Duration, us", {"duration", "dur"}, },
+       [DURATION] = { "Duration (us)", {"duration", "dur"}, },
        [TOTAL_INSNS] = { "Total insns", {"total_insns", "insns"}, },
        [TOTAL_STATES] = { "Total states", {"total_states", "states"}, },
        [PEAK_STATES] = { "Peak states", {"peak_states"}, },
@@ -385,7 +403,21 @@ static int cmp_prog_stats(const void *v1, const void *v2)
 #define HEADER_CHAR '-'
 #define COLUMN_SEP "  "
 
-static void output_headers(bool calc_len)
+static void output_header_underlines(void)
+{
+       int i, j, len;
+
+       for (i = 0; i < env.output_spec.spec_cnt; i++) {
+               len = env.output_spec.lens[i];
+
+               printf("%s", i == 0 ? "" : COLUMN_SEP);
+               for (j = 0; j < len; j++)
+                       printf("%c", HEADER_CHAR);
+       }
+       printf("\n");
+}
+
+static void output_headers(enum resfmt fmt)
 {
        int i, len;
 
@@ -393,34 +425,30 @@ static void output_headers(bool calc_len)
                int id = env.output_spec.ids[i];
                int *max_len = &env.output_spec.lens[i];
 
-               if (calc_len) {
+               switch (fmt) {
+               case RESFMT_TABLE_CALCLEN:
                        len = snprintf(NULL, 0, "%s", stat_defs[id].header);
                        if (len > *max_len)
                                *max_len = len;
-               } else {
+                       break;
+               case RESFMT_TABLE:
                        printf("%s%-*s", i == 0 ? "" : COLUMN_SEP,  *max_len, stat_defs[id].header);
+                       if (i == env.output_spec.spec_cnt - 1)
+                               printf("\n");
+                       break;
+               case RESFMT_CSV:
+                       printf("%s%s", i == 0 ? "" : ",", stat_defs[id].names[0]);
+                       if (i == env.output_spec.spec_cnt - 1)
+                               printf("\n");
+                       break;
                }
        }
 
-       if (!calc_len)
-               printf("\n");
+       if (fmt == RESFMT_TABLE)
+               output_header_underlines();
 }
 
-static void output_header_underlines(void)
-{
-       int i, j, len;
-
-       for (i = 0; i < env.output_spec.spec_cnt; i++) {
-               len = env.output_spec.lens[i];
-
-               printf("%s", i == 0 ? "" : COLUMN_SEP);
-               for (j = 0; j < len; j++)
-                       printf("%c", HEADER_CHAR);
-       }
-       printf("\n");
-}
-
-static void output_stats(const struct verif_stats *s, bool calc_len)
+static void output_stats(const struct verif_stats *s, enum resfmt fmt, bool last)
 {
        int i;
 
@@ -453,23 +481,36 @@ static void output_stats(const struct verif_stats *s, bool calc_len)
                        exit(1);
                }
 
-               if (calc_len) {
+               switch (fmt) {
+               case RESFMT_TABLE_CALCLEN:
                        if (str)
                                len = snprintf(NULL, 0, "%s", str);
                        else
                                len = snprintf(NULL, 0, "%ld", val);
                        if (len > *max_len)
                                *max_len = len;
-               } else {
+                       break;
+               case RESFMT_TABLE:
                        if (str)
                                printf("%s%-*s", i == 0 ? "" : COLUMN_SEP, *max_len, str);
                        else
                                printf("%s%*ld", i == 0 ? "" : COLUMN_SEP,  *max_len, val);
+                       if (i == env.output_spec.spec_cnt - 1)
+                               printf("\n");
+                       break;
+               case RESFMT_CSV:
+                       if (str)
+                               printf("%s%s", i == 0 ? "" : ",", str);
+                       else
+                               printf("%s%ld", i == 0 ? "" : ",", val);
+                       if (i == env.output_spec.spec_cnt - 1)
+                               printf("\n");
+                       break;
                }
        }
 
-       if (!calc_len)
-               printf("\n");
+       if (last && fmt == RESFMT_TABLE)
+               output_header_underlines();
 }
 
 int main(int argc, char **argv)
@@ -505,20 +546,18 @@ int main(int argc, char **argv)
 
        qsort(env.prog_stats, env.prog_stat_cnt, sizeof(*env.prog_stats), cmp_prog_stats);
 
-       /* calculate column widths */
-       output_headers(true);
-       for (i = 0; i < env.prog_stat_cnt; i++) {
-               output_stats(&env.prog_stats[i], true);
+       if (env.out_fmt == RESFMT_TABLE) {
+               /* calculate column widths */
+               output_headers(RESFMT_TABLE_CALCLEN);
+               for (i = 0; i < env.prog_stat_cnt; i++)
+                       output_stats(&env.prog_stats[i], RESFMT_TABLE_CALCLEN, false);
        }
 
        /* actually output the table */
-       output_headers(false);
-       output_header_underlines();
+       output_headers(env.out_fmt);
        for (i = 0; i < env.prog_stat_cnt; i++) {
-               output_stats(&env.prog_stats[i], false);
+               output_stats(&env.prog_stats[i], env.out_fmt, i == env.prog_stat_cnt - 1);
        }
-       output_header_underlines();
-       printf("\n");
 
        printf("Done. Processed %d object files, %d programs.\n",
               env.filename_cnt, env.prog_stat_cnt);