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