Skip to content

Commit 23cf4cf

Browse files
jkoong-fbNobody
authored and
Nobody
committed
selftests/bpf: Add tests for bpf_for_each
In this patch - 1) Add a new prog "for_each_helper" which tests the basic functionality of the bpf_for_each helper. 2) Add pyperf600_foreach and strobemeta_foreach to test the performance of using bpf_for_each instead of a for loop The results of pyperf600 and strobemeta are as follows: ~strobemeta~ Baseline verification time 6808200 usec stack depth 496 processed 592132 insns (limit 1000000) max_states_per_insn 14 total_states 16018 peak_states 13684 mark_read 3132 #188 verif_scale_strobemeta:OK (unrolled loop) Using bpf_for_each verification time 31589 usec stack depth 96+408 processed 1630 insns (limit 1000000) max_states_per_insn 4 total_states 107 peak_states 107 mark_read 60 #189 verif_scale_strobemeta_foreach:OK ~pyperf600~ Baseline verification time 29702486 usec stack depth 368 processed 626838 insns (limit 1000000) max_states_per_insn 7 total_states 30368 peak_states 30279 mark_read 748 #182 verif_scale_pyperf600:OK (unrolled loop) Using bpf_for_each verification time 148488 usec stack depth 320+40 processed 10518 insns (limit 1000000) max_states_per_insn 10 total_states 705 peak_states 517 mark_read 38 #183 verif_scale_pyperf600_foreach:OK Using the bpf_for_each helper led to approximately a 100% decrease in the verification time and in the number of instructions. Signed-off-by: Joanne Koong <[email protected]>
1 parent 297b18e commit 23cf4cf

File tree

7 files changed

+295
-4
lines changed

7 files changed

+295
-4
lines changed

tools/testing/selftests/bpf/prog_tests/bpf_verif_scale.c

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,12 @@ void test_verif_scale_pyperf600()
115115
scale_test("pyperf600.o", BPF_PROG_TYPE_RAW_TRACEPOINT, false);
116116
}
117117

118+
void test_verif_scale_pyperf600_foreach(void)
119+
{
120+
/* use the bpf_for_each helper*/
121+
scale_test("pyperf600_foreach.o", BPF_PROG_TYPE_RAW_TRACEPOINT, false);
122+
}
123+
118124
void test_verif_scale_pyperf600_nounroll()
119125
{
120126
/* no unroll at all.
@@ -165,6 +171,12 @@ void test_verif_scale_strobemeta()
165171
scale_test("strobemeta.o", BPF_PROG_TYPE_RAW_TRACEPOINT, false);
166172
}
167173

174+
void test_verif_scale_strobemeta_foreach(void)
175+
{
176+
/* use the bpf_for_each helper*/
177+
scale_test("strobemeta_foreach.o", BPF_PROG_TYPE_RAW_TRACEPOINT, false);
178+
}
179+
168180
void test_verif_scale_strobemeta_nounroll1()
169181
{
170182
/* no unroll, tiny loops */

tools/testing/selftests/bpf/prog_tests/for_each.c

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#include <network_helpers.h>
55
#include "for_each_hash_map_elem.skel.h"
66
#include "for_each_array_map_elem.skel.h"
7+
#include "for_each_helper.skel.h"
78

89
static unsigned int duration;
910

@@ -121,10 +122,70 @@ static void test_array_map(void)
121122
for_each_array_map_elem__destroy(skel);
122123
}
123124

