veristat.c 54 KB

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