From: Mykyta Yatsenko <mykyta.yatsenko5@gmail.com>
To: Emil Tsalapatis <emil@etsalapatis.com>,
bpf@vger.kernel.org, ast@kernel.org, andrii@kernel.org,
daniel@iogearbox.net, kafai@meta.com, kernel-team@meta.com,
eddyz87@gmail.com
Cc: Mykyta Yatsenko <yatsenko@meta.com>
Subject: Re: [PATCH bpf-next v2 2/2] selftests/bpf: Use bpf_program__clone() in veristat
Date: Mon, 23 Feb 2026 18:39:37 +0000 [thread overview]
Message-ID: <874in7cniu.fsf@gmail.com> (raw)
In-Reply-To: <DGMJ4DNC556M.16VOPU824MANQ@etsalapatis.com>
"Emil Tsalapatis" <emil@etsalapatis.com> writes:
> On Fri Feb 20, 2026 at 2:18 PM EST, Mykyta Yatsenko wrote:
>> From: Mykyta Yatsenko <yatsenko@meta.com>
>>
>> Replace veristat's per-program object re-opening with
>> bpf_program__clone().
>>
>> Previously, veristat opened a separate bpf_object for every program in a
>> multi-program object file, iterated all programs to enable only the
>> target one, and then loaded the entire object.
>>
>> Use bpf_object__prepare() once, then call bpf_program__clone() for each
>> program individually. This lets veristat load programs one at a time
>> from a single prepared object.
>>
>> The caller now owns the returned fd and closes it after collecting stats.
>> Remove the special single-program fast path and the per-file early exit
>> in handle_verif_mode() so all files are always processed.
>>
>> Split fixup_obj() into fixup_obj_maps() for object-wide map fixups that
>> must run before bpf_object__prepare(), and fixup_obj() for per-program
>> fixups (struct_ops masking, freplace type guessing) that run before each
>> bpf_program__clone() call.
>>
>> Signed-off-by: Mykyta Yatsenko <yatsenko@meta.com>
>> ---
>> tools/testing/selftests/bpf/veristat.c | 104 ++++++++++++++-------------------
>> 1 file changed, 44 insertions(+), 60 deletions(-)
>>
>> diff --git a/tools/testing/selftests/bpf/veristat.c b/tools/testing/selftests/bpf/veristat.c
>> index 1be1e353d40a..7d7eb56e04d0 100644
>> --- a/tools/testing/selftests/bpf/veristat.c
>> +++ b/tools/testing/selftests/bpf/veristat.c
>> @@ -1236,7 +1236,7 @@ static void mask_unrelated_struct_ops_progs(struct bpf_object *obj,
>> }
>> }
>>
>> -static void fixup_obj(struct bpf_object *obj, struct bpf_program *prog, const char *filename)
>> +static void fixup_obj_maps(struct bpf_object *obj)
>> {
>> struct bpf_map *map;
>>
>> @@ -1251,15 +1251,23 @@ static void fixup_obj(struct bpf_object *obj, struct bpf_program *prog, const ch
>> case BPF_MAP_TYPE_INODE_STORAGE:
>> case BPF_MAP_TYPE_CGROUP_STORAGE:
>> case BPF_MAP_TYPE_CGRP_STORAGE:
>> - break;
>> case BPF_MAP_TYPE_STRUCT_OPS:
>> - mask_unrelated_struct_ops_progs(obj, map, prog);
>> break;
>> default:
>> if (bpf_map__max_entries(map) == 0)
>> bpf_map__set_max_entries(map, 1);
>> }
>> }
>> +}
>> +
>> +static void fixup_obj(struct bpf_object *obj, struct bpf_program *prog, const char *filename)
>> +{
>> + struct bpf_map *map;
>> +
>> + bpf_object__for_each_map(map, obj) {
>> + if (bpf_map__type(map) == BPF_MAP_TYPE_STRUCT_OPS)
>> + mask_unrelated_struct_ops_progs(obj, map, prog);
>> + }
>>
>> /* SEC(freplace) programs can't be loaded with veristat as is,
>> * but we can try guessing their target program's expected type by
>> @@ -1608,6 +1616,7 @@ static int process_prog(const char *filename, struct bpf_object *obj, struct bpf
>> const char *base_filename = basename(strdupa(filename));
>> const char *prog_name = bpf_program__name(prog);
>> long mem_peak_a, mem_peak_b, mem_peak = -1;
>> + LIBBPF_OPTS(bpf_prog_load_opts, opts);
>> char *buf;
>> int buf_sz, log_level;
>> struct verif_stats *stats;
>> @@ -1647,9 +1656,6 @@ static int process_prog(const char *filename, struct bpf_object *obj, struct bpf
>> }
>> verif_log_buf[0] = '\0';
>>
>> - bpf_program__set_log_buf(prog, buf, buf_sz);
>> - bpf_program__set_log_level(prog, log_level);
>> -
>> /* increase chances of successful BPF object loading */
>> fixup_obj(obj, prog, base_filename);
>>
>> @@ -1658,15 +1664,21 @@ static int process_prog(const char *filename, struct bpf_object *obj, struct bpf
>> if (env.force_reg_invariants)
>> bpf_program__set_flags(prog, bpf_program__flags(prog) | BPF_F_TEST_REG_INVARIANTS);
>>
>> - err = bpf_object__prepare(obj);
>> - if (!err) {
>> - cgroup_err = reset_stat_cgroup();
>> - mem_peak_a = cgroup_memory_peak();
>> - err = bpf_object__load(obj);
>> - mem_peak_b = cgroup_memory_peak();
>> - if (!cgroup_err && mem_peak_a >= 0 && mem_peak_b >= 0)
>> - mem_peak = mem_peak_b - mem_peak_a;
>> + opts.log_buf = buf;
>> + opts.log_size = buf_sz;
>> + opts.log_level = log_level;
>> +
>> + cgroup_err = reset_stat_cgroup();
>> + mem_peak_a = cgroup_memory_peak();
>> + fd = bpf_program__clone(prog, &opts);
>> + if (fd < 0) {
>> + err = fd;
>> + fprintf(stderr, "Failed to load program %s %d\n", prog_name, err);
>> }
>> + mem_peak_b = cgroup_memory_peak();
>> + if (!cgroup_err && mem_peak_a >= 0 && mem_peak_b >= 0)
>> + mem_peak = mem_peak_b - mem_peak_a;
>> +
>
> AFAICT the meaning of mem_peak changes with this patch, right? Before it was the diff between
> prepare() and load(), with the patch as it is now it is max(prepare,
> load) since mem_peak_a is reset right above. If we do not automatically
> load OBJ_PREPARED programs (see the review for patch 1/2) we can keep
> the old behavior by storing mem_peak_a after the first prepare and
> reading mem_peak_b after clone+load.
>
I'm not sure if I understand why the meaning of mem_peak changed. Before
it was:
bpf_object__prepare()
peak_reset()
a = mem_peak()
bpf_object__load();
mem_peak = mem_peak() - a;
and now:
bpf_object__prepare()
...
peak_reset()
a = mem_peak()
bpf_program__clone();
mem_peak = mem_peak() - a;
I understand this code is supposed to measure the memory overhead for
the loading a single program.
Could you please elaborate what the issue is? Maybe some example?
>> env.progs_processed++;
>>
>> stats->file_name = strdup(base_filename);
>> @@ -1678,7 +1690,6 @@ static int process_prog(const char *filename, struct bpf_object *obj, struct bpf
>> stats->stats[MEMORY_PEAK] = mem_peak < 0 ? -1 : mem_peak / (1024 * 1024);
>>
>> memset(&info, 0, info_len);
>> - fd = bpf_program__fd(prog);
>> if (fd > 0 && bpf_prog_get_info_by_fd(fd, &info, &info_len) == 0) {
>> stats->stats[JITED_SIZE] = info.jited_prog_len;
>> if (env.dump_mode & DUMP_JITED)
>> @@ -1699,7 +1710,8 @@ static int process_prog(const char *filename, struct bpf_object *obj, struct bpf
>>
>> if (verif_log_buf != buf)
>> free(buf);
>> -
>> + if (fd > 0)
>> + close(fd);
>> return 0;
>> }
>>
>> @@ -2182,8 +2194,8 @@ static int set_global_vars(struct bpf_object *obj, struct var_preset *presets, i
>> static int process_obj(const char *filename)
>> {
>> const char *base_filename = basename(strdupa(filename));
>> - struct bpf_object *obj = NULL, *tobj;
>> - struct bpf_program *prog, *tprog, *lprog;
>> + struct bpf_object *obj = NULL;
>> + struct bpf_program *prog;
>> libbpf_print_fn_t old_libbpf_print_fn;
>> LIBBPF_OPTS(bpf_object_open_opts, opts);
>> int err = 0, prog_cnt = 0;
>> @@ -2222,51 +2234,26 @@ static int process_obj(const char *filename)
>> env.files_processed++;
>>
>> bpf_object__for_each_program(prog, obj) {
>> + bpf_program__set_autoload(prog, true);
>> prog_cnt++;
>> }
>>
>> - if (prog_cnt == 1) {
>> - prog = bpf_object__next_program(obj, NULL);
>> - bpf_program__set_autoload(prog, true);
>> - err = set_global_vars(obj, env.presets, env.npresets);
>> - if (err) {
>> - fprintf(stderr, "Failed to set global variables %d\n", err);
>> - goto cleanup;
>> - }
>> - process_prog(filename, obj, prog);
>> + fixup_obj_maps(obj);
>> +
>> + err = set_global_vars(obj, env.presets, env.npresets);
>> + if (err) {
>> + fprintf(stderr, "Failed to set global variables %d\n", err);
>> 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;
>> - }
>> -
>> - err = set_global_vars(tobj, env.presets, env.npresets);
>> - if (err) {
>> - fprintf(stderr, "Failed to set global variables %d\n", err);
>> - goto cleanup;
>> - }
>> -
>> - lprog = NULL;
>> - 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);
>> - }
>> - }
>> + err = bpf_object__prepare(obj);
>> + if (err) {
>> + fprintf(stderr, "Failed to prepare BPF object for loading %d\n", err);
>> + goto cleanup;
>> + }
>>
>> - process_prog(filename, tobj, lprog);
>> - bpf_object__close(tobj);
>> + bpf_object__for_each_program(prog, obj) {
>> + process_prog(filename, obj, prog);
>> }
>>
>> cleanup:
>> @@ -3264,17 +3251,14 @@ static int handle_verif_mode(void)
>> create_stat_cgroup();
>> for (i = 0; i < env.filename_cnt; i++) {
>> err = process_obj(env.filenames[i]);
>> - if (err) {
>> + if (err)
>> fprintf(stderr, "Failed to process '%s': %d\n", env.filenames[i], err);
>> - goto out;
>> - }
>
> This means we now do not stop on the first failure - this I assume is
> deliberate since it is an improvement over existing behavior imo, but
> still pointing it out in case it isn't.
>
>> }
>>
>> qsort(env.prog_stats, env.prog_stat_cnt, sizeof(*env.prog_stats), cmp_prog_stats);
>>
>> output_prog_stats();
>>
>> -out:
>> destroy_stat_cgroup();
>> return err;
>> }
next prev parent reply other threads:[~2026-02-23 18:39 UTC|newest]
Thread overview: 25+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-02-20 19:18 [PATCH bpf-next v2 0/2] libbpf: Add bpf_program__clone() for individual program loading Mykyta Yatsenko
2026-02-20 19:18 ` [PATCH bpf-next v2 1/2] libbpf: Introduce bpf_program__clone() Mykyta Yatsenko
2026-02-23 17:25 ` Emil Tsalapatis
2026-02-23 17:59 ` Mykyta Yatsenko
2026-02-23 18:04 ` Emil Tsalapatis
2026-02-24 19:28 ` Eduard Zingerman
2026-02-24 19:32 ` Eduard Zingerman
2026-02-24 20:47 ` Mykyta Yatsenko
2026-03-06 17:22 ` [External] " Andrey Grodzovsky
2026-03-10 0:08 ` Mykyta Yatsenko
2026-03-11 13:35 ` Andrey Grodzovsky
2026-03-11 22:52 ` Andrii Nakryiko
2026-03-16 14:23 ` Andrey Grodzovsky
2026-03-11 23:03 ` Andrii Nakryiko
2026-02-20 19:18 ` [PATCH bpf-next v2 2/2] selftests/bpf: Use bpf_program__clone() in veristat Mykyta Yatsenko
2026-02-23 17:49 ` Emil Tsalapatis
2026-02-23 18:39 ` Mykyta Yatsenko [this message]
2026-02-23 18:54 ` Emil Tsalapatis
2026-02-24 2:03 ` Eduard Zingerman
2026-02-24 12:20 ` Mykyta Yatsenko
2026-02-24 19:08 ` Eduard Zingerman
2026-02-24 19:12 ` Mykyta Yatsenko
2026-02-24 19:16 ` Eduard Zingerman
2026-02-20 22:48 ` [PATCH bpf-next v2 0/2] libbpf: Add bpf_program__clone() for individual program loading Alexei Starovoitov
2026-02-23 13:57 ` Mykyta Yatsenko
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=874in7cniu.fsf@gmail.com \
--to=mykyta.yatsenko5@gmail.com \
--cc=andrii@kernel.org \
--cc=ast@kernel.org \
--cc=bpf@vger.kernel.org \
--cc=daniel@iogearbox.net \
--cc=eddyz87@gmail.com \
--cc=emil@etsalapatis.com \
--cc=kafai@meta.com \
--cc=kernel-team@meta.com \
--cc=yatsenko@meta.com \
/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