125+
static void test_for_each_helper(void)
126+
{
127+
struct for_each_helper *skel;
128+
__u32 retval;
129+
int err;
130+
131+
skel = for_each_helper__open_and_load();
132+
if (!ASSERT_OK_PTR(skel, "for_each_helper__open_and_load"))
133+
return;
134+
135+
skel->bss->nr_iterations = 100;
136+
err = bpf_prog_test_run(bpf_program__fd(skel->progs.test_prog),
137+
1, &pkt_v4, sizeof(pkt_v4), NULL, NULL,
138+
&retval, &duration);
139+
if (CHECK(err || retval, "bpf_for_each helper test_prog",
140+
"err %d errno %d retval %d\n", err, errno, retval))
141+
goto out;
142+
ASSERT_EQ(skel->bss->nr_iterations_completed, skel->bss->nr_iterations,
143+
"nr_iterations mismatch");
144+
ASSERT_EQ(skel->bss->g_output, (100 * 99) / 2, "wrong output");
145+
146+
/* test callback_fn returning 1 to stop iteration */
147+
skel->bss->nr_iterations = 400;
148+
skel->data->stop_index = 50;
149+
err = bpf_prog_test_run(bpf_program__fd(skel->progs.test_prog),
150+
1, &pkt_v4, sizeof(pkt_v4), NULL, NULL,
151+
&retval, &duration);
152+
if (CHECK(err || retval, "bpf_for_each helper test_prog",
153+
"err %d errno %d retval %d\n", err, errno, retval))
154+
goto out;
155+
ASSERT_EQ(skel->bss->nr_iterations_completed, skel->data->stop_index + 1,
156+
"stop_index not followed");
157+
ASSERT_EQ(skel->bss->g_output, (50 * 49) / 2, "wrong output");
158+
159+
/* test passing in a null ctx */
160+
skel->bss->nr_iterations = 10;
161+
err = bpf_prog_test_run(bpf_program__fd(skel->progs.prog_null_ctx),
162+
1, &pkt_v4, sizeof(pkt_v4), NULL, NULL,
163+
&retval, &duration);
164+
if (CHECK(err || retval, "bpf_for_each helper prog_null_ctx",
165+
"err %d errno %d retval %d\n", err, errno, retval))
166+
goto out;
167+
ASSERT_EQ(skel->bss->nr_iterations_completed, skel->bss->nr_iterations,
168+
"nr_iterations mismatch");
169+
170+
/* test invalid flags */
171+
err = bpf_prog_test_run(bpf_program__fd(skel->progs.prog_invalid_flags),
172+
1, &pkt_v4, sizeof(pkt_v4), NULL, NULL,
173+
&retval, &duration);
174+
if (CHECK(err || retval, "bpf_for_each helper prog_invalid_flags",
175+
"err %d errno %d retval %d\n", err, errno, retval))
176+
goto out;
177+
ASSERT_EQ(skel->bss->err, -EINVAL, "invalid_flags");
178+
179+
out:
180+
for_each_helper__destroy(skel);
181+
}
182+
124183
void test_for_each(void)
125184
{
126185
if (test__start_subtest("hash_map"))
127186
test_hash_map();
128187
if (test__start_subtest("array_map"))
129188
test_array_map();
189+
if (test__start_subtest("for_each_helper"))
190+
test_for_each_helper();
130191
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
/* Copyright (c) 2021 Facebook */
3+
4+
#include "vmlinux.h"
5+
#include <bpf/bpf_helpers.h>
6+
7+
char _license[] SEC("license") = "GPL";
8+
9+
struct callback_ctx {
10+
int output;
11+
};
12+
13+
/* This should be set by the user program */
14+
u32 nr_iterations;
15+
u32 stop_index = -1;
16+
17+
/* Making these global variables so that the userspace program
18+
* can verify the output through the skeleton
19+
*/
20+
int nr_iterations_completed;
21+
int g_output;
22+
int err;
23+
24+
static int callback_fn(__u32 index, void *data)
25+
{
26+
struct callback_ctx *ctx = data;
27+
28+
if (index >= stop_index)
29+
return 1;
30+
31+
ctx->output += index;
32+
33+
return 0;
34+
}
35+
36+
static int empty_callback_fn(__u32 index, void *data)
37+
{
38+
return 0;
39+
}
40+
41+
SEC("tc")
42+
int test_prog(struct __sk_buff *skb)
43+
{
44+
struct callback_ctx data = {};
45+
46+
nr_iterations_completed = bpf_for_each(nr_iterations, callback_fn, &data, 0);
47+
48+
g_output = data.output;
49+
50+
return 0;
51+
}
52+
53+
SEC("tc")
54+
int prog_null_ctx(struct __sk_buff *skb)
55+
{
56+
nr_iterations_completed = bpf_for_each(nr_iterations, empty_callback_fn, NULL, 0);
57+
58+
return 0;
59+
}
60+
61+
SEC("tc")
62+
int prog_invalid_flags(struct __sk_buff *skb)
63+
{
64+
struct callback_ctx data = {};
65+
66+
err = bpf_for_each(nr_iterations, callback_fn, &data, 1);
67+
68+
return 0;
69+
}

tools/testing/selftests/bpf/progs/pyperf.h

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,57 @@ struct {
159159
__uint(value_size, sizeof(long long) * 127);
160160
} stackmap SEC(".maps");
161161

162+
struct process_frame_ctx {
163+
int cur_cpu;
164+
int32_t *symbol_counter;
165+
void *frame_ptr;
166+
FrameData *frame;
167+
PidData *pidData;
168+
Symbol *sym;
169+
Event *event;
170+
bool done;
171+
};
172+
173+
#define barrier_var(var) asm volatile("" : "=r"(var) : "0"(var))
174+
175+
static int process_frame_callback(__u32 i, struct process_frame_ctx *ctx)
176+
{
177+
int zero = 0;
178+
void *frame_ptr = ctx->frame_ptr;
179+
PidData *pidData = ctx->pidData;
180+
FrameData *frame = ctx->frame;
181+
int32_t *symbol_counter = ctx->symbol_counter;
182+
int cur_cpu = ctx->cur_cpu;
183+
Event *event = ctx->event;
184+
Symbol *sym = ctx->sym;
185+
186+
if (frame_ptr && get_frame_data(frame_ptr, pidData, frame, sym)) {
187+
int32_t new_symbol_id = *symbol_counter * 64 + cur_cpu;
188+
int32_t *symbol_id = bpf_map_lookup_elem(&symbolmap, sym);
189+
190+
if (!symbol_id) {
191+
bpf_map_update_elem(&symbolmap, sym, &zero, 0);
192+
symbol_id = bpf_map_lookup_elem(&symbolmap, sym);
193+
if (!symbol_id) {
194+
ctx->done = true;
195+
return 1;
196+
}
197+
}
198+
if (*symbol_id == new_symbol_id)
199+
(*symbol_counter)++;
200+
201+
barrier_var(i);
202+
if (i >= STACK_MAX_LEN)
203+
return 1;
204+
205+
event->stack[i] = *symbol_id;
206+
207+
event->stack_len = i + 1;
208+
frame_ptr = frame->f_back;
209+
}
210+
return 0;
211+
}
212+
162213
#ifdef GLOBAL_FUNC
163214
__noinline
164215
#elif defined(SUBPROGS)
@@ -228,11 +279,27 @@ int __on_event(struct bpf_raw_tracepoint_args *ctx)
228279
int32_t* symbol_counter = bpf_map_lookup_elem(&symbolmap, &sym);
229280
if (symbol_counter == NULL)
230281
return 0;
282+
#ifdef USE_FOREACH
283+
struct process_frame_ctx ctx;
284+
285+
ctx.cur_cpu = cur_cpu;
286+
ctx.symbol_counter = symbol_counter;
287+
ctx.frame_ptr = frame_ptr;
288+
ctx.frame = &frame;
289+
ctx.pidData = pidData;
290+
ctx.sym = &sym;
291+
ctx.event = event;
292+
ctx.done = false;
293+
294+
bpf_for_each(STACK_MAX_LEN, process_frame_callback, &ctx, 0);
295+
if (ctx.done)
296+
return 0;
297+
#else
231298
#ifdef NO_UNROLL
232299
#pragma clang loop unroll(disable)
233300
#else
234301
#pragma clang loop unroll(full)
235-
#endif
302+
#endif /* NO_UNROLL */
236303
/* Unwind python stack */
237304
for (int i = 0; i < STACK_MAX_LEN; ++i) {
238305
if (frame_ptr && get_frame_data(frame_ptr, pidData, &frame, &sym)) {
@@ -251,6 +318,7 @@ int __on_event(struct bpf_raw_tracepoint_args *ctx)
251318
frame_ptr = frame.f_back;
252319
}
253320
}
321+
#endif /* USE_FOREACH */
254322
event->stack_complete = frame_ptr == NULL;
255323
} else {
256324
event->stack_complete = 1;
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
// Copyright (c) 2021 Facebook
3+
#define STACK_MAX_LEN 600
4+
#define USE_FOREACH
5+
#include "pyperf.h"

tools/testing/selftests/bpf/progs/strobemeta.h

Lines changed: 70 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -445,6 +445,46 @@ static __always_inline void *read_map_var(struct strobemeta_cfg *cfg,
445445
return payload;
446446
}
447447

448+
enum read_type {
449+
READ_INT_VAR,
450+
READ_MAP_VAR,
451+
READ_STR_VAR,
452+
};
453+
454+
struct read_var_ctx {
455+
struct strobemeta_payload *data;
456+
void *tls_base;
457+
struct strobemeta_cfg *cfg;
458+
void *payload;
459+
/* value gets mutated */
460+
struct strobe_value_generic *value;
461+
enum read_type type;
462+
};
463+
464+
static int read_var_callback(__u32 index, struct read_var_ctx *ctx)
465+
{
466+
switch (ctx->type) {
467+
case READ_INT_VAR:
468+
if (index >= STROBE_MAX_INTS)
469+
return 1;
470+
read_int_var(ctx->cfg, index, ctx->tls_base, ctx->value, ctx->data);
471+
break;
472+
case READ_MAP_VAR:
473+
if (index >= STROBE_MAX_MAPS)
474+
return 1;
475+
ctx->payload = read_map_var(ctx->cfg, index, ctx->tls_base,
476+
ctx->value, ctx->data, ctx->payload);
477+
break;
478+
case READ_STR_VAR:
479+
if (index >= STROBE_MAX_STRS)
480+
return 1;
481+
ctx->payload += read_str_var(ctx->cfg, index, ctx->tls_base,
482+
ctx->value, ctx->data, ctx->payload);
483+
break;
484+
}
485+
return 0;
486+
}
487+
448488
/*
449489
* read_strobe_meta returns NULL, if no metadata was read; otherwise returns
450490
* pointer to *right after* payload ends
@@ -475,30 +515,57 @@ static void *read_strobe_meta(struct task_struct *task,
475515
*/
476516
tls_base = (void *)task;
477517

518+
#ifdef USE_FOREACH
519+
struct read_var_ctx ctx;
520+
int err;
521+
522+
ctx.cfg = cfg;
523+
ctx.tls_base = tls_base;
524+
ctx.value = &value;
525+
ctx.data = data;
526+
ctx.payload = payload;
527+
ctx.type = READ_INT_VAR;
528+
529+
err = bpf_for_each(STROBE_MAX_INTS, read_var_callback, &ctx, 0);
530+
if (err != STROBE_MAX_INTS)
531+
return NULL;
532+
533+
ctx.type = READ_STR_VAR;
534+
err = bpf_for_each(STROBE_MAX_STRS, read_var_callback, &ctx, 0);
535+
if (err != STROBE_MAX_STRS)
536+
return NULL;
537+
538+
ctx.type = READ_MAP_VAR;
539+
err = bpf_for_each(STROBE_MAX_MAPS, read_var_callback, &ctx, 0);
540+
if (err != STROBE_MAX_MAPS)
541+
return NULL;
542+
#else
478543
#ifdef NO_UNROLL
479544
#pragma clang loop unroll(disable)
480545
#else
481546
#pragma unroll
482-
#endif
547+
#endif /* NO_UNROLL */
483548
for (int i = 0; i < STROBE_MAX_INTS; ++i) {
484549
read_int_var(cfg, i, tls_base, &value, data);
485550
}
486551
#ifdef NO_UNROLL
487552
#pragma clang loop unroll(disable)
488553
#else
489554
#pragma unroll
490-
#endif
555+
#endif /* NO_UNROLL */
491556
for (int i = 0; i < STROBE_MAX_STRS; ++i) {
492557
payload += read_str_var(cfg, i, tls_base, &value, data, payload);
493558
}
494559
#ifdef NO_UNROLL
495560
#pragma clang loop unroll(disable)
496561
#else
497562
#pragma unroll
498-
#endif
563+
#endif /* NO_UNROLL */
499564
for (int i = 0; i < STROBE_MAX_MAPS; ++i) {
500565
payload = read_map_var(cfg, i, tls_base, &value, data, payload);
501566
}
567+
#endif /* USE_FOREACH */
568+
502569
/*
503570
* return pointer right after end of payload, so it's possible to
504571
* calculate exact amount of useful data that needs to be sent
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
2+
// Copyright (c) 2021 Facebook
3+
4+
#define STROBE_MAX_INTS 2
5+
#define STROBE_MAX_STRS 25
6+
#define STROBE_MAX_MAPS 100
7+
#define STROBE_MAX_MAP_ENTRIES 20
8+
#define USE_FOREACH
9+
#include "strobemeta.h"

0 commit comments

Comments
 (0)