From: Kui-Feng Lee <kuifeng@fb.com>
To: "daniel@iogearbox.net" <daniel@iogearbox.net>,
"ast@kernel.org" <ast@kernel.org>,
"andrii@kernel.org" <andrii@kernel.org>,
"bpf@vger.kernel.org" <bpf@vger.kernel.org>
Cc: Kernel Team <Kernel-team@fb.com>
Subject: Re: [PATCH bpf-next 3/3] selftests/bpf: add veristat tool for mass-verifying BPF object files
Date: Mon, 29 Aug 2022 06:02:04 +0000 [thread overview]
Message-ID: <937c74badbd88d0ffd25c8204db74797eebf2b47.camel@fb.com> (raw)
In-Reply-To: <20220826231531.1031943-4-andrii@kernel.org>
On Fri, 2022-08-26 at 16:15 -0700, Andrii Nakryiko wrote:
> Add a small tool, veristat, that allows mass-verification of
> a set of *libbpf-compatible* BPF ELF object files. For each such
> object
> file, veristat will attempt to verify each BPF program
> *individually*.
> Regardless of success or failure, it parses BPF verifier stats and
> outputs them in human-readable table format. In the future we can
> also
> add CSV and JSON output for more scriptable post-processing, if
> necessary.
>
> veristat allows to specify a set of stats that should be output and
> ordering between multiple objects and files (e.g., so that one can
> easily order by total instructions processed, instead of default file
> name, prog name, verdict, total instructions order).
>
> This tool should be useful for validating various BPF verifier
> changes
> or even validating different kernel versions for regressions.
>
> Here's an example for some of the heaviest selftests/bpf BPF object
> files:
>
> $ sudo ./veristat -s insns,file,prog
> {pyperf,loop,test_verif_scale,strobemeta,test_cls_redirect,profiler}*
> .linked3.o
> File
> Program Verdict Duration, us Total
> insns Total states Peak states
> ------------------------------------ -----------------------------
> ------- ------- ------------ ----------- ------------ ----------
> -
> loop3.linked3.o
> while_true failure 350990
> 1000001 9663 9663
> test_verif_scale3.linked3.o
> balancer_ingress success 115244
> 845499 8636 2141
> test_verif_scale2.linked3.o
> balancer_ingress success 77688
> 773445 3048 788
> pyperf600.linked3.o
> on_event success 2079872
> 624585 30335 30241
> pyperf600_nounroll.linked3.o
> on_event success 353972
> 568128 37101 2115
> strobemeta.linked3.o
> on_event success 455230
> 557149 15915 13537
> test_verif_scale1.linked3.o
> balancer_ingress success 89880
> 554754 8636 2141
> strobemeta_nounroll2.linked3.o
> on_event success 433906
> 501725 17087 1912
> loop6.linked3.o
> trace_virtqueue_add_sgs success 282205
> 398057 8717 919
> loop1.linked3.o
> nested_loops success 125630
> 361349 5504 5504
> pyperf180.linked3.o
> on_event success 2511740
> 160398 11470 11446
> pyperf100.linked3.o
> on_event success 744329
> 87681 6213 6191
> test_cls_redirect.linked3.o
> cls_redirect success 54087
> 78925 4782 903
> strobemeta_subprogs.linked3.o
> on_event success 57898
> 65420 1954 403
> test_cls_redirect_subprogs.linked3.o
> cls_redirect success 54522
> 64965 4619 958
> strobemeta_nounroll1.linked3.o
> on_event success 43313
> 57240 1757 382
> pyperf50.linked3.o
> on_event success 194355
> 46378 3263 3241
> profiler2.linked3.o
> tracepoint__syscalls__sys_enter_kill success 23869
> 43372 1423 542
> pyperf_subprogs.linked3.o
> on_event success 29179
> 36358 2499 2499
> profiler1.linked3.o
> tracepoint__syscalls__sys_enter_kill success 13052
> 27036 1946 936
> profiler3.linked3.o
> tracepoint__syscalls__sys_enter_kill success 21023
> 26016 2186 915
> profiler2.linked3.o
> kprobe__vfs_link success 5255
> 13896 303 271
> profiler1.linked3.o
> kprobe__vfs_link success 7792
> 12687 1042 1041
> profiler3.linked3.o
> kprobe__vfs_link success 7332
> 10601 865 865
> profiler2.linked3.o
> kprobe_ret__do_filp_open success 3417
> 8900 216 199
> profiler2.linked3.o
> kprobe__vfs_symlink success 3548
> 8775 203 186
> pyperf_global.linked3.o
> on_event success 10007
> 7563 520 520
> profiler3.linked3.o
> kprobe_ret__do_filp_open success 4708
> 6464 532 532
> profiler1.linked3.o
> kprobe_ret__do_filp_open success 3090
> 6445 508 508
> profiler3.linked3.o
> kprobe__vfs_symlink success 4477
> 6358 521 521
> profiler1.linked3.o
> kprobe__vfs_symlink success 3381
> 6347 507 507
> profiler2.linked3.o
> raw_tracepoint__sched_process_exec success 2464
> 5874 292 189
> profiler3.linked3.o
> raw_tracepoint__sched_process_exec success 2677
> 4363 397 283
> profiler2.linked3.o
> kprobe__proc_sys_write success 1800
> 4355 143 138
> profiler1.linked3.o
> raw_tracepoint__sched_process_exec success 1649
> 4019 333 240
> pyperf600_bpf_loop.linked3.o
> on_event success 2711
> 3966 306 306
> profiler2.linked3.o
> raw_tracepoint__sched_process_exit success 1234
> 3138 83 66
> profiler3.linked3.o
> kprobe__proc_sys_write success 1755
> 2623 223 223
> profiler1.linked3.o
> kprobe__proc_sys_write success 1222
> 2456 193 193
> loop2.linked3.o
> while_true success 608
> 1783 57 30
> profiler3.linked3.o
> raw_tracepoint__sched_process_exit success 789
> 1680 146 146
> profiler1.linked3.o
> raw_tracepoint__sched_process_exit success 592
> 1526 133 133
> strobemeta_bpf_loop.linked3.o
> on_event success 1015
> 1512 106 106
> loop4.linked3.o
> combinations success 165
> 524 18 17
> profiler3.linked3.o
> raw_tracepoint__sched_process_fork success 196
> 299 25 25
> profiler1.linked3.o
> raw_tracepoint__sched_process_fork success 109
> 265 19 19
> profiler2.linked3.o
> raw_tracepoint__sched_process_fork success 111
> 265 19 19
> loop5.linked3.o
> while_true success 47
> 84 9 9
> ------------------------------------ -----------------------------
> ------- ------- ------------ ----------- ------------ ----------
> -
>
> Signed-off-by: Andrii Nakryiko <andrii@kernel.org>
> ---
> tools/testing/selftests/bpf/.gitignore | 1 +
> tools/testing/selftests/bpf/Makefile | 6 +-
> tools/testing/selftests/bpf/veristat.c | 541
> +++++++++++++++++++++++++
> 3 files changed, 547 insertions(+), 1 deletion(-)
> create mode 100644 tools/testing/selftests/bpf/veristat.c
>
> diff --git a/tools/testing/selftests/bpf/.gitignore
> b/tools/testing/selftests/bpf/.gitignore
> index 3a8cb2404ea6..3b288562963e 100644
> --- a/tools/testing/selftests/bpf/.gitignore
> +++ b/tools/testing/selftests/bpf/.gitignore
> @@ -39,6 +39,7 @@ test_cpp
> /tools
> /runqslower
> /bench
> +/veristat
> *.ko
> *.tmp
> xskxceiver
[...]
> +
> +static int process_prog(const char *filename, struct bpf_object
> *obj, struct bpf_program *prog)
> +{
> + const char *prog_name = bpf_program__name(prog);
> + size_t buf_sz = sizeof(verif_log_buf);
> + char *buf = verif_log_buf;
> + struct verif_stats *stats;
> + int err = 0;
> + void *tmp;
> +
> + tmp = realloc(env.prog_stats, (env.prog_stat_cnt + 1) *
> sizeof(*env.prog_stats));
> + if (!tmp)
> + return -ENOMEM;
> + env.prog_stats = tmp;
> + stats = &env.prog_stats[env.prog_stat_cnt++];
> + memset(stats, 0, sizeof(*stats));
> +
> + if (env.verbose) {
> + buf_sz = 16 * 1024 * 1024;
> + buf = malloc(buf_sz);
> + if (!buf)
> + return -ENOMEM;
> + bpf_program__set_log_buf(prog, buf, buf_sz);
> + bpf_program__set_log_level(prog, 1 | 4); /* stats +
> log */
> + } else {
> + bpf_program__set_log_buf(prog, buf, buf_sz);
> + bpf_program__set_log_level(prog, 4); /* only verifier
> stats */
> + }
> + verif_log_buf[0] = '\0';
> +
> + err = bpf_object__load(obj);
> +
> + stats->file_name = strdup(basename(filename));
> + stats->prog_name = strdup(bpf_program__name(prog));
> + stats->stats[VERDICT] = err == 0; /* 1 - success, 0 - failure
> */
> + parse_verif_log(buf, buf_sz, stats);
> +
> + if (env.verbose) {
> + printf("PROCESSING %s/%s, DURATION US: %ld, VERDICT:
> %s, VERIFIER LOG:\n%s\n",
> + filename, prog_name, stats->stats[DURATION],
> + err ? "failure" : "success", buf);
> + }
> +
> + if (verif_log_buf != buf)
> + free(buf);
> +
> + return 0;
> +};
> +
> +static int process_obj(const char *filename)
> +{
> + struct bpf_object *obj = NULL, *tobj;
> + struct bpf_program *prog, *tprog, *lprog;
> + libbpf_print_fn_t old_libbpf_print_fn;
> + LIBBPF_OPTS(bpf_object_open_opts, opts);
> + int err = 0, prog_cnt = 0;
> +
> + old_libbpf_print_fn = libbpf_set_print(libbpf_print_fn);
> +
> + obj = bpf_object__open_file(filename, &opts);
> + if (!obj) {
> + err = -errno;
> + fprintf(stderr, "Failed to open '%s': %d\n",
> filename, err);
> + goto cleanup;
> + }
> +
> + bpf_object__for_each_program(prog, obj) {
> + prog_cnt++;
> + }
> +
> + if (prog_cnt == 1) {
> + prog = bpf_object__next_program(obj, NULL);
> + bpf_program__set_autoload(prog, true);
> + process_prog(filename, obj, prog);
> + bpf_object__close(obj);
> + goto cleanup;
> + }
> +
> + bpf_object__for_each_program(prog, obj) {
> + const char *prog_name = bpf_program__name(prog);
> +
> + tobj = bpf_object__open_file(filename, &opts);
> + if (!tobj) {
> + err = -errno;
> + fprintf(stderr, "Failed to open '%s': %d\n",
> filename, err);
> + goto cleanup;
> + }
> +
> + bpf_object__for_each_program(tprog, tobj) {
> + const char *tprog_name =
> bpf_program__name(tprog);
> +
> + if (strcmp(prog_name, tprog_name) == 0) {
> + bpf_program__set_autoload(tprog,
> true);
> + lprog = tprog;
> + } else {
> + bpf_program__set_autoload(tprog,
> false);
> + }
> + }
> +
> + process_prog(filename, tobj, lprog);
> + bpf_object__close(tobj);
> + }
It leaks obj.
> > +
> +cleanup:
> + libbpf_set_print(old_libbpf_print_fn);
> + return err;
> +}
> +
> +static int cmp_stat(const struct verif_stats *s1, const struct
> verif_stats *s2,
> + enum stat_id id, bool asc)
> +{
> + int cmp = 0;
> +
> + switch (id) {
> + case FILE_NAME:
> + cmp = strcmp(s1->file_name, s2->file_name);
> + break;
> + case PROG_NAME:
> + cmp = strcmp(s1->prog_name, s2->prog_name);
> + break;
> + case VERDICT:
> + case DURATION:
> + case TOTAL_INSNS:
> + case TOTAL_STATES:
> + case PEAK_STATES:
> + case MAX_STATES_PER_INSN:
> + case MARK_READ_MAX_LEN: {
> + long v1 = s1->stats[id];
> + long v2 = s2->stats[id];
> +
> + if (v1 != v2)
> + cmp = v1 < v2 ? -1 : 1;
> + break;
> + }
> + default:
> + fprintf(stderr, "Unrecognized stat #%d\n", id);
> + exit(1);
> + }
> +
> + return asc ? cmp : -cmp;
> +}
> +
> +static int cmp_prog_stats(const void *v1, const void *v2)
> +{
> + const struct verif_stats *s1 = v1, *s2 = v2;
> + int i, cmp;
> +
> + for (i = 0; i < env.sort_spec.spec_cnt; i++) {
> + cmp = cmp_stat(s1, s2, env.sort_spec.ids[i],
> env.sort_spec.asc[i]);
> + if (cmp != 0)
> + return cmp;
> + }
> +
> + return 0;
> +}
> +
> +#define snappendf(dst, len, fmt,
> args...) \
> + len += snprintf(dst +
> len, \
> + sizeof(dst) < len ? 0 : sizeof(dst) -
> len, \
> + fmt, ##args)
Never been used?
> +
> +#define HEADER_CHAR '-'
> +#define COLUMN_SEP " "
> +
> +static void output_headers(bool calc_len)
> +{
> + int i, len;
> +
> + for (i = 0; i < env.output_spec.spec_cnt; i++) {
> + int id = env.output_spec.ids[i];
> + int *max_len = &env.output_spec.lens[i];
> +
> + if (calc_len) {
> + len = snprintf(NULL, 0, "%s",
> stat_defs[id].header);
Is there any reason to no use strlen()?
It would be ok to merge this block to one line since this function is
always called exactly once before output_stats() with calc_len being
true. For example,
*max_len = strlen(...)
> + if (len > *max_len)
> + *max_len = len;
> + } else {
> + printf("%s%-*s", i == 0 ? "" : COLUMN_SEP,
> *max_len, stat_defs[id].header);
> + }
> + }
> +
> + if (!calc_len)
> + printf("\n");
> +}
> +
> +static void output_lines(void)
lines? It is confusing for me since it always prints exactly one line.
How about something like output_sep_line()?
> +{
> + int i, j, len;
> +
> + for (i = 0; i < env.output_spec.spec_cnt; i++) {
> + len = env.output_spec.lens[i];
> +
> + printf("%s", i == 0 ? "" : COLUMN_SEP);
> + for (j = 0; j < len; j++)
> + printf("%c", HEADER_CHAR);
> + }
> + printf("\n");
> +}
> +
> +static void output_stats(const struct verif_stats *s, bool calc_len)
> +{
> + int i;
> +
> + for (i = 0; i < env.output_spec.spec_cnt; i++) {
> + int id = env.output_spec.ids[i];
> + int *max_len = &env.output_spec.lens[i], len;
> + const char *str = NULL;
> + long val = 0;
> +
> + switch (id) {
> + case FILE_NAME:
> + str = s->file_name;
> + break;
> + case PROG_NAME:
> + str = s->prog_name;
> + break;
> + case VERDICT:
> + str = s->stats[VERDICT] ? "success" :
> "failure";
> + break;
> + case DURATION:
> + case TOTAL_INSNS:
> + case TOTAL_STATES:
> + case PEAK_STATES:
> + case MAX_STATES_PER_INSN:
> + case MARK_READ_MAX_LEN:
> + val = s->stats[id];
> + break;
> + default:
> + fprintf(stderr, "Unrecognized stat #%d\n",
> id);
> + exit(1);
> + }
> +
> + if (calc_len) {
> + if (str)
> + len = snprintf(NULL, 0, "%s", str);
> + else
> + len = snprintf(NULL, 0, "%ld", val);
> + if (len > *max_len)
> + *max_len = len;
> + } else {
> + if (str)
> + printf("%s%-*s", i == 0 ? "" :
> COLUMN_SEP, *max_len, str);
> + else
> + printf("%s%*ld", i == 0 ? "" :
> COLUMN_SEP, *max_len, val);
> + }
> + }
> +
> + if (!calc_len)
> + printf("\n");
> +}
> +
> +int main(int argc, char **argv)
> +{
> + static const struct argp argp = {
> + .options = opts,
> + .parser = parse_arg,
> + .doc = argp_program_doc,
> + };
> + int err = 0, i;
> +
> + if (argp_parse(&argp, argc, argv, 0, NULL, NULL))
> + return 1;
> +
> + if (env.filename_cnt == 0) {
> + fprintf(stderr, "Please provide path to BPF object
> file!\n");
> + argp_help(&argp, stderr, ARGP_HELP_USAGE,
> "veristat");
> + return 1;
> + }
> +
> + if (env.output_spec.spec_cnt == 0)
> + env.output_spec = default_output_spec;
> + if (env.sort_spec.spec_cnt == 0)
> + env.sort_spec = default_sort_spec;
> +
> + for (i = 0; i < env.filename_cnt; i++) {
> + err = process_obj(env.filenames[i]);
> + if (err) {
> + fprintf(stderr, "Failed to process '%s':
> %d\n", env.filenames[i], err);
> + goto cleanup;
> + }
> + }
> +
> + qsort(env.prog_stats, env.prog_stat_cnt,
> sizeof(*env.prog_stats), cmp_prog_stats);
> +
> + /* calculate column widths */
> + output_headers(true);
> + for (i = 0; i < env.prog_stat_cnt; i++) {
> + output_stats(&env.prog_stats[i], true);
> + }
> +
> + /* actually output the table */
> + output_headers(false);
> + output_lines();
> + for (i = 0; i < env.prog_stat_cnt; i++) {
> + output_stats(&env.prog_stats[i], false);
> + }
> + output_lines();
> + printf("\n");
> +
> + printf("Done. Processed %d object files, %d programs.\n",
> + env.filename_cnt, env.prog_stat_cnt);
> +
> +cleanup:
> + for (i = 0; i < env.prog_stat_cnt; i++) {
> + free(env.prog_stats[i].file_name);
> + free(env.prog_stats[i].prog_name);
> + }
> + free(env.prog_stats);
> + for (i = 0; i < env.filename_cnt; i++)
> + free(env.filenames[i]);
> + free(env.filenames);
> + return -err;
> +}
next prev parent reply other threads:[~2022-08-29 6:02 UTC|newest]
Thread overview: 12+ messages / expand[flat|nested] mbox.gz Atom feed top
2022-08-26 23:15 [PATCH bpf-next 0/3] BPF mass-verification veristat tool Andrii Nakryiko
2022-08-26 23:15 ` [PATCH bpf-next 1/3] selftests/bpf: fix test_verif_scale{1,3} SEC() annotations Andrii Nakryiko
2022-08-27 23:43 ` KP Singh
2022-08-26 23:15 ` [PATCH bpf-next 2/3] libbpf: fix crash if SEC("freplace") programs don't have attach_prog_fd set Andrii Nakryiko
2022-08-27 23:45 ` KP Singh
2022-08-26 23:15 ` [PATCH bpf-next 3/3] selftests/bpf: add veristat tool for mass-verifying BPF object files Andrii Nakryiko
2022-08-27 23:53 ` KP Singh
2022-08-29 14:50 ` Daniel Borkmann
2022-09-09 19:24 ` Andrii Nakryiko
2022-09-09 19:04 ` Andrii Nakryiko
2022-08-29 6:02 ` Kui-Feng Lee [this message]
2022-09-09 19:18 ` Andrii Nakryiko
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=937c74badbd88d0ffd25c8204db74797eebf2b47.camel@fb.com \
--to=kuifeng@fb.com \
--cc=Kernel-team@fb.com \
--cc=andrii@kernel.org \
--cc=ast@kernel.org \
--cc=bpf@vger.kernel.org \
--cc=daniel@iogearbox.net \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox