mirror of
https://github.com/torvalds/linux.git
synced 2026-11-07 06:17:02 +01:00
Merge branch 'bpftool-consume-ring-buffer-maps-with-event_pipe'
Tianyi Chen says: ==================== bpftool: Consume ring buffer maps with event_pipe Extend map event_pipe to consume BPF ring buffer records, with plain and JSON output and focused tests for records, shutdown and invalid arguments. Changes in v5: - Suppress cpu/index completion after selecting a ring buffer map by id, name or pinned path, using _bpftool_map_guess_map_type(). Keep the existing suggestions for perf event arrays and unresolved map types. - Rebase onto current bpf-next. No other changes from v4. This addresses the v4 review: https://lore.kernel.org/r/a89e2e325b87c215e0fb6e4a24284922d7656cfc0e2a247f0c8c1cd823764c23@mail.kernel.org Built bpftool and reran all 18 ringbuf subtests against it in an x86-64 VM; all passed. Also checked completion against live ringbuf and perf_event_array maps by id, name and pinned path, plus unresolved maps and already-specified selectors with mocked output. v4: https://lore.kernel.org/r/20260919110721.1230756-1-hi@tychen.cc ==================== Link: https://patch.msgid.link/20260921073556.99421-1-hi@tychen.cc Signed-off-by: Andrii Nakryiko <andrii@kernel.org>
This commit is contained in:
commit
a99a1b2663
|
|
@ -120,7 +120,8 @@ bpftool map pin *MAP* *FILE*
|
|||
character ('.'), which is reserved for future extensions of *bpffs*.
|
||||
|
||||
bpftool map event_pipe *MAP* [cpu *N* index *M*]
|
||||
Read events from a **BPF_MAP_TYPE_PERF_EVENT_ARRAY** map.
|
||||
Read events from a **BPF_MAP_TYPE_PERF_EVENT_ARRAY** or
|
||||
**BPF_MAP_TYPE_RINGBUF** map.
|
||||
|
||||
Install perf rings into a perf event array map and dump output of any
|
||||
**bpf_perf_event_output**\ () call in the kernel. By default read the
|
||||
|
|
@ -134,6 +135,17 @@ bpftool map event_pipe *MAP* [cpu *N* index *M*]
|
|||
existing ring. Any other application will stop receiving events if it
|
||||
installed its rings earlier.
|
||||
|
||||
For a ring buffer map, consume records submitted by BPF programs, including
|
||||
records already queued before the command starts. **cpu** and **index**
|
||||
are not supported. Each record is printed in full, including embedded zero
|
||||
bytes. Plain output reports the record size followed by hexadecimal
|
||||
bytes; JSON output contains **size** and **data** fields, with **data** an
|
||||
array of byte values. Ring buffer records have no implicit CPU or timestamp.
|
||||
|
||||
Consuming a ring buffer advances its shared consumer position, so this
|
||||
command must not run alongside another consumer of the same map.
|
||||
**BPF_MAP_TYPE_USER_RINGBUF** maps are not supported.
|
||||
|
||||
bpftool map peek *MAP*
|
||||
Peek next value in the queue or stack.
|
||||
|
||||
|
|
|
|||
|
|
@ -50,12 +50,19 @@ _bpftool_get_map_ids()
|
|||
command sed -n 's/.*"id": \(.*\),$/\1/p' )" -- "$cur" ) )
|
||||
}
|
||||
|
||||
# Takes map type and adds matching map ids to the list of suggestions.
|
||||
_bpftool_get_map_ids_for_type()
|
||||
# Takes map types and prints a pattern matching their JSON type fields.
|
||||
_bpftool_build_type_pattern()
|
||||
{
|
||||
local type="$1"
|
||||
local IFS='|'
|
||||
printf '"type": "(%s)"\n' "$*"
|
||||
}
|
||||
|
||||
# Takes map types and adds matching map ids to the list of suggestions.
|
||||
_bpftool_get_map_ids_for_types()
|
||||
{
|
||||
local types=$(_bpftool_build_type_pattern "$@")
|
||||
COMPREPLY+=( $( compgen -W "$( bpftool -jp map 2>&1 | \
|
||||
command grep -C2 "$type" | \
|
||||
command grep -C2 -E "$types" | \
|
||||
command sed -n 's/.*"id": \(.*\),$/\1/p' )" -- "$cur" ) )
|
||||
}
|
||||
|
||||
|
|
@ -65,12 +72,12 @@ _bpftool_get_map_names()
|
|||
command sed -n 's/.*"name": \(.*\),$/\1/p' )" -- "$cur" ) )
|
||||
}
|
||||
|
||||
# Takes map type and adds matching map names to the list of suggestions.
|
||||
_bpftool_get_map_names_for_type()
|
||||
# Takes map types and adds matching map names to the list of suggestions.
|
||||
_bpftool_get_map_names_for_types()
|
||||
{
|
||||
local type="$1"
|
||||
local types=$(_bpftool_build_type_pattern "$@")
|
||||
COMPREPLY+=( $( compgen -W "$( bpftool -jp map 2>&1 | \
|
||||
command grep -C2 "$type" | \
|
||||
command grep -C2 -E "$types" | \
|
||||
command sed -n 's/.*"name": \(.*\),$/\1/p' )" -- "$cur" ) )
|
||||
}
|
||||
|
||||
|
|
@ -139,7 +146,7 @@ _bpftool_map_guess_map_type()
|
|||
local keyword idx ref=""
|
||||
for (( idx=3; idx < ${#words[@]}-1; idx++ )); do
|
||||
case "${words[$((idx-2))]}" in
|
||||
lookup|update)
|
||||
lookup|update|event_pipe)
|
||||
keyword=${words[$((idx-1))]}
|
||||
ref=${words[$((idx))]}
|
||||
;;
|
||||
|
|
@ -177,10 +184,10 @@ _bpftool_map_update_get_id()
|
|||
if [[ $value -eq 0 ]]; then
|
||||
case "$command" in
|
||||
push)
|
||||
_bpftool_get_map_ids_for_type stack
|
||||
_bpftool_get_map_ids_for_types stack
|
||||
;;
|
||||
enqueue)
|
||||
_bpftool_get_map_ids_for_type queue
|
||||
_bpftool_get_map_ids_for_types queue
|
||||
;;
|
||||
*)
|
||||
_bpftool_get_map_ids
|
||||
|
|
@ -223,10 +230,10 @@ _bpftool_map_update_get_name()
|
|||
if [[ $value -eq 0 ]]; then
|
||||
case "$command" in
|
||||
push)
|
||||
_bpftool_get_map_names_for_type stack
|
||||
_bpftool_get_map_names_for_types stack
|
||||
;;
|
||||
enqueue)
|
||||
_bpftool_get_map_names_for_type queue
|
||||
_bpftool_get_map_names_for_types queue
|
||||
;;
|
||||
*)
|
||||
_bpftool_get_map_names
|
||||
|
|
@ -617,10 +624,10 @@ _bpftool()
|
|||
COMPREPLY=( $( compgen -W "$STRUCT_OPS_TYPE" -- "$cur" ) )
|
||||
;;
|
||||
id)
|
||||
_bpftool_get_map_ids_for_type struct_ops
|
||||
_bpftool_get_map_ids_for_types struct_ops
|
||||
;;
|
||||
name)
|
||||
_bpftool_get_map_names_for_type struct_ops
|
||||
_bpftool_get_map_names_for_types struct_ops
|
||||
;;
|
||||
esac
|
||||
return 0
|
||||
|
|
@ -679,14 +686,13 @@ _bpftool()
|
|||
id)
|
||||
case "$command" in
|
||||
peek)
|
||||
_bpftool_get_map_ids_for_type stack
|
||||
_bpftool_get_map_ids_for_type queue
|
||||
_bpftool_get_map_ids_for_types stack queue
|
||||
;;
|
||||
pop)
|
||||
_bpftool_get_map_ids_for_type stack
|
||||
_bpftool_get_map_ids_for_types stack
|
||||
;;
|
||||
dequeue)
|
||||
_bpftool_get_map_ids_for_type queue
|
||||
_bpftool_get_map_ids_for_types queue
|
||||
;;
|
||||
*)
|
||||
_bpftool_get_map_ids
|
||||
|
|
@ -697,14 +703,13 @@ _bpftool()
|
|||
name)
|
||||
case "$command" in
|
||||
peek)
|
||||
_bpftool_get_map_names_for_type stack
|
||||
_bpftool_get_map_names_for_type queue
|
||||
_bpftool_get_map_names_for_types stack queue
|
||||
;;
|
||||
pop)
|
||||
_bpftool_get_map_names_for_type stack
|
||||
_bpftool_get_map_names_for_types stack
|
||||
;;
|
||||
dequeue)
|
||||
_bpftool_get_map_names_for_type queue
|
||||
_bpftool_get_map_names_for_types queue
|
||||
;;
|
||||
*)
|
||||
_bpftool_get_map_names
|
||||
|
|
@ -878,11 +883,11 @@ _bpftool()
|
|||
return 0
|
||||
;;
|
||||
id)
|
||||
_bpftool_get_map_ids_for_type perf_event_array
|
||||
_bpftool_get_map_ids_for_types perf_event_array ringbuf
|
||||
return 0
|
||||
;;
|
||||
name)
|
||||
_bpftool_get_map_names_for_type perf_event_array
|
||||
_bpftool_get_map_names_for_types perf_event_array ringbuf
|
||||
return 0
|
||||
;;
|
||||
cpu)
|
||||
|
|
@ -892,7 +897,9 @@ _bpftool()
|
|||
return 0
|
||||
;;
|
||||
*)
|
||||
_bpftool_once_attr 'cpu index'
|
||||
if [[ $(_bpftool_map_guess_map_type) != ringbuf ]]; then
|
||||
_bpftool_once_attr 'cpu index'
|
||||
fi
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@
|
|||
|
||||
#define MMAP_PAGE_CNT 16
|
||||
|
||||
static volatile bool stop;
|
||||
static volatile sig_atomic_t stop;
|
||||
|
||||
struct perf_event_sample {
|
||||
struct perf_event_header header;
|
||||
|
|
@ -44,7 +44,6 @@ struct perf_event_lost {
|
|||
|
||||
static void int_exit(int signo)
|
||||
{
|
||||
fprintf(stderr, "Stopping...\n");
|
||||
stop = true;
|
||||
}
|
||||
|
||||
|
|
@ -107,6 +106,27 @@ print_bpf_output(void *private_data, int cpu, struct perf_event_header *event)
|
|||
return LIBBPF_PERF_EVENT_CONT;
|
||||
}
|
||||
|
||||
static int print_ringbuf_output(void *ctx, void *data, size_t size)
|
||||
{
|
||||
if (json_output) {
|
||||
jsonw_start_object(json_wtr);
|
||||
jsonw_uint_field(json_wtr, "size", size);
|
||||
jsonw_name(json_wtr, "data");
|
||||
print_data_json(data, size);
|
||||
jsonw_end_object(json_wtr);
|
||||
} else {
|
||||
printf("== size: %zu =====\n", size);
|
||||
fprint_hex(stdout, data, size, " ");
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
if (fflush(stdout))
|
||||
return errno ? -errno : -EIO;
|
||||
|
||||
/* A producer can keep poll() busy even after a signal arrives. */
|
||||
return stop ? -EINTR : 0;
|
||||
}
|
||||
|
||||
int do_event_pipe(int argc, char **argv)
|
||||
{
|
||||
struct perf_event_attr perf_attr = {
|
||||
|
|
@ -123,18 +143,27 @@ int do_event_pipe(int argc, char **argv)
|
|||
.cpu = -1,
|
||||
.idx = -1,
|
||||
};
|
||||
struct perf_buffer *pb;
|
||||
struct perf_buffer *pb = NULL;
|
||||
struct ring_buffer *rb = NULL;
|
||||
__u32 map_info_len;
|
||||
int err, map_fd;
|
||||
|
||||
stop = false;
|
||||
map_info_len = sizeof(map_info);
|
||||
map_fd = map_parse_fd_and_info(&argc, &argv, &map_info, &map_info_len,
|
||||
0);
|
||||
if (map_fd < 0)
|
||||
return -1;
|
||||
|
||||
if (map_info.type != BPF_MAP_TYPE_PERF_EVENT_ARRAY) {
|
||||
p_err("map is not a perf event array");
|
||||
if (map_info.type != BPF_MAP_TYPE_PERF_EVENT_ARRAY &&
|
||||
map_info.type != BPF_MAP_TYPE_RINGBUF) {
|
||||
p_err("map is not a perf event array or ring buffer");
|
||||
goto err_close_map;
|
||||
}
|
||||
|
||||
if (map_info.type == BPF_MAP_TYPE_RINGBUF && argc &&
|
||||
(is_prefix(*argv, "cpu") || is_prefix(*argv, "index"))) {
|
||||
p_err("ring buffer maps do not support cpu or index arguments");
|
||||
goto err_close_map;
|
||||
}
|
||||
|
||||
|
|
@ -184,15 +213,24 @@ int do_event_pipe(int argc, char **argv)
|
|||
ctx.idx = 0;
|
||||
}
|
||||
|
||||
opts.cpu_cnt = ctx.all_cpus ? 0 : 1;
|
||||
opts.cpus = &ctx.cpu;
|
||||
opts.map_keys = &ctx.idx;
|
||||
pb = perf_buffer__new_raw(map_fd, MMAP_PAGE_CNT, &perf_attr,
|
||||
print_bpf_output, &ctx, &opts);
|
||||
if (!pb) {
|
||||
p_err("failed to create perf buffer: %s (%d)",
|
||||
strerror(errno), errno);
|
||||
goto err_close_map;
|
||||
if (map_info.type == BPF_MAP_TYPE_RINGBUF) {
|
||||
rb = ring_buffer__new(map_fd, print_ringbuf_output, NULL, NULL);
|
||||
if (!rb) {
|
||||
p_err("failed to create ring buffer: %s (%d)",
|
||||
strerror(errno), errno);
|
||||
goto err_close_map;
|
||||
}
|
||||
} else {
|
||||
opts.cpu_cnt = ctx.all_cpus ? 0 : 1;
|
||||
opts.cpus = &ctx.cpu;
|
||||
opts.map_keys = &ctx.idx;
|
||||
pb = perf_buffer__new_raw(map_fd, MMAP_PAGE_CNT, &perf_attr,
|
||||
print_bpf_output, &ctx, &opts);
|
||||
if (!pb) {
|
||||
p_err("failed to create perf buffer: %s (%d)",
|
||||
strerror(errno), errno);
|
||||
goto err_close_map;
|
||||
}
|
||||
}
|
||||
|
||||
signal(SIGINT, int_exit);
|
||||
|
|
@ -202,25 +240,32 @@ int do_event_pipe(int argc, char **argv)
|
|||
if (json_output)
|
||||
jsonw_start_array(json_wtr);
|
||||
|
||||
err = 0;
|
||||
while (!stop) {
|
||||
err = perf_buffer__poll(pb, 200);
|
||||
err = rb ? ring_buffer__poll(rb, 200) : perf_buffer__poll(pb, 200);
|
||||
if (err < 0 && err != -EINTR) {
|
||||
p_err("perf buffer polling failed: %s (%d)",
|
||||
strerror(errno), errno);
|
||||
goto err_close_pb;
|
||||
p_err("%s buffer polling failed: %s (%d)",
|
||||
rb ? "ring" : "perf", strerror(-err), -err);
|
||||
break;
|
||||
}
|
||||
err = 0;
|
||||
}
|
||||
|
||||
if (stop)
|
||||
fprintf(stderr, "Stopping...\n");
|
||||
if (json_output)
|
||||
jsonw_end_array(json_wtr);
|
||||
if (fflush(stdout)) {
|
||||
p_err("failed to write events: %s", strerror(errno));
|
||||
err = -1;
|
||||
}
|
||||
|
||||
ring_buffer__free(rb);
|
||||
perf_buffer__free(pb);
|
||||
close(map_fd);
|
||||
|
||||
return 0;
|
||||
return err < 0 ? -1 : 0;
|
||||
|
||||
err_close_pb:
|
||||
perf_buffer__free(pb);
|
||||
err_close_map:
|
||||
close(map_fd);
|
||||
return -1;
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
|
||||
#define BPFTOOL_DEFAULT_PATH "tools/sbin/bpftool"
|
||||
|
||||
static int detect_bpftool_path(char *buffer, size_t size)
|
||||
int detect_bpftool_path(char *buffer, size_t size)
|
||||
{
|
||||
char tmp[PATH_MAX];
|
||||
const char *env_path;
|
||||
|
|
|
|||
|
|
@ -7,5 +7,6 @@
|
|||
|
||||
#define MAX_BPFTOOL_CMD_LEN (256)
|
||||
|
||||
int detect_bpftool_path(char *buffer, size_t size);
|
||||
int run_bpftool_command(char *args);
|
||||
int get_bpftool_command_output(char *args, char *output_buf, size_t output_max_len);
|
||||
|
|
|
|||
417
tools/testing/selftests/bpf/prog_tests/bpftool_ringbuf.c
Normal file
417
tools/testing/selftests/bpf/prog_tests/bpftool_ringbuf.c
Normal file
|
|
@ -0,0 +1,417 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
#include <ctype.h>
|
||||
#include <fcntl.h>
|
||||
#include <poll.h>
|
||||
#include <signal.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/wait.h>
|
||||
#include <test_progs.h>
|
||||
#include "bpftool_helpers.h"
|
||||
#include "bpftool_ringbuf.skel.h"
|
||||
|
||||
#define WAIT_STEPS 500
|
||||
#define WAIT_US 10000
|
||||
|
||||
struct consumer {
|
||||
pid_t pid;
|
||||
int fd;
|
||||
};
|
||||
|
||||
static void consumer_cleanup(struct consumer *child)
|
||||
{
|
||||
if (child->pid > 0) {
|
||||
kill(child->pid, SIGKILL);
|
||||
while (waitpid(child->pid, NULL, 0) < 0 && errno == EINTR)
|
||||
;
|
||||
child->pid = -1;
|
||||
}
|
||||
if (child->fd >= 0) {
|
||||
close(child->fd);
|
||||
child->fd = -1;
|
||||
}
|
||||
}
|
||||
|
||||
static bool consumer_start(struct consumer *child, int map_fd, const char *format,
|
||||
const char *pin_path, const char *option,
|
||||
bool pair, bool capture_errors)
|
||||
{
|
||||
struct bpf_map_info info = {};
|
||||
__u32 len = sizeof(info);
|
||||
char path[PATH_MAX];
|
||||
char *argv[12], id[16];
|
||||
int out[2], ready[2], n = 0, err, err_fd;
|
||||
struct pollfd pfd;
|
||||
|
||||
if (!ASSERT_OK(detect_bpftool_path(path, sizeof(path)), "bpftool path") ||
|
||||
!ASSERT_OK(bpf_map_get_info_by_fd(map_fd, &info, &len), "map info"))
|
||||
return false;
|
||||
snprintf(id, sizeof(id), "%u", info.id);
|
||||
argv[n++] = path;
|
||||
if (format)
|
||||
argv[n++] = (char *)format;
|
||||
argv[n++] = "map";
|
||||
argv[n++] = "event_pipe";
|
||||
argv[n++] = pin_path ? "pinned" : "id";
|
||||
argv[n++] = pin_path ? (char *)pin_path : id;
|
||||
if (option) {
|
||||
argv[n++] = (char *)option;
|
||||
argv[n++] = "0";
|
||||
if (pair) {
|
||||
argv[n++] = "index";
|
||||
argv[n++] = "0";
|
||||
}
|
||||
}
|
||||
argv[n] = NULL;
|
||||
if (!ASSERT_OK(pipe2(out, O_CLOEXEC), "output pipe"))
|
||||
return false;
|
||||
if (!ASSERT_OK(pipe2(ready, O_CLOEXEC), "exec pipe")) {
|
||||
close(out[0]);
|
||||
close(out[1]);
|
||||
return false;
|
||||
}
|
||||
child->pid = fork();
|
||||
if (!child->pid) {
|
||||
close(out[0]);
|
||||
close(ready[0]);
|
||||
err_fd = capture_errors ? out[1] : open("/dev/null", O_WRONLY);
|
||||
if (dup2(out[1], STDOUT_FILENO) < 0 ||
|
||||
dup2(err_fd, STDERR_FILENO) < 0)
|
||||
goto exec_fail;
|
||||
if (!capture_errors)
|
||||
close(err_fd);
|
||||
close(out[1]);
|
||||
execv(path, argv);
|
||||
exec_fail:
|
||||
err = errno;
|
||||
if (write(ready[1], &err, sizeof(err)) != sizeof(err))
|
||||
_exit(126);
|
||||
_exit(127);
|
||||
}
|
||||
close(out[1]);
|
||||
close(ready[1]);
|
||||
child->fd = out[0];
|
||||
pfd = (struct pollfd) { .fd = ready[0], .events = POLLIN };
|
||||
/* EOF on the close-on-exec pipe distinguishes exec from inherited handlers. */
|
||||
err = child->pid > 0 ? poll(&pfd, 1, WAIT_STEPS * WAIT_US / 1000) : -1;
|
||||
if (!ASSERT_GT(child->pid, 0, "fork") ||
|
||||
!ASSERT_GT(err, 0, "exec timeout") ||
|
||||
!ASSERT_EQ(read(ready[0], &err, sizeof(err)), 0, "exec")) {
|
||||
close(ready[0]);
|
||||
consumer_cleanup(child);
|
||||
return false;
|
||||
}
|
||||
close(ready[0]);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool consumer_finish(struct consumer *child, int signo, bool success,
|
||||
char *output, size_t size)
|
||||
{
|
||||
int status = 0, i;
|
||||
pid_t ret = 0;
|
||||
ssize_t n;
|
||||
size_t used = 0;
|
||||
|
||||
if (!ASSERT_OK(fcntl(child->fd, F_SETFL, O_NONBLOCK), "nonblocking output"))
|
||||
return false;
|
||||
if (signo && !ASSERT_OK(kill(child->pid, signo), "signal consumer"))
|
||||
return false;
|
||||
for (i = 0; i < WAIT_STEPS; i++) {
|
||||
while (used < size - 1 &&
|
||||
(n = read(child->fd, output + used, size - 1 - used)) > 0)
|
||||
used += n;
|
||||
ret = waitpid(child->pid, &status, WNOHANG);
|
||||
if (ret == child->pid)
|
||||
break;
|
||||
if (ret < 0 && errno != EINTR)
|
||||
break;
|
||||
usleep(WAIT_US);
|
||||
}
|
||||
if (!ASSERT_EQ(ret, child->pid, "bounded consumer exit"))
|
||||
return false;
|
||||
child->pid = -1;
|
||||
while (used < size - 1 && (n = read(child->fd, output + used, size - 1 - used)) > 0)
|
||||
used += n;
|
||||
output[used] = '\0';
|
||||
return ASSERT_TRUE(WIFEXITED(status), "normal exit") &&
|
||||
ASSERT_EQ(WEXITSTATUS(status) == 0, success, "exit status");
|
||||
}
|
||||
|
||||
static bool consumer_ready(struct consumer *child)
|
||||
{
|
||||
unsigned long long caught;
|
||||
char path[64], line[256];
|
||||
int i;
|
||||
FILE *f;
|
||||
|
||||
snprintf(path, sizeof(path), "/proc/%d/status", child->pid);
|
||||
for (i = 0; i < WAIT_STEPS; i++) {
|
||||
f = fopen(path, "r");
|
||||
if (!f)
|
||||
break;
|
||||
while (fgets(line, sizeof(line), f)) {
|
||||
if (sscanf(line, "SigCgt: %llx", &caught) == 1 &&
|
||||
(caught & (1ULL << (SIGINT - 1))) &&
|
||||
(caught & (1ULL << (SIGTERM - 1)))) {
|
||||
fclose(f);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
fclose(f);
|
||||
usleep(WAIT_US);
|
||||
}
|
||||
return ASSERT_TRUE(false, "consumer signal handlers ready");
|
||||
}
|
||||
|
||||
static bool emit_record(struct bpftool_ringbuf *skel, int record)
|
||||
{
|
||||
char packet[64] = {};
|
||||
|
||||
LIBBPF_OPTS(bpf_test_run_opts, opts,
|
||||
.data_in = packet,
|
||||
.data_size_in = sizeof(packet),
|
||||
);
|
||||
|
||||
skel->bss->record = record;
|
||||
return ASSERT_OK(bpf_prog_test_run_opts(bpf_program__fd(skel->progs.produce),
|
||||
&opts), "produce record") &&
|
||||
ASSERT_OK(skel->bss->output_err, "ringbuf output");
|
||||
}
|
||||
|
||||
static bool consumed(unsigned long *position, unsigned long expected)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < WAIT_STEPS; i++) {
|
||||
if (__atomic_load_n(position, __ATOMIC_ACQUIRE) == expected)
|
||||
return true;
|
||||
usleep(WAIT_US);
|
||||
}
|
||||
return ASSERT_EQ(*position, expected, "consumer position");
|
||||
}
|
||||
|
||||
static void check_json(char *output, const char *expected)
|
||||
{
|
||||
char *src = output, *dst = output;
|
||||
bool quoted = false, escaped = false;
|
||||
|
||||
/* Ignore formatting whitespace while checking the entire JSON document. */
|
||||
while (*src) {
|
||||
if (quoted || !isspace((unsigned char)*src))
|
||||
*dst++ = *src;
|
||||
if (!escaped && *src == '"')
|
||||
quoted = !quoted;
|
||||
escaped = !escaped && quoted && *src == '\\';
|
||||
src++;
|
||||
}
|
||||
*dst = '\0';
|
||||
ASSERT_STREQ(output, expected, "JSON records");
|
||||
}
|
||||
|
||||
static void test_consumer(const char *format, bool idle, int signo, bool pinned)
|
||||
{
|
||||
struct consumer child = { .pid = -1, .fd = -1 };
|
||||
struct bpftool_ringbuf *skel;
|
||||
unsigned long *position = MAP_FAILED;
|
||||
int page_size = getpagesize(), fd;
|
||||
char pin_dir[] = "/sys/fs/bpf/bpftool_ringbuf_XXXXXX";
|
||||
char pin_path[sizeof(pin_dir) + sizeof("/map")];
|
||||
bool dir_created = false, map_pinned = false;
|
||||
char output[4096];
|
||||
struct pollfd pfd;
|
||||
|
||||
skel = bpftool_ringbuf__open();
|
||||
if (!ASSERT_OK_PTR(skel, "open"))
|
||||
return;
|
||||
bpf_map__set_max_entries(skel->maps.ringbuf, page_size);
|
||||
if (!ASSERT_OK(bpftool_ringbuf__load(skel), "load"))
|
||||
goto out;
|
||||
fd = bpf_map__fd(skel->maps.ringbuf);
|
||||
if (pinned) {
|
||||
if (!ASSERT_OK_PTR(mkdtemp(pin_dir), "create pin directory"))
|
||||
goto out;
|
||||
dir_created = true;
|
||||
snprintf(pin_path, sizeof(pin_path), "%s/map", pin_dir);
|
||||
if (!ASSERT_OK(bpf_obj_pin(fd, pin_path), "pin ringbuf"))
|
||||
goto out;
|
||||
map_pinned = true;
|
||||
}
|
||||
position = mmap(NULL, page_size, PROT_READ, MAP_SHARED, fd, 0);
|
||||
if (!ASSERT_NEQ(position, MAP_FAILED, "consumer mmap"))
|
||||
goto out;
|
||||
if (!idle && (!emit_record(skel, 0) || !emit_record(skel, 1)))
|
||||
goto out;
|
||||
if (!consumer_start(&child, fd, format, pinned ? pin_path : NULL,
|
||||
NULL, false, false) ||
|
||||
!consumer_ready(&child))
|
||||
goto out;
|
||||
if (!idle) {
|
||||
/* Both prefilled records occupy 16 bytes including their headers. */
|
||||
if (!consumed(position, 32))
|
||||
goto out;
|
||||
pfd = (struct pollfd) { .fd = child.fd, .events = POLLIN };
|
||||
if (!ASSERT_GT(poll(&pfd, 1, WAIT_STEPS * WAIT_US / 1000), 0,
|
||||
"records flushed before exit") ||
|
||||
!ASSERT_TRUE(pfd.revents & POLLIN, "record output readable") ||
|
||||
!emit_record(skel, 2) || !consumed(position, 64))
|
||||
goto out;
|
||||
}
|
||||
if (!consumer_finish(&child, signo, true, output, sizeof(output)))
|
||||
goto out;
|
||||
if (format && !strcmp(format, "-p"))
|
||||
ASSERT_STREQ(output, idle ? "[]\n" :
|
||||
"[{\n"
|
||||
" \"size\": 2,\n"
|
||||
" \"data\": [0,255\n"
|
||||
" ]\n"
|
||||
" },{\n"
|
||||
" \"size\": 5,\n"
|
||||
" \"data\": [1,2,3,4,5\n"
|
||||
" ]\n"
|
||||
" },{\n"
|
||||
" \"size\": 17,\n"
|
||||
" \"data\": [16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32\n"
|
||||
" ]\n"
|
||||
" }\n"
|
||||
"]\n", "pretty JSON records");
|
||||
else if (format)
|
||||
check_json(output, idle ? "[]" :
|
||||
"[{\"size\":2,\"data\":[0,255]},"
|
||||
"{\"size\":5,\"data\":[1,2,3,4,5]},"
|
||||
"{\"size\":17,\"data\":[16,17,18,19,20,21,22,23,"
|
||||
"24,25,26,27,28,29,30,31,32]}]");
|
||||
else
|
||||
ASSERT_STREQ(output, idle ? "" :
|
||||
"== size: 2 =====\n00 ff\n"
|
||||
"== size: 5 =====\n01 02 03 04 05\n"
|
||||
"== size: 17 =====\n10 11 12 13 14 15 16 17 "
|
||||
"18 19 1a 1b 1c 1d 1e 1f\n20\n", "plain records");
|
||||
out:
|
||||
consumer_cleanup(&child);
|
||||
if (position != MAP_FAILED)
|
||||
munmap(position, page_size);
|
||||
if (map_pinned)
|
||||
ASSERT_OK(unlink(pin_path), "unpin ringbuf");
|
||||
if (dir_created)
|
||||
ASSERT_OK(rmdir(pin_dir), "remove pin directory");
|
||||
bpftool_ringbuf__destroy(skel);
|
||||
}
|
||||
|
||||
static void test_perf_consumer(void)
|
||||
{
|
||||
struct consumer child = { .pid = -1, .fd = -1 };
|
||||
unsigned long long seconds, nanoseconds;
|
||||
struct bpftool_ringbuf *skel;
|
||||
char output[16384], expected[16384];
|
||||
int nr_cpus, cpu, index, offset = 0, i, used = 0, fields;
|
||||
struct pollfd pfd;
|
||||
|
||||
nr_cpus = libbpf_num_possible_cpus();
|
||||
if (!ASSERT_GT(nr_cpus, 0, "possible cpus"))
|
||||
return;
|
||||
skel = bpftool_ringbuf__open();
|
||||
if (!ASSERT_OK_PTR(skel, "open"))
|
||||
return;
|
||||
bpf_map__set_max_entries(skel->maps.ringbuf, getpagesize());
|
||||
bpf_map__set_max_entries(skel->maps.perfbuf, nr_cpus);
|
||||
if (!ASSERT_OK(bpftool_ringbuf__load(skel), "load") ||
|
||||
!consumer_start(&child, bpf_map__fd(skel->maps.perfbuf), NULL,
|
||||
NULL, NULL, false, false) || !consumer_ready(&child) ||
|
||||
!emit_record(skel, 3))
|
||||
goto out;
|
||||
/* One large record flushes the existing buffered perf output callback. */
|
||||
pfd = (struct pollfd) { .fd = child.fd, .events = POLLIN };
|
||||
if (!ASSERT_GT(poll(&pfd, 1, WAIT_STEPS * WAIT_US / 1000), 0,
|
||||
"perf record output") ||
|
||||
!ASSERT_TRUE(pfd.revents & POLLIN, "perf output readable") ||
|
||||
!consumer_finish(&child, SIGINT, true, output, sizeof(output)))
|
||||
goto out;
|
||||
fields = sscanf(output, "== @%llu.%llu CPU: %d index: %d =====\n%n",
|
||||
&seconds, &nanoseconds, &cpu, &index, &offset);
|
||||
if (!ASSERT_EQ(fields, 4, "perf header") ||
|
||||
!ASSERT_GT(offset, 0, "perf payload offset"))
|
||||
goto out;
|
||||
ASSERT_GT(seconds * 1000000000ULL + nanoseconds, 0, "perf timestamp");
|
||||
ASSERT_LT(nanoseconds, 1000000000ULL, "perf timestamp nanoseconds");
|
||||
ASSERT_GE(cpu, 0, "perf cpu");
|
||||
ASSERT_LT(cpu, nr_cpus, "perf cpu range");
|
||||
ASSERT_EQ(index, cpu, "perf index");
|
||||
for (i = 0; i < sizeof(skel->rodata->perf_payload); i++) {
|
||||
const char *separator = !i ? "" : !(i % 16) ? "\n" :
|
||||
!(i % 8) ? " " : " ";
|
||||
|
||||
used += snprintf(expected + used, sizeof(expected) - used,
|
||||
"%s%02x", separator, i == 1 ? 0xff : 0);
|
||||
}
|
||||
snprintf(expected + used, sizeof(expected) - used, "\n");
|
||||
ASSERT_STREQ(output + offset, expected, "perf payload");
|
||||
out:
|
||||
consumer_cleanup(&child);
|
||||
bpftool_ringbuf__destroy(skel);
|
||||
}
|
||||
|
||||
static void test_reject(enum bpf_map_type type, const char *option, bool pair, bool json)
|
||||
{
|
||||
struct consumer child = { .pid = -1, .fd = -1 };
|
||||
bool ring = type == BPF_MAP_TYPE_RINGBUF || type == BPF_MAP_TYPE_USER_RINGBUF;
|
||||
const char *expected = option && !strcmp(option, "foobar") ?
|
||||
"{\"error\":\"what is 'foobar'?\"}" : option ?
|
||||
"{\"error\":\"ring buffer maps do not support cpu or index arguments\"}" :
|
||||
"{\"error\":\"map is not a perf event array or ring buffer\"}";
|
||||
char output[4096];
|
||||
int fd;
|
||||
|
||||
fd = bpf_map_create(type, NULL, ring ? 0 : 4, ring ? 0 : 4,
|
||||
ring ? getpagesize() : 1, NULL);
|
||||
if (!ASSERT_GE(fd, 0, "create map"))
|
||||
return;
|
||||
if (consumer_start(&child, fd, json ? "-j" : NULL, NULL, option, pair, true) &&
|
||||
consumer_finish(&child, 0, false, output, sizeof(output))) {
|
||||
if (json)
|
||||
check_json(output, expected);
|
||||
else
|
||||
ASSERT_GT(strlen(output), 0, "error diagnostic");
|
||||
}
|
||||
consumer_cleanup(&child);
|
||||
close(fd);
|
||||
}
|
||||
|
||||
void test_bpftool_ringbuf(void)
|
||||
{
|
||||
if (test__start_subtest("perf_event_array"))
|
||||
test_perf_consumer();
|
||||
if (test__start_subtest("plain"))
|
||||
test_consumer(NULL, false, SIGINT, false);
|
||||
if (test__start_subtest("json"))
|
||||
test_consumer("-j", false, SIGTERM, false);
|
||||
if (test__start_subtest("pretty_json"))
|
||||
test_consumer("-p", false, SIGINT, false);
|
||||
if (test__start_subtest("pinned_plain"))
|
||||
test_consumer(NULL, false, SIGTERM, true);
|
||||
if (test__start_subtest("pinned_json"))
|
||||
test_consumer("-j", false, SIGINT, true);
|
||||
if (test__start_subtest("pinned_pretty_json"))
|
||||
test_consumer("-p", false, SIGTERM, true);
|
||||
if (test__start_subtest("idle_sigint"))
|
||||
test_consumer(NULL, true, SIGINT, false);
|
||||
if (test__start_subtest("idle_sigterm_json"))
|
||||
test_consumer("-j", true, SIGTERM, false);
|
||||
if (test__start_subtest("idle_sigint_pretty_json"))
|
||||
test_consumer("-p", true, SIGINT, false);
|
||||
if (test__start_subtest("reject_array"))
|
||||
test_reject(BPF_MAP_TYPE_ARRAY, NULL, false, false);
|
||||
if (test__start_subtest("reject_user_ringbuf"))
|
||||
test_reject(BPF_MAP_TYPE_USER_RINGBUF, NULL, false, false);
|
||||
if (test__start_subtest("reject_cpu"))
|
||||
test_reject(BPF_MAP_TYPE_RINGBUF, "cpu", false, false);
|
||||
if (test__start_subtest("reject_index"))
|
||||
test_reject(BPF_MAP_TYPE_RINGBUF, "index", false, false);
|
||||
if (test__start_subtest("reject_cpu_index"))
|
||||
test_reject(BPF_MAP_TYPE_RINGBUF, "cpu", true, false);
|
||||
if (test__start_subtest("reject_user_ringbuf_json"))
|
||||
test_reject(BPF_MAP_TYPE_USER_RINGBUF, NULL, false, true);
|
||||
if (test__start_subtest("reject_unknown_json"))
|
||||
test_reject(BPF_MAP_TYPE_RINGBUF, "foobar", false, true);
|
||||
if (test__start_subtest("reject_cpu_index_json"))
|
||||
test_reject(BPF_MAP_TYPE_RINGBUF, "cpu", true, true);
|
||||
}
|
||||
47
tools/testing/selftests/bpf/progs/bpftool_ringbuf.c
Normal file
47
tools/testing/selftests/bpf/progs/bpftool_ringbuf.c
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
#include <linux/bpf.h>
|
||||
#include <bpf/bpf_helpers.h>
|
||||
|
||||
struct {
|
||||
__uint(type, BPF_MAP_TYPE_RINGBUF);
|
||||
} ringbuf SEC(".maps");
|
||||
|
||||
struct {
|
||||
__uint(type, BPF_MAP_TYPE_PERF_EVENT_ARRAY);
|
||||
__uint(key_size, sizeof(__u32));
|
||||
__uint(value_size, sizeof(__u32));
|
||||
__uint(max_entries, 1);
|
||||
} perfbuf SEC(".maps");
|
||||
|
||||
/*
|
||||
* Include the u32 raw size in perf's 8-byte alignment to avoid padding.
|
||||
* Keep plain output large enough to flush stdio before the consumer exits.
|
||||
*/
|
||||
const unsigned char perf_payload[4092] = { 0x00, 0xff };
|
||||
|
||||
int record;
|
||||
int output_err;
|
||||
|
||||
SEC("socket")
|
||||
int produce(struct __sk_buff *skb)
|
||||
{
|
||||
unsigned char first[] = { 0x00, 0xff };
|
||||
unsigned char second[] = { 1, 2, 3, 4, 5 };
|
||||
unsigned char third[] = {
|
||||
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
|
||||
0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20,
|
||||
};
|
||||
|
||||
if (record == 0)
|
||||
output_err = bpf_ringbuf_output(&ringbuf, first, sizeof(first), 0);
|
||||
else if (record == 1)
|
||||
output_err = bpf_ringbuf_output(&ringbuf, second, sizeof(second), 0);
|
||||
else if (record == 2)
|
||||
output_err = bpf_ringbuf_output(&ringbuf, third, sizeof(third), 0);
|
||||
else
|
||||
output_err = bpf_perf_event_output(skb, &perfbuf, BPF_F_CURRENT_CPU,
|
||||
(void *)perf_payload, sizeof(perf_payload));
|
||||
return 0;
|
||||
}
|
||||
|
||||
char LICENSE[] SEC("license") = "GPL";
|
||||
Loading…
Reference in New Issue
Block a user