162306a36Sopenharmony_ci// SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) 262306a36Sopenharmony_ci/* Copyright (c) 2022 Meta Platforms, Inc. and affiliates. */ 362306a36Sopenharmony_ci#define _GNU_SOURCE 462306a36Sopenharmony_ci#include <argp.h> 562306a36Sopenharmony_ci#include <string.h> 662306a36Sopenharmony_ci#include <stdlib.h> 762306a36Sopenharmony_ci#include <sched.h> 862306a36Sopenharmony_ci#include <pthread.h> 962306a36Sopenharmony_ci#include <dirent.h> 1062306a36Sopenharmony_ci#include <signal.h> 1162306a36Sopenharmony_ci#include <fcntl.h> 1262306a36Sopenharmony_ci#include <unistd.h> 1362306a36Sopenharmony_ci#include <sys/time.h> 1462306a36Sopenharmony_ci#include <sys/sysinfo.h> 1562306a36Sopenharmony_ci#include <sys/stat.h> 1662306a36Sopenharmony_ci#include <bpf/libbpf.h> 1762306a36Sopenharmony_ci#include <bpf/btf.h> 1862306a36Sopenharmony_ci#include <libelf.h> 1962306a36Sopenharmony_ci#include <gelf.h> 2062306a36Sopenharmony_ci#include <float.h> 2162306a36Sopenharmony_ci 2262306a36Sopenharmony_ci#ifndef ARRAY_SIZE 2362306a36Sopenharmony_ci#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) 2462306a36Sopenharmony_ci#endif 2562306a36Sopenharmony_ci 2662306a36Sopenharmony_cienum stat_id { 2762306a36Sopenharmony_ci VERDICT, 2862306a36Sopenharmony_ci DURATION, 2962306a36Sopenharmony_ci TOTAL_INSNS, 3062306a36Sopenharmony_ci TOTAL_STATES, 3162306a36Sopenharmony_ci PEAK_STATES, 3262306a36Sopenharmony_ci MAX_STATES_PER_INSN, 3362306a36Sopenharmony_ci MARK_READ_MAX_LEN, 3462306a36Sopenharmony_ci 3562306a36Sopenharmony_ci FILE_NAME, 3662306a36Sopenharmony_ci PROG_NAME, 3762306a36Sopenharmony_ci 3862306a36Sopenharmony_ci ALL_STATS_CNT, 3962306a36Sopenharmony_ci NUM_STATS_CNT = FILE_NAME - VERDICT, 4062306a36Sopenharmony_ci}; 4162306a36Sopenharmony_ci 4262306a36Sopenharmony_ci/* In comparison mode each stat can specify up to four different values: 4362306a36Sopenharmony_ci * - A side value; 4462306a36Sopenharmony_ci * - B side value; 4562306a36Sopenharmony_ci * - absolute diff value; 4662306a36Sopenharmony_ci * - relative (percentage) diff value. 4762306a36Sopenharmony_ci * 4862306a36Sopenharmony_ci * When specifying stat specs in comparison mode, user can use one of the 4962306a36Sopenharmony_ci * following variant suffixes to specify which exact variant should be used for 5062306a36Sopenharmony_ci * ordering or filtering: 5162306a36Sopenharmony_ci * - `_a` for A side value; 5262306a36Sopenharmony_ci * - `_b` for B side value; 5362306a36Sopenharmony_ci * - `_diff` for absolute diff value; 5462306a36Sopenharmony_ci * - `_pct` for relative (percentage) diff value. 5562306a36Sopenharmony_ci * 5662306a36Sopenharmony_ci * If no variant suffix is provided, then `_b` (control data) is assumed. 5762306a36Sopenharmony_ci * 5862306a36Sopenharmony_ci * As an example, let's say instructions stat has the following output: 5962306a36Sopenharmony_ci * 6062306a36Sopenharmony_ci * Insns (A) Insns (B) Insns (DIFF) 6162306a36Sopenharmony_ci * --------- --------- -------------- 6262306a36Sopenharmony_ci * 21547 20920 -627 (-2.91%) 6362306a36Sopenharmony_ci * 6462306a36Sopenharmony_ci * Then: 6562306a36Sopenharmony_ci * - 21547 is A side value (insns_a); 6662306a36Sopenharmony_ci * - 20920 is B side value (insns_b); 6762306a36Sopenharmony_ci * - -627 is absolute diff value (insns_diff); 6862306a36Sopenharmony_ci * - -2.91% is relative diff value (insns_pct). 6962306a36Sopenharmony_ci * 7062306a36Sopenharmony_ci * For verdict there is no verdict_pct variant. 7162306a36Sopenharmony_ci * For file and program name, _a and _b variants are equivalent and there are 7262306a36Sopenharmony_ci * no _diff or _pct variants. 7362306a36Sopenharmony_ci */ 7462306a36Sopenharmony_cienum stat_variant { 7562306a36Sopenharmony_ci VARIANT_A, 7662306a36Sopenharmony_ci VARIANT_B, 7762306a36Sopenharmony_ci VARIANT_DIFF, 7862306a36Sopenharmony_ci VARIANT_PCT, 7962306a36Sopenharmony_ci}; 8062306a36Sopenharmony_ci 8162306a36Sopenharmony_cistruct verif_stats { 8262306a36Sopenharmony_ci char *file_name; 8362306a36Sopenharmony_ci char *prog_name; 8462306a36Sopenharmony_ci 8562306a36Sopenharmony_ci long stats[NUM_STATS_CNT]; 8662306a36Sopenharmony_ci}; 8762306a36Sopenharmony_ci 8862306a36Sopenharmony_ci/* joined comparison mode stats */ 8962306a36Sopenharmony_cistruct verif_stats_join { 9062306a36Sopenharmony_ci char *file_name; 9162306a36Sopenharmony_ci char *prog_name; 9262306a36Sopenharmony_ci 9362306a36Sopenharmony_ci const struct verif_stats *stats_a; 9462306a36Sopenharmony_ci const struct verif_stats *stats_b; 9562306a36Sopenharmony_ci}; 9662306a36Sopenharmony_ci 9762306a36Sopenharmony_cistruct stat_specs { 9862306a36Sopenharmony_ci int spec_cnt; 9962306a36Sopenharmony_ci enum stat_id ids[ALL_STATS_CNT]; 10062306a36Sopenharmony_ci enum stat_variant variants[ALL_STATS_CNT]; 10162306a36Sopenharmony_ci bool asc[ALL_STATS_CNT]; 10262306a36Sopenharmony_ci int lens[ALL_STATS_CNT * 3]; /* 3x for comparison mode */ 10362306a36Sopenharmony_ci}; 10462306a36Sopenharmony_ci 10562306a36Sopenharmony_cienum resfmt { 10662306a36Sopenharmony_ci RESFMT_TABLE, 10762306a36Sopenharmony_ci RESFMT_TABLE_CALCLEN, /* fake format to pre-calculate table's column widths */ 10862306a36Sopenharmony_ci RESFMT_CSV, 10962306a36Sopenharmony_ci}; 11062306a36Sopenharmony_ci 11162306a36Sopenharmony_cienum filter_kind { 11262306a36Sopenharmony_ci FILTER_NAME, 11362306a36Sopenharmony_ci FILTER_STAT, 11462306a36Sopenharmony_ci}; 11562306a36Sopenharmony_ci 11662306a36Sopenharmony_cienum operator_kind { 11762306a36Sopenharmony_ci OP_EQ, /* == or = */ 11862306a36Sopenharmony_ci OP_NEQ, /* != or <> */ 11962306a36Sopenharmony_ci OP_LT, /* < */ 12062306a36Sopenharmony_ci OP_LE, /* <= */ 12162306a36Sopenharmony_ci OP_GT, /* > */ 12262306a36Sopenharmony_ci OP_GE, /* >= */ 12362306a36Sopenharmony_ci}; 12462306a36Sopenharmony_ci 12562306a36Sopenharmony_cistruct filter { 12662306a36Sopenharmony_ci enum filter_kind kind; 12762306a36Sopenharmony_ci /* FILTER_NAME */ 12862306a36Sopenharmony_ci char *any_glob; 12962306a36Sopenharmony_ci char *file_glob; 13062306a36Sopenharmony_ci char *prog_glob; 13162306a36Sopenharmony_ci /* FILTER_STAT */ 13262306a36Sopenharmony_ci enum operator_kind op; 13362306a36Sopenharmony_ci int stat_id; 13462306a36Sopenharmony_ci enum stat_variant stat_var; 13562306a36Sopenharmony_ci long value; 13662306a36Sopenharmony_ci}; 13762306a36Sopenharmony_ci 13862306a36Sopenharmony_cistatic struct env { 13962306a36Sopenharmony_ci char **filenames; 14062306a36Sopenharmony_ci int filename_cnt; 14162306a36Sopenharmony_ci bool verbose; 14262306a36Sopenharmony_ci bool debug; 14362306a36Sopenharmony_ci bool quiet; 14462306a36Sopenharmony_ci bool force_checkpoints; 14562306a36Sopenharmony_ci enum resfmt out_fmt; 14662306a36Sopenharmony_ci bool show_version; 14762306a36Sopenharmony_ci bool comparison_mode; 14862306a36Sopenharmony_ci bool replay_mode; 14962306a36Sopenharmony_ci 15062306a36Sopenharmony_ci int log_level; 15162306a36Sopenharmony_ci int log_size; 15262306a36Sopenharmony_ci bool log_fixed; 15362306a36Sopenharmony_ci 15462306a36Sopenharmony_ci struct verif_stats *prog_stats; 15562306a36Sopenharmony_ci int prog_stat_cnt; 15662306a36Sopenharmony_ci 15762306a36Sopenharmony_ci /* baseline_stats is allocated and used only in comparison mode */ 15862306a36Sopenharmony_ci struct verif_stats *baseline_stats; 15962306a36Sopenharmony_ci int baseline_stat_cnt; 16062306a36Sopenharmony_ci 16162306a36Sopenharmony_ci struct verif_stats_join *join_stats; 16262306a36Sopenharmony_ci int join_stat_cnt; 16362306a36Sopenharmony_ci 16462306a36Sopenharmony_ci struct stat_specs output_spec; 16562306a36Sopenharmony_ci struct stat_specs sort_spec; 16662306a36Sopenharmony_ci 16762306a36Sopenharmony_ci struct filter *allow_filters; 16862306a36Sopenharmony_ci struct filter *deny_filters; 16962306a36Sopenharmony_ci int allow_filter_cnt; 17062306a36Sopenharmony_ci int deny_filter_cnt; 17162306a36Sopenharmony_ci 17262306a36Sopenharmony_ci int files_processed; 17362306a36Sopenharmony_ci int files_skipped; 17462306a36Sopenharmony_ci int progs_processed; 17562306a36Sopenharmony_ci int progs_skipped; 17662306a36Sopenharmony_ci} env; 17762306a36Sopenharmony_ci 17862306a36Sopenharmony_cistatic int libbpf_print_fn(enum libbpf_print_level level, const char *format, va_list args) 17962306a36Sopenharmony_ci{ 18062306a36Sopenharmony_ci if (!env.verbose) 18162306a36Sopenharmony_ci return 0; 18262306a36Sopenharmony_ci if (level == LIBBPF_DEBUG && !env.debug) 18362306a36Sopenharmony_ci return 0; 18462306a36Sopenharmony_ci return vfprintf(stderr, format, args); 18562306a36Sopenharmony_ci} 18662306a36Sopenharmony_ci 18762306a36Sopenharmony_ci#ifndef VERISTAT_VERSION 18862306a36Sopenharmony_ci#define VERISTAT_VERSION "<kernel>" 18962306a36Sopenharmony_ci#endif 19062306a36Sopenharmony_ci 19162306a36Sopenharmony_ciconst char *argp_program_version = "veristat v" VERISTAT_VERSION; 19262306a36Sopenharmony_ciconst char *argp_program_bug_address = "<bpf@vger.kernel.org>"; 19362306a36Sopenharmony_ciconst char argp_program_doc[] = 19462306a36Sopenharmony_ci"veristat BPF verifier stats collection and comparison tool.\n" 19562306a36Sopenharmony_ci"\n" 19662306a36Sopenharmony_ci"USAGE: veristat <obj-file> [<obj-file>...]\n" 19762306a36Sopenharmony_ci" OR: veristat -C <baseline.csv> <comparison.csv>\n" 19862306a36Sopenharmony_ci" OR: veristat -R <results.csv>\n"; 19962306a36Sopenharmony_ci 20062306a36Sopenharmony_cienum { 20162306a36Sopenharmony_ci OPT_LOG_FIXED = 1000, 20262306a36Sopenharmony_ci OPT_LOG_SIZE = 1001, 20362306a36Sopenharmony_ci}; 20462306a36Sopenharmony_ci 20562306a36Sopenharmony_cistatic const struct argp_option opts[] = { 20662306a36Sopenharmony_ci { NULL, 'h', NULL, OPTION_HIDDEN, "Show the full help" }, 20762306a36Sopenharmony_ci { "version", 'V', NULL, 0, "Print version" }, 20862306a36Sopenharmony_ci { "verbose", 'v', NULL, 0, "Verbose mode" }, 20962306a36Sopenharmony_ci { "debug", 'd', NULL, 0, "Debug mode (turns on libbpf debug logging)" }, 21062306a36Sopenharmony_ci { "log-level", 'l', "LEVEL", 0, "Verifier log level (default 0 for normal mode, 1 for verbose mode)" }, 21162306a36Sopenharmony_ci { "log-fixed", OPT_LOG_FIXED, NULL, 0, "Disable verifier log rotation" }, 21262306a36Sopenharmony_ci { "log-size", OPT_LOG_SIZE, "BYTES", 0, "Customize verifier log size (default to 16MB)" }, 21362306a36Sopenharmony_ci { "test-states", 't', NULL, 0, 21462306a36Sopenharmony_ci "Force frequent BPF verifier state checkpointing (set BPF_F_TEST_STATE_FREQ program flag)" }, 21562306a36Sopenharmony_ci { "quiet", 'q', NULL, 0, "Quiet mode" }, 21662306a36Sopenharmony_ci { "emit", 'e', "SPEC", 0, "Specify stats to be emitted" }, 21762306a36Sopenharmony_ci { "sort", 's', "SPEC", 0, "Specify sort order" }, 21862306a36Sopenharmony_ci { "output-format", 'o', "FMT", 0, "Result output format (table, csv), default is table." }, 21962306a36Sopenharmony_ci { "compare", 'C', NULL, 0, "Comparison mode" }, 22062306a36Sopenharmony_ci { "replay", 'R', NULL, 0, "Replay mode" }, 22162306a36Sopenharmony_ci { "filter", 'f', "FILTER", 0, "Filter expressions (or @filename for file with expressions)." }, 22262306a36Sopenharmony_ci {}, 22362306a36Sopenharmony_ci}; 22462306a36Sopenharmony_ci 22562306a36Sopenharmony_cistatic int parse_stats(const char *stats_str, struct stat_specs *specs); 22662306a36Sopenharmony_cistatic int append_filter(struct filter **filters, int *cnt, const char *str); 22762306a36Sopenharmony_cistatic int append_filter_file(const char *path); 22862306a36Sopenharmony_ci 22962306a36Sopenharmony_cistatic error_t parse_arg(int key, char *arg, struct argp_state *state) 23062306a36Sopenharmony_ci{ 23162306a36Sopenharmony_ci void *tmp; 23262306a36Sopenharmony_ci int err; 23362306a36Sopenharmony_ci 23462306a36Sopenharmony_ci switch (key) { 23562306a36Sopenharmony_ci case 'h': 23662306a36Sopenharmony_ci argp_state_help(state, stderr, ARGP_HELP_STD_HELP); 23762306a36Sopenharmony_ci break; 23862306a36Sopenharmony_ci case 'V': 23962306a36Sopenharmony_ci env.show_version = true; 24062306a36Sopenharmony_ci break; 24162306a36Sopenharmony_ci case 'v': 24262306a36Sopenharmony_ci env.verbose = true; 24362306a36Sopenharmony_ci break; 24462306a36Sopenharmony_ci case 'd': 24562306a36Sopenharmony_ci env.debug = true; 24662306a36Sopenharmony_ci env.verbose = true; 24762306a36Sopenharmony_ci break; 24862306a36Sopenharmony_ci case 'q': 24962306a36Sopenharmony_ci env.quiet = true; 25062306a36Sopenharmony_ci break; 25162306a36Sopenharmony_ci case 'e': 25262306a36Sopenharmony_ci err = parse_stats(arg, &env.output_spec); 25362306a36Sopenharmony_ci if (err) 25462306a36Sopenharmony_ci return err; 25562306a36Sopenharmony_ci break; 25662306a36Sopenharmony_ci case 's': 25762306a36Sopenharmony_ci err = parse_stats(arg, &env.sort_spec); 25862306a36Sopenharmony_ci if (err) 25962306a36Sopenharmony_ci return err; 26062306a36Sopenharmony_ci break; 26162306a36Sopenharmony_ci case 'o': 26262306a36Sopenharmony_ci if (strcmp(arg, "table") == 0) { 26362306a36Sopenharmony_ci env.out_fmt = RESFMT_TABLE; 26462306a36Sopenharmony_ci } else if (strcmp(arg, "csv") == 0) { 26562306a36Sopenharmony_ci env.out_fmt = RESFMT_CSV; 26662306a36Sopenharmony_ci } else { 26762306a36Sopenharmony_ci fprintf(stderr, "Unrecognized output format '%s'\n", arg); 26862306a36Sopenharmony_ci return -EINVAL; 26962306a36Sopenharmony_ci } 27062306a36Sopenharmony_ci break; 27162306a36Sopenharmony_ci case 'l': 27262306a36Sopenharmony_ci errno = 0; 27362306a36Sopenharmony_ci env.log_level = strtol(arg, NULL, 10); 27462306a36Sopenharmony_ci if (errno) { 27562306a36Sopenharmony_ci fprintf(stderr, "invalid log level: %s\n", arg); 27662306a36Sopenharmony_ci argp_usage(state); 27762306a36Sopenharmony_ci } 27862306a36Sopenharmony_ci break; 27962306a36Sopenharmony_ci case OPT_LOG_FIXED: 28062306a36Sopenharmony_ci env.log_fixed = true; 28162306a36Sopenharmony_ci break; 28262306a36Sopenharmony_ci case OPT_LOG_SIZE: 28362306a36Sopenharmony_ci errno = 0; 28462306a36Sopenharmony_ci env.log_size = strtol(arg, NULL, 10); 28562306a36Sopenharmony_ci if (errno) { 28662306a36Sopenharmony_ci fprintf(stderr, "invalid log size: %s\n", arg); 28762306a36Sopenharmony_ci argp_usage(state); 28862306a36Sopenharmony_ci } 28962306a36Sopenharmony_ci break; 29062306a36Sopenharmony_ci case 't': 29162306a36Sopenharmony_ci env.force_checkpoints = true; 29262306a36Sopenharmony_ci break; 29362306a36Sopenharmony_ci case 'C': 29462306a36Sopenharmony_ci env.comparison_mode = true; 29562306a36Sopenharmony_ci break; 29662306a36Sopenharmony_ci case 'R': 29762306a36Sopenharmony_ci env.replay_mode = true; 29862306a36Sopenharmony_ci break; 29962306a36Sopenharmony_ci case 'f': 30062306a36Sopenharmony_ci if (arg[0] == '@') 30162306a36Sopenharmony_ci err = append_filter_file(arg + 1); 30262306a36Sopenharmony_ci else if (arg[0] == '!') 30362306a36Sopenharmony_ci err = append_filter(&env.deny_filters, &env.deny_filter_cnt, arg + 1); 30462306a36Sopenharmony_ci else 30562306a36Sopenharmony_ci err = append_filter(&env.allow_filters, &env.allow_filter_cnt, arg); 30662306a36Sopenharmony_ci if (err) { 30762306a36Sopenharmony_ci fprintf(stderr, "Failed to collect program filter expressions: %d\n", err); 30862306a36Sopenharmony_ci return err; 30962306a36Sopenharmony_ci } 31062306a36Sopenharmony_ci break; 31162306a36Sopenharmony_ci case ARGP_KEY_ARG: 31262306a36Sopenharmony_ci tmp = realloc(env.filenames, (env.filename_cnt + 1) * sizeof(*env.filenames)); 31362306a36Sopenharmony_ci if (!tmp) 31462306a36Sopenharmony_ci return -ENOMEM; 31562306a36Sopenharmony_ci env.filenames = tmp; 31662306a36Sopenharmony_ci env.filenames[env.filename_cnt] = strdup(arg); 31762306a36Sopenharmony_ci if (!env.filenames[env.filename_cnt]) 31862306a36Sopenharmony_ci return -ENOMEM; 31962306a36Sopenharmony_ci env.filename_cnt++; 32062306a36Sopenharmony_ci break; 32162306a36Sopenharmony_ci default: 32262306a36Sopenharmony_ci return ARGP_ERR_UNKNOWN; 32362306a36Sopenharmony_ci } 32462306a36Sopenharmony_ci return 0; 32562306a36Sopenharmony_ci} 32662306a36Sopenharmony_ci 32762306a36Sopenharmony_cistatic const struct argp argp = { 32862306a36Sopenharmony_ci .options = opts, 32962306a36Sopenharmony_ci .parser = parse_arg, 33062306a36Sopenharmony_ci .doc = argp_program_doc, 33162306a36Sopenharmony_ci}; 33262306a36Sopenharmony_ci 33362306a36Sopenharmony_ci 33462306a36Sopenharmony_ci/* Adapted from perf/util/string.c */ 33562306a36Sopenharmony_cistatic bool glob_matches(const char *str, const char *pat) 33662306a36Sopenharmony_ci{ 33762306a36Sopenharmony_ci while (*str && *pat && *pat != '*') { 33862306a36Sopenharmony_ci if (*str != *pat) 33962306a36Sopenharmony_ci return false; 34062306a36Sopenharmony_ci str++; 34162306a36Sopenharmony_ci pat++; 34262306a36Sopenharmony_ci } 34362306a36Sopenharmony_ci /* Check wild card */ 34462306a36Sopenharmony_ci if (*pat == '*') { 34562306a36Sopenharmony_ci while (*pat == '*') 34662306a36Sopenharmony_ci pat++; 34762306a36Sopenharmony_ci if (!*pat) /* Tail wild card matches all */ 34862306a36Sopenharmony_ci return true; 34962306a36Sopenharmony_ci while (*str) 35062306a36Sopenharmony_ci if (glob_matches(str++, pat)) 35162306a36Sopenharmony_ci return true; 35262306a36Sopenharmony_ci } 35362306a36Sopenharmony_ci return !*str && !*pat; 35462306a36Sopenharmony_ci} 35562306a36Sopenharmony_ci 35662306a36Sopenharmony_cistatic bool is_bpf_obj_file(const char *path) { 35762306a36Sopenharmony_ci Elf64_Ehdr *ehdr; 35862306a36Sopenharmony_ci int fd, err = -EINVAL; 35962306a36Sopenharmony_ci Elf *elf = NULL; 36062306a36Sopenharmony_ci 36162306a36Sopenharmony_ci fd = open(path, O_RDONLY | O_CLOEXEC); 36262306a36Sopenharmony_ci if (fd < 0) 36362306a36Sopenharmony_ci return true; /* we'll fail later and propagate error */ 36462306a36Sopenharmony_ci 36562306a36Sopenharmony_ci /* ensure libelf is initialized */ 36662306a36Sopenharmony_ci (void)elf_version(EV_CURRENT); 36762306a36Sopenharmony_ci 36862306a36Sopenharmony_ci elf = elf_begin(fd, ELF_C_READ, NULL); 36962306a36Sopenharmony_ci if (!elf) 37062306a36Sopenharmony_ci goto cleanup; 37162306a36Sopenharmony_ci 37262306a36Sopenharmony_ci if (elf_kind(elf) != ELF_K_ELF || gelf_getclass(elf) != ELFCLASS64) 37362306a36Sopenharmony_ci goto cleanup; 37462306a36Sopenharmony_ci 37562306a36Sopenharmony_ci ehdr = elf64_getehdr(elf); 37662306a36Sopenharmony_ci /* Old LLVM set e_machine to EM_NONE */ 37762306a36Sopenharmony_ci if (!ehdr || ehdr->e_type != ET_REL || (ehdr->e_machine && ehdr->e_machine != EM_BPF)) 37862306a36Sopenharmony_ci goto cleanup; 37962306a36Sopenharmony_ci 38062306a36Sopenharmony_ci err = 0; 38162306a36Sopenharmony_cicleanup: 38262306a36Sopenharmony_ci if (elf) 38362306a36Sopenharmony_ci elf_end(elf); 38462306a36Sopenharmony_ci close(fd); 38562306a36Sopenharmony_ci return err == 0; 38662306a36Sopenharmony_ci} 38762306a36Sopenharmony_ci 38862306a36Sopenharmony_cistatic bool should_process_file_prog(const char *filename, const char *prog_name) 38962306a36Sopenharmony_ci{ 39062306a36Sopenharmony_ci struct filter *f; 39162306a36Sopenharmony_ci int i, allow_cnt = 0; 39262306a36Sopenharmony_ci 39362306a36Sopenharmony_ci for (i = 0; i < env.deny_filter_cnt; i++) { 39462306a36Sopenharmony_ci f = &env.deny_filters[i]; 39562306a36Sopenharmony_ci if (f->kind != FILTER_NAME) 39662306a36Sopenharmony_ci continue; 39762306a36Sopenharmony_ci 39862306a36Sopenharmony_ci if (f->any_glob && glob_matches(filename, f->any_glob)) 39962306a36Sopenharmony_ci return false; 40062306a36Sopenharmony_ci if (f->any_glob && prog_name && glob_matches(prog_name, f->any_glob)) 40162306a36Sopenharmony_ci return false; 40262306a36Sopenharmony_ci if (f->file_glob && glob_matches(filename, f->file_glob)) 40362306a36Sopenharmony_ci return false; 40462306a36Sopenharmony_ci if (f->prog_glob && prog_name && glob_matches(prog_name, f->prog_glob)) 40562306a36Sopenharmony_ci return false; 40662306a36Sopenharmony_ci } 40762306a36Sopenharmony_ci 40862306a36Sopenharmony_ci for (i = 0; i < env.allow_filter_cnt; i++) { 40962306a36Sopenharmony_ci f = &env.allow_filters[i]; 41062306a36Sopenharmony_ci if (f->kind != FILTER_NAME) 41162306a36Sopenharmony_ci continue; 41262306a36Sopenharmony_ci 41362306a36Sopenharmony_ci allow_cnt++; 41462306a36Sopenharmony_ci if (f->any_glob) { 41562306a36Sopenharmony_ci if (glob_matches(filename, f->any_glob)) 41662306a36Sopenharmony_ci return true; 41762306a36Sopenharmony_ci /* If we don't know program name yet, any_glob filter 41862306a36Sopenharmony_ci * has to assume that current BPF object file might be 41962306a36Sopenharmony_ci * relevant; we'll check again later on after opening 42062306a36Sopenharmony_ci * BPF object file, at which point program name will 42162306a36Sopenharmony_ci * be known finally. 42262306a36Sopenharmony_ci */ 42362306a36Sopenharmony_ci if (!prog_name || glob_matches(prog_name, f->any_glob)) 42462306a36Sopenharmony_ci return true; 42562306a36Sopenharmony_ci } else { 42662306a36Sopenharmony_ci if (f->file_glob && !glob_matches(filename, f->file_glob)) 42762306a36Sopenharmony_ci continue; 42862306a36Sopenharmony_ci if (f->prog_glob && prog_name && !glob_matches(prog_name, f->prog_glob)) 42962306a36Sopenharmony_ci continue; 43062306a36Sopenharmony_ci return true; 43162306a36Sopenharmony_ci } 43262306a36Sopenharmony_ci } 43362306a36Sopenharmony_ci 43462306a36Sopenharmony_ci /* if there are no file/prog name allow filters, allow all progs, 43562306a36Sopenharmony_ci * unless they are denied earlier explicitly 43662306a36Sopenharmony_ci */ 43762306a36Sopenharmony_ci return allow_cnt == 0; 43862306a36Sopenharmony_ci} 43962306a36Sopenharmony_ci 44062306a36Sopenharmony_cistatic struct { 44162306a36Sopenharmony_ci enum operator_kind op_kind; 44262306a36Sopenharmony_ci const char *op_str; 44362306a36Sopenharmony_ci} operators[] = { 44462306a36Sopenharmony_ci /* Order of these definitions matter to avoid situations like '<' 44562306a36Sopenharmony_ci * matching part of what is actually a '<>' operator. That is, 44662306a36Sopenharmony_ci * substrings should go last. 44762306a36Sopenharmony_ci */ 44862306a36Sopenharmony_ci { OP_EQ, "==" }, 44962306a36Sopenharmony_ci { OP_NEQ, "!=" }, 45062306a36Sopenharmony_ci { OP_NEQ, "<>" }, 45162306a36Sopenharmony_ci { OP_LE, "<=" }, 45262306a36Sopenharmony_ci { OP_LT, "<" }, 45362306a36Sopenharmony_ci { OP_GE, ">=" }, 45462306a36Sopenharmony_ci { OP_GT, ">" }, 45562306a36Sopenharmony_ci { OP_EQ, "=" }, 45662306a36Sopenharmony_ci}; 45762306a36Sopenharmony_ci 45862306a36Sopenharmony_cistatic bool parse_stat_id_var(const char *name, size_t len, int *id, enum stat_variant *var); 45962306a36Sopenharmony_ci 46062306a36Sopenharmony_cistatic int append_filter(struct filter **filters, int *cnt, const char *str) 46162306a36Sopenharmony_ci{ 46262306a36Sopenharmony_ci struct filter *f; 46362306a36Sopenharmony_ci void *tmp; 46462306a36Sopenharmony_ci const char *p; 46562306a36Sopenharmony_ci int i; 46662306a36Sopenharmony_ci 46762306a36Sopenharmony_ci tmp = realloc(*filters, (*cnt + 1) * sizeof(**filters)); 46862306a36Sopenharmony_ci if (!tmp) 46962306a36Sopenharmony_ci return -ENOMEM; 47062306a36Sopenharmony_ci *filters = tmp; 47162306a36Sopenharmony_ci 47262306a36Sopenharmony_ci f = &(*filters)[*cnt]; 47362306a36Sopenharmony_ci memset(f, 0, sizeof(*f)); 47462306a36Sopenharmony_ci 47562306a36Sopenharmony_ci /* First, let's check if it's a stats filter of the following form: 47662306a36Sopenharmony_ci * <stat><op><value, where: 47762306a36Sopenharmony_ci * - <stat> is one of supported numerical stats (verdict is also 47862306a36Sopenharmony_ci * considered numerical, failure == 0, success == 1); 47962306a36Sopenharmony_ci * - <op> is comparison operator (see `operators` definitions); 48062306a36Sopenharmony_ci * - <value> is an integer (or failure/success, or false/true as 48162306a36Sopenharmony_ci * special aliases for 0 and 1, respectively). 48262306a36Sopenharmony_ci * If the form doesn't match what user provided, we assume file/prog 48362306a36Sopenharmony_ci * glob filter. 48462306a36Sopenharmony_ci */ 48562306a36Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(operators); i++) { 48662306a36Sopenharmony_ci enum stat_variant var; 48762306a36Sopenharmony_ci int id; 48862306a36Sopenharmony_ci long val; 48962306a36Sopenharmony_ci const char *end = str; 49062306a36Sopenharmony_ci const char *op_str; 49162306a36Sopenharmony_ci 49262306a36Sopenharmony_ci op_str = operators[i].op_str; 49362306a36Sopenharmony_ci p = strstr(str, op_str); 49462306a36Sopenharmony_ci if (!p) 49562306a36Sopenharmony_ci continue; 49662306a36Sopenharmony_ci 49762306a36Sopenharmony_ci if (!parse_stat_id_var(str, p - str, &id, &var)) { 49862306a36Sopenharmony_ci fprintf(stderr, "Unrecognized stat name in '%s'!\n", str); 49962306a36Sopenharmony_ci return -EINVAL; 50062306a36Sopenharmony_ci } 50162306a36Sopenharmony_ci if (id >= FILE_NAME) { 50262306a36Sopenharmony_ci fprintf(stderr, "Non-integer stat is specified in '%s'!\n", str); 50362306a36Sopenharmony_ci return -EINVAL; 50462306a36Sopenharmony_ci } 50562306a36Sopenharmony_ci 50662306a36Sopenharmony_ci p += strlen(op_str); 50762306a36Sopenharmony_ci 50862306a36Sopenharmony_ci if (strcasecmp(p, "true") == 0 || 50962306a36Sopenharmony_ci strcasecmp(p, "t") == 0 || 51062306a36Sopenharmony_ci strcasecmp(p, "success") == 0 || 51162306a36Sopenharmony_ci strcasecmp(p, "succ") == 0 || 51262306a36Sopenharmony_ci strcasecmp(p, "s") == 0 || 51362306a36Sopenharmony_ci strcasecmp(p, "match") == 0 || 51462306a36Sopenharmony_ci strcasecmp(p, "m") == 0) { 51562306a36Sopenharmony_ci val = 1; 51662306a36Sopenharmony_ci } else if (strcasecmp(p, "false") == 0 || 51762306a36Sopenharmony_ci strcasecmp(p, "f") == 0 || 51862306a36Sopenharmony_ci strcasecmp(p, "failure") == 0 || 51962306a36Sopenharmony_ci strcasecmp(p, "fail") == 0 || 52062306a36Sopenharmony_ci strcasecmp(p, "mismatch") == 0 || 52162306a36Sopenharmony_ci strcasecmp(p, "mis") == 0) { 52262306a36Sopenharmony_ci val = 0; 52362306a36Sopenharmony_ci } else { 52462306a36Sopenharmony_ci errno = 0; 52562306a36Sopenharmony_ci val = strtol(p, (char **)&end, 10); 52662306a36Sopenharmony_ci if (errno || end == p || *end != '\0' ) { 52762306a36Sopenharmony_ci fprintf(stderr, "Invalid integer value in '%s'!\n", str); 52862306a36Sopenharmony_ci return -EINVAL; 52962306a36Sopenharmony_ci } 53062306a36Sopenharmony_ci } 53162306a36Sopenharmony_ci 53262306a36Sopenharmony_ci f->kind = FILTER_STAT; 53362306a36Sopenharmony_ci f->stat_id = id; 53462306a36Sopenharmony_ci f->stat_var = var; 53562306a36Sopenharmony_ci f->op = operators[i].op_kind; 53662306a36Sopenharmony_ci f->value = val; 53762306a36Sopenharmony_ci 53862306a36Sopenharmony_ci *cnt += 1; 53962306a36Sopenharmony_ci return 0; 54062306a36Sopenharmony_ci } 54162306a36Sopenharmony_ci 54262306a36Sopenharmony_ci /* File/prog filter can be specified either as '<glob>' or 54362306a36Sopenharmony_ci * '<file-glob>/<prog-glob>'. In the former case <glob> is applied to 54462306a36Sopenharmony_ci * both file and program names. This seems to be way more useful in 54562306a36Sopenharmony_ci * practice. If user needs full control, they can use '/<prog-glob>' 54662306a36Sopenharmony_ci * form to glob just program name, or '<file-glob>/' to glob only file 54762306a36Sopenharmony_ci * name. But usually common <glob> seems to be the most useful and 54862306a36Sopenharmony_ci * ergonomic way. 54962306a36Sopenharmony_ci */ 55062306a36Sopenharmony_ci f->kind = FILTER_NAME; 55162306a36Sopenharmony_ci p = strchr(str, '/'); 55262306a36Sopenharmony_ci if (!p) { 55362306a36Sopenharmony_ci f->any_glob = strdup(str); 55462306a36Sopenharmony_ci if (!f->any_glob) 55562306a36Sopenharmony_ci return -ENOMEM; 55662306a36Sopenharmony_ci } else { 55762306a36Sopenharmony_ci if (str != p) { 55862306a36Sopenharmony_ci /* non-empty file glob */ 55962306a36Sopenharmony_ci f->file_glob = strndup(str, p - str); 56062306a36Sopenharmony_ci if (!f->file_glob) 56162306a36Sopenharmony_ci return -ENOMEM; 56262306a36Sopenharmony_ci } 56362306a36Sopenharmony_ci if (strlen(p + 1) > 0) { 56462306a36Sopenharmony_ci /* non-empty prog glob */ 56562306a36Sopenharmony_ci f->prog_glob = strdup(p + 1); 56662306a36Sopenharmony_ci if (!f->prog_glob) { 56762306a36Sopenharmony_ci free(f->file_glob); 56862306a36Sopenharmony_ci f->file_glob = NULL; 56962306a36Sopenharmony_ci return -ENOMEM; 57062306a36Sopenharmony_ci } 57162306a36Sopenharmony_ci } 57262306a36Sopenharmony_ci } 57362306a36Sopenharmony_ci 57462306a36Sopenharmony_ci *cnt += 1; 57562306a36Sopenharmony_ci return 0; 57662306a36Sopenharmony_ci} 57762306a36Sopenharmony_ci 57862306a36Sopenharmony_cistatic int append_filter_file(const char *path) 57962306a36Sopenharmony_ci{ 58062306a36Sopenharmony_ci char buf[1024]; 58162306a36Sopenharmony_ci FILE *f; 58262306a36Sopenharmony_ci int err = 0; 58362306a36Sopenharmony_ci 58462306a36Sopenharmony_ci f = fopen(path, "r"); 58562306a36Sopenharmony_ci if (!f) { 58662306a36Sopenharmony_ci err = -errno; 58762306a36Sopenharmony_ci fprintf(stderr, "Failed to open filters in '%s': %d\n", path, err); 58862306a36Sopenharmony_ci return err; 58962306a36Sopenharmony_ci } 59062306a36Sopenharmony_ci 59162306a36Sopenharmony_ci while (fscanf(f, " %1023[^\n]\n", buf) == 1) { 59262306a36Sopenharmony_ci /* lines starting with # are comments, skip them */ 59362306a36Sopenharmony_ci if (buf[0] == '\0' || buf[0] == '#') 59462306a36Sopenharmony_ci continue; 59562306a36Sopenharmony_ci /* lines starting with ! are negative match filters */ 59662306a36Sopenharmony_ci if (buf[0] == '!') 59762306a36Sopenharmony_ci err = append_filter(&env.deny_filters, &env.deny_filter_cnt, buf + 1); 59862306a36Sopenharmony_ci else 59962306a36Sopenharmony_ci err = append_filter(&env.allow_filters, &env.allow_filter_cnt, buf); 60062306a36Sopenharmony_ci if (err) 60162306a36Sopenharmony_ci goto cleanup; 60262306a36Sopenharmony_ci } 60362306a36Sopenharmony_ci 60462306a36Sopenharmony_cicleanup: 60562306a36Sopenharmony_ci fclose(f); 60662306a36Sopenharmony_ci return err; 60762306a36Sopenharmony_ci} 60862306a36Sopenharmony_ci 60962306a36Sopenharmony_cistatic const struct stat_specs default_output_spec = { 61062306a36Sopenharmony_ci .spec_cnt = 7, 61162306a36Sopenharmony_ci .ids = { 61262306a36Sopenharmony_ci FILE_NAME, PROG_NAME, VERDICT, DURATION, 61362306a36Sopenharmony_ci TOTAL_INSNS, TOTAL_STATES, PEAK_STATES, 61462306a36Sopenharmony_ci }, 61562306a36Sopenharmony_ci}; 61662306a36Sopenharmony_ci 61762306a36Sopenharmony_cistatic const struct stat_specs default_csv_output_spec = { 61862306a36Sopenharmony_ci .spec_cnt = 9, 61962306a36Sopenharmony_ci .ids = { 62062306a36Sopenharmony_ci FILE_NAME, PROG_NAME, VERDICT, DURATION, 62162306a36Sopenharmony_ci TOTAL_INSNS, TOTAL_STATES, PEAK_STATES, 62262306a36Sopenharmony_ci MAX_STATES_PER_INSN, MARK_READ_MAX_LEN, 62362306a36Sopenharmony_ci }, 62462306a36Sopenharmony_ci}; 62562306a36Sopenharmony_ci 62662306a36Sopenharmony_cistatic const struct stat_specs default_sort_spec = { 62762306a36Sopenharmony_ci .spec_cnt = 2, 62862306a36Sopenharmony_ci .ids = { 62962306a36Sopenharmony_ci FILE_NAME, PROG_NAME, 63062306a36Sopenharmony_ci }, 63162306a36Sopenharmony_ci .asc = { true, true, }, 63262306a36Sopenharmony_ci}; 63362306a36Sopenharmony_ci 63462306a36Sopenharmony_ci/* sorting for comparison mode to join two data sets */ 63562306a36Sopenharmony_cistatic const struct stat_specs join_sort_spec = { 63662306a36Sopenharmony_ci .spec_cnt = 2, 63762306a36Sopenharmony_ci .ids = { 63862306a36Sopenharmony_ci FILE_NAME, PROG_NAME, 63962306a36Sopenharmony_ci }, 64062306a36Sopenharmony_ci .asc = { true, true, }, 64162306a36Sopenharmony_ci}; 64262306a36Sopenharmony_ci 64362306a36Sopenharmony_cistatic struct stat_def { 64462306a36Sopenharmony_ci const char *header; 64562306a36Sopenharmony_ci const char *names[4]; 64662306a36Sopenharmony_ci bool asc_by_default; 64762306a36Sopenharmony_ci bool left_aligned; 64862306a36Sopenharmony_ci} stat_defs[] = { 64962306a36Sopenharmony_ci [FILE_NAME] = { "File", {"file_name", "filename", "file"}, true /* asc */, true /* left */ }, 65062306a36Sopenharmony_ci [PROG_NAME] = { "Program", {"prog_name", "progname", "prog"}, true /* asc */, true /* left */ }, 65162306a36Sopenharmony_ci [VERDICT] = { "Verdict", {"verdict"}, true /* asc: failure, success */, true /* left */ }, 65262306a36Sopenharmony_ci [DURATION] = { "Duration (us)", {"duration", "dur"}, }, 65362306a36Sopenharmony_ci [TOTAL_INSNS] = { "Insns", {"total_insns", "insns"}, }, 65462306a36Sopenharmony_ci [TOTAL_STATES] = { "States", {"total_states", "states"}, }, 65562306a36Sopenharmony_ci [PEAK_STATES] = { "Peak states", {"peak_states"}, }, 65662306a36Sopenharmony_ci [MAX_STATES_PER_INSN] = { "Max states per insn", {"max_states_per_insn"}, }, 65762306a36Sopenharmony_ci [MARK_READ_MAX_LEN] = { "Max mark read length", {"max_mark_read_len", "mark_read"}, }, 65862306a36Sopenharmony_ci}; 65962306a36Sopenharmony_ci 66062306a36Sopenharmony_cistatic bool parse_stat_id_var(const char *name, size_t len, int *id, enum stat_variant *var) 66162306a36Sopenharmony_ci{ 66262306a36Sopenharmony_ci static const char *var_sfxs[] = { 66362306a36Sopenharmony_ci [VARIANT_A] = "_a", 66462306a36Sopenharmony_ci [VARIANT_B] = "_b", 66562306a36Sopenharmony_ci [VARIANT_DIFF] = "_diff", 66662306a36Sopenharmony_ci [VARIANT_PCT] = "_pct", 66762306a36Sopenharmony_ci }; 66862306a36Sopenharmony_ci int i, j, k; 66962306a36Sopenharmony_ci 67062306a36Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(stat_defs); i++) { 67162306a36Sopenharmony_ci struct stat_def *def = &stat_defs[i]; 67262306a36Sopenharmony_ci size_t alias_len, sfx_len; 67362306a36Sopenharmony_ci const char *alias; 67462306a36Sopenharmony_ci 67562306a36Sopenharmony_ci for (j = 0; j < ARRAY_SIZE(stat_defs[i].names); j++) { 67662306a36Sopenharmony_ci alias = def->names[j]; 67762306a36Sopenharmony_ci if (!alias) 67862306a36Sopenharmony_ci continue; 67962306a36Sopenharmony_ci 68062306a36Sopenharmony_ci alias_len = strlen(alias); 68162306a36Sopenharmony_ci if (strncmp(name, alias, alias_len) != 0) 68262306a36Sopenharmony_ci continue; 68362306a36Sopenharmony_ci 68462306a36Sopenharmony_ci if (alias_len == len) { 68562306a36Sopenharmony_ci /* If no variant suffix is specified, we 68662306a36Sopenharmony_ci * assume control group (just in case we are 68762306a36Sopenharmony_ci * in comparison mode. Variant is ignored in 68862306a36Sopenharmony_ci * non-comparison mode. 68962306a36Sopenharmony_ci */ 69062306a36Sopenharmony_ci *var = VARIANT_B; 69162306a36Sopenharmony_ci *id = i; 69262306a36Sopenharmony_ci return true; 69362306a36Sopenharmony_ci } 69462306a36Sopenharmony_ci 69562306a36Sopenharmony_ci for (k = 0; k < ARRAY_SIZE(var_sfxs); k++) { 69662306a36Sopenharmony_ci sfx_len = strlen(var_sfxs[k]); 69762306a36Sopenharmony_ci if (alias_len + sfx_len != len) 69862306a36Sopenharmony_ci continue; 69962306a36Sopenharmony_ci 70062306a36Sopenharmony_ci if (strncmp(name + alias_len, var_sfxs[k], sfx_len) == 0) { 70162306a36Sopenharmony_ci *var = (enum stat_variant)k; 70262306a36Sopenharmony_ci *id = i; 70362306a36Sopenharmony_ci return true; 70462306a36Sopenharmony_ci } 70562306a36Sopenharmony_ci } 70662306a36Sopenharmony_ci } 70762306a36Sopenharmony_ci } 70862306a36Sopenharmony_ci 70962306a36Sopenharmony_ci return false; 71062306a36Sopenharmony_ci} 71162306a36Sopenharmony_ci 71262306a36Sopenharmony_cistatic bool is_asc_sym(char c) 71362306a36Sopenharmony_ci{ 71462306a36Sopenharmony_ci return c == '^'; 71562306a36Sopenharmony_ci} 71662306a36Sopenharmony_ci 71762306a36Sopenharmony_cistatic bool is_desc_sym(char c) 71862306a36Sopenharmony_ci{ 71962306a36Sopenharmony_ci return c == 'v' || c == 'V' || c == '.' || c == '!' || c == '_'; 72062306a36Sopenharmony_ci} 72162306a36Sopenharmony_ci 72262306a36Sopenharmony_cistatic int parse_stat(const char *stat_name, struct stat_specs *specs) 72362306a36Sopenharmony_ci{ 72462306a36Sopenharmony_ci int id; 72562306a36Sopenharmony_ci bool has_order = false, is_asc = false; 72662306a36Sopenharmony_ci size_t len = strlen(stat_name); 72762306a36Sopenharmony_ci enum stat_variant var; 72862306a36Sopenharmony_ci 72962306a36Sopenharmony_ci if (specs->spec_cnt >= ARRAY_SIZE(specs->ids)) { 73062306a36Sopenharmony_ci fprintf(stderr, "Can't specify more than %zd stats\n", ARRAY_SIZE(specs->ids)); 73162306a36Sopenharmony_ci return -E2BIG; 73262306a36Sopenharmony_ci } 73362306a36Sopenharmony_ci 73462306a36Sopenharmony_ci if (len > 1 && (is_asc_sym(stat_name[len - 1]) || is_desc_sym(stat_name[len - 1]))) { 73562306a36Sopenharmony_ci has_order = true; 73662306a36Sopenharmony_ci is_asc = is_asc_sym(stat_name[len - 1]); 73762306a36Sopenharmony_ci len -= 1; 73862306a36Sopenharmony_ci } 73962306a36Sopenharmony_ci 74062306a36Sopenharmony_ci if (!parse_stat_id_var(stat_name, len, &id, &var)) { 74162306a36Sopenharmony_ci fprintf(stderr, "Unrecognized stat name '%s'\n", stat_name); 74262306a36Sopenharmony_ci return -ESRCH; 74362306a36Sopenharmony_ci } 74462306a36Sopenharmony_ci 74562306a36Sopenharmony_ci specs->ids[specs->spec_cnt] = id; 74662306a36Sopenharmony_ci specs->variants[specs->spec_cnt] = var; 74762306a36Sopenharmony_ci specs->asc[specs->spec_cnt] = has_order ? is_asc : stat_defs[id].asc_by_default; 74862306a36Sopenharmony_ci specs->spec_cnt++; 74962306a36Sopenharmony_ci 75062306a36Sopenharmony_ci return 0; 75162306a36Sopenharmony_ci} 75262306a36Sopenharmony_ci 75362306a36Sopenharmony_cistatic int parse_stats(const char *stats_str, struct stat_specs *specs) 75462306a36Sopenharmony_ci{ 75562306a36Sopenharmony_ci char *input, *state = NULL, *next; 75662306a36Sopenharmony_ci int err; 75762306a36Sopenharmony_ci 75862306a36Sopenharmony_ci input = strdup(stats_str); 75962306a36Sopenharmony_ci if (!input) 76062306a36Sopenharmony_ci return -ENOMEM; 76162306a36Sopenharmony_ci 76262306a36Sopenharmony_ci while ((next = strtok_r(state ? NULL : input, ",", &state))) { 76362306a36Sopenharmony_ci err = parse_stat(next, specs); 76462306a36Sopenharmony_ci if (err) 76562306a36Sopenharmony_ci return err; 76662306a36Sopenharmony_ci } 76762306a36Sopenharmony_ci 76862306a36Sopenharmony_ci return 0; 76962306a36Sopenharmony_ci} 77062306a36Sopenharmony_ci 77162306a36Sopenharmony_cistatic void free_verif_stats(struct verif_stats *stats, size_t stat_cnt) 77262306a36Sopenharmony_ci{ 77362306a36Sopenharmony_ci int i; 77462306a36Sopenharmony_ci 77562306a36Sopenharmony_ci if (!stats) 77662306a36Sopenharmony_ci return; 77762306a36Sopenharmony_ci 77862306a36Sopenharmony_ci for (i = 0; i < stat_cnt; i++) { 77962306a36Sopenharmony_ci free(stats[i].file_name); 78062306a36Sopenharmony_ci free(stats[i].prog_name); 78162306a36Sopenharmony_ci } 78262306a36Sopenharmony_ci free(stats); 78362306a36Sopenharmony_ci} 78462306a36Sopenharmony_ci 78562306a36Sopenharmony_cistatic char verif_log_buf[64 * 1024]; 78662306a36Sopenharmony_ci 78762306a36Sopenharmony_ci#define MAX_PARSED_LOG_LINES 100 78862306a36Sopenharmony_ci 78962306a36Sopenharmony_cistatic int parse_verif_log(char * const buf, size_t buf_sz, struct verif_stats *s) 79062306a36Sopenharmony_ci{ 79162306a36Sopenharmony_ci const char *cur; 79262306a36Sopenharmony_ci int pos, lines; 79362306a36Sopenharmony_ci 79462306a36Sopenharmony_ci buf[buf_sz - 1] = '\0'; 79562306a36Sopenharmony_ci 79662306a36Sopenharmony_ci for (pos = strlen(buf) - 1, lines = 0; pos >= 0 && lines < MAX_PARSED_LOG_LINES; lines++) { 79762306a36Sopenharmony_ci /* find previous endline or otherwise take the start of log buf */ 79862306a36Sopenharmony_ci for (cur = &buf[pos]; cur > buf && cur[0] != '\n'; cur--, pos--) { 79962306a36Sopenharmony_ci } 80062306a36Sopenharmony_ci /* next time start from end of previous line (or pos goes to <0) */ 80162306a36Sopenharmony_ci pos--; 80262306a36Sopenharmony_ci /* if we found endline, point right after endline symbol; 80362306a36Sopenharmony_ci * otherwise, stay at the beginning of log buf 80462306a36Sopenharmony_ci */ 80562306a36Sopenharmony_ci if (cur[0] == '\n') 80662306a36Sopenharmony_ci cur++; 80762306a36Sopenharmony_ci 80862306a36Sopenharmony_ci if (1 == sscanf(cur, "verification time %ld usec\n", &s->stats[DURATION])) 80962306a36Sopenharmony_ci continue; 81062306a36Sopenharmony_ci if (6 == sscanf(cur, "processed %ld insns (limit %*d) max_states_per_insn %ld total_states %ld peak_states %ld mark_read %ld", 81162306a36Sopenharmony_ci &s->stats[TOTAL_INSNS], 81262306a36Sopenharmony_ci &s->stats[MAX_STATES_PER_INSN], 81362306a36Sopenharmony_ci &s->stats[TOTAL_STATES], 81462306a36Sopenharmony_ci &s->stats[PEAK_STATES], 81562306a36Sopenharmony_ci &s->stats[MARK_READ_MAX_LEN])) 81662306a36Sopenharmony_ci continue; 81762306a36Sopenharmony_ci } 81862306a36Sopenharmony_ci 81962306a36Sopenharmony_ci return 0; 82062306a36Sopenharmony_ci} 82162306a36Sopenharmony_ci 82262306a36Sopenharmony_cistatic int guess_prog_type_by_ctx_name(const char *ctx_name, 82362306a36Sopenharmony_ci enum bpf_prog_type *prog_type, 82462306a36Sopenharmony_ci enum bpf_attach_type *attach_type) 82562306a36Sopenharmony_ci{ 82662306a36Sopenharmony_ci /* We need to guess program type based on its declared context type. 82762306a36Sopenharmony_ci * This guess can't be perfect as many different program types might 82862306a36Sopenharmony_ci * share the same context type. So we can only hope to reasonably 82962306a36Sopenharmony_ci * well guess this and get lucky. 83062306a36Sopenharmony_ci * 83162306a36Sopenharmony_ci * Just in case, we support both UAPI-side type names and 83262306a36Sopenharmony_ci * kernel-internal names. 83362306a36Sopenharmony_ci */ 83462306a36Sopenharmony_ci static struct { 83562306a36Sopenharmony_ci const char *uapi_name; 83662306a36Sopenharmony_ci const char *kern_name; 83762306a36Sopenharmony_ci enum bpf_prog_type prog_type; 83862306a36Sopenharmony_ci enum bpf_attach_type attach_type; 83962306a36Sopenharmony_ci } ctx_map[] = { 84062306a36Sopenharmony_ci /* __sk_buff is most ambiguous, we assume TC program */ 84162306a36Sopenharmony_ci { "__sk_buff", "sk_buff", BPF_PROG_TYPE_SCHED_CLS }, 84262306a36Sopenharmony_ci { "bpf_sock", "sock", BPF_PROG_TYPE_CGROUP_SOCK, BPF_CGROUP_INET4_POST_BIND }, 84362306a36Sopenharmony_ci { "bpf_sock_addr", "bpf_sock_addr_kern", BPF_PROG_TYPE_CGROUP_SOCK_ADDR, BPF_CGROUP_INET4_BIND }, 84462306a36Sopenharmony_ci { "bpf_sock_ops", "bpf_sock_ops_kern", BPF_PROG_TYPE_SOCK_OPS, BPF_CGROUP_SOCK_OPS }, 84562306a36Sopenharmony_ci { "sk_msg_md", "sk_msg", BPF_PROG_TYPE_SK_MSG, BPF_SK_MSG_VERDICT }, 84662306a36Sopenharmony_ci { "bpf_cgroup_dev_ctx", "bpf_cgroup_dev_ctx", BPF_PROG_TYPE_CGROUP_DEVICE, BPF_CGROUP_DEVICE }, 84762306a36Sopenharmony_ci { "bpf_sysctl", "bpf_sysctl_kern", BPF_PROG_TYPE_CGROUP_SYSCTL, BPF_CGROUP_SYSCTL }, 84862306a36Sopenharmony_ci { "bpf_sockopt", "bpf_sockopt_kern", BPF_PROG_TYPE_CGROUP_SOCKOPT, BPF_CGROUP_SETSOCKOPT }, 84962306a36Sopenharmony_ci { "sk_reuseport_md", "sk_reuseport_kern", BPF_PROG_TYPE_SK_REUSEPORT, BPF_SK_REUSEPORT_SELECT_OR_MIGRATE }, 85062306a36Sopenharmony_ci { "bpf_sk_lookup", "bpf_sk_lookup_kern", BPF_PROG_TYPE_SK_LOOKUP, BPF_SK_LOOKUP }, 85162306a36Sopenharmony_ci { "xdp_md", "xdp_buff", BPF_PROG_TYPE_XDP, BPF_XDP }, 85262306a36Sopenharmony_ci /* tracing types with no expected attach type */ 85362306a36Sopenharmony_ci { "bpf_user_pt_regs_t", "pt_regs", BPF_PROG_TYPE_KPROBE }, 85462306a36Sopenharmony_ci { "bpf_perf_event_data", "bpf_perf_event_data_kern", BPF_PROG_TYPE_PERF_EVENT }, 85562306a36Sopenharmony_ci /* raw_tp programs use u64[] from kernel side, we don't want 85662306a36Sopenharmony_ci * to match on that, probably; so NULL for kern-side type 85762306a36Sopenharmony_ci */ 85862306a36Sopenharmony_ci { "bpf_raw_tracepoint_args", NULL, BPF_PROG_TYPE_RAW_TRACEPOINT }, 85962306a36Sopenharmony_ci }; 86062306a36Sopenharmony_ci int i; 86162306a36Sopenharmony_ci 86262306a36Sopenharmony_ci if (!ctx_name) 86362306a36Sopenharmony_ci return -EINVAL; 86462306a36Sopenharmony_ci 86562306a36Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(ctx_map); i++) { 86662306a36Sopenharmony_ci if (strcmp(ctx_map[i].uapi_name, ctx_name) == 0 || 86762306a36Sopenharmony_ci (ctx_map[i].kern_name && strcmp(ctx_map[i].kern_name, ctx_name) == 0)) { 86862306a36Sopenharmony_ci *prog_type = ctx_map[i].prog_type; 86962306a36Sopenharmony_ci *attach_type = ctx_map[i].attach_type; 87062306a36Sopenharmony_ci return 0; 87162306a36Sopenharmony_ci } 87262306a36Sopenharmony_ci } 87362306a36Sopenharmony_ci 87462306a36Sopenharmony_ci return -ESRCH; 87562306a36Sopenharmony_ci} 87662306a36Sopenharmony_ci 87762306a36Sopenharmony_cistatic void fixup_obj(struct bpf_object *obj, struct bpf_program *prog, const char *filename) 87862306a36Sopenharmony_ci{ 87962306a36Sopenharmony_ci struct bpf_map *map; 88062306a36Sopenharmony_ci 88162306a36Sopenharmony_ci bpf_object__for_each_map(map, obj) { 88262306a36Sopenharmony_ci /* disable pinning */ 88362306a36Sopenharmony_ci bpf_map__set_pin_path(map, NULL); 88462306a36Sopenharmony_ci 88562306a36Sopenharmony_ci /* fix up map size, if necessary */ 88662306a36Sopenharmony_ci switch (bpf_map__type(map)) { 88762306a36Sopenharmony_ci case BPF_MAP_TYPE_SK_STORAGE: 88862306a36Sopenharmony_ci case BPF_MAP_TYPE_TASK_STORAGE: 88962306a36Sopenharmony_ci case BPF_MAP_TYPE_INODE_STORAGE: 89062306a36Sopenharmony_ci case BPF_MAP_TYPE_CGROUP_STORAGE: 89162306a36Sopenharmony_ci break; 89262306a36Sopenharmony_ci default: 89362306a36Sopenharmony_ci if (bpf_map__max_entries(map) == 0) 89462306a36Sopenharmony_ci bpf_map__set_max_entries(map, 1); 89562306a36Sopenharmony_ci } 89662306a36Sopenharmony_ci } 89762306a36Sopenharmony_ci 89862306a36Sopenharmony_ci /* SEC(freplace) programs can't be loaded with veristat as is, 89962306a36Sopenharmony_ci * but we can try guessing their target program's expected type by 90062306a36Sopenharmony_ci * looking at the type of program's first argument and substituting 90162306a36Sopenharmony_ci * corresponding program type 90262306a36Sopenharmony_ci */ 90362306a36Sopenharmony_ci if (bpf_program__type(prog) == BPF_PROG_TYPE_EXT) { 90462306a36Sopenharmony_ci const struct btf *btf = bpf_object__btf(obj); 90562306a36Sopenharmony_ci const char *prog_name = bpf_program__name(prog); 90662306a36Sopenharmony_ci enum bpf_prog_type prog_type; 90762306a36Sopenharmony_ci enum bpf_attach_type attach_type; 90862306a36Sopenharmony_ci const struct btf_type *t; 90962306a36Sopenharmony_ci const char *ctx_name; 91062306a36Sopenharmony_ci int id; 91162306a36Sopenharmony_ci 91262306a36Sopenharmony_ci if (!btf) 91362306a36Sopenharmony_ci goto skip_freplace_fixup; 91462306a36Sopenharmony_ci 91562306a36Sopenharmony_ci id = btf__find_by_name_kind(btf, prog_name, BTF_KIND_FUNC); 91662306a36Sopenharmony_ci t = btf__type_by_id(btf, id); 91762306a36Sopenharmony_ci t = btf__type_by_id(btf, t->type); 91862306a36Sopenharmony_ci if (!btf_is_func_proto(t) || btf_vlen(t) != 1) 91962306a36Sopenharmony_ci goto skip_freplace_fixup; 92062306a36Sopenharmony_ci 92162306a36Sopenharmony_ci /* context argument is a pointer to a struct/typedef */ 92262306a36Sopenharmony_ci t = btf__type_by_id(btf, btf_params(t)[0].type); 92362306a36Sopenharmony_ci while (t && btf_is_mod(t)) 92462306a36Sopenharmony_ci t = btf__type_by_id(btf, t->type); 92562306a36Sopenharmony_ci if (!t || !btf_is_ptr(t)) 92662306a36Sopenharmony_ci goto skip_freplace_fixup; 92762306a36Sopenharmony_ci t = btf__type_by_id(btf, t->type); 92862306a36Sopenharmony_ci while (t && btf_is_mod(t)) 92962306a36Sopenharmony_ci t = btf__type_by_id(btf, t->type); 93062306a36Sopenharmony_ci if (!t) 93162306a36Sopenharmony_ci goto skip_freplace_fixup; 93262306a36Sopenharmony_ci 93362306a36Sopenharmony_ci ctx_name = btf__name_by_offset(btf, t->name_off); 93462306a36Sopenharmony_ci 93562306a36Sopenharmony_ci if (guess_prog_type_by_ctx_name(ctx_name, &prog_type, &attach_type) == 0) { 93662306a36Sopenharmony_ci bpf_program__set_type(prog, prog_type); 93762306a36Sopenharmony_ci bpf_program__set_expected_attach_type(prog, attach_type); 93862306a36Sopenharmony_ci 93962306a36Sopenharmony_ci if (!env.quiet) { 94062306a36Sopenharmony_ci printf("Using guessed program type '%s' for %s/%s...\n", 94162306a36Sopenharmony_ci libbpf_bpf_prog_type_str(prog_type), 94262306a36Sopenharmony_ci filename, prog_name); 94362306a36Sopenharmony_ci } 94462306a36Sopenharmony_ci } else { 94562306a36Sopenharmony_ci if (!env.quiet) { 94662306a36Sopenharmony_ci 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", 94762306a36Sopenharmony_ci ctx_name, filename, prog_name); 94862306a36Sopenharmony_ci } 94962306a36Sopenharmony_ci } 95062306a36Sopenharmony_ci } 95162306a36Sopenharmony_ciskip_freplace_fixup: 95262306a36Sopenharmony_ci return; 95362306a36Sopenharmony_ci} 95462306a36Sopenharmony_ci 95562306a36Sopenharmony_cistatic int process_prog(const char *filename, struct bpf_object *obj, struct bpf_program *prog) 95662306a36Sopenharmony_ci{ 95762306a36Sopenharmony_ci const char *prog_name = bpf_program__name(prog); 95862306a36Sopenharmony_ci const char *base_filename = basename(filename); 95962306a36Sopenharmony_ci char *buf; 96062306a36Sopenharmony_ci int buf_sz, log_level; 96162306a36Sopenharmony_ci struct verif_stats *stats; 96262306a36Sopenharmony_ci int err = 0; 96362306a36Sopenharmony_ci void *tmp; 96462306a36Sopenharmony_ci 96562306a36Sopenharmony_ci if (!should_process_file_prog(base_filename, bpf_program__name(prog))) { 96662306a36Sopenharmony_ci env.progs_skipped++; 96762306a36Sopenharmony_ci return 0; 96862306a36Sopenharmony_ci } 96962306a36Sopenharmony_ci 97062306a36Sopenharmony_ci tmp = realloc(env.prog_stats, (env.prog_stat_cnt + 1) * sizeof(*env.prog_stats)); 97162306a36Sopenharmony_ci if (!tmp) 97262306a36Sopenharmony_ci return -ENOMEM; 97362306a36Sopenharmony_ci env.prog_stats = tmp; 97462306a36Sopenharmony_ci stats = &env.prog_stats[env.prog_stat_cnt++]; 97562306a36Sopenharmony_ci memset(stats, 0, sizeof(*stats)); 97662306a36Sopenharmony_ci 97762306a36Sopenharmony_ci if (env.verbose) { 97862306a36Sopenharmony_ci buf_sz = env.log_size ? env.log_size : 16 * 1024 * 1024; 97962306a36Sopenharmony_ci buf = malloc(buf_sz); 98062306a36Sopenharmony_ci if (!buf) 98162306a36Sopenharmony_ci return -ENOMEM; 98262306a36Sopenharmony_ci /* ensure we always request stats */ 98362306a36Sopenharmony_ci log_level = env.log_level | 4 | (env.log_fixed ? 8 : 0); 98462306a36Sopenharmony_ci } else { 98562306a36Sopenharmony_ci buf = verif_log_buf; 98662306a36Sopenharmony_ci buf_sz = sizeof(verif_log_buf); 98762306a36Sopenharmony_ci /* request only verifier stats */ 98862306a36Sopenharmony_ci log_level = 4 | (env.log_fixed ? 8 : 0); 98962306a36Sopenharmony_ci } 99062306a36Sopenharmony_ci verif_log_buf[0] = '\0'; 99162306a36Sopenharmony_ci 99262306a36Sopenharmony_ci bpf_program__set_log_buf(prog, buf, buf_sz); 99362306a36Sopenharmony_ci bpf_program__set_log_level(prog, log_level); 99462306a36Sopenharmony_ci 99562306a36Sopenharmony_ci /* increase chances of successful BPF object loading */ 99662306a36Sopenharmony_ci fixup_obj(obj, prog, base_filename); 99762306a36Sopenharmony_ci 99862306a36Sopenharmony_ci if (env.force_checkpoints) 99962306a36Sopenharmony_ci bpf_program__set_flags(prog, bpf_program__flags(prog) | BPF_F_TEST_STATE_FREQ); 100062306a36Sopenharmony_ci 100162306a36Sopenharmony_ci err = bpf_object__load(obj); 100262306a36Sopenharmony_ci env.progs_processed++; 100362306a36Sopenharmony_ci 100462306a36Sopenharmony_ci stats->file_name = strdup(base_filename); 100562306a36Sopenharmony_ci stats->prog_name = strdup(bpf_program__name(prog)); 100662306a36Sopenharmony_ci stats->stats[VERDICT] = err == 0; /* 1 - success, 0 - failure */ 100762306a36Sopenharmony_ci parse_verif_log(buf, buf_sz, stats); 100862306a36Sopenharmony_ci 100962306a36Sopenharmony_ci if (env.verbose) { 101062306a36Sopenharmony_ci printf("PROCESSING %s/%s, DURATION US: %ld, VERDICT: %s, VERIFIER LOG:\n%s\n", 101162306a36Sopenharmony_ci filename, prog_name, stats->stats[DURATION], 101262306a36Sopenharmony_ci err ? "failure" : "success", buf); 101362306a36Sopenharmony_ci } 101462306a36Sopenharmony_ci 101562306a36Sopenharmony_ci if (verif_log_buf != buf) 101662306a36Sopenharmony_ci free(buf); 101762306a36Sopenharmony_ci 101862306a36Sopenharmony_ci return 0; 101962306a36Sopenharmony_ci}; 102062306a36Sopenharmony_ci 102162306a36Sopenharmony_cistatic int process_obj(const char *filename) 102262306a36Sopenharmony_ci{ 102362306a36Sopenharmony_ci struct bpf_object *obj = NULL, *tobj; 102462306a36Sopenharmony_ci struct bpf_program *prog, *tprog, *lprog; 102562306a36Sopenharmony_ci libbpf_print_fn_t old_libbpf_print_fn; 102662306a36Sopenharmony_ci LIBBPF_OPTS(bpf_object_open_opts, opts); 102762306a36Sopenharmony_ci int err = 0, prog_cnt = 0; 102862306a36Sopenharmony_ci 102962306a36Sopenharmony_ci if (!should_process_file_prog(basename(filename), NULL)) { 103062306a36Sopenharmony_ci if (env.verbose) 103162306a36Sopenharmony_ci printf("Skipping '%s' due to filters...\n", filename); 103262306a36Sopenharmony_ci env.files_skipped++; 103362306a36Sopenharmony_ci return 0; 103462306a36Sopenharmony_ci } 103562306a36Sopenharmony_ci if (!is_bpf_obj_file(filename)) { 103662306a36Sopenharmony_ci if (env.verbose) 103762306a36Sopenharmony_ci printf("Skipping '%s' as it's not a BPF object file...\n", filename); 103862306a36Sopenharmony_ci env.files_skipped++; 103962306a36Sopenharmony_ci return 0; 104062306a36Sopenharmony_ci } 104162306a36Sopenharmony_ci 104262306a36Sopenharmony_ci if (!env.quiet && env.out_fmt == RESFMT_TABLE) 104362306a36Sopenharmony_ci printf("Processing '%s'...\n", basename(filename)); 104462306a36Sopenharmony_ci 104562306a36Sopenharmony_ci old_libbpf_print_fn = libbpf_set_print(libbpf_print_fn); 104662306a36Sopenharmony_ci obj = bpf_object__open_file(filename, &opts); 104762306a36Sopenharmony_ci if (!obj) { 104862306a36Sopenharmony_ci /* if libbpf can't open BPF object file, it could be because 104962306a36Sopenharmony_ci * that BPF object file is incomplete and has to be statically 105062306a36Sopenharmony_ci * linked into a final BPF object file; instead of bailing 105162306a36Sopenharmony_ci * out, report it into stderr, mark it as skipped, and 105262306a36Sopenharmony_ci * proceed 105362306a36Sopenharmony_ci */ 105462306a36Sopenharmony_ci fprintf(stderr, "Failed to open '%s': %d\n", filename, -errno); 105562306a36Sopenharmony_ci env.files_skipped++; 105662306a36Sopenharmony_ci err = 0; 105762306a36Sopenharmony_ci goto cleanup; 105862306a36Sopenharmony_ci } 105962306a36Sopenharmony_ci 106062306a36Sopenharmony_ci env.files_processed++; 106162306a36Sopenharmony_ci 106262306a36Sopenharmony_ci bpf_object__for_each_program(prog, obj) { 106362306a36Sopenharmony_ci prog_cnt++; 106462306a36Sopenharmony_ci } 106562306a36Sopenharmony_ci 106662306a36Sopenharmony_ci if (prog_cnt == 1) { 106762306a36Sopenharmony_ci prog = bpf_object__next_program(obj, NULL); 106862306a36Sopenharmony_ci bpf_program__set_autoload(prog, true); 106962306a36Sopenharmony_ci process_prog(filename, obj, prog); 107062306a36Sopenharmony_ci goto cleanup; 107162306a36Sopenharmony_ci } 107262306a36Sopenharmony_ci 107362306a36Sopenharmony_ci bpf_object__for_each_program(prog, obj) { 107462306a36Sopenharmony_ci const char *prog_name = bpf_program__name(prog); 107562306a36Sopenharmony_ci 107662306a36Sopenharmony_ci tobj = bpf_object__open_file(filename, &opts); 107762306a36Sopenharmony_ci if (!tobj) { 107862306a36Sopenharmony_ci err = -errno; 107962306a36Sopenharmony_ci fprintf(stderr, "Failed to open '%s': %d\n", filename, err); 108062306a36Sopenharmony_ci goto cleanup; 108162306a36Sopenharmony_ci } 108262306a36Sopenharmony_ci 108362306a36Sopenharmony_ci lprog = NULL; 108462306a36Sopenharmony_ci bpf_object__for_each_program(tprog, tobj) { 108562306a36Sopenharmony_ci const char *tprog_name = bpf_program__name(tprog); 108662306a36Sopenharmony_ci 108762306a36Sopenharmony_ci if (strcmp(prog_name, tprog_name) == 0) { 108862306a36Sopenharmony_ci bpf_program__set_autoload(tprog, true); 108962306a36Sopenharmony_ci lprog = tprog; 109062306a36Sopenharmony_ci } else { 109162306a36Sopenharmony_ci bpf_program__set_autoload(tprog, false); 109262306a36Sopenharmony_ci } 109362306a36Sopenharmony_ci } 109462306a36Sopenharmony_ci 109562306a36Sopenharmony_ci process_prog(filename, tobj, lprog); 109662306a36Sopenharmony_ci bpf_object__close(tobj); 109762306a36Sopenharmony_ci } 109862306a36Sopenharmony_ci 109962306a36Sopenharmony_cicleanup: 110062306a36Sopenharmony_ci bpf_object__close(obj); 110162306a36Sopenharmony_ci libbpf_set_print(old_libbpf_print_fn); 110262306a36Sopenharmony_ci return err; 110362306a36Sopenharmony_ci} 110462306a36Sopenharmony_ci 110562306a36Sopenharmony_cistatic int cmp_stat(const struct verif_stats *s1, const struct verif_stats *s2, 110662306a36Sopenharmony_ci enum stat_id id, bool asc) 110762306a36Sopenharmony_ci{ 110862306a36Sopenharmony_ci int cmp = 0; 110962306a36Sopenharmony_ci 111062306a36Sopenharmony_ci switch (id) { 111162306a36Sopenharmony_ci case FILE_NAME: 111262306a36Sopenharmony_ci cmp = strcmp(s1->file_name, s2->file_name); 111362306a36Sopenharmony_ci break; 111462306a36Sopenharmony_ci case PROG_NAME: 111562306a36Sopenharmony_ci cmp = strcmp(s1->prog_name, s2->prog_name); 111662306a36Sopenharmony_ci break; 111762306a36Sopenharmony_ci case VERDICT: 111862306a36Sopenharmony_ci case DURATION: 111962306a36Sopenharmony_ci case TOTAL_INSNS: 112062306a36Sopenharmony_ci case TOTAL_STATES: 112162306a36Sopenharmony_ci case PEAK_STATES: 112262306a36Sopenharmony_ci case MAX_STATES_PER_INSN: 112362306a36Sopenharmony_ci case MARK_READ_MAX_LEN: { 112462306a36Sopenharmony_ci long v1 = s1->stats[id]; 112562306a36Sopenharmony_ci long v2 = s2->stats[id]; 112662306a36Sopenharmony_ci 112762306a36Sopenharmony_ci if (v1 != v2) 112862306a36Sopenharmony_ci cmp = v1 < v2 ? -1 : 1; 112962306a36Sopenharmony_ci break; 113062306a36Sopenharmony_ci } 113162306a36Sopenharmony_ci default: 113262306a36Sopenharmony_ci fprintf(stderr, "Unrecognized stat #%d\n", id); 113362306a36Sopenharmony_ci exit(1); 113462306a36Sopenharmony_ci } 113562306a36Sopenharmony_ci 113662306a36Sopenharmony_ci return asc ? cmp : -cmp; 113762306a36Sopenharmony_ci} 113862306a36Sopenharmony_ci 113962306a36Sopenharmony_cistatic int cmp_prog_stats(const void *v1, const void *v2) 114062306a36Sopenharmony_ci{ 114162306a36Sopenharmony_ci const struct verif_stats *s1 = v1, *s2 = v2; 114262306a36Sopenharmony_ci int i, cmp; 114362306a36Sopenharmony_ci 114462306a36Sopenharmony_ci for (i = 0; i < env.sort_spec.spec_cnt; i++) { 114562306a36Sopenharmony_ci cmp = cmp_stat(s1, s2, env.sort_spec.ids[i], env.sort_spec.asc[i]); 114662306a36Sopenharmony_ci if (cmp != 0) 114762306a36Sopenharmony_ci return cmp; 114862306a36Sopenharmony_ci } 114962306a36Sopenharmony_ci 115062306a36Sopenharmony_ci /* always disambiguate with file+prog, which are unique */ 115162306a36Sopenharmony_ci cmp = strcmp(s1->file_name, s2->file_name); 115262306a36Sopenharmony_ci if (cmp != 0) 115362306a36Sopenharmony_ci return cmp; 115462306a36Sopenharmony_ci return strcmp(s1->prog_name, s2->prog_name); 115562306a36Sopenharmony_ci} 115662306a36Sopenharmony_ci 115762306a36Sopenharmony_cistatic void fetch_join_stat_value(const struct verif_stats_join *s, 115862306a36Sopenharmony_ci enum stat_id id, enum stat_variant var, 115962306a36Sopenharmony_ci const char **str_val, 116062306a36Sopenharmony_ci double *num_val) 116162306a36Sopenharmony_ci{ 116262306a36Sopenharmony_ci long v1, v2; 116362306a36Sopenharmony_ci 116462306a36Sopenharmony_ci if (id == FILE_NAME) { 116562306a36Sopenharmony_ci *str_val = s->file_name; 116662306a36Sopenharmony_ci return; 116762306a36Sopenharmony_ci } 116862306a36Sopenharmony_ci if (id == PROG_NAME) { 116962306a36Sopenharmony_ci *str_val = s->prog_name; 117062306a36Sopenharmony_ci return; 117162306a36Sopenharmony_ci } 117262306a36Sopenharmony_ci 117362306a36Sopenharmony_ci v1 = s->stats_a ? s->stats_a->stats[id] : 0; 117462306a36Sopenharmony_ci v2 = s->stats_b ? s->stats_b->stats[id] : 0; 117562306a36Sopenharmony_ci 117662306a36Sopenharmony_ci switch (var) { 117762306a36Sopenharmony_ci case VARIANT_A: 117862306a36Sopenharmony_ci if (!s->stats_a) 117962306a36Sopenharmony_ci *num_val = -DBL_MAX; 118062306a36Sopenharmony_ci else 118162306a36Sopenharmony_ci *num_val = s->stats_a->stats[id]; 118262306a36Sopenharmony_ci return; 118362306a36Sopenharmony_ci case VARIANT_B: 118462306a36Sopenharmony_ci if (!s->stats_b) 118562306a36Sopenharmony_ci *num_val = -DBL_MAX; 118662306a36Sopenharmony_ci else 118762306a36Sopenharmony_ci *num_val = s->stats_b->stats[id]; 118862306a36Sopenharmony_ci return; 118962306a36Sopenharmony_ci case VARIANT_DIFF: 119062306a36Sopenharmony_ci if (!s->stats_a || !s->stats_b) 119162306a36Sopenharmony_ci *num_val = -DBL_MAX; 119262306a36Sopenharmony_ci else if (id == VERDICT) 119362306a36Sopenharmony_ci *num_val = v1 == v2 ? 1.0 /* MATCH */ : 0.0 /* MISMATCH */; 119462306a36Sopenharmony_ci else 119562306a36Sopenharmony_ci *num_val = (double)(v2 - v1); 119662306a36Sopenharmony_ci return; 119762306a36Sopenharmony_ci case VARIANT_PCT: 119862306a36Sopenharmony_ci if (!s->stats_a || !s->stats_b) { 119962306a36Sopenharmony_ci *num_val = -DBL_MAX; 120062306a36Sopenharmony_ci } else if (v1 == 0) { 120162306a36Sopenharmony_ci if (v1 == v2) 120262306a36Sopenharmony_ci *num_val = 0.0; 120362306a36Sopenharmony_ci else 120462306a36Sopenharmony_ci *num_val = v2 < v1 ? -100.0 : 100.0; 120562306a36Sopenharmony_ci } else { 120662306a36Sopenharmony_ci *num_val = (v2 - v1) * 100.0 / v1; 120762306a36Sopenharmony_ci } 120862306a36Sopenharmony_ci return; 120962306a36Sopenharmony_ci } 121062306a36Sopenharmony_ci} 121162306a36Sopenharmony_ci 121262306a36Sopenharmony_cistatic int cmp_join_stat(const struct verif_stats_join *s1, 121362306a36Sopenharmony_ci const struct verif_stats_join *s2, 121462306a36Sopenharmony_ci enum stat_id id, enum stat_variant var, bool asc) 121562306a36Sopenharmony_ci{ 121662306a36Sopenharmony_ci const char *str1 = NULL, *str2 = NULL; 121762306a36Sopenharmony_ci double v1 = 0.0, v2 = 0.0; 121862306a36Sopenharmony_ci int cmp = 0; 121962306a36Sopenharmony_ci 122062306a36Sopenharmony_ci fetch_join_stat_value(s1, id, var, &str1, &v1); 122162306a36Sopenharmony_ci fetch_join_stat_value(s2, id, var, &str2, &v2); 122262306a36Sopenharmony_ci 122362306a36Sopenharmony_ci if (str1) 122462306a36Sopenharmony_ci cmp = strcmp(str1, str2); 122562306a36Sopenharmony_ci else if (v1 != v2) 122662306a36Sopenharmony_ci cmp = v1 < v2 ? -1 : 1; 122762306a36Sopenharmony_ci 122862306a36Sopenharmony_ci return asc ? cmp : -cmp; 122962306a36Sopenharmony_ci} 123062306a36Sopenharmony_ci 123162306a36Sopenharmony_cistatic int cmp_join_stats(const void *v1, const void *v2) 123262306a36Sopenharmony_ci{ 123362306a36Sopenharmony_ci const struct verif_stats_join *s1 = v1, *s2 = v2; 123462306a36Sopenharmony_ci int i, cmp; 123562306a36Sopenharmony_ci 123662306a36Sopenharmony_ci for (i = 0; i < env.sort_spec.spec_cnt; i++) { 123762306a36Sopenharmony_ci cmp = cmp_join_stat(s1, s2, 123862306a36Sopenharmony_ci env.sort_spec.ids[i], 123962306a36Sopenharmony_ci env.sort_spec.variants[i], 124062306a36Sopenharmony_ci env.sort_spec.asc[i]); 124162306a36Sopenharmony_ci if (cmp != 0) 124262306a36Sopenharmony_ci return cmp; 124362306a36Sopenharmony_ci } 124462306a36Sopenharmony_ci 124562306a36Sopenharmony_ci /* always disambiguate with file+prog, which are unique */ 124662306a36Sopenharmony_ci cmp = strcmp(s1->file_name, s2->file_name); 124762306a36Sopenharmony_ci if (cmp != 0) 124862306a36Sopenharmony_ci return cmp; 124962306a36Sopenharmony_ci return strcmp(s1->prog_name, s2->prog_name); 125062306a36Sopenharmony_ci} 125162306a36Sopenharmony_ci 125262306a36Sopenharmony_ci#define HEADER_CHAR '-' 125362306a36Sopenharmony_ci#define COLUMN_SEP " " 125462306a36Sopenharmony_ci 125562306a36Sopenharmony_cistatic void output_header_underlines(void) 125662306a36Sopenharmony_ci{ 125762306a36Sopenharmony_ci int i, j, len; 125862306a36Sopenharmony_ci 125962306a36Sopenharmony_ci for (i = 0; i < env.output_spec.spec_cnt; i++) { 126062306a36Sopenharmony_ci len = env.output_spec.lens[i]; 126162306a36Sopenharmony_ci 126262306a36Sopenharmony_ci printf("%s", i == 0 ? "" : COLUMN_SEP); 126362306a36Sopenharmony_ci for (j = 0; j < len; j++) 126462306a36Sopenharmony_ci printf("%c", HEADER_CHAR); 126562306a36Sopenharmony_ci } 126662306a36Sopenharmony_ci printf("\n"); 126762306a36Sopenharmony_ci} 126862306a36Sopenharmony_ci 126962306a36Sopenharmony_cistatic void output_headers(enum resfmt fmt) 127062306a36Sopenharmony_ci{ 127162306a36Sopenharmony_ci const char *fmt_str; 127262306a36Sopenharmony_ci int i, len; 127362306a36Sopenharmony_ci 127462306a36Sopenharmony_ci for (i = 0; i < env.output_spec.spec_cnt; i++) { 127562306a36Sopenharmony_ci int id = env.output_spec.ids[i]; 127662306a36Sopenharmony_ci int *max_len = &env.output_spec.lens[i]; 127762306a36Sopenharmony_ci 127862306a36Sopenharmony_ci switch (fmt) { 127962306a36Sopenharmony_ci case RESFMT_TABLE_CALCLEN: 128062306a36Sopenharmony_ci len = snprintf(NULL, 0, "%s", stat_defs[id].header); 128162306a36Sopenharmony_ci if (len > *max_len) 128262306a36Sopenharmony_ci *max_len = len; 128362306a36Sopenharmony_ci break; 128462306a36Sopenharmony_ci case RESFMT_TABLE: 128562306a36Sopenharmony_ci fmt_str = stat_defs[id].left_aligned ? "%s%-*s" : "%s%*s"; 128662306a36Sopenharmony_ci printf(fmt_str, i == 0 ? "" : COLUMN_SEP, *max_len, stat_defs[id].header); 128762306a36Sopenharmony_ci if (i == env.output_spec.spec_cnt - 1) 128862306a36Sopenharmony_ci printf("\n"); 128962306a36Sopenharmony_ci break; 129062306a36Sopenharmony_ci case RESFMT_CSV: 129162306a36Sopenharmony_ci printf("%s%s", i == 0 ? "" : ",", stat_defs[id].names[0]); 129262306a36Sopenharmony_ci if (i == env.output_spec.spec_cnt - 1) 129362306a36Sopenharmony_ci printf("\n"); 129462306a36Sopenharmony_ci break; 129562306a36Sopenharmony_ci } 129662306a36Sopenharmony_ci } 129762306a36Sopenharmony_ci 129862306a36Sopenharmony_ci if (fmt == RESFMT_TABLE) 129962306a36Sopenharmony_ci output_header_underlines(); 130062306a36Sopenharmony_ci} 130162306a36Sopenharmony_ci 130262306a36Sopenharmony_cistatic void prepare_value(const struct verif_stats *s, enum stat_id id, 130362306a36Sopenharmony_ci const char **str, long *val) 130462306a36Sopenharmony_ci{ 130562306a36Sopenharmony_ci switch (id) { 130662306a36Sopenharmony_ci case FILE_NAME: 130762306a36Sopenharmony_ci *str = s ? s->file_name : "N/A"; 130862306a36Sopenharmony_ci break; 130962306a36Sopenharmony_ci case PROG_NAME: 131062306a36Sopenharmony_ci *str = s ? s->prog_name : "N/A"; 131162306a36Sopenharmony_ci break; 131262306a36Sopenharmony_ci case VERDICT: 131362306a36Sopenharmony_ci if (!s) 131462306a36Sopenharmony_ci *str = "N/A"; 131562306a36Sopenharmony_ci else 131662306a36Sopenharmony_ci *str = s->stats[VERDICT] ? "success" : "failure"; 131762306a36Sopenharmony_ci break; 131862306a36Sopenharmony_ci case DURATION: 131962306a36Sopenharmony_ci case TOTAL_INSNS: 132062306a36Sopenharmony_ci case TOTAL_STATES: 132162306a36Sopenharmony_ci case PEAK_STATES: 132262306a36Sopenharmony_ci case MAX_STATES_PER_INSN: 132362306a36Sopenharmony_ci case MARK_READ_MAX_LEN: 132462306a36Sopenharmony_ci *val = s ? s->stats[id] : 0; 132562306a36Sopenharmony_ci break; 132662306a36Sopenharmony_ci default: 132762306a36Sopenharmony_ci fprintf(stderr, "Unrecognized stat #%d\n", id); 132862306a36Sopenharmony_ci exit(1); 132962306a36Sopenharmony_ci } 133062306a36Sopenharmony_ci} 133162306a36Sopenharmony_ci 133262306a36Sopenharmony_cistatic void output_stats(const struct verif_stats *s, enum resfmt fmt, bool last) 133362306a36Sopenharmony_ci{ 133462306a36Sopenharmony_ci int i; 133562306a36Sopenharmony_ci 133662306a36Sopenharmony_ci for (i = 0; i < env.output_spec.spec_cnt; i++) { 133762306a36Sopenharmony_ci int id = env.output_spec.ids[i]; 133862306a36Sopenharmony_ci int *max_len = &env.output_spec.lens[i], len; 133962306a36Sopenharmony_ci const char *str = NULL; 134062306a36Sopenharmony_ci long val = 0; 134162306a36Sopenharmony_ci 134262306a36Sopenharmony_ci prepare_value(s, id, &str, &val); 134362306a36Sopenharmony_ci 134462306a36Sopenharmony_ci switch (fmt) { 134562306a36Sopenharmony_ci case RESFMT_TABLE_CALCLEN: 134662306a36Sopenharmony_ci if (str) 134762306a36Sopenharmony_ci len = snprintf(NULL, 0, "%s", str); 134862306a36Sopenharmony_ci else 134962306a36Sopenharmony_ci len = snprintf(NULL, 0, "%ld", val); 135062306a36Sopenharmony_ci if (len > *max_len) 135162306a36Sopenharmony_ci *max_len = len; 135262306a36Sopenharmony_ci break; 135362306a36Sopenharmony_ci case RESFMT_TABLE: 135462306a36Sopenharmony_ci if (str) 135562306a36Sopenharmony_ci printf("%s%-*s", i == 0 ? "" : COLUMN_SEP, *max_len, str); 135662306a36Sopenharmony_ci else 135762306a36Sopenharmony_ci printf("%s%*ld", i == 0 ? "" : COLUMN_SEP, *max_len, val); 135862306a36Sopenharmony_ci if (i == env.output_spec.spec_cnt - 1) 135962306a36Sopenharmony_ci printf("\n"); 136062306a36Sopenharmony_ci break; 136162306a36Sopenharmony_ci case RESFMT_CSV: 136262306a36Sopenharmony_ci if (str) 136362306a36Sopenharmony_ci printf("%s%s", i == 0 ? "" : ",", str); 136462306a36Sopenharmony_ci else 136562306a36Sopenharmony_ci printf("%s%ld", i == 0 ? "" : ",", val); 136662306a36Sopenharmony_ci if (i == env.output_spec.spec_cnt - 1) 136762306a36Sopenharmony_ci printf("\n"); 136862306a36Sopenharmony_ci break; 136962306a36Sopenharmony_ci } 137062306a36Sopenharmony_ci } 137162306a36Sopenharmony_ci 137262306a36Sopenharmony_ci if (last && fmt == RESFMT_TABLE) { 137362306a36Sopenharmony_ci output_header_underlines(); 137462306a36Sopenharmony_ci printf("Done. Processed %d files, %d programs. Skipped %d files, %d programs.\n", 137562306a36Sopenharmony_ci env.files_processed, env.files_skipped, env.progs_processed, env.progs_skipped); 137662306a36Sopenharmony_ci } 137762306a36Sopenharmony_ci} 137862306a36Sopenharmony_ci 137962306a36Sopenharmony_cistatic int parse_stat_value(const char *str, enum stat_id id, struct verif_stats *st) 138062306a36Sopenharmony_ci{ 138162306a36Sopenharmony_ci switch (id) { 138262306a36Sopenharmony_ci case FILE_NAME: 138362306a36Sopenharmony_ci st->file_name = strdup(str); 138462306a36Sopenharmony_ci if (!st->file_name) 138562306a36Sopenharmony_ci return -ENOMEM; 138662306a36Sopenharmony_ci break; 138762306a36Sopenharmony_ci case PROG_NAME: 138862306a36Sopenharmony_ci st->prog_name = strdup(str); 138962306a36Sopenharmony_ci if (!st->prog_name) 139062306a36Sopenharmony_ci return -ENOMEM; 139162306a36Sopenharmony_ci break; 139262306a36Sopenharmony_ci case VERDICT: 139362306a36Sopenharmony_ci if (strcmp(str, "success") == 0) { 139462306a36Sopenharmony_ci st->stats[VERDICT] = true; 139562306a36Sopenharmony_ci } else if (strcmp(str, "failure") == 0) { 139662306a36Sopenharmony_ci st->stats[VERDICT] = false; 139762306a36Sopenharmony_ci } else { 139862306a36Sopenharmony_ci fprintf(stderr, "Unrecognized verification verdict '%s'\n", str); 139962306a36Sopenharmony_ci return -EINVAL; 140062306a36Sopenharmony_ci } 140162306a36Sopenharmony_ci break; 140262306a36Sopenharmony_ci case DURATION: 140362306a36Sopenharmony_ci case TOTAL_INSNS: 140462306a36Sopenharmony_ci case TOTAL_STATES: 140562306a36Sopenharmony_ci case PEAK_STATES: 140662306a36Sopenharmony_ci case MAX_STATES_PER_INSN: 140762306a36Sopenharmony_ci case MARK_READ_MAX_LEN: { 140862306a36Sopenharmony_ci long val; 140962306a36Sopenharmony_ci int err, n; 141062306a36Sopenharmony_ci 141162306a36Sopenharmony_ci if (sscanf(str, "%ld %n", &val, &n) != 1 || n != strlen(str)) { 141262306a36Sopenharmony_ci err = -errno; 141362306a36Sopenharmony_ci fprintf(stderr, "Failed to parse '%s' as integer\n", str); 141462306a36Sopenharmony_ci return err; 141562306a36Sopenharmony_ci } 141662306a36Sopenharmony_ci 141762306a36Sopenharmony_ci st->stats[id] = val; 141862306a36Sopenharmony_ci break; 141962306a36Sopenharmony_ci } 142062306a36Sopenharmony_ci default: 142162306a36Sopenharmony_ci fprintf(stderr, "Unrecognized stat #%d\n", id); 142262306a36Sopenharmony_ci return -EINVAL; 142362306a36Sopenharmony_ci } 142462306a36Sopenharmony_ci return 0; 142562306a36Sopenharmony_ci} 142662306a36Sopenharmony_ci 142762306a36Sopenharmony_cistatic int parse_stats_csv(const char *filename, struct stat_specs *specs, 142862306a36Sopenharmony_ci struct verif_stats **statsp, int *stat_cntp) 142962306a36Sopenharmony_ci{ 143062306a36Sopenharmony_ci char line[4096]; 143162306a36Sopenharmony_ci FILE *f; 143262306a36Sopenharmony_ci int err = 0; 143362306a36Sopenharmony_ci bool header = true; 143462306a36Sopenharmony_ci 143562306a36Sopenharmony_ci f = fopen(filename, "r"); 143662306a36Sopenharmony_ci if (!f) { 143762306a36Sopenharmony_ci err = -errno; 143862306a36Sopenharmony_ci fprintf(stderr, "Failed to open '%s': %d\n", filename, err); 143962306a36Sopenharmony_ci return err; 144062306a36Sopenharmony_ci } 144162306a36Sopenharmony_ci 144262306a36Sopenharmony_ci *stat_cntp = 0; 144362306a36Sopenharmony_ci 144462306a36Sopenharmony_ci while (fgets(line, sizeof(line), f)) { 144562306a36Sopenharmony_ci char *input = line, *state = NULL, *next; 144662306a36Sopenharmony_ci struct verif_stats *st = NULL; 144762306a36Sopenharmony_ci int col = 0; 144862306a36Sopenharmony_ci 144962306a36Sopenharmony_ci if (!header) { 145062306a36Sopenharmony_ci void *tmp; 145162306a36Sopenharmony_ci 145262306a36Sopenharmony_ci tmp = realloc(*statsp, (*stat_cntp + 1) * sizeof(**statsp)); 145362306a36Sopenharmony_ci if (!tmp) { 145462306a36Sopenharmony_ci err = -ENOMEM; 145562306a36Sopenharmony_ci goto cleanup; 145662306a36Sopenharmony_ci } 145762306a36Sopenharmony_ci *statsp = tmp; 145862306a36Sopenharmony_ci 145962306a36Sopenharmony_ci st = &(*statsp)[*stat_cntp]; 146062306a36Sopenharmony_ci memset(st, 0, sizeof(*st)); 146162306a36Sopenharmony_ci 146262306a36Sopenharmony_ci *stat_cntp += 1; 146362306a36Sopenharmony_ci } 146462306a36Sopenharmony_ci 146562306a36Sopenharmony_ci while ((next = strtok_r(state ? NULL : input, ",\n", &state))) { 146662306a36Sopenharmony_ci if (header) { 146762306a36Sopenharmony_ci /* for the first line, set up spec stats */ 146862306a36Sopenharmony_ci err = parse_stat(next, specs); 146962306a36Sopenharmony_ci if (err) 147062306a36Sopenharmony_ci goto cleanup; 147162306a36Sopenharmony_ci continue; 147262306a36Sopenharmony_ci } 147362306a36Sopenharmony_ci 147462306a36Sopenharmony_ci /* for all other lines, parse values based on spec */ 147562306a36Sopenharmony_ci if (col >= specs->spec_cnt) { 147662306a36Sopenharmony_ci fprintf(stderr, "Found extraneous column #%d in row #%d of '%s'\n", 147762306a36Sopenharmony_ci col, *stat_cntp, filename); 147862306a36Sopenharmony_ci err = -EINVAL; 147962306a36Sopenharmony_ci goto cleanup; 148062306a36Sopenharmony_ci } 148162306a36Sopenharmony_ci err = parse_stat_value(next, specs->ids[col], st); 148262306a36Sopenharmony_ci if (err) 148362306a36Sopenharmony_ci goto cleanup; 148462306a36Sopenharmony_ci col++; 148562306a36Sopenharmony_ci } 148662306a36Sopenharmony_ci 148762306a36Sopenharmony_ci if (header) { 148862306a36Sopenharmony_ci header = false; 148962306a36Sopenharmony_ci continue; 149062306a36Sopenharmony_ci } 149162306a36Sopenharmony_ci 149262306a36Sopenharmony_ci if (col < specs->spec_cnt) { 149362306a36Sopenharmony_ci fprintf(stderr, "Not enough columns in row #%d in '%s'\n", 149462306a36Sopenharmony_ci *stat_cntp, filename); 149562306a36Sopenharmony_ci err = -EINVAL; 149662306a36Sopenharmony_ci goto cleanup; 149762306a36Sopenharmony_ci } 149862306a36Sopenharmony_ci 149962306a36Sopenharmony_ci if (!st->file_name || !st->prog_name) { 150062306a36Sopenharmony_ci fprintf(stderr, "Row #%d in '%s' is missing file and/or program name\n", 150162306a36Sopenharmony_ci *stat_cntp, filename); 150262306a36Sopenharmony_ci err = -EINVAL; 150362306a36Sopenharmony_ci goto cleanup; 150462306a36Sopenharmony_ci } 150562306a36Sopenharmony_ci 150662306a36Sopenharmony_ci /* in comparison mode we can only check filters after we 150762306a36Sopenharmony_ci * parsed entire line; if row should be ignored we pretend we 150862306a36Sopenharmony_ci * never parsed it 150962306a36Sopenharmony_ci */ 151062306a36Sopenharmony_ci if (!should_process_file_prog(st->file_name, st->prog_name)) { 151162306a36Sopenharmony_ci free(st->file_name); 151262306a36Sopenharmony_ci free(st->prog_name); 151362306a36Sopenharmony_ci *stat_cntp -= 1; 151462306a36Sopenharmony_ci } 151562306a36Sopenharmony_ci } 151662306a36Sopenharmony_ci 151762306a36Sopenharmony_ci if (!feof(f)) { 151862306a36Sopenharmony_ci err = -errno; 151962306a36Sopenharmony_ci fprintf(stderr, "Failed I/O for '%s': %d\n", filename, err); 152062306a36Sopenharmony_ci } 152162306a36Sopenharmony_ci 152262306a36Sopenharmony_cicleanup: 152362306a36Sopenharmony_ci fclose(f); 152462306a36Sopenharmony_ci return err; 152562306a36Sopenharmony_ci} 152662306a36Sopenharmony_ci 152762306a36Sopenharmony_ci/* empty/zero stats for mismatched rows */ 152862306a36Sopenharmony_cistatic const struct verif_stats fallback_stats = { .file_name = "", .prog_name = "" }; 152962306a36Sopenharmony_ci 153062306a36Sopenharmony_cistatic bool is_key_stat(enum stat_id id) 153162306a36Sopenharmony_ci{ 153262306a36Sopenharmony_ci return id == FILE_NAME || id == PROG_NAME; 153362306a36Sopenharmony_ci} 153462306a36Sopenharmony_ci 153562306a36Sopenharmony_cistatic void output_comp_header_underlines(void) 153662306a36Sopenharmony_ci{ 153762306a36Sopenharmony_ci int i, j, k; 153862306a36Sopenharmony_ci 153962306a36Sopenharmony_ci for (i = 0; i < env.output_spec.spec_cnt; i++) { 154062306a36Sopenharmony_ci int id = env.output_spec.ids[i]; 154162306a36Sopenharmony_ci int max_j = is_key_stat(id) ? 1 : 3; 154262306a36Sopenharmony_ci 154362306a36Sopenharmony_ci for (j = 0; j < max_j; j++) { 154462306a36Sopenharmony_ci int len = env.output_spec.lens[3 * i + j]; 154562306a36Sopenharmony_ci 154662306a36Sopenharmony_ci printf("%s", i + j == 0 ? "" : COLUMN_SEP); 154762306a36Sopenharmony_ci 154862306a36Sopenharmony_ci for (k = 0; k < len; k++) 154962306a36Sopenharmony_ci printf("%c", HEADER_CHAR); 155062306a36Sopenharmony_ci } 155162306a36Sopenharmony_ci } 155262306a36Sopenharmony_ci printf("\n"); 155362306a36Sopenharmony_ci} 155462306a36Sopenharmony_ci 155562306a36Sopenharmony_cistatic void output_comp_headers(enum resfmt fmt) 155662306a36Sopenharmony_ci{ 155762306a36Sopenharmony_ci static const char *table_sfxs[3] = {" (A)", " (B)", " (DIFF)"}; 155862306a36Sopenharmony_ci static const char *name_sfxs[3] = {"_base", "_comp", "_diff"}; 155962306a36Sopenharmony_ci int i, j, len; 156062306a36Sopenharmony_ci 156162306a36Sopenharmony_ci for (i = 0; i < env.output_spec.spec_cnt; i++) { 156262306a36Sopenharmony_ci int id = env.output_spec.ids[i]; 156362306a36Sopenharmony_ci /* key stats don't have A/B/DIFF columns, they are common for both data sets */ 156462306a36Sopenharmony_ci int max_j = is_key_stat(id) ? 1 : 3; 156562306a36Sopenharmony_ci 156662306a36Sopenharmony_ci for (j = 0; j < max_j; j++) { 156762306a36Sopenharmony_ci int *max_len = &env.output_spec.lens[3 * i + j]; 156862306a36Sopenharmony_ci bool last = (i == env.output_spec.spec_cnt - 1) && (j == max_j - 1); 156962306a36Sopenharmony_ci const char *sfx; 157062306a36Sopenharmony_ci 157162306a36Sopenharmony_ci switch (fmt) { 157262306a36Sopenharmony_ci case RESFMT_TABLE_CALCLEN: 157362306a36Sopenharmony_ci sfx = is_key_stat(id) ? "" : table_sfxs[j]; 157462306a36Sopenharmony_ci len = snprintf(NULL, 0, "%s%s", stat_defs[id].header, sfx); 157562306a36Sopenharmony_ci if (len > *max_len) 157662306a36Sopenharmony_ci *max_len = len; 157762306a36Sopenharmony_ci break; 157862306a36Sopenharmony_ci case RESFMT_TABLE: 157962306a36Sopenharmony_ci sfx = is_key_stat(id) ? "" : table_sfxs[j]; 158062306a36Sopenharmony_ci printf("%s%-*s%s", i + j == 0 ? "" : COLUMN_SEP, 158162306a36Sopenharmony_ci *max_len - (int)strlen(sfx), stat_defs[id].header, sfx); 158262306a36Sopenharmony_ci if (last) 158362306a36Sopenharmony_ci printf("\n"); 158462306a36Sopenharmony_ci break; 158562306a36Sopenharmony_ci case RESFMT_CSV: 158662306a36Sopenharmony_ci sfx = is_key_stat(id) ? "" : name_sfxs[j]; 158762306a36Sopenharmony_ci printf("%s%s%s", i + j == 0 ? "" : ",", stat_defs[id].names[0], sfx); 158862306a36Sopenharmony_ci if (last) 158962306a36Sopenharmony_ci printf("\n"); 159062306a36Sopenharmony_ci break; 159162306a36Sopenharmony_ci } 159262306a36Sopenharmony_ci } 159362306a36Sopenharmony_ci } 159462306a36Sopenharmony_ci 159562306a36Sopenharmony_ci if (fmt == RESFMT_TABLE) 159662306a36Sopenharmony_ci output_comp_header_underlines(); 159762306a36Sopenharmony_ci} 159862306a36Sopenharmony_ci 159962306a36Sopenharmony_cistatic void output_comp_stats(const struct verif_stats_join *join_stats, 160062306a36Sopenharmony_ci enum resfmt fmt, bool last) 160162306a36Sopenharmony_ci{ 160262306a36Sopenharmony_ci const struct verif_stats *base = join_stats->stats_a; 160362306a36Sopenharmony_ci const struct verif_stats *comp = join_stats->stats_b; 160462306a36Sopenharmony_ci char base_buf[1024] = {}, comp_buf[1024] = {}, diff_buf[1024] = {}; 160562306a36Sopenharmony_ci int i; 160662306a36Sopenharmony_ci 160762306a36Sopenharmony_ci for (i = 0; i < env.output_spec.spec_cnt; i++) { 160862306a36Sopenharmony_ci int id = env.output_spec.ids[i], len; 160962306a36Sopenharmony_ci int *max_len_base = &env.output_spec.lens[3 * i + 0]; 161062306a36Sopenharmony_ci int *max_len_comp = &env.output_spec.lens[3 * i + 1]; 161162306a36Sopenharmony_ci int *max_len_diff = &env.output_spec.lens[3 * i + 2]; 161262306a36Sopenharmony_ci const char *base_str = NULL, *comp_str = NULL; 161362306a36Sopenharmony_ci long base_val = 0, comp_val = 0, diff_val = 0; 161462306a36Sopenharmony_ci 161562306a36Sopenharmony_ci prepare_value(base, id, &base_str, &base_val); 161662306a36Sopenharmony_ci prepare_value(comp, id, &comp_str, &comp_val); 161762306a36Sopenharmony_ci 161862306a36Sopenharmony_ci /* normalize all the outputs to be in string buffers for simplicity */ 161962306a36Sopenharmony_ci if (is_key_stat(id)) { 162062306a36Sopenharmony_ci /* key stats (file and program name) are always strings */ 162162306a36Sopenharmony_ci if (base) 162262306a36Sopenharmony_ci snprintf(base_buf, sizeof(base_buf), "%s", base_str); 162362306a36Sopenharmony_ci else 162462306a36Sopenharmony_ci snprintf(base_buf, sizeof(base_buf), "%s", comp_str); 162562306a36Sopenharmony_ci } else if (base_str) { 162662306a36Sopenharmony_ci snprintf(base_buf, sizeof(base_buf), "%s", base_str); 162762306a36Sopenharmony_ci snprintf(comp_buf, sizeof(comp_buf), "%s", comp_str); 162862306a36Sopenharmony_ci if (!base || !comp) 162962306a36Sopenharmony_ci snprintf(diff_buf, sizeof(diff_buf), "%s", "N/A"); 163062306a36Sopenharmony_ci else if (strcmp(base_str, comp_str) == 0) 163162306a36Sopenharmony_ci snprintf(diff_buf, sizeof(diff_buf), "%s", "MATCH"); 163262306a36Sopenharmony_ci else 163362306a36Sopenharmony_ci snprintf(diff_buf, sizeof(diff_buf), "%s", "MISMATCH"); 163462306a36Sopenharmony_ci } else { 163562306a36Sopenharmony_ci double p = 0.0; 163662306a36Sopenharmony_ci 163762306a36Sopenharmony_ci if (base) 163862306a36Sopenharmony_ci snprintf(base_buf, sizeof(base_buf), "%ld", base_val); 163962306a36Sopenharmony_ci else 164062306a36Sopenharmony_ci snprintf(base_buf, sizeof(base_buf), "%s", "N/A"); 164162306a36Sopenharmony_ci if (comp) 164262306a36Sopenharmony_ci snprintf(comp_buf, sizeof(comp_buf), "%ld", comp_val); 164362306a36Sopenharmony_ci else 164462306a36Sopenharmony_ci snprintf(comp_buf, sizeof(comp_buf), "%s", "N/A"); 164562306a36Sopenharmony_ci 164662306a36Sopenharmony_ci diff_val = comp_val - base_val; 164762306a36Sopenharmony_ci if (!base || !comp) { 164862306a36Sopenharmony_ci snprintf(diff_buf, sizeof(diff_buf), "%s", "N/A"); 164962306a36Sopenharmony_ci } else { 165062306a36Sopenharmony_ci if (base_val == 0) { 165162306a36Sopenharmony_ci if (comp_val == base_val) 165262306a36Sopenharmony_ci p = 0.0; /* avoid +0 (+100%) case */ 165362306a36Sopenharmony_ci else 165462306a36Sopenharmony_ci p = comp_val < base_val ? -100.0 : 100.0; 165562306a36Sopenharmony_ci } else { 165662306a36Sopenharmony_ci p = diff_val * 100.0 / base_val; 165762306a36Sopenharmony_ci } 165862306a36Sopenharmony_ci snprintf(diff_buf, sizeof(diff_buf), "%+ld (%+.2lf%%)", diff_val, p); 165962306a36Sopenharmony_ci } 166062306a36Sopenharmony_ci } 166162306a36Sopenharmony_ci 166262306a36Sopenharmony_ci switch (fmt) { 166362306a36Sopenharmony_ci case RESFMT_TABLE_CALCLEN: 166462306a36Sopenharmony_ci len = strlen(base_buf); 166562306a36Sopenharmony_ci if (len > *max_len_base) 166662306a36Sopenharmony_ci *max_len_base = len; 166762306a36Sopenharmony_ci if (!is_key_stat(id)) { 166862306a36Sopenharmony_ci len = strlen(comp_buf); 166962306a36Sopenharmony_ci if (len > *max_len_comp) 167062306a36Sopenharmony_ci *max_len_comp = len; 167162306a36Sopenharmony_ci len = strlen(diff_buf); 167262306a36Sopenharmony_ci if (len > *max_len_diff) 167362306a36Sopenharmony_ci *max_len_diff = len; 167462306a36Sopenharmony_ci } 167562306a36Sopenharmony_ci break; 167662306a36Sopenharmony_ci case RESFMT_TABLE: { 167762306a36Sopenharmony_ci /* string outputs are left-aligned, number outputs are right-aligned */ 167862306a36Sopenharmony_ci const char *fmt = base_str ? "%s%-*s" : "%s%*s"; 167962306a36Sopenharmony_ci 168062306a36Sopenharmony_ci printf(fmt, i == 0 ? "" : COLUMN_SEP, *max_len_base, base_buf); 168162306a36Sopenharmony_ci if (!is_key_stat(id)) { 168262306a36Sopenharmony_ci printf(fmt, COLUMN_SEP, *max_len_comp, comp_buf); 168362306a36Sopenharmony_ci printf(fmt, COLUMN_SEP, *max_len_diff, diff_buf); 168462306a36Sopenharmony_ci } 168562306a36Sopenharmony_ci if (i == env.output_spec.spec_cnt - 1) 168662306a36Sopenharmony_ci printf("\n"); 168762306a36Sopenharmony_ci break; 168862306a36Sopenharmony_ci } 168962306a36Sopenharmony_ci case RESFMT_CSV: 169062306a36Sopenharmony_ci printf("%s%s", i == 0 ? "" : ",", base_buf); 169162306a36Sopenharmony_ci if (!is_key_stat(id)) { 169262306a36Sopenharmony_ci printf("%s%s", i == 0 ? "" : ",", comp_buf); 169362306a36Sopenharmony_ci printf("%s%s", i == 0 ? "" : ",", diff_buf); 169462306a36Sopenharmony_ci } 169562306a36Sopenharmony_ci if (i == env.output_spec.spec_cnt - 1) 169662306a36Sopenharmony_ci printf("\n"); 169762306a36Sopenharmony_ci break; 169862306a36Sopenharmony_ci } 169962306a36Sopenharmony_ci } 170062306a36Sopenharmony_ci 170162306a36Sopenharmony_ci if (last && fmt == RESFMT_TABLE) 170262306a36Sopenharmony_ci output_comp_header_underlines(); 170362306a36Sopenharmony_ci} 170462306a36Sopenharmony_ci 170562306a36Sopenharmony_cistatic int cmp_stats_key(const struct verif_stats *base, const struct verif_stats *comp) 170662306a36Sopenharmony_ci{ 170762306a36Sopenharmony_ci int r; 170862306a36Sopenharmony_ci 170962306a36Sopenharmony_ci r = strcmp(base->file_name, comp->file_name); 171062306a36Sopenharmony_ci if (r != 0) 171162306a36Sopenharmony_ci return r; 171262306a36Sopenharmony_ci return strcmp(base->prog_name, comp->prog_name); 171362306a36Sopenharmony_ci} 171462306a36Sopenharmony_ci 171562306a36Sopenharmony_cistatic bool is_join_stat_filter_matched(struct filter *f, const struct verif_stats_join *stats) 171662306a36Sopenharmony_ci{ 171762306a36Sopenharmony_ci static const double eps = 1e-9; 171862306a36Sopenharmony_ci const char *str = NULL; 171962306a36Sopenharmony_ci double value = 0.0; 172062306a36Sopenharmony_ci 172162306a36Sopenharmony_ci fetch_join_stat_value(stats, f->stat_id, f->stat_var, &str, &value); 172262306a36Sopenharmony_ci 172362306a36Sopenharmony_ci switch (f->op) { 172462306a36Sopenharmony_ci case OP_EQ: return value > f->value - eps && value < f->value + eps; 172562306a36Sopenharmony_ci case OP_NEQ: return value < f->value - eps || value > f->value + eps; 172662306a36Sopenharmony_ci case OP_LT: return value < f->value - eps; 172762306a36Sopenharmony_ci case OP_LE: return value <= f->value + eps; 172862306a36Sopenharmony_ci case OP_GT: return value > f->value + eps; 172962306a36Sopenharmony_ci case OP_GE: return value >= f->value - eps; 173062306a36Sopenharmony_ci } 173162306a36Sopenharmony_ci 173262306a36Sopenharmony_ci fprintf(stderr, "BUG: unknown filter op %d!\n", f->op); 173362306a36Sopenharmony_ci return false; 173462306a36Sopenharmony_ci} 173562306a36Sopenharmony_ci 173662306a36Sopenharmony_cistatic bool should_output_join_stats(const struct verif_stats_join *stats) 173762306a36Sopenharmony_ci{ 173862306a36Sopenharmony_ci struct filter *f; 173962306a36Sopenharmony_ci int i, allow_cnt = 0; 174062306a36Sopenharmony_ci 174162306a36Sopenharmony_ci for (i = 0; i < env.deny_filter_cnt; i++) { 174262306a36Sopenharmony_ci f = &env.deny_filters[i]; 174362306a36Sopenharmony_ci if (f->kind != FILTER_STAT) 174462306a36Sopenharmony_ci continue; 174562306a36Sopenharmony_ci 174662306a36Sopenharmony_ci if (is_join_stat_filter_matched(f, stats)) 174762306a36Sopenharmony_ci return false; 174862306a36Sopenharmony_ci } 174962306a36Sopenharmony_ci 175062306a36Sopenharmony_ci for (i = 0; i < env.allow_filter_cnt; i++) { 175162306a36Sopenharmony_ci f = &env.allow_filters[i]; 175262306a36Sopenharmony_ci if (f->kind != FILTER_STAT) 175362306a36Sopenharmony_ci continue; 175462306a36Sopenharmony_ci allow_cnt++; 175562306a36Sopenharmony_ci 175662306a36Sopenharmony_ci if (is_join_stat_filter_matched(f, stats)) 175762306a36Sopenharmony_ci return true; 175862306a36Sopenharmony_ci } 175962306a36Sopenharmony_ci 176062306a36Sopenharmony_ci /* if there are no stat allowed filters, pass everything through */ 176162306a36Sopenharmony_ci return allow_cnt == 0; 176262306a36Sopenharmony_ci} 176362306a36Sopenharmony_ci 176462306a36Sopenharmony_cistatic int handle_comparison_mode(void) 176562306a36Sopenharmony_ci{ 176662306a36Sopenharmony_ci struct stat_specs base_specs = {}, comp_specs = {}; 176762306a36Sopenharmony_ci struct stat_specs tmp_sort_spec; 176862306a36Sopenharmony_ci enum resfmt cur_fmt; 176962306a36Sopenharmony_ci int err, i, j, last_idx; 177062306a36Sopenharmony_ci 177162306a36Sopenharmony_ci if (env.filename_cnt != 2) { 177262306a36Sopenharmony_ci fprintf(stderr, "Comparison mode expects exactly two input CSV files!\n\n"); 177362306a36Sopenharmony_ci argp_help(&argp, stderr, ARGP_HELP_USAGE, "veristat"); 177462306a36Sopenharmony_ci return -EINVAL; 177562306a36Sopenharmony_ci } 177662306a36Sopenharmony_ci 177762306a36Sopenharmony_ci err = parse_stats_csv(env.filenames[0], &base_specs, 177862306a36Sopenharmony_ci &env.baseline_stats, &env.baseline_stat_cnt); 177962306a36Sopenharmony_ci if (err) { 178062306a36Sopenharmony_ci fprintf(stderr, "Failed to parse stats from '%s': %d\n", env.filenames[0], err); 178162306a36Sopenharmony_ci return err; 178262306a36Sopenharmony_ci } 178362306a36Sopenharmony_ci err = parse_stats_csv(env.filenames[1], &comp_specs, 178462306a36Sopenharmony_ci &env.prog_stats, &env.prog_stat_cnt); 178562306a36Sopenharmony_ci if (err) { 178662306a36Sopenharmony_ci fprintf(stderr, "Failed to parse stats from '%s': %d\n", env.filenames[1], err); 178762306a36Sopenharmony_ci return err; 178862306a36Sopenharmony_ci } 178962306a36Sopenharmony_ci 179062306a36Sopenharmony_ci /* To keep it simple we validate that the set and order of stats in 179162306a36Sopenharmony_ci * both CSVs are exactly the same. This can be lifted with a bit more 179262306a36Sopenharmony_ci * pre-processing later. 179362306a36Sopenharmony_ci */ 179462306a36Sopenharmony_ci if (base_specs.spec_cnt != comp_specs.spec_cnt) { 179562306a36Sopenharmony_ci fprintf(stderr, "Number of stats in '%s' and '%s' differs (%d != %d)!\n", 179662306a36Sopenharmony_ci env.filenames[0], env.filenames[1], 179762306a36Sopenharmony_ci base_specs.spec_cnt, comp_specs.spec_cnt); 179862306a36Sopenharmony_ci return -EINVAL; 179962306a36Sopenharmony_ci } 180062306a36Sopenharmony_ci for (i = 0; i < base_specs.spec_cnt; i++) { 180162306a36Sopenharmony_ci if (base_specs.ids[i] != comp_specs.ids[i]) { 180262306a36Sopenharmony_ci fprintf(stderr, "Stats composition differs between '%s' and '%s' (%s != %s)!\n", 180362306a36Sopenharmony_ci env.filenames[0], env.filenames[1], 180462306a36Sopenharmony_ci stat_defs[base_specs.ids[i]].names[0], 180562306a36Sopenharmony_ci stat_defs[comp_specs.ids[i]].names[0]); 180662306a36Sopenharmony_ci return -EINVAL; 180762306a36Sopenharmony_ci } 180862306a36Sopenharmony_ci } 180962306a36Sopenharmony_ci 181062306a36Sopenharmony_ci /* Replace user-specified sorting spec with file+prog sorting rule to 181162306a36Sopenharmony_ci * be able to join two datasets correctly. Once we are done, we will 181262306a36Sopenharmony_ci * restore the original sort spec. 181362306a36Sopenharmony_ci */ 181462306a36Sopenharmony_ci tmp_sort_spec = env.sort_spec; 181562306a36Sopenharmony_ci env.sort_spec = join_sort_spec; 181662306a36Sopenharmony_ci qsort(env.prog_stats, env.prog_stat_cnt, sizeof(*env.prog_stats), cmp_prog_stats); 181762306a36Sopenharmony_ci qsort(env.baseline_stats, env.baseline_stat_cnt, sizeof(*env.baseline_stats), cmp_prog_stats); 181862306a36Sopenharmony_ci env.sort_spec = tmp_sort_spec; 181962306a36Sopenharmony_ci 182062306a36Sopenharmony_ci /* Join two datasets together. If baseline and comparison datasets 182162306a36Sopenharmony_ci * have different subset of rows (we match by 'object + prog' as 182262306a36Sopenharmony_ci * a unique key) then assume empty/missing/zero value for rows that 182362306a36Sopenharmony_ci * are missing in the opposite data set. 182462306a36Sopenharmony_ci */ 182562306a36Sopenharmony_ci i = j = 0; 182662306a36Sopenharmony_ci while (i < env.baseline_stat_cnt || j < env.prog_stat_cnt) { 182762306a36Sopenharmony_ci const struct verif_stats *base, *comp; 182862306a36Sopenharmony_ci struct verif_stats_join *join; 182962306a36Sopenharmony_ci void *tmp; 183062306a36Sopenharmony_ci int r; 183162306a36Sopenharmony_ci 183262306a36Sopenharmony_ci base = i < env.baseline_stat_cnt ? &env.baseline_stats[i] : &fallback_stats; 183362306a36Sopenharmony_ci comp = j < env.prog_stat_cnt ? &env.prog_stats[j] : &fallback_stats; 183462306a36Sopenharmony_ci 183562306a36Sopenharmony_ci if (!base->file_name || !base->prog_name) { 183662306a36Sopenharmony_ci fprintf(stderr, "Entry #%d in '%s' doesn't have file and/or program name specified!\n", 183762306a36Sopenharmony_ci i, env.filenames[0]); 183862306a36Sopenharmony_ci return -EINVAL; 183962306a36Sopenharmony_ci } 184062306a36Sopenharmony_ci if (!comp->file_name || !comp->prog_name) { 184162306a36Sopenharmony_ci fprintf(stderr, "Entry #%d in '%s' doesn't have file and/or program name specified!\n", 184262306a36Sopenharmony_ci j, env.filenames[1]); 184362306a36Sopenharmony_ci return -EINVAL; 184462306a36Sopenharmony_ci } 184562306a36Sopenharmony_ci 184662306a36Sopenharmony_ci tmp = realloc(env.join_stats, (env.join_stat_cnt + 1) * sizeof(*env.join_stats)); 184762306a36Sopenharmony_ci if (!tmp) 184862306a36Sopenharmony_ci return -ENOMEM; 184962306a36Sopenharmony_ci env.join_stats = tmp; 185062306a36Sopenharmony_ci 185162306a36Sopenharmony_ci join = &env.join_stats[env.join_stat_cnt]; 185262306a36Sopenharmony_ci memset(join, 0, sizeof(*join)); 185362306a36Sopenharmony_ci 185462306a36Sopenharmony_ci r = cmp_stats_key(base, comp); 185562306a36Sopenharmony_ci if (r == 0) { 185662306a36Sopenharmony_ci join->file_name = base->file_name; 185762306a36Sopenharmony_ci join->prog_name = base->prog_name; 185862306a36Sopenharmony_ci join->stats_a = base; 185962306a36Sopenharmony_ci join->stats_b = comp; 186062306a36Sopenharmony_ci i++; 186162306a36Sopenharmony_ci j++; 186262306a36Sopenharmony_ci } else if (base != &fallback_stats && (comp == &fallback_stats || r < 0)) { 186362306a36Sopenharmony_ci join->file_name = base->file_name; 186462306a36Sopenharmony_ci join->prog_name = base->prog_name; 186562306a36Sopenharmony_ci join->stats_a = base; 186662306a36Sopenharmony_ci join->stats_b = NULL; 186762306a36Sopenharmony_ci i++; 186862306a36Sopenharmony_ci } else if (comp != &fallback_stats && (base == &fallback_stats || r > 0)) { 186962306a36Sopenharmony_ci join->file_name = comp->file_name; 187062306a36Sopenharmony_ci join->prog_name = comp->prog_name; 187162306a36Sopenharmony_ci join->stats_a = NULL; 187262306a36Sopenharmony_ci join->stats_b = comp; 187362306a36Sopenharmony_ci j++; 187462306a36Sopenharmony_ci } else { 187562306a36Sopenharmony_ci fprintf(stderr, "%s:%d: should never reach here i=%i, j=%i", 187662306a36Sopenharmony_ci __FILE__, __LINE__, i, j); 187762306a36Sopenharmony_ci return -EINVAL; 187862306a36Sopenharmony_ci } 187962306a36Sopenharmony_ci env.join_stat_cnt += 1; 188062306a36Sopenharmony_ci } 188162306a36Sopenharmony_ci 188262306a36Sopenharmony_ci /* now sort joined results accorsing to sort spec */ 188362306a36Sopenharmony_ci qsort(env.join_stats, env.join_stat_cnt, sizeof(*env.join_stats), cmp_join_stats); 188462306a36Sopenharmony_ci 188562306a36Sopenharmony_ci /* for human-readable table output we need to do extra pass to 188662306a36Sopenharmony_ci * calculate column widths, so we substitute current output format 188762306a36Sopenharmony_ci * with RESFMT_TABLE_CALCLEN and later revert it back to RESFMT_TABLE 188862306a36Sopenharmony_ci * and do everything again. 188962306a36Sopenharmony_ci */ 189062306a36Sopenharmony_ci if (env.out_fmt == RESFMT_TABLE) 189162306a36Sopenharmony_ci cur_fmt = RESFMT_TABLE_CALCLEN; 189262306a36Sopenharmony_ci else 189362306a36Sopenharmony_ci cur_fmt = env.out_fmt; 189462306a36Sopenharmony_ci 189562306a36Sopenharmony_cione_more_time: 189662306a36Sopenharmony_ci output_comp_headers(cur_fmt); 189762306a36Sopenharmony_ci 189862306a36Sopenharmony_ci last_idx = -1; 189962306a36Sopenharmony_ci for (i = 0; i < env.join_stat_cnt; i++) { 190062306a36Sopenharmony_ci const struct verif_stats_join *join = &env.join_stats[i]; 190162306a36Sopenharmony_ci 190262306a36Sopenharmony_ci if (!should_output_join_stats(join)) 190362306a36Sopenharmony_ci continue; 190462306a36Sopenharmony_ci 190562306a36Sopenharmony_ci if (cur_fmt == RESFMT_TABLE_CALCLEN) 190662306a36Sopenharmony_ci last_idx = i; 190762306a36Sopenharmony_ci 190862306a36Sopenharmony_ci output_comp_stats(join, cur_fmt, i == last_idx); 190962306a36Sopenharmony_ci } 191062306a36Sopenharmony_ci 191162306a36Sopenharmony_ci if (cur_fmt == RESFMT_TABLE_CALCLEN) { 191262306a36Sopenharmony_ci cur_fmt = RESFMT_TABLE; 191362306a36Sopenharmony_ci goto one_more_time; /* ... this time with feeling */ 191462306a36Sopenharmony_ci } 191562306a36Sopenharmony_ci 191662306a36Sopenharmony_ci return 0; 191762306a36Sopenharmony_ci} 191862306a36Sopenharmony_ci 191962306a36Sopenharmony_cistatic bool is_stat_filter_matched(struct filter *f, const struct verif_stats *stats) 192062306a36Sopenharmony_ci{ 192162306a36Sopenharmony_ci long value = stats->stats[f->stat_id]; 192262306a36Sopenharmony_ci 192362306a36Sopenharmony_ci switch (f->op) { 192462306a36Sopenharmony_ci case OP_EQ: return value == f->value; 192562306a36Sopenharmony_ci case OP_NEQ: return value != f->value; 192662306a36Sopenharmony_ci case OP_LT: return value < f->value; 192762306a36Sopenharmony_ci case OP_LE: return value <= f->value; 192862306a36Sopenharmony_ci case OP_GT: return value > f->value; 192962306a36Sopenharmony_ci case OP_GE: return value >= f->value; 193062306a36Sopenharmony_ci } 193162306a36Sopenharmony_ci 193262306a36Sopenharmony_ci fprintf(stderr, "BUG: unknown filter op %d!\n", f->op); 193362306a36Sopenharmony_ci return false; 193462306a36Sopenharmony_ci} 193562306a36Sopenharmony_ci 193662306a36Sopenharmony_cistatic bool should_output_stats(const struct verif_stats *stats) 193762306a36Sopenharmony_ci{ 193862306a36Sopenharmony_ci struct filter *f; 193962306a36Sopenharmony_ci int i, allow_cnt = 0; 194062306a36Sopenharmony_ci 194162306a36Sopenharmony_ci for (i = 0; i < env.deny_filter_cnt; i++) { 194262306a36Sopenharmony_ci f = &env.deny_filters[i]; 194362306a36Sopenharmony_ci if (f->kind != FILTER_STAT) 194462306a36Sopenharmony_ci continue; 194562306a36Sopenharmony_ci 194662306a36Sopenharmony_ci if (is_stat_filter_matched(f, stats)) 194762306a36Sopenharmony_ci return false; 194862306a36Sopenharmony_ci } 194962306a36Sopenharmony_ci 195062306a36Sopenharmony_ci for (i = 0; i < env.allow_filter_cnt; i++) { 195162306a36Sopenharmony_ci f = &env.allow_filters[i]; 195262306a36Sopenharmony_ci if (f->kind != FILTER_STAT) 195362306a36Sopenharmony_ci continue; 195462306a36Sopenharmony_ci allow_cnt++; 195562306a36Sopenharmony_ci 195662306a36Sopenharmony_ci if (is_stat_filter_matched(f, stats)) 195762306a36Sopenharmony_ci return true; 195862306a36Sopenharmony_ci } 195962306a36Sopenharmony_ci 196062306a36Sopenharmony_ci /* if there are no stat allowed filters, pass everything through */ 196162306a36Sopenharmony_ci return allow_cnt == 0; 196262306a36Sopenharmony_ci} 196362306a36Sopenharmony_ci 196462306a36Sopenharmony_cistatic void output_prog_stats(void) 196562306a36Sopenharmony_ci{ 196662306a36Sopenharmony_ci const struct verif_stats *stats; 196762306a36Sopenharmony_ci int i, last_stat_idx = 0; 196862306a36Sopenharmony_ci 196962306a36Sopenharmony_ci if (env.out_fmt == RESFMT_TABLE) { 197062306a36Sopenharmony_ci /* calculate column widths */ 197162306a36Sopenharmony_ci output_headers(RESFMT_TABLE_CALCLEN); 197262306a36Sopenharmony_ci for (i = 0; i < env.prog_stat_cnt; i++) { 197362306a36Sopenharmony_ci stats = &env.prog_stats[i]; 197462306a36Sopenharmony_ci if (!should_output_stats(stats)) 197562306a36Sopenharmony_ci continue; 197662306a36Sopenharmony_ci output_stats(stats, RESFMT_TABLE_CALCLEN, false); 197762306a36Sopenharmony_ci last_stat_idx = i; 197862306a36Sopenharmony_ci } 197962306a36Sopenharmony_ci } 198062306a36Sopenharmony_ci 198162306a36Sopenharmony_ci /* actually output the table */ 198262306a36Sopenharmony_ci output_headers(env.out_fmt); 198362306a36Sopenharmony_ci for (i = 0; i < env.prog_stat_cnt; i++) { 198462306a36Sopenharmony_ci stats = &env.prog_stats[i]; 198562306a36Sopenharmony_ci if (!should_output_stats(stats)) 198662306a36Sopenharmony_ci continue; 198762306a36Sopenharmony_ci output_stats(stats, env.out_fmt, i == last_stat_idx); 198862306a36Sopenharmony_ci } 198962306a36Sopenharmony_ci} 199062306a36Sopenharmony_ci 199162306a36Sopenharmony_cistatic int handle_verif_mode(void) 199262306a36Sopenharmony_ci{ 199362306a36Sopenharmony_ci int i, err; 199462306a36Sopenharmony_ci 199562306a36Sopenharmony_ci if (env.filename_cnt == 0) { 199662306a36Sopenharmony_ci fprintf(stderr, "Please provide path to BPF object file!\n\n"); 199762306a36Sopenharmony_ci argp_help(&argp, stderr, ARGP_HELP_USAGE, "veristat"); 199862306a36Sopenharmony_ci return -EINVAL; 199962306a36Sopenharmony_ci } 200062306a36Sopenharmony_ci 200162306a36Sopenharmony_ci for (i = 0; i < env.filename_cnt; i++) { 200262306a36Sopenharmony_ci err = process_obj(env.filenames[i]); 200362306a36Sopenharmony_ci if (err) { 200462306a36Sopenharmony_ci fprintf(stderr, "Failed to process '%s': %d\n", env.filenames[i], err); 200562306a36Sopenharmony_ci return err; 200662306a36Sopenharmony_ci } 200762306a36Sopenharmony_ci } 200862306a36Sopenharmony_ci 200962306a36Sopenharmony_ci qsort(env.prog_stats, env.prog_stat_cnt, sizeof(*env.prog_stats), cmp_prog_stats); 201062306a36Sopenharmony_ci 201162306a36Sopenharmony_ci output_prog_stats(); 201262306a36Sopenharmony_ci 201362306a36Sopenharmony_ci return 0; 201462306a36Sopenharmony_ci} 201562306a36Sopenharmony_ci 201662306a36Sopenharmony_cistatic int handle_replay_mode(void) 201762306a36Sopenharmony_ci{ 201862306a36Sopenharmony_ci struct stat_specs specs = {}; 201962306a36Sopenharmony_ci int err; 202062306a36Sopenharmony_ci 202162306a36Sopenharmony_ci if (env.filename_cnt != 1) { 202262306a36Sopenharmony_ci fprintf(stderr, "Replay mode expects exactly one input CSV file!\n\n"); 202362306a36Sopenharmony_ci argp_help(&argp, stderr, ARGP_HELP_USAGE, "veristat"); 202462306a36Sopenharmony_ci return -EINVAL; 202562306a36Sopenharmony_ci } 202662306a36Sopenharmony_ci 202762306a36Sopenharmony_ci err = parse_stats_csv(env.filenames[0], &specs, 202862306a36Sopenharmony_ci &env.prog_stats, &env.prog_stat_cnt); 202962306a36Sopenharmony_ci if (err) { 203062306a36Sopenharmony_ci fprintf(stderr, "Failed to parse stats from '%s': %d\n", env.filenames[0], err); 203162306a36Sopenharmony_ci return err; 203262306a36Sopenharmony_ci } 203362306a36Sopenharmony_ci 203462306a36Sopenharmony_ci qsort(env.prog_stats, env.prog_stat_cnt, sizeof(*env.prog_stats), cmp_prog_stats); 203562306a36Sopenharmony_ci 203662306a36Sopenharmony_ci output_prog_stats(); 203762306a36Sopenharmony_ci 203862306a36Sopenharmony_ci return 0; 203962306a36Sopenharmony_ci} 204062306a36Sopenharmony_ci 204162306a36Sopenharmony_ciint main(int argc, char **argv) 204262306a36Sopenharmony_ci{ 204362306a36Sopenharmony_ci int err = 0, i; 204462306a36Sopenharmony_ci 204562306a36Sopenharmony_ci if (argp_parse(&argp, argc, argv, 0, NULL, NULL)) 204662306a36Sopenharmony_ci return 1; 204762306a36Sopenharmony_ci 204862306a36Sopenharmony_ci if (env.show_version) { 204962306a36Sopenharmony_ci printf("%s\n", argp_program_version); 205062306a36Sopenharmony_ci return 0; 205162306a36Sopenharmony_ci } 205262306a36Sopenharmony_ci 205362306a36Sopenharmony_ci if (env.verbose && env.quiet) { 205462306a36Sopenharmony_ci fprintf(stderr, "Verbose and quiet modes are incompatible, please specify just one or neither!\n\n"); 205562306a36Sopenharmony_ci argp_help(&argp, stderr, ARGP_HELP_USAGE, "veristat"); 205662306a36Sopenharmony_ci return 1; 205762306a36Sopenharmony_ci } 205862306a36Sopenharmony_ci if (env.verbose && env.log_level == 0) 205962306a36Sopenharmony_ci env.log_level = 1; 206062306a36Sopenharmony_ci 206162306a36Sopenharmony_ci if (env.output_spec.spec_cnt == 0) { 206262306a36Sopenharmony_ci if (env.out_fmt == RESFMT_CSV) 206362306a36Sopenharmony_ci env.output_spec = default_csv_output_spec; 206462306a36Sopenharmony_ci else 206562306a36Sopenharmony_ci env.output_spec = default_output_spec; 206662306a36Sopenharmony_ci } 206762306a36Sopenharmony_ci if (env.sort_spec.spec_cnt == 0) 206862306a36Sopenharmony_ci env.sort_spec = default_sort_spec; 206962306a36Sopenharmony_ci 207062306a36Sopenharmony_ci if (env.comparison_mode && env.replay_mode) { 207162306a36Sopenharmony_ci fprintf(stderr, "Can't specify replay and comparison mode at the same time!\n\n"); 207262306a36Sopenharmony_ci argp_help(&argp, stderr, ARGP_HELP_USAGE, "veristat"); 207362306a36Sopenharmony_ci return 1; 207462306a36Sopenharmony_ci } 207562306a36Sopenharmony_ci 207662306a36Sopenharmony_ci if (env.comparison_mode) 207762306a36Sopenharmony_ci err = handle_comparison_mode(); 207862306a36Sopenharmony_ci else if (env.replay_mode) 207962306a36Sopenharmony_ci err = handle_replay_mode(); 208062306a36Sopenharmony_ci else 208162306a36Sopenharmony_ci err = handle_verif_mode(); 208262306a36Sopenharmony_ci 208362306a36Sopenharmony_ci free_verif_stats(env.prog_stats, env.prog_stat_cnt); 208462306a36Sopenharmony_ci free_verif_stats(env.baseline_stats, env.baseline_stat_cnt); 208562306a36Sopenharmony_ci free(env.join_stats); 208662306a36Sopenharmony_ci for (i = 0; i < env.filename_cnt; i++) 208762306a36Sopenharmony_ci free(env.filenames[i]); 208862306a36Sopenharmony_ci free(env.filenames); 208962306a36Sopenharmony_ci for (i = 0; i < env.allow_filter_cnt; i++) { 209062306a36Sopenharmony_ci free(env.allow_filters[i].any_glob); 209162306a36Sopenharmony_ci free(env.allow_filters[i].file_glob); 209262306a36Sopenharmony_ci free(env.allow_filters[i].prog_glob); 209362306a36Sopenharmony_ci } 209462306a36Sopenharmony_ci free(env.allow_filters); 209562306a36Sopenharmony_ci for (i = 0; i < env.deny_filter_cnt; i++) { 209662306a36Sopenharmony_ci free(env.deny_filters[i].any_glob); 209762306a36Sopenharmony_ci free(env.deny_filters[i].file_glob); 209862306a36Sopenharmony_ci free(env.deny_filters[i].prog_glob); 209962306a36Sopenharmony_ci } 210062306a36Sopenharmony_ci free(env.deny_filters); 210162306a36Sopenharmony_ci return -err; 210262306a36Sopenharmony_ci} 2103