From nobody Sat Sep 26 01:04:55 2026 Received: from out-2uec-a100.jellyfish.systems (out-2uec-a100.jellyfish.systems [63.250.43.100]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id A7FB435A93C for ; Sun, 6 Sep 2026 17:08:17 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=63.250.43.100 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788714499; cv=none; b=togaExLkoyiEjoTawhFWQP4l/HmIFQBO3V3oLYG+XRcBQvZm4XdojjSWbZGDfX6+jJy3XANKM9PiuNAffNeMLGcJ1Wje7HVPZVQhbnAieA1dB+sMB5rgRrFlURbM4rZDAvIOkqTgUPGfgzlC6O65juorNmCOGe+xgUGJ1FDllBk= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788714499; c=relaxed/simple; bh=DS9iwohj+c+XpqsXPIoaYl5JW0FQadBGMTyGtZVVb/s=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=sCA5Son0HLKq/zKaws53PH3lG9Mp/Dyx55+RDjEa7AF+DnaAQCqgTNeDe4oCU9VEPBb3FLPwNabC7PCHeFglutH2M8UH5hGGHyQ67au4oqnlDFp0XGCGFdDBGh0VietEYcZtWKg26/uD0NtqsbV58QFb87XUs6AmftF7nurmT2g= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=tychen.cc; spf=pass smtp.mailfrom=tychen.cc; dkim=pass (2048-bit key) header.d=tychen.cc header.i=@tychen.cc header.b=NIB3l7Mi; arc=none smtp.client-ip=63.250.43.100 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=tychen.cc Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=tychen.cc Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=tychen.cc header.i=@tychen.cc header.b="NIB3l7Mi" Received: from fedora (unknown [69.5.53.41]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mail.spacemail.com (Postfix) with ESMTPSA id 4hdGqR349Yz8sWP; Sun, 06 Sep 2026 17:08:03 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=tychen.cc; s=spacemail; t=1788714490; bh=2UZ6a0ZXQ/tONmzFl9gXaGavlzhwmEH+J/CxZ37mLwQ=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=NIB3l7MiZmYbSE5sj0tT7dlSoMc8soZSqKvmm2iQjnd1mOEoRIa6qnWTY67n5HPRc zpsvt+O/SsjGH7/vZcn5t15bVyTW3P3yVL0pPwb9PpeQMlmZC53cXLSmM5xLxf7mj2 csbP6g2KLt+JFh7G9kMxuGVXwCFYXuASbeJpchWiFd3O8oVX1Ko+vzx2AGsCkVV4or JB5AHy0l6g9LR47uxCXTlc6j2p1YC5C37SJyyt+tuOyOX1kMlHdRJ1IcKqODyV55wF BJwn4c35jI4hdl+2YSqCcfjkIuepqqzd9m8+mIAl5G7Hz2Maou9Ipzkn9eOexWiP25 ELAlf/mq9O4Kg== From: Tianyi Chen To: bpf@vger.kernel.org Cc: Tianyi Chen , Quentin Monnet , Alexei Starovoitov , Daniel Borkmann , Andrii Nakryiko , Eduard Zingerman , Kumar Kartikeya Dwivedi , Martin KaFai Lau , Song Liu , Yonghong Song , Jiri Olsa , Emil Tsalapatis , Ihor Solodrai , Shuah Khan , linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org Subject: [PATCH bpf-next 1/2] bpftool: Read ring buffer maps with event_pipe Date: Mon, 7 Sep 2026 01:07:46 +0800 Message-ID: <20260906170747.1212378-2-hi@tychen.cc> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260906170747.1212378-1-hi@tychen.cc> References: <20260906170747.1212378-1-hi@tychen.cc> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Envelope-From: hi@tychen.cc Content-Type: text/plain; charset="utf-8" Allow map event_pipe to consume BPF_MAP_TYPE_RINGBUF maps using libbpf's ring buffer manager. Print each record's size and raw bytes in plain or JSON output, and flush output as records arrive. Reject CPU and index selectors for ring buffers and retain the existing perf event array path. Keep signal handlers limited to setting a stop flag, reset that flag for each command, and print the stopping message on stderr from the main path. Close the JSON array on poll errors and preserve output errors returned by the ring buffer callback. Document that consuming a ring buffer advances its shared consumer position and add ring buffer maps to event_pipe completion. Link: https://github.com/libbpf/bpftool/issues/54 Assisted-by: Codex:GPT-6 Signed-off-by: Tianyi Chen --- .../bpf/bpftool/Documentation/bpftool-map.rst | 15 +++- tools/bpf/bpftool/bash-completion/bpftool | 4 +- tools/bpf/bpftool/map_perf_ring.c | 87 ++++++++++++++----- 3 files changed, 82 insertions(+), 24 deletions(-) diff --git a/tools/bpf/bpftool/Documentation/bpftool-map.rst b/tools/bpf/bp= ftool/Documentation/bpftool-map.rst index 5daf3de5c74..c44e6f797e5 100644 --- a/tools/bpf/bpftool/Documentation/bpftool-map.rst +++ b/tools/bpf/bpftool/Documentation/bpftool-map.rst @@ -120,7 +120,8 @@ bpftool map pin *MAP* *FILE* character ('.'), which is reserved for future extensions of *bpffs*. =20 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. =20 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,18 @@ bpftool map event_pipe *MAP* [cpu *N* index *M*] existing ring. Any other application will stop receiving events if it installed its rings earlier. =20 + For a ring buffer map, consume records submitted by BPF programs, incl= uding + records already queued before the command starts. **cpu** and **index** + are not supported. Each record is printed as raw bytes, including embe= dded + zero bytes. Plain output reports the record size followed by hexadecim= al + bytes; JSON output contains **size** and **data** fields, with **data*= * an + array of byte values. Ring buffer records have no implicit CPU or time= stamp. + + Ring buffers support a single consumer. This command advances the shar= ed + consumer position and must not run alongside another consumer of the s= ame + map; it does not provide a passive view of events. **BPF_MAP_TYPE_USER= _RINGBUF** + maps are not supported. + bpftool map peek *MAP* Peek next value in the queue or stack. =20 diff --git a/tools/bpf/bpftool/bash-completion/bpftool b/tools/bpf/bpftool/= bash-completion/bpftool index 75cbcb512eb..1750b488c9e 100644 --- a/tools/bpf/bpftool/bash-completion/bpftool +++ b/tools/bpf/bpftool/bash-completion/bpftool @@ -878,11 +878,11 @@ _bpftool() return 0 ;; id) - _bpftool_get_map_ids_for_type perf_event_array + _bpftool_get_map_ids_for_type '"type": "\(perf= _event_array\|ringbuf\)"' return 0 ;; name) - _bpftool_get_map_names_for_type perf_event_arr= ay + _bpftool_get_map_names_for_type '"type": "\(pe= rf_event_array\|ringbuf\)"' return 0 ;; cpu) diff --git a/tools/bpf/bpftool/map_perf_ring.c b/tools/bpf/bpftool/map_perf= _ring.c index bcb767e2d67..7d555331f44 100644 --- a/tools/bpf/bpftool/map_perf_ring.c +++ b/tools/bpf/bpftool/map_perf_ring.c @@ -27,7 +27,7 @@ =20 #define MMAP_PAGE_CNT 16 =20 -static volatile bool stop; +static volatile sig_atomic_t stop; =20 struct perf_event_sample { struct perf_event_header header; @@ -44,7 +44,6 @@ struct perf_event_lost { =20 static void int_exit(int signo) { - fprintf(stderr, "Stopping...\n"); stop =3D true; } =20 @@ -107,6 +106,27 @@ print_bpf_output(void *private_data, int cpu, struct p= erf_event_header *event) return LIBBPF_PERF_EVENT_CONT; } =20 +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("=3D=3D size: %zu =3D=3D=3D=3D=3D\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 =3D { @@ -123,18 +143,26 @@ int do_event_pipe(int argc, char **argv) .cpu =3D -1, .idx =3D -1, }; - struct perf_buffer *pb; + struct perf_buffer *pb =3D NULL; + struct ring_buffer *rb =3D NULL; __u32 map_info_len; int err, map_fd; =20 + stop =3D false; map_info_len =3D sizeof(map_info); map_fd =3D map_parse_fd_and_info(&argc, &argv, &map_info, &map_info_len, 0); if (map_fd < 0) return -1; =20 - if (map_info.type !=3D BPF_MAP_TYPE_PERF_EVENT_ARRAY) { - p_err("map is not a perf event array"); + if (map_info.type !=3D BPF_MAP_TYPE_PERF_EVENT_ARRAY && + map_info.type !=3D BPF_MAP_TYPE_RINGBUF) { + p_err("map is not a perf event array or ring buffer"); + goto err_close_map; + } + + if (map_info.type =3D=3D BPF_MAP_TYPE_RINGBUF && argc) { + p_err("ring buffer maps do not support cpu or index arguments"); goto err_close_map; } =20 @@ -184,15 +212,24 @@ int do_event_pipe(int argc, char **argv) ctx.idx =3D 0; } =20 - opts.cpu_cnt =3D ctx.all_cpus ? 0 : 1; - opts.cpus =3D &ctx.cpu; - opts.map_keys =3D &ctx.idx; - pb =3D 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 =3D=3D BPF_MAP_TYPE_RINGBUF) { + rb =3D 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 =3D ctx.all_cpus ? 0 : 1; + opts.cpus =3D &ctx.cpu; + opts.map_keys =3D &ctx.idx; + pb =3D 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; + } } =20 signal(SIGINT, int_exit); @@ -202,25 +239,33 @@ int do_event_pipe(int argc, char **argv) if (json_output) jsonw_start_array(json_wtr); =20 + err =3D 0; while (!stop) { - err =3D perf_buffer__poll(pb, 200); + err =3D rb ? ring_buffer__poll(rb, 200) : perf_buffer__poll(pb, 200); if (err < 0 && err !=3D -EINTR) { - p_err("perf buffer polling failed: %s (%d)", - strerror(errno), errno); - goto err_close_pb; + fprintf(stderr, "Error: %s buffer polling failed: %s (%d)\n", + rb ? "ring" : "perf", strerror(-err), -err); + break; } + err =3D 0; } =20 + if (stop) + fprintf(stderr, "Stopping...\n"); if (json_output) jsonw_end_array(json_wtr); + if (fflush(stdout)) { + fprintf(stderr, "Error: failed to write events: %s\n", strerror(errno)); + err =3D -1; + } =20 + ring_buffer__free(rb); perf_buffer__free(pb); + /* Both buffer managers borrow map_fd. */ close(map_fd); =20 - return 0; + return err < 0 ? -1 : 0; =20 -err_close_pb: - perf_buffer__free(pb); err_close_map: close(map_fd); return -1; --=20 2.55.0 From nobody Sat Sep 26 01:04:55 2026 Received: from out-zbxj-a77.jellyfish.systems (out-zbxj-a77.jellyfish.systems [198.54.127.77]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 1E8503A963A for ; Sun, 6 Sep 2026 17:08:36 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=198.54.127.77 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788714520; cv=none; b=iOByxeIV4WCs3BjyBEGuxAF/FHwFnS4ym4n76UFfdG0qzD6U2XQYgnd8vRQZzu4YryqEqnuYWY+NWGVq3+RWSLkuGPou6eIusGp0iqAE+9vtmuvtaNPfqS4SJ0W0HFfg6Qyv96UJgLy2WyzXuvrXNbB6hxtbdKBGQV5Tjd3UCDA= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788714520; c=relaxed/simple; bh=Xvw1+VHZ09mD1fLpUZHFGVtJ66gzAkZUw+EA9SImGBw=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=ktdikegWbfcJ+5SA37GyXbqygr21rlUSx3EiPXr8oES300nvVYDgkHALiX4ent0YTJXpSy3BqKF7msVAZoCkcBSLN0oGo9fHbkZeWlDMYzzz+w8UvUCLKqPYJniYeLfNQifn27JAxLvC0xtDp+ogewbUy1lR9ImY8LXl304LySs= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=tychen.cc; spf=pass smtp.mailfrom=tychen.cc; dkim=pass (2048-bit key) header.d=tychen.cc header.i=@tychen.cc header.b=Tk61iM2a; arc=none smtp.client-ip=198.54.127.77 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=tychen.cc Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=tychen.cc Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=tychen.cc header.i=@tychen.cc header.b="Tk61iM2a" Received: from fedora (unknown [69.5.53.41]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mail.spacemail.com (Postfix) with ESMTPSA id 4hdGqb0G3yz8sWR; Sun, 06 Sep 2026 17:08:10 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=tychen.cc; s=spacemail; t=1788714498; bh=/MdlgTeuE3uztSOZKTAUbSHOQgduElJ/go9RNr7SVsE=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=Tk61iM2aCu5amOyazVZX/26QVpqd16oURGeHlxMXuCJMrRBjyPlF9Dw3OlreewhpS gElqrA/0q7Jc9NxM9oopiMdwmDJD5ItZ17YBmidVAnaFPeUhQ3W0CiK9Z2P43/nHpU vMsechOmm3RCUmX71s5xzlrywWy8qtV3gSJpsbXX6bBCNMiBFMCI4j+9jPWK0ATJgR gIJX5hdtE5bzmdoBmbRZKed7tzKH5iMNlNVdWDMRb0wKD/wLm/qJSRUoyFvCHa2+UM jAOndy9DGSJDGyu+YpcxVJu7Ap8qzEVw20PzlkdSROIs8piGouF85ZYulMi5k7BbjO rc0BwmHwdubTQ== From: Tianyi Chen To: bpf@vger.kernel.org Cc: Tianyi Chen , Quentin Monnet , Alexei Starovoitov , Daniel Borkmann , Andrii Nakryiko , Eduard Zingerman , Kumar Kartikeya Dwivedi , Martin KaFai Lau , Song Liu , Yonghong Song , Jiri Olsa , Emil Tsalapatis , Ihor Solodrai , Shuah Khan , linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org Subject: [PATCH bpf-next 2/2] selftests/bpf: Cover bpftool ring buffer event_pipe Date: Mon, 7 Sep 2026 01:07:47 +0800 Message-ID: <20260906170747.1212378-3-hi@tychen.cc> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260906170747.1212378-1-hi@tychen.cc> References: <20260906170747.1212378-1-hi@tychen.cc> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Envelope-From: hi@tychen.cc Content-Type: text/plain; charset="utf-8" Produce known ring buffer records and check complete plain, JSON and pretty JSON output. Exercise ID and pinned map selection, SIGINT and SIGTERM shutdown, empty streams and invalid map types or selectors. Also produce a perf event sample and check its existing header and raw payload output. Use a payload whose size plus the raw sample length field is aligned to eight bytes so the expected bytes exclude implicit perf padding. Assisted-by: Codex:GPT-6 Signed-off-by: Tianyi Chen --- .../bpf/prog_tests/bpftool_ringbuf.c | 381 ++++++++++++++++++ .../selftests/bpf/progs/bpftool_ringbuf.c | 47 +++ 2 files changed, 428 insertions(+) create mode 100644 tools/testing/selftests/bpf/prog_tests/bpftool_ringbuf.c create mode 100644 tools/testing/selftests/bpf/progs/bpftool_ringbuf.c diff --git a/tools/testing/selftests/bpf/prog_tests/bpftool_ringbuf.c b/too= ls/testing/selftests/bpf/prog_tests/bpftool_ringbuf.c new file mode 100644 index 00000000000..2099bf10209 --- /dev/null +++ b/tools/testing/selftests/bpf/prog_tests/bpftool_ringbuf.c @@ -0,0 +1,381 @@ +// SPDX-License-Identifier: GPL-2.0 +#include +#include +#include +#include +#include +#include +#include +#include "bpftool_ringbuf.skel.h" + +#define WAIT_STEPS 500 +#define WAIT_US 10000 + +struct consumer { + pid_t pid; + int fd; +}; + +static const char *bpftool_path(void) +{ + const char *path =3D getenv("BPFTOOL"); + + if (path) + return access(path, X_OK) ? NULL : path; + if (!access("./tools/sbin/bpftool", X_OK)) + return "./tools/sbin/bpftool"; + if (!access("../tools/sbin/bpftool", X_OK)) + return "../tools/sbin/bpftool"; + return NULL; +} + +static void consumer_cleanup(struct consumer *child) +{ + if (child->pid > 0) { + kill(child->pid, SIGKILL); + while (waitpid(child->pid, NULL, 0) < 0 && errno =3D=3D EINTR) + ; + child->pid =3D -1; + } + if (child->fd >=3D 0) { + close(child->fd); + child->fd =3D -1; + } +} + +static bool consumer_start(struct consumer *child, int map_fd, bool json, + const char *option, bool pair, bool capture_errors) +{ + struct bpf_map_info info =3D {}; + __u32 len =3D sizeof(info); + const char *path =3D bpftool_path(); + char *argv[12], id[16]; + int out[2], ready[2], n =3D 0, err, err_fd; + struct pollfd pfd; + + if (!ASSERT_OK_PTR(path, "bpftool path (set BPFTOOL to override)") || + !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++] =3D (char *)path; + if (json) + argv[n++] =3D "-j"; + argv[n++] =3D "map"; + argv[n++] =3D "event_pipe"; + argv[n++] =3D "id"; + argv[n++] =3D id; + if (option) { + argv[n++] =3D (char *)option; + argv[n++] =3D "0"; + if (pair) { + argv[n++] =3D "index"; + argv[n++] =3D "0"; + } + } + argv[n] =3D 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 =3D fork(); + if (!child->pid) { + close(out[0]); + close(ready[0]); + err_fd =3D 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 =3D errno; + write(ready[1], &err, sizeof(err)); + _exit(127); + } + close(out[1]); + close(ready[1]); + child->fd =3D out[0]; + pfd =3D (struct pollfd) { .fd =3D ready[0], .events =3D POLLIN }; + /* EOF on the close-on-exec pipe distinguishes exec from inherited handle= rs. */ + err =3D 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 succes= s, + char *output, size_t size) +{ + int status =3D 0, i; + pid_t ret =3D 0; + ssize_t n; + size_t used =3D 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 =3D 0; i < WAIT_STEPS; i++) { + while (used < size - 1 && + (n =3D read(child->fd, output + used, size - 1 - used)) > 0) + used +=3D n; + ret =3D waitpid(child->pid, &status, WNOHANG); + if (ret =3D=3D child->pid) + break; + if (ret < 0 && errno !=3D EINTR) + break; + usleep(WAIT_US); + } + if (!ASSERT_EQ(ret, child->pid, "bounded consumer exit")) + return false; + child->pid =3D -1; + while (used < size - 1 && (n =3D read(child->fd, output + used, size - 1 = - used)) > 0) + used +=3D n; + output[used] =3D '\0'; + return ASSERT_TRUE(WIFEXITED(status), "normal exit") && + ASSERT_EQ(WEXITSTATUS(status) =3D=3D 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 =3D 0; i < WAIT_STEPS; i++) { + f =3D fopen(path, "r"); + if (!f) + break; + while (fgets(line, sizeof(line), f)) { + if (sscanf(line, "SigCgt: %llx", &caught) =3D=3D 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] =3D {}; + + LIBBPF_OPTS(bpf_test_run_opts, opts, + .data_in =3D packet, + .data_size_in =3D sizeof(packet), + ); + + skel->bss->record =3D record; + return ASSERT_OK(bpf_prog_test_run_opts(bpf_program__fd(skel->progs.produ= ce), + &opts), "produce record") && + ASSERT_OK(skel->bss->output_err, "ringbuf output"); +} + +static bool consumed(unsigned long *position, unsigned long expected) +{ + int i; + + for (i =3D 0; i < WAIT_STEPS; i++) { + if (__atomic_load_n(position, __ATOMIC_ACQUIRE) =3D=3D expected) + return true; + usleep(WAIT_US); + } + return ASSERT_EQ(*position, expected, "consumer position"); +} + +static void check_json(char *output, const char *expected) +{ + char *src =3D output, *dst =3D output; + bool quoted =3D false, escaped =3D false; + + /* Ignore formatting whitespace while checking the entire JSON document. = */ + while (*src) { + if (quoted || !isspace((unsigned char)*src)) + *dst++ =3D *src; + if (!escaped && *src =3D=3D '"') + quoted =3D !quoted; + escaped =3D !escaped && quoted && *src =3D=3D '\\'; + src++; + } + *dst =3D '\0'; + ASSERT_STREQ(output, expected, "JSON records"); +} + +static void test_consumer(bool json, bool idle, int signo) +{ + struct consumer child =3D { .pid =3D -1, .fd =3D -1 }; + struct bpftool_ringbuf *skel; + unsigned long *position =3D MAP_FAILED; + int page_size =3D getpagesize(), fd; + char output[4096]; + struct pollfd pfd; + + skel =3D 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 =3D bpf_map__fd(skel->maps.ringbuf); + position =3D 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, json, 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 =3D (struct pollfd) { .fd =3D child.fd, .events =3D 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 (json) + 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 ? "" : + "=3D=3D size: 2 =3D=3D=3D=3D=3D\n00 ff\n" + "=3D=3D size: 5 =3D=3D=3D=3D=3D\n01 02 03 04 05\n" + "=3D=3D size: 17 =3D=3D=3D=3D=3D\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 !=3D MAP_FAILED) + munmap(position, page_size); + bpftool_ringbuf__destroy(skel); +} + +static void test_perf_consumer(void) +{ + struct consumer child =3D { .pid =3D -1, .fd =3D -1 }; + unsigned long long seconds, nanoseconds; + struct bpftool_ringbuf *skel; + char output[16384], expected[16384]; + int nr_cpus, cpu, index, offset =3D 0, i, used =3D 0, fields; + struct pollfd pfd; + + nr_cpus =3D libbpf_num_possible_cpus(); + if (!ASSERT_GT(nr_cpus, 0, "possible cpus")) + return; + skel =3D 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), false, + NULL, false, false) || !consumer_ready(&child) || + !emit_record(skel, 3)) + goto out; + /* One large record flushes the existing buffered perf output callback. */ + pfd =3D (struct pollfd) { .fd =3D child.fd, .events =3D 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 =3D sscanf(output, "=3D=3D @%llu.%llu CPU: %d index: %d =3D=3D=3D= =3D=3D\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 =3D 0; i < sizeof(skel->rodata->perf_payload); i++) { + const char *separator =3D !i ? "" : !(i % 16) ? "\n" : + !(i % 8) ? " " : " "; + + used +=3D snprintf(expected + used, sizeof(expected) - used, + "%s%02x", separator, i =3D=3D 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 p= air, bool json) +{ + struct consumer child =3D { .pid =3D -1, .fd =3D -1 }; + bool ring =3D type =3D=3D BPF_MAP_TYPE_RINGBUF || type =3D=3D BPF_MAP_TYP= E_USER_RINGBUF; + const char *expected =3D 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 =3D 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, 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(false, false, SIGINT); + if (test__start_subtest("json")) + test_consumer(true, false, SIGTERM); + if (test__start_subtest("idle_sigint")) + test_consumer(false, true, SIGINT); + if (test__start_subtest("idle_sigterm_json")) + test_consumer(true, true, SIGTERM); + 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_cpu_index_json")) + test_reject(BPF_MAP_TYPE_RINGBUF, "cpu", true, true); +} diff --git a/tools/testing/selftests/bpf/progs/bpftool_ringbuf.c b/tools/te= sting/selftests/bpf/progs/bpftool_ringbuf.c new file mode 100644 index 00000000000..bbbc93c30a8 --- /dev/null +++ b/tools/testing/selftests/bpf/progs/bpftool_ringbuf.c @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: GPL-2.0 +#include +#include + +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] =3D { 0x00, 0xff }; + +int record; +int output_err; + +SEC("socket") +int produce(struct __sk_buff *skb) +{ + unsigned char first[] =3D { 0x00, 0xff }; + unsigned char second[] =3D { 1, 2, 3, 4, 5 }; + unsigned char third[] =3D { + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, + }; + + if (record =3D=3D 0) + output_err =3D bpf_ringbuf_output(&ringbuf, first, sizeof(first), 0); + else if (record =3D=3D 1) + output_err =3D bpf_ringbuf_output(&ringbuf, second, sizeof(second), 0); + else if (record =3D=3D 2) + output_err =3D bpf_ringbuf_output(&ringbuf, third, sizeof(third), 0); + else + output_err =3D bpf_perf_event_output(skb, &perfbuf, BPF_F_CURRENT_CPU, + (void *)perf_payload, sizeof(perf_payload)); + return 0; +} + +char LICENSE[] SEC("license") =3D "GPL"; --=20 2.55.0