diff --git a/tools/perf/tests/Build b/tools/perf/tests/Build index a47b21193fb2..f41ebf8849fe 100644 --- a/tools/perf/tests/Build +++ b/tools/perf/tests/Build @@ -32,6 +32,7 @@ perf-y += parse-no-sample-id-all.o perf-y += kmod-path.o perf-y += thread-map.o perf-y += llvm.o llvm-src-base.o llvm-src-kbuild.o +perf-y += bpf.o perf-y += topology.o $(OUTPUT)tests/llvm-src-base.c: tests/bpf-script-example.c diff --git a/tools/perf/tests/bpf.c b/tools/perf/tests/bpf.c new file mode 100644 index 000000000000..ec16f7812c8b --- /dev/null +++ b/tools/perf/tests/bpf.c @@ -0,0 +1,209 @@ +#include +#include +#include +#include +#include "tests.h" +#include "llvm.h" +#include "debug.h" +#define NR_ITERS 111 + +#ifdef HAVE_LIBBPF_SUPPORT + +static int epoll_pwait_loop(void) +{ + int i; + + /* Should fail NR_ITERS times */ + for (i = 0; i < NR_ITERS; i++) + epoll_pwait(-(i + 1), NULL, 0, 0, NULL); + return 0; +} + +static struct { + enum test_llvm__testcase prog_id; + const char *desc; + const char *name; + const char *msg_compile_fail; + const char *msg_load_fail; + int (*target_func)(void); + int expect_result; +} bpf_testcase_table[] = { + { + LLVM_TESTCASE_BASE, + "Test basic BPF filtering", + "[basic_bpf_test]", + "fix 'perf test LLVM' first", + "load bpf object failed", + &epoll_pwait_loop, + (NR_ITERS + 1) / 2, + }, +}; + +static int do_test(struct bpf_object *obj, int (*func)(void), + int expect) +{ + struct record_opts opts = { + .target = { + .uid = UINT_MAX, + .uses_mmap = true, + }, + .freq = 0, + .mmap_pages = 256, + .default_interval = 1, + }; + + char pid[16]; + char sbuf[STRERR_BUFSIZE]; + struct perf_evlist *evlist; + int i, ret = TEST_FAIL, err = 0, count = 0; + + struct parse_events_evlist parse_evlist; + struct parse_events_error parse_error; + + bzero(&parse_error, sizeof(parse_error)); + bzero(&parse_evlist, sizeof(parse_evlist)); + parse_evlist.error = &parse_error; + INIT_LIST_HEAD(&parse_evlist.list); + + err = parse_events_load_bpf_obj(&parse_evlist, &parse_evlist.list, obj); + if (err || list_empty(&parse_evlist.list)) { + pr_debug("Failed to add events selected by BPF\n"); + if (!err) + return TEST_FAIL; + } + + snprintf(pid, sizeof(pid), "%d", getpid()); + pid[sizeof(pid) - 1] = '\0'; + opts.target.tid = opts.target.pid = pid; + + /* Instead of perf_evlist__new_default, don't add default events */ + evlist = perf_evlist__new(); + if (!evlist) { + pr_debug("No ehough memory to create evlist\n"); + return TEST_FAIL; + } + + err = perf_evlist__create_maps(evlist, &opts.target); + if (err < 0) { + pr_debug("Not enough memory to create thread/cpu maps\n"); + goto out_delete_evlist; + } + + perf_evlist__splice_list_tail(evlist, &parse_evlist.list); + evlist->nr_groups = parse_evlist.nr_groups; + + perf_evlist__config(evlist, &opts); + + err = perf_evlist__open(evlist); + if (err < 0) { + pr_debug("perf_evlist__open: %s\n", + strerror_r(errno, sbuf, sizeof(sbuf))); + goto out_delete_evlist; + } + + err = perf_evlist__mmap(evlist, opts.mmap_pages, false); + if (err < 0) { + pr_debug("perf_evlist__mmap: %s\n", + strerror_r(errno, sbuf, sizeof(sbuf))); + goto out_delete_evlist; + } + + perf_evlist__enable(evlist); + (*func)(); + perf_evlist__disable(evlist); + + for (i = 0; i < evlist->nr_mmaps; i++) { + union perf_event *event; + + while ((event = perf_evlist__mmap_read(evlist, i)) != NULL) { + const u32 type = event->header.type; + + if (type == PERF_RECORD_SAMPLE) + count ++; + } + } + + if (count != expect) + pr_debug("BPF filter result incorrect\n"); + + ret = TEST_OK; + +out_delete_evlist: + perf_evlist__delete(evlist); + return ret; +} + +static struct bpf_object * +prepare_bpf(void *obj_buf, size_t obj_buf_sz, const char *name) +{ + struct bpf_object *obj; + + obj = bpf__prepare_load_buffer(obj_buf, obj_buf_sz, name); + if (IS_ERR(obj)) { + pr_debug("Compile BPF program failed.\n"); + return NULL; + } + return obj; +} + +static int __test__bpf(int index) +{ + int ret; + void *obj_buf; + size_t obj_buf_sz; + struct bpf_object *obj; + + ret = test_llvm__fetch_bpf_obj(&obj_buf, &obj_buf_sz, + bpf_testcase_table[index].prog_id, + true); + if (ret != TEST_OK || !obj_buf || !obj_buf_sz) { + pr_debug("Unable to get BPF object, %s\n", + bpf_testcase_table[index].msg_compile_fail); + if (index == 0) + return TEST_SKIP; + else + return TEST_FAIL; + } + + obj = prepare_bpf(obj_buf, obj_buf_sz, + bpf_testcase_table[index].name); + if (!obj) { + ret = TEST_FAIL; + goto out; + } + + ret = do_test(obj, + bpf_testcase_table[index].target_func, + bpf_testcase_table[index].expect_result); +out: + bpf__clear(); + return ret; +} + +int test__bpf(void) +{ + unsigned int i; + int err; + + if (geteuid() != 0) { + pr_debug("Only root can run BPF test\n"); + return TEST_SKIP; + } + + for (i = 0; i < ARRAY_SIZE(bpf_testcase_table); i++) { + err = __test__bpf(i); + + if (err != TEST_OK) + return err; + } + + return TEST_OK; +} + +#else +int test__bpf(void) +{ + pr_debug("Skip BPF test because BPF support is not compiled\n"); + return TEST_SKIP; +} +#endif diff --git a/tools/perf/tests/builtin-test.c b/tools/perf/tests/builtin-test.c index 66f72d3d6677..7b0120abc137 100644 --- a/tools/perf/tests/builtin-test.c +++ b/tools/perf/tests/builtin-test.c @@ -165,6 +165,10 @@ static struct test generic_tests[] = { .desc = "Test topology in session", .func = test_session_topology, }, + { + .desc = "Test BPF filter", + .func = test__bpf, + }, { .func = NULL, }, diff --git a/tools/perf/tests/tests.h b/tools/perf/tests/tests.h index c80486969f83..3c8734a3abbc 100644 --- a/tools/perf/tests/tests.h +++ b/tools/perf/tests/tests.h @@ -66,6 +66,7 @@ int test__fdarray__add(void); int test__kmod_path__parse(void); int test__thread_map(void); int test__llvm(void); +int test__bpf(void); int test_session_topology(void); #if defined(__arm__) || defined(__aarch64__) diff --git a/tools/perf/util/bpf-loader.c b/tools/perf/util/bpf-loader.c index e3afa1b60bb5..4c50411371db 100644 --- a/tools/perf/util/bpf-loader.c +++ b/tools/perf/util/bpf-loader.c @@ -34,10 +34,32 @@ struct bpf_prog_priv { struct perf_probe_event pev; }; +static bool libbpf_initialized; + +struct bpf_object * +bpf__prepare_load_buffer(void *obj_buf, size_t obj_buf_sz, const char *name) +{ + struct bpf_object *obj; + + if (!libbpf_initialized) { + libbpf_set_print(libbpf_warning, + libbpf_info, + libbpf_debug); + libbpf_initialized = true; + } + + obj = bpf_object__open_buffer(obj_buf, obj_buf_sz, name); + if (IS_ERR(obj)) { + pr_debug("bpf: failed to load buffer\n"); + return ERR_PTR(-EINVAL); + } + + return obj; +} + struct bpf_object *bpf__prepare_load(const char *filename, bool source) { struct bpf_object *obj; - static bool libbpf_initialized; if (!libbpf_initialized) { libbpf_set_print(libbpf_warning, diff --git a/tools/perf/util/bpf-loader.h b/tools/perf/util/bpf-loader.h index 5eb3629eed8b..9caf3ae4acf3 100644 --- a/tools/perf/util/bpf-loader.h +++ b/tools/perf/util/bpf-loader.h @@ -34,6 +34,9 @@ struct bpf_object *bpf__prepare_load(const char *filename, bool source); int bpf__strerror_prepare_load(const char *filename, bool source, int err, char *buf, size_t size); +struct bpf_object *bpf__prepare_load_buffer(void *obj_buf, size_t obj_buf_sz, + const char *name); + void bpf__clear(void); int bpf__probe(struct bpf_object *obj); @@ -55,6 +58,13 @@ bpf__prepare_load(const char *filename __maybe_unused, return ERR_PTR(-ENOTSUP); } +static inline struct bpf_object * +bpf__prepare_load_buffer(void *obj_buf __maybe_unused, + size_t obj_buf_sz __maybe_unused) +{ + return ERR_PTR(-ENOTSUP); +} + static inline void bpf__clear(void) { } static inline int bpf__probe(struct bpf_object *obj __maybe_unused) { return 0;}