1 // SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) 2 /* Copyright (c) 2022 Meta Platforms, Inc. and affiliates. */ 3 #define _GNU_SOURCE 4 #include <argp.h> 5 #include <string.h> 6 #include <stdlib.h> 7 #include <sched.h> 8 #include <pthread.h> 9 #include <dirent.h> 10 #include <signal.h> 11 #include <fcntl.h> 12 #include <unistd.h> 13 #include <sys/time.h> 14 #include <sys/sysinfo.h> 15 #include <sys/stat.h> 16 #include <bpf/libbpf.h> 17 #include <bpf/btf.h> 18 #include <libelf.h> 19 #include <gelf.h> 20 #include <float.h> 21 22 #ifndef ARRAY_SIZE 23 #define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) 24 #endif 25 26 enum stat_id { 27 VERDICT, 28 DURATION, 29 TOTAL_INSNS, 30 TOTAL_STATES, 31 PEAK_STATES, 32 MAX_STATES_PER_INSN, 33 MARK_READ_MAX_LEN, 34 35 FILE_NAME, 36 PROG_NAME, 37 38 ALL_STATS_CNT, 39 NUM_STATS_CNT = FILE_NAME - VERDICT, 40 }; 41 42 /* In comparison mode each stat can specify up to four different values: 43 * - A side value; 44 * - B side value; 45 * - absolute diff value; 46 * - relative (percentage) diff value. 47 * 48 * When specifying stat specs in comparison mode, user can use one of the 49 * following variant suffixes to specify which exact variant should be used for 50 * ordering or filtering: 51 * - `_a` for A side value; 52 * - `_b` for B side value; 53 * - `_diff` for absolute diff value; 54 * - `_pct` for relative (percentage) diff value. 55 * 56 * If no variant suffix is provided, then `_b` (control data) is assumed. 57 * 58 * As an example, let's say instructions stat has the following output: 59 * 60 * Insns (A) Insns (B) Insns (DIFF) 61 * --------- --------- -------------- 62 * 21547 20920 -627 (-2.91%) 63 * 64 * Then: 65 * - 21547 is A side value (insns_a); 66 * - 20920 is B side value (insns_b); 67 * - -627 is absolute diff value (insns_diff); 68 * - -2.91% is relative diff value (insns_pct). 69 * 70 * For verdict there is no verdict_pct variant. 71 * For file and program name, _a and _b variants are equivalent and there are 72 * no _diff or _pct variants. 73 */ 74 enum stat_variant { 75 VARIANT_A, 76 VARIANT_B, 77 VARIANT_DIFF, 78 VARIANT_PCT, 79 }; 80 81 struct verif_stats { 82 char *file_name; 83 char *prog_name; 84 85 long stats[NUM_STATS_CNT]; 86 }; 87 88 /* joined comparison mode stats */ 89 struct verif_stats_join { 90 char *file_name; 91 char *prog_name; 92 93 const struct verif_stats *stats_a; 94 const struct verif_stats *stats_b; 95 }; 96 97 struct stat_specs { 98 int spec_cnt; 99 enum stat_id ids[ALL_STATS_CNT]; 100 enum stat_variant variants[ALL_STATS_CNT]; 101 bool asc[ALL_STATS_CNT]; 102 int lens[ALL_STATS_CNT * 3]; /* 3x for comparison mode */ 103 }; 104 105 enum resfmt { 106 RESFMT_TABLE, 107 RESFMT_TABLE_CALCLEN, /* fake format to pre-calculate table's column widths */ 108 RESFMT_CSV, 109 }; 110 111 enum filter_kind { 112 FILTER_NAME, 113 FILTER_STAT, 114 }; 115 116 enum operator_kind { 117 OP_EQ, /* == or = */ 118 OP_NEQ, /* != or <> */ 119 OP_LT, /* < */ 120 OP_LE, /* <= */ 121 OP_GT, /* > */ 122 OP_GE, /* >= */ 123 }; 124 125 struct filter { 126 enum filter_kind kind; 127 /* FILTER_NAME */ 128 char *any_glob; 129 char *file_glob; 130 char *prog_glob; 131 /* FILTER_STAT */ 132 enum operator_kind op; 133 int stat_id; 134 enum stat_variant stat_var; 135 long value; 136 }; 137 138 static struct env { 139 char **filenames; 140 int filename_cnt; 141 bool verbose; 142 bool debug; 143 bool quiet; 144 bool force_checkpoints; 145 enum resfmt out_fmt; 146 bool show_version; 147 bool comparison_mode; 148 bool replay_mode; 149 150 int log_level; 151 int log_size; 152 bool log_fixed; 153 154 struct verif_stats *prog_stats; 155 int prog_stat_cnt; 156 157 /* baseline_stats is allocated and used only in comparison mode */ 158 struct verif_stats *baseline_stats; 159 int baseline_stat_cnt; 160 161 struct verif_stats_join *join_stats; 162 int join_stat_cnt; 163 164 struct stat_specs output_spec; 165 struct stat_specs sort_spec; 166 167 struct filter *allow_filters; 168 struct filter *deny_filters; 169 int allow_filter_cnt; 170 int deny_filter_cnt; 171 172 int files_processed; 173 int files_skipped; 174 int progs_processed; 175 int progs_skipped; 176 } env; 177 178 static int libbpf_print_fn(enum libbpf_print_level level, const char *format, va_list args) 179 { 180 if (!env.verbose) 181 return 0; 182 if (level == LIBBPF_DEBUG && !env.debug) 183 return 0; 184 return vfprintf(stderr, format, args); 185 } 186 187 #ifndef VERISTAT_VERSION 188 #define VERISTAT_VERSION "<kernel>" 189 #endif 190 191 const char *argp_program_version = "veristat v" VERISTAT_VERSION; 192 const char *argp_program_bug_address = "<bpf@vger.kernel.org>"; 193 const char argp_program_doc[] = 194 "veristat BPF verifier stats collection and comparison tool.\n" 195 "\n" 196 "USAGE: veristat <obj-file> [<obj-file>...]\n" 197 " OR: veristat -C <baseline.csv> <comparison.csv>\n" 198 " OR: veristat -R <results.csv>\n"; 199 200 enum { 201 OPT_LOG_FIXED = 1000, 202 OPT_LOG_SIZE = 1001, 203 }; 204 205 static const struct argp_option opts[] = { 206 { NULL, 'h', NULL, OPTION_HIDDEN, "Show the full help" }, 207 { "version", 'V', NULL, 0, "Print version" }, 208 { "verbose", 'v', NULL, 0, "Verbose mode" }, 209 { "debug", 'd', NULL, 0, "Debug mode (turns on libbpf debug logging)" }, 210 { "log-level", 'l', "LEVEL", 0, "Verifier log level (default 0 for normal mode, 1 for verbose mode)" }, 211 { "log-fixed", OPT_LOG_FIXED, NULL, 0, "Disable verifier log rotation" }, 212 { "log-size", OPT_LOG_SIZE, "BYTES", 0, "Customize verifier log size (default to 16MB)" }, 213 { "test-states", 't', NULL, 0, 214 "Force frequent BPF verifier state checkpointing (set BPF_F_TEST_STATE_FREQ program flag)" }, 215 { "quiet", 'q', NULL, 0, "Quiet mode" }, 216 { "emit", 'e', "SPEC", 0, "Specify stats to be emitted" }, 217 { "sort", 's', "SPEC", 0, "Specify sort order" }, 218 { "output-format", 'o', "FMT", 0, "Result output format (table, csv), default is table." }, 219 { "compare", 'C', NULL, 0, "Comparison mode" }, 220 { "replay", 'R', NULL, 0, "Replay mode" }, 221 { "filter", 'f', "FILTER", 0, "Filter expressions (or @filename for file with expressions)." }, 222 {}, 223 }; 224 225 static int parse_stats(const char *stats_str, struct stat_specs *specs); 226 static int append_filter(struct filter **filters, int *cnt, const char *str); 227 static int append_filter_file(const char *path); 228 229 static error_t parse_arg(int key, char *arg, struct argp_state *state) 230 { 231 void *tmp; 232 int err; 233 234 switch (key) { 235 case 'h': 236 argp_state_help(state, stderr, ARGP_HELP_STD_HELP); 237 break; 238 case 'V': 239 env.show_version = true; 240 break; 241 case 'v': 242 env.verbose = true; 243 break; 244 case 'd': 245 env.debug = true; 246 env.verbose = true; 247 break; 248 case 'q': 249 env.quiet = true; 250 break; 251 case 'e': 252 err = parse_stats(arg, &env.output_spec); 253 if (err) 254 return err; 255 break; 256 case 's': 257 err = parse_stats(arg, &env.sort_spec); 258 if (err) 259 return err; 260 break; 261 case 'o': 262 if (strcmp(arg, "table") == 0) { 263 env.out_fmt = RESFMT_TABLE; 264 } else if (strcmp(arg, "csv") == 0) { 265 env.out_fmt = RESFMT_CSV; 266 } else { 267 fprintf(stderr, "Unrecognized output format '%s'\n", arg); 268 return -EINVAL; 269 } 270 break; 271 case 'l': 272 errno = 0; 273 env.log_level = strtol(arg, NULL, 10); 274 if (errno) { 275 fprintf(stderr, "invalid log level: %s\n", arg); 276 argp_usage(state); 277 } 278 break; 279 case OPT_LOG_FIXED: 280 env.log_fixed = true; 281 break; 282 case OPT_LOG_SIZE: 283 errno = 0; 284 env.log_size = strtol(arg, NULL, 10); 285 if (errno) { 286 fprintf(stderr, "invalid log size: %s\n", arg); 287 argp_usage(state); 288 } 289 break; 290 case 't': 291 env.force_checkpoints = true; 292 break; 293 case 'C': 294 env.comparison_mode = true; 295 break; 296 case 'R': 297 env.replay_mode = true; 298 break; 299 case 'f': 300 if (arg[0] == '@') 301 err = append_filter_file(arg + 1); 302 else if (arg[0] == '!') 303 err = append_filter(&env.deny_filters, &env.deny_filter_cnt, arg + 1); 304 else 305 err = append_filter(&env.allow_filters, &env.allow_filter_cnt, arg); 306 if (err) { 307 fprintf(stderr, "Failed to collect program filter expressions: %d\n", err); 308 return err; 309 } 310 break; 311 case ARGP_KEY_ARG: 312 tmp = realloc(env.filenames, (env.filename_cnt + 1) * sizeof(*env.filenames)); 313 if (!tmp) 314 return -ENOMEM; 315 env.filenames = tmp; 316 env.filenames[env.filename_cnt] = strdup(arg); 317 if (!env.filenames[env.filename_cnt]) 318 return -ENOMEM; 319 env.filename_cnt++; 320 break; 321 default: 322 return ARGP_ERR_UNKNOWN; 323 } 324 return 0; 325 } 326 327 static const struct argp argp = { 328 .options = opts, 329 .parser = parse_arg, 330 .doc = argp_program_doc, 331 }; 332 333 334 /* Adapted from perf/util/string.c */ 335 static bool glob_matches(const char *str, const char *pat) 336 { 337 while (*str && *pat && *pat != '*') { 338 if (*str != *pat) 339 return false; 340 str++; 341 pat++; 342 } 343 /* Check wild card */ 344 if (*pat == '*') { 345 while (*pat == '*') 346 pat++; 347 if (!*pat) /* Tail wild card matches all */ 348 return true; 349 while (*str) 350 if (glob_matches(str++, pat)) 351 return true; 352 } 353 return !*str && !*pat; 354 } 355 356 static bool is_bpf_obj_file(const char *path) { 357 Elf64_Ehdr *ehdr; 358 int fd, err = -EINVAL; 359 Elf *elf = NULL; 360 361 fd = open(path, O_RDONLY | O_CLOEXEC); 362 if (fd < 0) 363 return true; /* we'll fail later and propagate error */ 364 365 /* ensure libelf is initialized */ 366 (void)elf_version(EV_CURRENT); 367 368 elf = elf_begin(fd, ELF_C_READ, NULL); 369 if (!elf) 370 goto cleanup; 371 372 if (elf_kind(elf) != ELF_K_ELF || gelf_getclass(elf) != ELFCLASS64) 373 goto cleanup; 374 375 ehdr = elf64_getehdr(elf); 376 /* Old LLVM set e_machine to EM_NONE */ 377 if (!ehdr || ehdr->e_type != ET_REL || (ehdr->e_machine && ehdr->e_machine != EM_BPF)) 378 goto cleanup; 379 380 err = 0; 381 cleanup: 382 if (elf) 383 elf_end(elf); 384 close(fd); 385 return err == 0; 386 } 387 388 static bool should_process_file_prog(const char *filename, const char *prog_name) 389 { 390 struct filter *f; 391 int i, allow_cnt = 0; 392 393 for (i = 0; i < env.deny_filter_cnt; i++) { 394 f = &env.deny_filters[i]; 395 if (f->kind != FILTER_NAME) 396 continue; 397 398 if (f->any_glob && glob_matches(filename, f->any_glob)) 399 return false; 400 if (f->any_glob && prog_name && glob_matches(prog_name, f->any_glob)) 401 return false; 402 if (f->file_glob && glob_matches(filename, f->file_glob)) 403 return false; 404 if (f->prog_glob && prog_name && glob_matches(prog_name, f->prog_glob)) 405 return false; 406 } 407 408 for (i = 0; i < env.allow_filter_cnt; i++) { 409 f = &env.allow_filters[i]; 410 if (f->kind != FILTER_NAME) 411 continue; 412 413 allow_cnt++; 414 if (f->any_glob) { 415 if (glob_matches(filename, f->any_glob)) 416 return true; 417 /* If we don't know program name yet, any_glob filter 418 * has to assume that current BPF object file might be 419 * relevant; we'll check again later on after opening 420 * BPF object file, at which point program name will 421 * be known finally. 422 */ 423 if (!prog_name || glob_matches(prog_name, f->any_glob)) 424 return true; 425 } else { 426 if (f->file_glob && !glob_matches(filename, f->file_glob)) 427 continue; 428 if (f->prog_glob && prog_name && !glob_matches(prog_name, f->prog_glob)) 429 continue; 430 return true; 431 } 432 } 433 434 /* if there are no file/prog name allow filters, allow all progs, 435 * unless they are denied earlier explicitly 436 */ 437 return allow_cnt == 0; 438 } 439 440 static struct { 441 enum operator_kind op_kind; 442 const char *op_str; 443 } operators[] = { 444 /* Order of these definitions matter to avoid situations like '<' 445 * matching part of what is actually a '<>' operator. That is, 446 * substrings should go last. 447 */ 448 { OP_EQ, "==" }, 449 { OP_NEQ, "!=" }, 450 { OP_NEQ, "<>" }, 451 { OP_LE, "<=" }, 452 { OP_LT, "<" }, 453 { OP_GE, ">=" }, 454 { OP_GT, ">" }, 455 { OP_EQ, "=" }, 456 }; 457 458 static bool parse_stat_id_var(const char *name, size_t len, int *id, enum stat_variant *var); 459 460 static int append_filter(struct filter **filters, int *cnt, const char *str) 461 { 462 struct filter *f; 463 void *tmp; 464 const char *p; 465 int i; 466 467 tmp = realloc(*filters, (*cnt + 1) * sizeof(**filters)); 468 if (!tmp) 469 return -ENOMEM; 470 *filters = tmp; 471 472 f = &(*filters)[*cnt]; 473 memset(f, 0, sizeof(*f)); 474 475 /* First, let's check if it's a stats filter of the following form: 476 * <stat><op><value, where: 477 * - <stat> is one of supported numerical stats (verdict is also 478 * considered numerical, failure == 0, success == 1); 479 * - <op> is comparison operator (see `operators` definitions); 480 * - <value> is an integer (or failure/success, or false/true as 481 * special aliases for 0 and 1, respectively). 482 * If the form doesn't match what user provided, we assume file/prog 483 * glob filter. 484 */ 485 for (i = 0; i < ARRAY_SIZE(operators); i++) { 486 enum stat_variant var; 487 int id; 488 long val; 489 const char *end = str; 490 const char *op_str; 491 492 op_str = operators[i].op_str; 493 p = strstr(str, op_str); 494 if (!p) 495 continue; 496 497 if (!parse_stat_id_var(str, p - str, &id, &var)) { 498 fprintf(stderr, "Unrecognized stat name in '%s'!\n", str); 499 return -EINVAL; 500 } 501 if (id >= FILE_NAME) { 502 fprintf(stderr, "Non-integer stat is specified in '%s'!\n", str); 503 return -EINVAL; 504 } 505 506 p += strlen(op_str); 507 508 if (strcasecmp(p, "true") == 0 || 509 strcasecmp(p, "t") == 0 || 510 strcasecmp(p, "success") == 0 || 511 strcasecmp(p, "succ") == 0 || 512 strcasecmp(p, "s") == 0 || 513 strcasecmp(p, "match") == 0 || 514 strcasecmp(p, "m") == 0) { 515 val = 1; 516 } else if (strcasecmp(p, "false") == 0 || 517 strcasecmp(p, "f") == 0 || 518 strcasecmp(p, "failure") == 0 || 519 strcasecmp(p, "fail") == 0 || 520 strcasecmp(p, "mismatch") == 0 || 521 strcasecmp(p, "mis") == 0) { 522 val = 0; 523 } else { 524 errno = 0; 525 val = strtol(p, (char **)&end, 10); 526 if (errno || end == p || *end != '\0' ) { 527 fprintf(stderr, "Invalid integer value in '%s'!\n", str); 528 return -EINVAL; 529 } 530 } 531 532 f->kind = FILTER_STAT; 533 f->stat_id = id; 534 f->stat_var = var; 535 f->op = operators[i].op_kind; 536 f->value = val; 537 538 *cnt += 1; 539 return 0; 540 } 541 542 /* File/prog filter can be specified either as '<glob>' or 543 * '<file-glob>/<prog-glob>'. In the former case <glob> is applied to 544 * both file and program names. This seems to be way more useful in 545 * practice. If user needs full control, they can use '/<prog-glob>' 546 * form to glob just program name, or '<file-glob>/' to glob only file 547 * name. But usually common <glob> seems to be the most useful and 548 * ergonomic way. 549 */ 550 f->kind = FILTER_NAME; 551 p = strchr(str, '/'); 552 if (!p) { 553 f->any_glob = strdup(str); 554 if (!f->any_glob) 555 return -ENOMEM; 556 } else { 557 if (str != p) { 558 /* non-empty file glob */ 559 f->file_glob = strndup(str, p - str); 560 if (!f->file_glob) 561 return -ENOMEM; 562 } 563 if (strlen(p + 1) > 0) { 564 /* non-empty prog glob */ 565 f->prog_glob = strdup(p + 1); 566 if (!f->prog_glob) { 567 free(f->file_glob); 568 f->file_glob = NULL; 569 return -ENOMEM; 570 } 571 } 572 } 573 574 *cnt += 1; 575 return 0; 576 } 577 578 static int append_filter_file(const char *path) 579 { 580 char buf[1024]; 581 FILE *f; 582 int err = 0; 583 584 f = fopen(path, "r"); 585 if (!f) { 586 err = -errno; 587 fprintf(stderr, "Failed to open filters in '%s': %d\n", path, err); 588 return err; 589 } 590 591 while (fscanf(f, " %1023[^\n]\n", buf) == 1) { 592 /* lines starting with # are comments, skip them */ 593 if (buf[0] == '\0' || buf[0] == '#') 594 continue; 595 /* lines starting with ! are negative match filters */ 596 if (buf[0] == '!') 597 err = append_filter(&env.deny_filters, &env.deny_filter_cnt, buf + 1); 598 else 599 err = append_filter(&env.allow_filters, &env.allow_filter_cnt, buf); 600 if (err) 601 goto cleanup; 602 } 603 604 cleanup: 605 fclose(f); 606 return err; 607 } 608 609 static const struct stat_specs default_output_spec = { 610 .spec_cnt = 7, 611 .ids = { 612 FILE_NAME, PROG_NAME, VERDICT, DURATION, 613 TOTAL_INSNS, TOTAL_STATES, PEAK_STATES, 614 }, 615 }; 616 617 static const struct stat_specs default_csv_output_spec = { 618 .spec_cnt = 9, 619 .ids = { 620 FILE_NAME, PROG_NAME, VERDICT, DURATION, 621 TOTAL_INSNS, TOTAL_STATES, PEAK_STATES, 622 MAX_STATES_PER_INSN, MARK_READ_MAX_LEN, 623 }, 624 }; 625 626 static const struct stat_specs default_sort_spec = { 627 .spec_cnt = 2, 628 .ids = { 629 FILE_NAME, PROG_NAME, 630 }, 631 .asc = { true, true, }, 632 }; 633 634 /* sorting for comparison mode to join two data sets */ 635 static const struct stat_specs join_sort_spec = { 636 .spec_cnt = 2, 637 .ids = { 638 FILE_NAME, PROG_NAME, 639 }, 640 .asc = { true, true, }, 641 }; 642 643 static struct stat_def { 644 const char *header; 645 const char *names[4]; 646 bool asc_by_default; 647 bool left_aligned; 648 } stat_defs[] = { 649 [FILE_NAME] = { "File", {"file_name", "filename", "file"}, true /* asc */, true /* left */ }, 650 [PROG_NAME] = { "Program", {"prog_name", "progname", "prog"}, true /* asc */, true /* left */ }, 651 [VERDICT] = { "Verdict", {"verdict"}, true /* asc: failure, success */, true /* left */ }, 652 [DURATION] = { "Duration (us)", {"duration", "dur"}, }, 653 [TOTAL_INSNS] = { "Insns", {"total_insns", "insns"}, }, 654 [TOTAL_STATES] = { "States", {"total_states", "states"}, }, 655 [PEAK_STATES] = { "Peak states", {"peak_states"}, }, 656 [MAX_STATES_PER_INSN] = { "Max states per insn", {"max_states_per_insn"}, }, 657 [MARK_READ_MAX_LEN] = { "Max mark read length", {"max_mark_read_len", "mark_read"}, }, 658 }; 659 660 static bool parse_stat_id_var(const char *name, size_t len, int *id, enum stat_variant *var) 661 { 662 static const char *var_sfxs[] = { 663 [VARIANT_A] = "_a", 664 [VARIANT_B] = "_b", 665 [VARIANT_DIFF] = "_diff", 666 [VARIANT_PCT] = "_pct", 667 }; 668 int i, j, k; 669 670 for (i = 0; i < ARRAY_SIZE(stat_defs); i++) { 671 struct stat_def *def = &stat_defs[i]; 672 size_t alias_len, sfx_len; 673 const char *alias; 674 675 for (j = 0; j < ARRAY_SIZE(stat_defs[i].names); j++) { 676 alias = def->names[j]; 677 if (!alias) 678 continue; 679 680 alias_len = strlen(alias); 681 if (strncmp(name, alias, alias_len) != 0) 682 continue; 683 684 if (alias_len == len) { 685 /* If no variant suffix is specified, we 686 * assume control group (just in case we are 687 * in comparison mode. Variant is ignored in 688 * non-comparison mode. 689 */ 690 *var = VARIANT_B; 691 *id = i; 692 return true; 693 } 694 695 for (k = 0; k < ARRAY_SIZE(var_sfxs); k++) { 696 sfx_len = strlen(var_sfxs[k]); 697 if (alias_len + sfx_len != len) 698 continue; 699 700 if (strncmp(name + alias_len, var_sfxs[k], sfx_len) == 0) { 701 *var = (enum stat_variant)k; 702 *id = i; 703 return true; 704 } 705 } 706 } 707 } 708 709 return false; 710 } 711 712 static bool is_asc_sym(char c) 713 { 714 return c == '^'; 715 } 716 717 static bool is_desc_sym(char c) 718 { 719 return c == 'v' || c == 'V' || c == '.' || c == '!' || c == '_'; 720 } 721 722 static int parse_stat(const char *stat_name, struct stat_specs *specs) 723 { 724 int id; 725 bool has_order = false, is_asc = false; 726 size_t len = strlen(stat_name); 727 enum stat_variant var; 728 729 if (specs->spec_cnt >= ARRAY_SIZE(specs->ids)) { 730 fprintf(stderr, "Can't specify more than %zd stats\n", ARRAY_SIZE(specs->ids)); 731 return -E2BIG; 732 } 733 734 if (len > 1 && (is_asc_sym(stat_name[len - 1]) || is_desc_sym(stat_name[len - 1]))) { 735 has_order = true; 736 is_asc = is_asc_sym(stat_name[len - 1]); 737 len -= 1; 738 } 739 740 if (!parse_stat_id_var(stat_name, len, &id, &var)) { 741 fprintf(stderr, "Unrecognized stat name '%s'\n", stat_name); 742 return -ESRCH; 743 } 744 745 specs->ids[specs->spec_cnt] = id; 746 specs->variants[specs->spec_cnt] = var; 747 specs->asc[specs->spec_cnt] = has_order ? is_asc : stat_defs[id].asc_by_default; 748 specs->spec_cnt++; 749 750 return 0; 751 } 752 753 static int parse_stats(const char *stats_str, struct stat_specs *specs) 754 { 755 char *input, *state = NULL, *next; 756 int err; 757 758 input = strdup(stats_str); 759 if (!input) 760 return -ENOMEM; 761 762 while ((next = strtok_r(state ? NULL : input, ",", &state))) { 763 err = parse_stat(next, specs); 764 if (err) 765 return err; 766 } 767 768 return 0; 769 } 770 771 static void free_verif_stats(struct verif_stats *stats, size_t stat_cnt) 772 { 773 int i; 774 775 if (!stats) 776 return; 777 778 for (i = 0; i < stat_cnt; i++) { 779 free(stats[i].file_name); 780 free(stats[i].prog_name); 781 } 782 free(stats); 783 } 784 785 static char verif_log_buf[64 * 1024]; 786 787 #define MAX_PARSED_LOG_LINES 100 788 789 static int parse_verif_log(char * const buf, size_t buf_sz, struct verif_stats *s) 790 { 791 const char *cur; 792 int pos, lines; 793 794 buf[buf_sz - 1] = '\0'; 795 796 for (pos = strlen(buf) - 1, lines = 0; pos >= 0 && lines < MAX_PARSED_LOG_LINES; lines++) { 797 /* find previous endline or otherwise take the start of log buf */ 798 for (cur = &buf[pos]; cur > buf && cur[0] != '\n'; cur--, pos--) { 799 } 800 /* next time start from end of previous line (or pos goes to <0) */ 801 pos--; 802 /* if we found endline, point right after endline symbol; 803 * otherwise, stay at the beginning of log buf 804 */ 805 if (cur[0] == '\n') 806 cur++; 807 808 if (1 == sscanf(cur, "verification time %ld usec\n", &s->stats[DURATION])) 809 continue; 810 if (6 == sscanf(cur, "processed %ld insns (limit %*d) max_states_per_insn %ld total_states %ld peak_states %ld mark_read %ld", 811 &s->stats[TOTAL_INSNS], 812 &s->stats[MAX_STATES_PER_INSN], 813 &s->stats[TOTAL_STATES], 814 &s->stats[PEAK_STATES], 815 &s->stats[MARK_READ_MAX_LEN])) 816 continue; 817 } 818 819 return 0; 820 } 821 822 static int guess_prog_type_by_ctx_name(const char *ctx_name, 823 enum bpf_prog_type *prog_type, 824 enum bpf_attach_type *attach_type) 825 { 826 /* We need to guess program type based on its declared context type. 827 * This guess can't be perfect as many different program types might 828 * share the same context type. So we can only hope to reasonably 829 * well guess this and get lucky. 830 * 831 * Just in case, we support both UAPI-side type names and 832 * kernel-internal names. 833 */ 834 static struct { 835 const char *uapi_name; 836 const char *kern_name; 837 enum bpf_prog_type prog_type; 838 enum bpf_attach_type attach_type; 839 } ctx_map[] = { 840 /* __sk_buff is most ambiguous, we assume TC program */ 841 { "__sk_buff", "sk_buff", BPF_PROG_TYPE_SCHED_CLS }, 842 { "bpf_sock", "sock", BPF_PROG_TYPE_CGROUP_SOCK, BPF_CGROUP_INET4_POST_BIND }, 843 { "bpf_sock_addr", "bpf_sock_addr_kern", BPF_PROG_TYPE_CGROUP_SOCK_ADDR, BPF_CGROUP_INET4_BIND }, 844 { "bpf_sock_ops", "bpf_sock_ops_kern", BPF_PROG_TYPE_SOCK_OPS, BPF_CGROUP_SOCK_OPS }, 845 { "sk_msg_md", "sk_msg", BPF_PROG_TYPE_SK_MSG, BPF_SK_MSG_VERDICT }, 846 { "bpf_cgroup_dev_ctx", "bpf_cgroup_dev_ctx", BPF_PROG_TYPE_CGROUP_DEVICE, BPF_CGROUP_DEVICE }, 847 { "bpf_sysctl", "bpf_sysctl_kern", BPF_PROG_TYPE_CGROUP_SYSCTL, BPF_CGROUP_SYSCTL }, 848 { "bpf_sockopt", "bpf_sockopt_kern", BPF_PROG_TYPE_CGROUP_SOCKOPT, BPF_CGROUP_SETSOCKOPT }, 849 { "sk_reuseport_md", "sk_reuseport_kern", BPF_PROG_TYPE_SK_REUSEPORT, BPF_SK_REUSEPORT_SELECT_OR_MIGRATE }, 850 { "bpf_sk_lookup", "bpf_sk_lookup_kern", BPF_PROG_TYPE_SK_LOOKUP, BPF_SK_LOOKUP }, 851 { "xdp_md", "xdp_buff", BPF_PROG_TYPE_XDP, BPF_XDP }, 852 /* tracing types with no expected attach type */ 853 { "bpf_user_pt_regs_t", "pt_regs", BPF_PROG_TYPE_KPROBE }, 854 { "bpf_perf_event_data", "bpf_perf_event_data_kern", BPF_PROG_TYPE_PERF_EVENT }, 855 /* raw_tp programs use u64[] from kernel side, we don't want 856 * to match on that, probably; so NULL for kern-side type 857 */ 858 { "bpf_raw_tracepoint_args", NULL, BPF_PROG_TYPE_RAW_TRACEPOINT }, 859 }; 860 int i; 861 862 if (!ctx_name) 863 return -EINVAL; 864 865 for (i = 0; i < ARRAY_SIZE(ctx_map); i++) { 866 if (strcmp(ctx_map[i].uapi_name, ctx_name) == 0 || 867 (ctx_map[i].kern_name && strcmp(ctx_map[i].kern_name, ctx_name) == 0)) { 868 *prog_type = ctx_map[i].prog_type; 869 *attach_type = ctx_map[i].attach_type; 870 return 0; 871 } 872 } 873 874 return -ESRCH; 875 } 876 877 static void fixup_obj(struct bpf_object *obj, struct bpf_program *prog, const char *filename) 878 { 879 struct bpf_map *map; 880 881 bpf_object__for_each_map(map, obj) { 882 /* disable pinning */ 883 bpf_map__set_pin_path(map, NULL); 884 885 /* fix up map size, if necessary */ 886 switch (bpf_map__type(map)) { 887 case BPF_MAP_TYPE_SK_STORAGE: 888 case BPF_MAP_TYPE_TASK_STORAGE: 889 case BPF_MAP_TYPE_INODE_STORAGE: 890 case BPF_MAP_TYPE_CGROUP_STORAGE: 891 break; 892 default: 893 if (bpf_map__max_entries(map) == 0) 894 bpf_map__set_max_entries(map, 1); 895 } 896 } 897 898 /* SEC(freplace) programs can't be loaded with veristat as is, 899 * but we can try guessing their target program's expected type by 900 * looking at the type of program's first argument and substituting 901 * corresponding program type 902 */ 903 if (bpf_program__type(prog) == BPF_PROG_TYPE_EXT) { 904 const struct btf *btf = bpf_object__btf(obj); 905 const char *prog_name = bpf_program__name(prog); 906 enum bpf_prog_type prog_type; 907 enum bpf_attach_type attach_type; 908 const struct btf_type *t; 909 const char *ctx_name; 910 int id; 911 912 if (!btf) 913 goto skip_freplace_fixup; 914 915 id = btf__find_by_name_kind(btf, prog_name, BTF_KIND_FUNC); 916 t = btf__type_by_id(btf, id); 917 t = btf__type_by_id(btf, t->type); 918 if (!btf_is_func_proto(t) || btf_vlen(t) != 1) 919 goto skip_freplace_fixup; 920 921 /* context argument is a pointer to a struct/typedef */ 922 t = btf__type_by_id(btf, btf_params(t)[0].type); 923 while (t && btf_is_mod(t)) 924 t = btf__type_by_id(btf, t->type); 925 if (!t || !btf_is_ptr(t)) 926 goto skip_freplace_fixup; 927 t = btf__type_by_id(btf, t->type); 928 while (t && btf_is_mod(t)) 929 t = btf__type_by_id(btf, t->type); 930 if (!t) 931 goto skip_freplace_fixup; 932 933 ctx_name = btf__name_by_offset(btf, t->name_off); 934 935 if (guess_prog_type_by_ctx_name(ctx_name, &prog_type, &attach_type) == 0) { 936 bpf_program__set_type(prog, prog_type); 937 bpf_program__set_expected_attach_type(prog, attach_type); 938 939 if (!env.quiet) { 940 printf("Using guessed program type '%s' for %s/%s...\n", 941 libbpf_bpf_prog_type_str(prog_type), 942 filename, prog_name); 943 } 944 } else { 945 if (!env.quiet) { 946 printf("Failed to guess program type for freplace program with context type name '%s' for %s/%s. Consider using canonical type names to help veristat...\n", 947 ctx_name, filename, prog_name); 948 } 949 } 950 } 951 skip_freplace_fixup: 952 return; 953 } 954 955 static int process_prog(const char *filename, struct bpf_object *obj, struct bpf_program *prog) 956 { 957 const char *prog_name = bpf_program__name(prog); 958 const char *base_filename = basename(filename); 959 char *buf; 960 int buf_sz, log_level; 961 struct verif_stats *stats; 962 int err = 0; 963 void *tmp; 964 965 if (!should_process_file_prog(base_filename, bpf_program__name(prog))) { 966 env.progs_skipped++; 967 return 0; 968 } 969 970 tmp = realloc(env.prog_stats, (env.prog_stat_cnt + 1) * sizeof(*env.prog_stats)); 971 if (!tmp) 972 return -ENOMEM; 973 env.prog_stats = tmp; 974 stats = &env.prog_stats[env.prog_stat_cnt++]; 975 memset(stats, 0, sizeof(*stats)); 976 977 if (env.verbose) { 978 buf_sz = env.log_size ? env.log_size : 16 * 1024 * 1024; 979 buf = malloc(buf_sz); 980 if (!buf) 981 return -ENOMEM; 982 /* ensure we always request stats */ 983 log_level = env.log_level | 4 | (env.log_fixed ? 8 : 0); 984 } else { 985 buf = verif_log_buf; 986 buf_sz = sizeof(verif_log_buf); 987 /* request only verifier stats */ 988 log_level = 4 | (env.log_fixed ? 8 : 0); 989 } 990 verif_log_buf[0] = '\0'; 991 992 bpf_program__set_log_buf(prog, buf, buf_sz); 993 bpf_program__set_log_level(prog, log_level); 994 995 /* increase chances of successful BPF object loading */ 996 fixup_obj(obj, prog, base_filename); 997 998 if (env.force_checkpoints) 999 bpf_program__set_flags(prog, bpf_program__flags(prog) | BPF_F_TEST_STATE_FREQ); 1000 1001 err = bpf_object__load(obj); 1002 env.progs_processed++; 1003 1004 stats->file_name = strdup(base_filename); 1005 stats->prog_name = strdup(bpf_program__name(prog)); 1006 stats->stats[VERDICT] = err == 0; /* 1 - success, 0 - failure */ 1007 parse_verif_log(buf, buf_sz, stats); 1008 1009 if (env.verbose) { 1010 printf("PROCESSING %s/%s, DURATION US: %ld, VERDICT: %s, VERIFIER LOG:\n%s\n", 1011 filename, prog_name, stats->stats[DURATION], 1012 err ? "failure" : "success", buf); 1013 } 1014 1015 if (verif_log_buf != buf) 1016 free(buf); 1017 1018 return 0; 1019 }; 1020 1021 static int process_obj(const char *filename) 1022 { 1023 struct bpf_object *obj = NULL, *tobj; 1024 struct bpf_program *prog, *tprog, *lprog; 1025 libbpf_print_fn_t old_libbpf_print_fn; 1026 LIBBPF_OPTS(bpf_object_open_opts, opts); 1027 int err = 0, prog_cnt = 0; 1028 1029 if (!should_process_file_prog(basename(filename), NULL)) { 1030 if (env.verbose) 1031 printf("Skipping '%s' due to filters...\n", filename); 1032 env.files_skipped++; 1033 return 0; 1034 } 1035 if (!is_bpf_obj_file(filename)) { 1036 if (env.verbose) 1037 printf("Skipping '%s' as it's not a BPF object file...\n", filename); 1038 env.files_skipped++; 1039 return 0; 1040 } 1041 1042 if (!env.quiet && env.out_fmt == RESFMT_TABLE) 1043 printf("Processing '%s'...\n", basename(filename)); 1044 1045 old_libbpf_print_fn = libbpf_set_print(libbpf_print_fn); 1046 obj = bpf_object__open_file(filename, &opts); 1047 if (!obj) { 1048 /* if libbpf can't open BPF object file, it could be because 1049 * that BPF object file is incomplete and has to be statically 1050 * linked into a final BPF object file; instead of bailing 1051 * out, report it into stderr, mark it as skipped, and 1052 * proceed 1053 */ 1054 fprintf(stderr, "Failed to open '%s': %d\n", filename, -errno); 1055 env.files_skipped++; 1056 err = 0; 1057 goto cleanup; 1058 } 1059 1060 env.files_processed++; 1061 1062 bpf_object__for_each_program(prog, obj) { 1063 prog_cnt++; 1064 } 1065 1066 if (prog_cnt == 1) { 1067 prog = bpf_object__next_program(obj, NULL); 1068 bpf_program__set_autoload(prog, true); 1069 process_prog(filename, obj, prog); 1070 goto cleanup; 1071 } 1072 1073 bpf_object__for_each_program(prog, obj) { 1074 const char *prog_name = bpf_program__name(prog); 1075 1076 tobj = bpf_object__open_file(filename, &opts); 1077 if (!tobj) { 1078 err = -errno; 1079 fprintf(stderr, "Failed to open '%s': %d\n", filename, err); 1080 goto cleanup; 1081 } 1082 1083 lprog = NULL; 1084 bpf_object__for_each_program(tprog, tobj) { 1085 const char *tprog_name = bpf_program__name(tprog); 1086 1087 if (strcmp(prog_name, tprog_name) == 0) { 1088 bpf_program__set_autoload(tprog, true); 1089 lprog = tprog; 1090 } else { 1091 bpf_program__set_autoload(tprog, false); 1092 } 1093 } 1094 1095 process_prog(filename, tobj, lprog); 1096 bpf_object__close(tobj); 1097 } 1098 1099 cleanup: 1100 bpf_object__close(obj); 1101 libbpf_set_print(old_libbpf_print_fn); 1102 return err; 1103 } 1104 1105 static int cmp_stat(const struct verif_stats *s1, const struct verif_stats *s2, 1106 enum stat_id id, bool asc) 1107 { 1108 int cmp = 0; 1109 1110 switch (id) { 1111 case FILE_NAME: 1112 cmp = strcmp(s1->file_name, s2->file_name); 1113 break; 1114 case PROG_NAME: 1115 cmp = strcmp(s1->prog_name, s2->prog_name); 1116 break; 1117 case VERDICT: 1118 case DURATION: 1119 case TOTAL_INSNS: 1120 case TOTAL_STATES: 1121 case PEAK_STATES: 1122 case MAX_STATES_PER_INSN: 1123 case MARK_READ_MAX_LEN: { 1124 long v1 = s1->stats[id]; 1125 long v2 = s2->stats[id]; 1126 1127 if (v1 != v2) 1128 cmp = v1 < v2 ? -1 : 1; 1129 break; 1130 } 1131 default: 1132 fprintf(stderr, "Unrecognized stat #%d\n", id); 1133 exit(1); 1134 } 1135 1136 return asc ? cmp : -cmp; 1137 } 1138 1139 static int cmp_prog_stats(const void *v1, const void *v2) 1140 { 1141 const struct verif_stats *s1 = v1, *s2 = v2; 1142 int i, cmp; 1143 1144 for (i = 0; i < env.sort_spec.spec_cnt; i++) { 1145 cmp = cmp_stat(s1, s2, env.sort_spec.ids[i], env.sort_spec.asc[i]); 1146 if (cmp != 0) 1147 return cmp; 1148 } 1149 1150 /* always disambiguate with file+prog, which are unique */ 1151 cmp = strcmp(s1->file_name, s2->file_name); 1152 if (cmp != 0) 1153 return cmp; 1154 return strcmp(s1->prog_name, s2->prog_name); 1155 } 1156 1157 static void fetch_join_stat_value(const struct verif_stats_join *s, 1158 enum stat_id id, enum stat_variant var, 1159 const char **str_val, 1160 double *num_val) 1161 { 1162 long v1, v2; 1163 1164 if (id == FILE_NAME) { 1165 *str_val = s->file_name; 1166 return; 1167 } 1168 if (id == PROG_NAME) { 1169 *str_val = s->prog_name; 1170 return; 1171 } 1172 1173 v1 = s->stats_a ? s->stats_a->stats[id] : 0; 1174 v2 = s->stats_b ? s->stats_b->stats[id] : 0; 1175 1176 switch (var) { 1177 case VARIANT_A: 1178 if (!s->stats_a) 1179 *num_val = -DBL_MAX; 1180 else 1181 *num_val = s->stats_a->stats[id]; 1182 return; 1183 case VARIANT_B: 1184 if (!s->stats_b) 1185 *num_val = -DBL_MAX; 1186 else 1187 *num_val = s->stats_b->stats[id]; 1188 return; 1189 case VARIANT_DIFF: 1190 if (!s->stats_a || !s->stats_b) 1191 *num_val = -DBL_MAX; 1192 else if (id == VERDICT) 1193 *num_val = v1 == v2 ? 1.0 /* MATCH */ : 0.0 /* MISMATCH */; 1194 else 1195 *num_val = (double)(v2 - v1); 1196 return; 1197 case VARIANT_PCT: 1198 if (!s->stats_a || !s->stats_b) { 1199 *num_val = -DBL_MAX; 1200 } else if (v1 == 0) { 1201 if (v1 == v2) 1202 *num_val = 0.0; 1203 else 1204 *num_val = v2 < v1 ? -100.0 : 100.0; 1205 } else { 1206 *num_val = (v2 - v1) * 100.0 / v1; 1207 } 1208 return; 1209 } 1210 } 1211 1212 static int cmp_join_stat(const struct verif_stats_join *s1, 1213 const struct verif_stats_join *s2, 1214 enum stat_id id, enum stat_variant var, bool asc) 1215 { 1216 const char *str1 = NULL, *str2 = NULL; 1217 double v1 = 0.0, v2 = 0.0; 1218 int cmp = 0; 1219 1220 fetch_join_stat_value(s1, id, var, &str1, &v1); 1221 fetch_join_stat_value(s2, id, var, &str2, &v2); 1222 1223 if (str1) 1224 cmp = strcmp(str1, str2); 1225 else if (v1 != v2) 1226 cmp = v1 < v2 ? -1 : 1; 1227 1228 return asc ? cmp : -cmp; 1229 } 1230 1231 static int cmp_join_stats(const void *v1, const void *v2) 1232 { 1233 const struct verif_stats_join *s1 = v1, *s2 = v2; 1234 int i, cmp; 1235 1236 for (i = 0; i < env.sort_spec.spec_cnt; i++) { 1237 cmp = cmp_join_stat(s1, s2, 1238 env.sort_spec.ids[i], 1239 env.sort_spec.variants[i], 1240 env.sort_spec.asc[i]); 1241 if (cmp != 0) 1242 return cmp; 1243 } 1244 1245 /* always disambiguate with file+prog, which are unique */ 1246 cmp = strcmp(s1->file_name, s2->file_name); 1247 if (cmp != 0) 1248 return cmp; 1249 return strcmp(s1->prog_name, s2->prog_name); 1250 } 1251 1252 #define HEADER_CHAR '-' 1253 #define COLUMN_SEP " " 1254 1255 static void output_header_underlines(void) 1256 { 1257 int i, j, len; 1258 1259 for (i = 0; i < env.output_spec.spec_cnt; i++) { 1260 len = env.output_spec.lens[i]; 1261 1262 printf("%s", i == 0 ? "" : COLUMN_SEP); 1263 for (j = 0; j < len; j++) 1264 printf("%c", HEADER_CHAR); 1265 } 1266 printf("\n"); 1267 } 1268 1269 static void output_headers(enum resfmt fmt) 1270 { 1271 const char *fmt_str; 1272 int i, len; 1273 1274 for (i = 0; i < env.output_spec.spec_cnt; i++) { 1275 int id = env.output_spec.ids[i]; 1276 int *max_len = &env.output_spec.lens[i]; 1277 1278 switch (fmt) { 1279 case RESFMT_TABLE_CALCLEN: 1280 len = snprintf(NULL, 0, "%s", stat_defs[id].header); 1281 if (len > *max_len) 1282 *max_len = len; 1283 break; 1284 case RESFMT_TABLE: 1285 fmt_str = stat_defs[id].left_aligned ? "%s%-*s" : "%s%*s"; 1286 printf(fmt_str, i == 0 ? "" : COLUMN_SEP, *max_len, stat_defs[id].header); 1287 if (i == env.output_spec.spec_cnt - 1) 1288 printf("\n"); 1289 break; 1290 case RESFMT_CSV: 1291 printf("%s%s", i == 0 ? "" : ",", stat_defs[id].names[0]); 1292 if (i == env.output_spec.spec_cnt - 1) 1293 printf("\n"); 1294 break; 1295 } 1296 } 1297 1298 if (fmt == RESFMT_TABLE) 1299 output_header_underlines(); 1300 } 1301 1302 static void prepare_value(const struct verif_stats *s, enum stat_id id, 1303 const char **str, long *val) 1304 { 1305 switch (id) { 1306 case FILE_NAME: 1307 *str = s ? s->file_name : "N/A"; 1308 break; 1309 case PROG_NAME: 1310 *str = s ? s->prog_name : "N/A"; 1311 break; 1312 case VERDICT: 1313 if (!s) 1314 *str = "N/A"; 1315 else 1316 *str = s->stats[VERDICT] ? "success" : "failure"; 1317 break; 1318 case DURATION: 1319 case TOTAL_INSNS: 1320 case TOTAL_STATES: 1321 case PEAK_STATES: 1322 case MAX_STATES_PER_INSN: 1323 case MARK_READ_MAX_LEN: 1324 *val = s ? s->stats[id] : 0; 1325 break; 1326 default: 1327 fprintf(stderr, "Unrecognized stat #%d\n", id); 1328 exit(1); 1329 } 1330 } 1331 1332 static void output_stats(const struct verif_stats *s, enum resfmt fmt, bool last) 1333 { 1334 int i; 1335 1336 for (i = 0; i < env.output_spec.spec_cnt; i++) { 1337 int id = env.output_spec.ids[i]; 1338 int *max_len = &env.output_spec.lens[i], len; 1339 const char *str = NULL; 1340 long val = 0; 1341 1342 prepare_value(s, id, &str, &val); 1343 1344 switch (fmt) { 1345 case RESFMT_TABLE_CALCLEN: 1346 if (str) 1347 len = snprintf(NULL, 0, "%s", str); 1348 else 1349 len = snprintf(NULL, 0, "%ld", val); 1350 if (len > *max_len) 1351 *max_len = len; 1352 break; 1353 case RESFMT_TABLE: 1354 if (str) 1355 printf("%s%-*s", i == 0 ? "" : COLUMN_SEP, *max_len, str); 1356 else 1357 printf("%s%*ld", i == 0 ? "" : COLUMN_SEP, *max_len, val); 1358 if (i == env.output_spec.spec_cnt - 1) 1359 printf("\n"); 1360 break; 1361 case RESFMT_CSV: 1362 if (str) 1363 printf("%s%s", i == 0 ? "" : ",", str); 1364 else 1365 printf("%s%ld", i == 0 ? "" : ",", val); 1366 if (i == env.output_spec.spec_cnt - 1) 1367 printf("\n"); 1368 break; 1369 } 1370 } 1371 1372 if (last && fmt == RESFMT_TABLE) { 1373 output_header_underlines(); 1374 printf("Done. Processed %d files, %d programs. Skipped %d files, %d programs.\n", 1375 env.files_processed, env.files_skipped, env.progs_processed, env.progs_skipped); 1376 } 1377 } 1378 1379 static int parse_stat_value(const char *str, enum stat_id id, struct verif_stats *st) 1380 { 1381 switch (id) { 1382 case FILE_NAME: 1383 st->file_name = strdup(str); 1384 if (!st->file_name) 1385 return -ENOMEM; 1386 break; 1387 case PROG_NAME: 1388 st->prog_name = strdup(str); 1389 if (!st->prog_name) 1390 return -ENOMEM; 1391 break; 1392 case VERDICT: 1393 if (strcmp(str, "success") == 0) { 1394 st->stats[VERDICT] = true; 1395 } else if (strcmp(str, "failure") == 0) { 1396 st->stats[VERDICT] = false; 1397 } else { 1398 fprintf(stderr, "Unrecognized verification verdict '%s'\n", str); 1399 return -EINVAL; 1400 } 1401 break; 1402 case DURATION: 1403 case TOTAL_INSNS: 1404 case TOTAL_STATES: 1405 case PEAK_STATES: 1406 case MAX_STATES_PER_INSN: 1407 case MARK_READ_MAX_LEN: { 1408 long val; 1409 int err, n; 1410 1411 if (sscanf(str, "%ld %n", &val, &n) != 1 || n != strlen(str)) { 1412 err = -errno; 1413 fprintf(stderr, "Failed to parse '%s' as integer\n", str); 1414 return err; 1415 } 1416 1417 st->stats[id] = val; 1418 break; 1419 } 1420 default: 1421 fprintf(stderr, "Unrecognized stat #%d\n", id); 1422 return -EINVAL; 1423 } 1424 return 0; 1425 } 1426 1427 static int parse_stats_csv(const char *filename, struct stat_specs *specs, 1428 struct verif_stats **statsp, int *stat_cntp) 1429 { 1430 char line[4096]; 1431 FILE *f; 1432 int err = 0; 1433 bool header = true; 1434 1435 f = fopen(filename, "r"); 1436 if (!f) { 1437 err = -errno; 1438 fprintf(stderr, "Failed to open '%s': %d\n", filename, err); 1439 return err; 1440 } 1441 1442 *stat_cntp = 0; 1443 1444 while (fgets(line, sizeof(line), f)) { 1445 char *input = line, *state = NULL, *next; 1446 struct verif_stats *st = NULL; 1447 int col = 0; 1448 1449 if (!header) { 1450 void *tmp; 1451 1452 tmp = realloc(*statsp, (*stat_cntp + 1) * sizeof(**statsp)); 1453 if (!tmp) { 1454 err = -ENOMEM; 1455 goto cleanup; 1456 } 1457 *statsp = tmp; 1458 1459 st = &(*statsp)[*stat_cntp]; 1460 memset(st, 0, sizeof(*st)); 1461 1462 *stat_cntp += 1; 1463 } 1464 1465 while ((next = strtok_r(state ? NULL : input, ",\n", &state))) { 1466 if (header) { 1467 /* for the first line, set up spec stats */ 1468 err = parse_stat(next, specs); 1469 if (err) 1470 goto cleanup; 1471 continue; 1472 } 1473 1474 /* for all other lines, parse values based on spec */ 1475 if (col >= specs->spec_cnt) { 1476 fprintf(stderr, "Found extraneous column #%d in row #%d of '%s'\n", 1477 col, *stat_cntp, filename); 1478 err = -EINVAL; 1479 goto cleanup; 1480 } 1481 err = parse_stat_value(next, specs->ids[col], st); 1482 if (err) 1483 goto cleanup; 1484 col++; 1485 } 1486 1487 if (header) { 1488 header = false; 1489 continue; 1490 } 1491 1492 if (col < specs->spec_cnt) { 1493 fprintf(stderr, "Not enough columns in row #%d in '%s'\n", 1494 *stat_cntp, filename); 1495 err = -EINVAL; 1496 goto cleanup; 1497 } 1498 1499 if (!st->file_name || !st->prog_name) { 1500 fprintf(stderr, "Row #%d in '%s' is missing file and/or program name\n", 1501 *stat_cntp, filename); 1502 err = -EINVAL; 1503 goto cleanup; 1504 } 1505 1506 /* in comparison mode we can only check filters after we 1507 * parsed entire line; if row should be ignored we pretend we 1508 * never parsed it 1509 */ 1510 if (!should_process_file_prog(st->file_name, st->prog_name)) { 1511 free(st->file_name); 1512 free(st->prog_name); 1513 *stat_cntp -= 1; 1514 } 1515 } 1516 1517 if (!feof(f)) { 1518 err = -errno; 1519 fprintf(stderr, "Failed I/O for '%s': %d\n", filename, err); 1520 } 1521 1522 cleanup: 1523 fclose(f); 1524 return err; 1525 } 1526 1527 /* empty/zero stats for mismatched rows */ 1528 static const struct verif_stats fallback_stats = { .file_name = "", .prog_name = "" }; 1529 1530 static bool is_key_stat(enum stat_id id) 1531 { 1532 return id == FILE_NAME || id == PROG_NAME; 1533 } 1534 1535 static void output_comp_header_underlines(void) 1536 { 1537 int i, j, k; 1538 1539 for (i = 0; i < env.output_spec.spec_cnt; i++) { 1540 int id = env.output_spec.ids[i]; 1541 int max_j = is_key_stat(id) ? 1 : 3; 1542 1543 for (j = 0; j < max_j; j++) { 1544 int len = env.output_spec.lens[3 * i + j]; 1545 1546 printf("%s", i + j == 0 ? "" : COLUMN_SEP); 1547 1548 for (k = 0; k < len; k++) 1549 printf("%c", HEADER_CHAR); 1550 } 1551 } 1552 printf("\n"); 1553 } 1554 1555 static void output_comp_headers(enum resfmt fmt) 1556 { 1557 static const char *table_sfxs[3] = {" (A)", " (B)", " (DIFF)"}; 1558 static const char *name_sfxs[3] = {"_base", "_comp", "_diff"}; 1559 int i, j, len; 1560 1561 for (i = 0; i < env.output_spec.spec_cnt; i++) { 1562 int id = env.output_spec.ids[i]; 1563 /* key stats don't have A/B/DIFF columns, they are common for both data sets */ 1564 int max_j = is_key_stat(id) ? 1 : 3; 1565 1566 for (j = 0; j < max_j; j++) { 1567 int *max_len = &env.output_spec.lens[3 * i + j]; 1568 bool last = (i == env.output_spec.spec_cnt - 1) && (j == max_j - 1); 1569 const char *sfx; 1570 1571 switch (fmt) { 1572 case RESFMT_TABLE_CALCLEN: 1573 sfx = is_key_stat(id) ? "" : table_sfxs[j]; 1574 len = snprintf(NULL, 0, "%s%s", stat_defs[id].header, sfx); 1575 if (len > *max_len) 1576 *max_len = len; 1577 break; 1578 case RESFMT_TABLE: 1579 sfx = is_key_stat(id) ? "" : table_sfxs[j]; 1580 printf("%s%-*s%s", i + j == 0 ? "" : COLUMN_SEP, 1581 *max_len - (int)strlen(sfx), stat_defs[id].header, sfx); 1582 if (last) 1583 printf("\n"); 1584 break; 1585 case RESFMT_CSV: 1586 sfx = is_key_stat(id) ? "" : name_sfxs[j]; 1587 printf("%s%s%s", i + j == 0 ? "" : ",", stat_defs[id].names[0], sfx); 1588 if (last) 1589 printf("\n"); 1590 break; 1591 } 1592 } 1593 } 1594 1595 if (fmt == RESFMT_TABLE) 1596 output_comp_header_underlines(); 1597 } 1598 1599 static void output_comp_stats(const struct verif_stats_join *join_stats, 1600 enum resfmt fmt, bool last) 1601 { 1602 const struct verif_stats *base = join_stats->stats_a; 1603 const struct verif_stats *comp = join_stats->stats_b; 1604 char base_buf[1024] = {}, comp_buf[1024] = {}, diff_buf[1024] = {}; 1605 int i; 1606 1607 for (i = 0; i < env.output_spec.spec_cnt; i++) { 1608 int id = env.output_spec.ids[i], len; 1609 int *max_len_base = &env.output_spec.lens[3 * i + 0]; 1610 int *max_len_comp = &env.output_spec.lens[3 * i + 1]; 1611 int *max_len_diff = &env.output_spec.lens[3 * i + 2]; 1612 const char *base_str = NULL, *comp_str = NULL; 1613 long base_val = 0, comp_val = 0, diff_val = 0; 1614 1615 prepare_value(base, id, &base_str, &base_val); 1616 prepare_value(comp, id, &comp_str, &comp_val); 1617 1618 /* normalize all the outputs to be in string buffers for simplicity */ 1619 if (is_key_stat(id)) { 1620 /* key stats (file and program name) are always strings */ 1621 if (base) 1622 snprintf(base_buf, sizeof(base_buf), "%s", base_str); 1623 else 1624 snprintf(base_buf, sizeof(base_buf), "%s", comp_str); 1625 } else if (base_str) { 1626 snprintf(base_buf, sizeof(base_buf), "%s", base_str); 1627 snprintf(comp_buf, sizeof(comp_buf), "%s", comp_str); 1628 if (!base || !comp) 1629 snprintf(diff_buf, sizeof(diff_buf), "%s", "N/A"); 1630 else if (strcmp(base_str, comp_str) == 0) 1631 snprintf(diff_buf, sizeof(diff_buf), "%s", "MATCH"); 1632 else 1633 snprintf(diff_buf, sizeof(diff_buf), "%s", "MISMATCH"); 1634 } else { 1635 double p = 0.0; 1636 1637 if (base) 1638 snprintf(base_buf, sizeof(base_buf), "%ld", base_val); 1639 else 1640 snprintf(base_buf, sizeof(base_buf), "%s", "N/A"); 1641 if (comp) 1642 snprintf(comp_buf, sizeof(comp_buf), "%ld", comp_val); 1643 else 1644 snprintf(comp_buf, sizeof(comp_buf), "%s", "N/A"); 1645 1646 diff_val = comp_val - base_val; 1647 if (!base || !comp) { 1648 snprintf(diff_buf, sizeof(diff_buf), "%s", "N/A"); 1649 } else { 1650 if (base_val == 0) { 1651 if (comp_val == base_val) 1652 p = 0.0; /* avoid +0 (+100%) case */ 1653 else 1654 p = comp_val < base_val ? -100.0 : 100.0; 1655 } else { 1656 p = diff_val * 100.0 / base_val; 1657 } 1658 snprintf(diff_buf, sizeof(diff_buf), "%+ld (%+.2lf%%)", diff_val, p); 1659 } 1660 } 1661 1662 switch (fmt) { 1663 case RESFMT_TABLE_CALCLEN: 1664 len = strlen(base_buf); 1665 if (len > *max_len_base) 1666 *max_len_base = len; 1667 if (!is_key_stat(id)) { 1668 len = strlen(comp_buf); 1669 if (len > *max_len_comp) 1670 *max_len_comp = len; 1671 len = strlen(diff_buf); 1672 if (len > *max_len_diff) 1673 *max_len_diff = len; 1674 } 1675 break; 1676 case RESFMT_TABLE: { 1677 /* string outputs are left-aligned, number outputs are right-aligned */ 1678 const char *fmt = base_str ? "%s%-*s" : "%s%*s"; 1679 1680 printf(fmt, i == 0 ? "" : COLUMN_SEP, *max_len_base, base_buf); 1681 if (!is_key_stat(id)) { 1682 printf(fmt, COLUMN_SEP, *max_len_comp, comp_buf); 1683 printf(fmt, COLUMN_SEP, *max_len_diff, diff_buf); 1684 } 1685 if (i == env.output_spec.spec_cnt - 1) 1686 printf("\n"); 1687 break; 1688 } 1689 case RESFMT_CSV: 1690 printf("%s%s", i == 0 ? "" : ",", base_buf); 1691 if (!is_key_stat(id)) { 1692 printf("%s%s", i == 0 ? "" : ",", comp_buf); 1693 printf("%s%s", i == 0 ? "" : ",", diff_buf); 1694 } 1695 if (i == env.output_spec.spec_cnt - 1) 1696 printf("\n"); 1697 break; 1698 } 1699 } 1700 1701 if (last && fmt == RESFMT_TABLE) 1702 output_comp_header_underlines(); 1703 } 1704 1705 static int cmp_stats_key(const struct verif_stats *base, const struct verif_stats *comp) 1706 { 1707 int r; 1708 1709 r = strcmp(base->file_name, comp->file_name); 1710 if (r != 0) 1711 return r; 1712 return strcmp(base->prog_name, comp->prog_name); 1713 } 1714 1715 static bool is_join_stat_filter_matched(struct filter *f, const struct verif_stats_join *stats) 1716 { 1717 static const double eps = 1e-9; 1718 const char *str = NULL; 1719 double value = 0.0; 1720 1721 fetch_join_stat_value(stats, f->stat_id, f->stat_var, &str, &value); 1722 1723 switch (f->op) { 1724 case OP_EQ: return value > f->value - eps && value < f->value + eps; 1725 case OP_NEQ: return value < f->value - eps || value > f->value + eps; 1726 case OP_LT: return value < f->value - eps; 1727 case OP_LE: return value <= f->value + eps; 1728 case OP_GT: return value > f->value + eps; 1729 case OP_GE: return value >= f->value - eps; 1730 } 1731 1732 fprintf(stderr, "BUG: unknown filter op %d!\n", f->op); 1733 return false; 1734 } 1735 1736 static bool should_output_join_stats(const struct verif_stats_join *stats) 1737 { 1738 struct filter *f; 1739 int i, allow_cnt = 0; 1740 1741 for (i = 0; i < env.deny_filter_cnt; i++) { 1742 f = &env.deny_filters[i]; 1743 if (f->kind != FILTER_STAT) 1744 continue; 1745 1746 if (is_join_stat_filter_matched(f, stats)) 1747 return false; 1748 } 1749 1750 for (i = 0; i < env.allow_filter_cnt; i++) { 1751 f = &env.allow_filters[i]; 1752 if (f->kind != FILTER_STAT) 1753 continue; 1754 allow_cnt++; 1755 1756 if (is_join_stat_filter_matched(f, stats)) 1757 return true; 1758 } 1759 1760 /* if there are no stat allowed filters, pass everything through */ 1761 return allow_cnt == 0; 1762 } 1763 1764 static int handle_comparison_mode(void) 1765 { 1766 struct stat_specs base_specs = {}, comp_specs = {}; 1767 struct stat_specs tmp_sort_spec; 1768 enum resfmt cur_fmt; 1769 int err, i, j, last_idx; 1770 1771 if (env.filename_cnt != 2) { 1772 fprintf(stderr, "Comparison mode expects exactly two input CSV files!\n\n"); 1773 argp_help(&argp, stderr, ARGP_HELP_USAGE, "veristat"); 1774 return -EINVAL; 1775 } 1776 1777 err = parse_stats_csv(env.filenames[0], &base_specs, 1778 &env.baseline_stats, &env.baseline_stat_cnt); 1779 if (err) { 1780 fprintf(stderr, "Failed to parse stats from '%s': %d\n", env.filenames[0], err); 1781 return err; 1782 } 1783 err = parse_stats_csv(env.filenames[1], &comp_specs, 1784 &env.prog_stats, &env.prog_stat_cnt); 1785 if (err) { 1786 fprintf(stderr, "Failed to parse stats from '%s': %d\n", env.filenames[1], err); 1787 return err; 1788 } 1789 1790 /* To keep it simple we validate that the set and order of stats in 1791 * both CSVs are exactly the same. This can be lifted with a bit more 1792 * pre-processing later. 1793 */ 1794 if (base_specs.spec_cnt != comp_specs.spec_cnt) { 1795 fprintf(stderr, "Number of stats in '%s' and '%s' differs (%d != %d)!\n", 1796 env.filenames[0], env.filenames[1], 1797 base_specs.spec_cnt, comp_specs.spec_cnt); 1798 return -EINVAL; 1799 } 1800 for (i = 0; i < base_specs.spec_cnt; i++) { 1801 if (base_specs.ids[i] != comp_specs.ids[i]) { 1802 fprintf(stderr, "Stats composition differs between '%s' and '%s' (%s != %s)!\n", 1803 env.filenames[0], env.filenames[1], 1804 stat_defs[base_specs.ids[i]].names[0], 1805 stat_defs[comp_specs.ids[i]].names[0]); 1806 return -EINVAL; 1807 } 1808 } 1809 1810 /* Replace user-specified sorting spec with file+prog sorting rule to 1811 * be able to join two datasets correctly. Once we are done, we will 1812 * restore the original sort spec. 1813 */ 1814 tmp_sort_spec = env.sort_spec; 1815 env.sort_spec = join_sort_spec; 1816 qsort(env.prog_stats, env.prog_stat_cnt, sizeof(*env.prog_stats), cmp_prog_stats); 1817 qsort(env.baseline_stats, env.baseline_stat_cnt, sizeof(*env.baseline_stats), cmp_prog_stats); 1818 env.sort_spec = tmp_sort_spec; 1819 1820 /* Join two datasets together. If baseline and comparison datasets 1821 * have different subset of rows (we match by 'object + prog' as 1822 * a unique key) then assume empty/missing/zero value for rows that 1823 * are missing in the opposite data set. 1824 */ 1825 i = j = 0; 1826 while (i < env.baseline_stat_cnt || j < env.prog_stat_cnt) { 1827 const struct verif_stats *base, *comp; 1828 struct verif_stats_join *join; 1829 void *tmp; 1830 int r; 1831 1832 base = i < env.baseline_stat_cnt ? &env.baseline_stats[i] : &fallback_stats; 1833 comp = j < env.prog_stat_cnt ? &env.prog_stats[j] : &fallback_stats; 1834 1835 if (!base->file_name || !base->prog_name) { 1836 fprintf(stderr, "Entry #%d in '%s' doesn't have file and/or program name specified!\n", 1837 i, env.filenames[0]); 1838 return -EINVAL; 1839 } 1840 if (!comp->file_name || !comp->prog_name) { 1841 fprintf(stderr, "Entry #%d in '%s' doesn't have file and/or program name specified!\n", 1842 j, env.filenames[1]); 1843 return -EINVAL; 1844 } 1845 1846 tmp = realloc(env.join_stats, (env.join_stat_cnt + 1) * sizeof(*env.join_stats)); 1847 if (!tmp) 1848 return -ENOMEM; 1849 env.join_stats = tmp; 1850 1851 join = &env.join_stats[env.join_stat_cnt]; 1852 memset(join, 0, sizeof(*join)); 1853 1854 r = cmp_stats_key(base, comp); 1855 if (r == 0) { 1856 join->file_name = base->file_name; 1857 join->prog_name = base->prog_name; 1858 join->stats_a = base; 1859 join->stats_b = comp; 1860 i++; 1861 j++; 1862 } else if (base != &fallback_stats && (comp == &fallback_stats || r < 0)) { 1863 join->file_name = base->file_name; 1864 join->prog_name = base->prog_name; 1865 join->stats_a = base; 1866 join->stats_b = NULL; 1867 i++; 1868 } else if (comp != &fallback_stats && (base == &fallback_stats || r > 0)) { 1869 join->file_name = comp->file_name; 1870 join->prog_name = comp->prog_name; 1871 join->stats_a = NULL; 1872 join->stats_b = comp; 1873 j++; 1874 } else { 1875 fprintf(stderr, "%s:%d: should never reach here i=%i, j=%i", 1876 __FILE__, __LINE__, i, j); 1877 return -EINVAL; 1878 } 1879 env.join_stat_cnt += 1; 1880 } 1881 1882 /* now sort joined results accorsing to sort spec */ 1883 qsort(env.join_stats, env.join_stat_cnt, sizeof(*env.join_stats), cmp_join_stats); 1884 1885 /* for human-readable table output we need to do extra pass to 1886 * calculate column widths, so we substitute current output format 1887 * with RESFMT_TABLE_CALCLEN and later revert it back to RESFMT_TABLE 1888 * and do everything again. 1889 */ 1890 if (env.out_fmt == RESFMT_TABLE) 1891 cur_fmt = RESFMT_TABLE_CALCLEN; 1892 else 1893 cur_fmt = env.out_fmt; 1894 1895 one_more_time: 1896 output_comp_headers(cur_fmt); 1897 1898 last_idx = -1; 1899 for (i = 0; i < env.join_stat_cnt; i++) { 1900 const struct verif_stats_join *join = &env.join_stats[i]; 1901 1902 if (!should_output_join_stats(join)) 1903 continue; 1904 1905 if (cur_fmt == RESFMT_TABLE_CALCLEN) 1906 last_idx = i; 1907 1908 output_comp_stats(join, cur_fmt, i == last_idx); 1909 } 1910 1911 if (cur_fmt == RESFMT_TABLE_CALCLEN) { 1912 cur_fmt = RESFMT_TABLE; 1913 goto one_more_time; /* ... this time with feeling */ 1914 } 1915 1916 return 0; 1917 } 1918 1919 static bool is_stat_filter_matched(struct filter *f, const struct verif_stats *stats) 1920 { 1921 long value = stats->stats[f->stat_id]; 1922 1923 switch (f->op) { 1924 case OP_EQ: return value == f->value; 1925 case OP_NEQ: return value != f->value; 1926 case OP_LT: return value < f->value; 1927 case OP_LE: return value <= f->value; 1928 case OP_GT: return value > f->value; 1929 case OP_GE: return value >= f->value; 1930 } 1931 1932 fprintf(stderr, "BUG: unknown filter op %d!\n", f->op); 1933 return false; 1934 } 1935 1936 static bool should_output_stats(const struct verif_stats *stats) 1937 { 1938 struct filter *f; 1939 int i, allow_cnt = 0; 1940 1941 for (i = 0; i < env.deny_filter_cnt; i++) { 1942 f = &env.deny_filters[i]; 1943 if (f->kind != FILTER_STAT) 1944 continue; 1945 1946 if (is_stat_filter_matched(f, stats)) 1947 return false; 1948 } 1949 1950 for (i = 0; i < env.allow_filter_cnt; i++) { 1951 f = &env.allow_filters[i]; 1952 if (f->kind != FILTER_STAT) 1953 continue; 1954 allow_cnt++; 1955 1956 if (is_stat_filter_matched(f, stats)) 1957 return true; 1958 } 1959 1960 /* if there are no stat allowed filters, pass everything through */ 1961 return allow_cnt == 0; 1962 } 1963 1964 static void output_prog_stats(void) 1965 { 1966 const struct verif_stats *stats; 1967 int i, last_stat_idx = 0; 1968 1969 if (env.out_fmt == RESFMT_TABLE) { 1970 /* calculate column widths */ 1971 output_headers(RESFMT_TABLE_CALCLEN); 1972 for (i = 0; i < env.prog_stat_cnt; i++) { 1973 stats = &env.prog_stats[i]; 1974 if (!should_output_stats(stats)) 1975 continue; 1976 output_stats(stats, RESFMT_TABLE_CALCLEN, false); 1977 last_stat_idx = i; 1978 } 1979 } 1980 1981 /* actually output the table */ 1982 output_headers(env.out_fmt); 1983 for (i = 0; i < env.prog_stat_cnt; i++) { 1984 stats = &env.prog_stats[i]; 1985 if (!should_output_stats(stats)) 1986 continue; 1987 output_stats(stats, env.out_fmt, i == last_stat_idx); 1988 } 1989 } 1990 1991 static int handle_verif_mode(void) 1992 { 1993 int i, err; 1994 1995 if (env.filename_cnt == 0) { 1996 fprintf(stderr, "Please provide path to BPF object file!\n\n"); 1997 argp_help(&argp, stderr, ARGP_HELP_USAGE, "veristat"); 1998 return -EINVAL; 1999 } 2000 2001 for (i = 0; i < env.filename_cnt; i++) { 2002 err = process_obj(env.filenames[i]); 2003 if (err) { 2004 fprintf(stderr, "Failed to process '%s': %d\n", env.filenames[i], err); 2005 return err; 2006 } 2007 } 2008 2009 qsort(env.prog_stats, env.prog_stat_cnt, sizeof(*env.prog_stats), cmp_prog_stats); 2010 2011 output_prog_stats(); 2012 2013 return 0; 2014 } 2015 2016 static int handle_replay_mode(void) 2017 { 2018 struct stat_specs specs = {}; 2019 int err; 2020 2021 if (env.filename_cnt != 1) { 2022 fprintf(stderr, "Replay mode expects exactly one input CSV file!\n\n"); 2023 argp_help(&argp, stderr, ARGP_HELP_USAGE, "veristat"); 2024 return -EINVAL; 2025 } 2026 2027 err = parse_stats_csv(env.filenames[0], &specs, 2028 &env.prog_stats, &env.prog_stat_cnt); 2029 if (err) { 2030 fprintf(stderr, "Failed to parse stats from '%s': %d\n", env.filenames[0], err); 2031 return err; 2032 } 2033 2034 qsort(env.prog_stats, env.prog_stat_cnt, sizeof(*env.prog_stats), cmp_prog_stats); 2035 2036 output_prog_stats(); 2037 2038 return 0; 2039 } 2040 2041 int main(int argc, char **argv) 2042 { 2043 int err = 0, i; 2044 2045 if (argp_parse(&argp, argc, argv, 0, NULL, NULL)) 2046 return 1; 2047 2048 if (env.show_version) { 2049 printf("%s\n", argp_program_version); 2050 return 0; 2051 } 2052 2053 if (env.verbose && env.quiet) { 2054 fprintf(stderr, "Verbose and quiet modes are incompatible, please specify just one or neither!\n\n"); 2055 argp_help(&argp, stderr, ARGP_HELP_USAGE, "veristat"); 2056 return 1; 2057 } 2058 if (env.verbose && env.log_level == 0) 2059 env.log_level = 1; 2060 2061 if (env.output_spec.spec_cnt == 0) { 2062 if (env.out_fmt == RESFMT_CSV) 2063 env.output_spec = default_csv_output_spec; 2064 else 2065 env.output_spec = default_output_spec; 2066 } 2067 if (env.sort_spec.spec_cnt == 0) 2068 env.sort_spec = default_sort_spec; 2069 2070 if (env.comparison_mode && env.replay_mode) { 2071 fprintf(stderr, "Can't specify replay and comparison mode at the same time!\n\n"); 2072 argp_help(&argp, stderr, ARGP_HELP_USAGE, "veristat"); 2073 return 1; 2074 } 2075 2076 if (env.comparison_mode) 2077 err = handle_comparison_mode(); 2078 else if (env.replay_mode) 2079 err = handle_replay_mode(); 2080 else 2081 err = handle_verif_mode(); 2082 2083 free_verif_stats(env.prog_stats, env.prog_stat_cnt); 2084 free_verif_stats(env.baseline_stats, env.baseline_stat_cnt); 2085 free(env.join_stats); 2086 for (i = 0; i < env.filename_cnt; i++) 2087 free(env.filenames[i]); 2088 free(env.filenames); 2089 for (i = 0; i < env.allow_filter_cnt; i++) { 2090 free(env.allow_filters[i].any_glob); 2091 free(env.allow_filters[i].file_glob); 2092 free(env.allow_filters[i].prog_glob); 2093 } 2094 free(env.allow_filters); 2095 for (i = 0; i < env.deny_filter_cnt; i++) { 2096 free(env.deny_filters[i].any_glob); 2097 free(env.deny_filters[i].file_glob); 2098 free(env.deny_filters[i].prog_glob); 2099 } 2100 free(env.deny_filters); 2101 return -err; 2102 } 2103