Linux Kernel Selftest development
 help / color / mirror / Atom feed
* [PATCH bpf-next v5 0/2] bpftool: Consume ring buffer maps with event_pipe
@ 2026-09-21  7:35 Tianyi Chen
  2026-09-21  7:35 ` [PATCH bpf-next v5 1/2] bpftool: Read " Tianyi Chen
                   ` (3 more replies)
  0 siblings, 4 replies; 8+ messages in thread
From: Tianyi Chen @ 2026-09-21  7:35 UTC (permalink / raw)
  To: bpf; +Cc: qmo, andrii, eddyz87, ihor.solodrai, linux-kselftest

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

Tianyi Chen (2):
  bpftool: Read ring buffer maps with event_pipe
  selftests/bpf: Cover bpftool ring buffer event_pipe

 .../bpf/bpftool/Documentation/bpftool-map.rst |  14 +-
 tools/bpf/bpftool/bash-completion/bpftool     |  59 +--
 tools/bpf/bpftool/map_perf_ring.c             |  87 +++-
 tools/testing/selftests/bpf/bpftool_helpers.c |   2 +-
 tools/testing/selftests/bpf/bpftool_helpers.h |   1 +
 .../bpf/prog_tests/bpftool_ringbuf.c          | 417 ++++++++++++++++++
 .../selftests/bpf/progs/bpftool_ringbuf.c     |  47 ++
 7 files changed, 578 insertions(+), 49 deletions(-)
 create mode 100644 tools/testing/selftests/bpf/prog_tests/bpftool_ringbuf.c
 create mode 100644 tools/testing/selftests/bpf/progs/bpftool_ringbuf.c


base-commit: 48ea1ba704aa3a9975e917411ee05bbe67d65744
-- 
2.55.0


^ permalink raw reply	[flat|nested] 8+ messages in thread

* [PATCH bpf-next v5 1/2] bpftool: Read ring buffer maps with event_pipe
  2026-09-21  7:35 [PATCH bpf-next v5 0/2] bpftool: Consume ring buffer maps with event_pipe Tianyi Chen
@ 2026-09-21  7:35 ` Tianyi Chen
  2026-09-21  8:34   ` bot+bpf-ci
  2026-09-21  9:23   ` Quentin Monnet
  2026-09-21  7:35 ` [PATCH bpf-next v5 2/2] selftests/bpf: Cover bpftool ring buffer event_pipe Tianyi Chen
                   ` (2 subsequent siblings)
  3 siblings, 2 replies; 8+ messages in thread
From: Tianyi Chen @ 2026-09-21  7:35 UTC (permalink / raw)
  To: bpf; +Cc: qmo, andrii, eddyz87, ihor.solodrai, linux-kselftest

map event_pipe only accepts perf event arrays, leaving no built-in way
to inspect records produced through the BPF ring buffer API. Extend it
to consume queued and live BPF_MAP_TYPE_RINGBUF records in plain or
JSON output, retaining the existing perf event array path.

Ring buffers have a shared consumer position and no implicit CPU or
timestamp. Document that this command consumes records rather than
observing them passively, and reject perf-only CPU/index selectors.

A producer can keep ring_buffer__poll() busy after a stop signal, so
return -EINTR from the record callback when stopping. Keep stdio out of
the shared signal handler and preserve callback output errors.

Link: https://github.com/libbpf/bpftool/issues/54

Assisted-by: LLM
Signed-off-by: Tianyi Chen <hi@tychen.cc>
---
 .../bpf/bpftool/Documentation/bpftool-map.rst | 14 ++-
 tools/bpf/bpftool/bash-completion/bpftool     | 59 +++++++------
 tools/bpf/bpftool/map_perf_ring.c             | 87 ++++++++++++++-----
 3 files changed, 112 insertions(+), 48 deletions(-)

diff --git a/tools/bpf/bpftool/Documentation/bpftool-map.rst b/tools/bpf/bpftool/Documentation/bpftool-map.rst
index 5daf3de5c744..e4ed7e701c9e 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*.
 
 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.
 
diff --git a/tools/bpf/bpftool/bash-completion/bpftool b/tools/bpf/bpftool/bash-completion/bpftool
index 75cbcb512eba..c9e8761e4ef2 100644
--- a/tools/bpf/bpftool/bash-completion/bpftool
+++ b/tools/bpf/bpftool/bash-completion/bpftool
@@ -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
diff --git a/tools/bpf/bpftool/map_perf_ring.c b/tools/bpf/bpftool/map_perf_ring.c
index bcb767e2d673..5878938cf8d9 100644
--- a/tools/bpf/bpftool/map_perf_ring.c
+++ b/tools/bpf/bpftool/map_perf_ring.c
@@ -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;
-- 
2.55.0


^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [PATCH bpf-next v5 2/2] selftests/bpf: Cover bpftool ring buffer event_pipe
  2026-09-21  7:35 [PATCH bpf-next v5 0/2] bpftool: Consume ring buffer maps with event_pipe Tianyi Chen
  2026-09-21  7:35 ` [PATCH bpf-next v5 1/2] bpftool: Read " Tianyi Chen
@ 2026-09-21  7:35 ` Tianyi Chen
  2026-09-21  8:48   ` bot+bpf-ci
  2026-09-21  9:18 ` [PATCH bpf-next v5 0/2] bpftool: Consume ring buffer maps with event_pipe Quentin Monnet
  2026-09-21 22:10 ` patchwork-bot+netdevbpf
  3 siblings, 1 reply; 8+ messages in thread
From: Tianyi Chen @ 2026-09-21  7:35 UTC (permalink / raw)
  To: bpf; +Cc: qmo, andrii, eddyz87, ihor.solodrai, linux-kselftest

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: LLM
Signed-off-by: Tianyi Chen <hi@tychen.cc>
---
 tools/testing/selftests/bpf/bpftool_helpers.c |   2 +-
 tools/testing/selftests/bpf/bpftool_helpers.h |   1 +
 .../bpf/prog_tests/bpftool_ringbuf.c          | 417 ++++++++++++++++++
 .../selftests/bpf/progs/bpftool_ringbuf.c     |  47 ++
 4 files changed, 466 insertions(+), 1 deletion(-)
 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/bpftool_helpers.c b/tools/testing/selftests/bpf/bpftool_helpers.c
index c49fdd90eb03..9380b24bbf52 100644
--- a/tools/testing/selftests/bpf/bpftool_helpers.c
+++ b/tools/testing/selftests/bpf/bpftool_helpers.c
@@ -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;
diff --git a/tools/testing/selftests/bpf/bpftool_helpers.h b/tools/testing/selftests/bpf/bpftool_helpers.h
index dec1ba201410..17e3be8e7073 100644
--- a/tools/testing/selftests/bpf/bpftool_helpers.h
+++ b/tools/testing/selftests/bpf/bpftool_helpers.h
@@ -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);
diff --git a/tools/testing/selftests/bpf/prog_tests/bpftool_ringbuf.c b/tools/testing/selftests/bpf/prog_tests/bpftool_ringbuf.c
new file mode 100644
index 000000000000..e9724c55d1dc
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/bpftool_ringbuf.c
@@ -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);
+}
diff --git a/tools/testing/selftests/bpf/progs/bpftool_ringbuf.c b/tools/testing/selftests/bpf/progs/bpftool_ringbuf.c
new file mode 100644
index 000000000000..bbbc93c30a83
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/bpftool_ringbuf.c
@@ -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";
-- 
2.55.0


^ permalink raw reply related	[flat|nested] 8+ messages in thread

* Re: [PATCH bpf-next v5 1/2] bpftool: Read ring buffer maps with event_pipe
  2026-09-21  7:35 ` [PATCH bpf-next v5 1/2] bpftool: Read " Tianyi Chen
@ 2026-09-21  8:34   ` bot+bpf-ci
  2026-09-21  9:23   ` Quentin Monnet
  1 sibling, 0 replies; 8+ messages in thread
From: bot+bpf-ci @ 2026-09-21  8:34 UTC (permalink / raw)
  To: hi, bpf
  Cc: qmo, andrii, eddyz87, ihor.solodrai, linux-kselftest, ast, andrii,
	daniel, martin.lau, eddyz87, yonghong.song, mason, ihor.solodrai

[-- Attachment #1: Type: text/plain, Size: 2141 bytes --]

> diff --git a/tools/bpf/bpftool/bash-completion/bpftool b/tools/bpf/bpftool/bash-completion/bpftool
> index 75cbcb512ebae..c9e8761e4ef2a 100644
> --- a/tools/bpf/bpftool/bash-completion/bpftool
> +++ b/tools/bpf/bpftool/bash-completion/bpftool

[ ... ]

> @@ -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" ) )
>  }

This isn't a bug, but the switch from a bare substring grep to an
anchored '"type": "..."' match also tightens the existing
stack/queue/struct_ops completions. For example, 'bpftool map peek id'
previously matched "type": "stack_trace" because it contained the
substring 'stack', so stack_trace map IDs were suggested; they no longer
are. Maps whose name contained 'stack' or 'queue' were likewise picked
up via the -C2 window and no longer are.

The tightening is well motivated for the new ringbuf call (it keeps
user_ringbuf out of the event_pipe suggestions), and every type string
used is an exact libbpf map_type_name[] entry, so nothing is wrongly
excluded. Would it be worth a line in the changelog about this behavior
change to unrelated completions, or splitting the anchoring out from the
ringbuf support?


---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md

CI run summary: https://github.com/kernel-patches/bpf/actions/runs/35575071830

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH bpf-next v5 2/2] selftests/bpf: Cover bpftool ring buffer event_pipe
  2026-09-21  7:35 ` [PATCH bpf-next v5 2/2] selftests/bpf: Cover bpftool ring buffer event_pipe Tianyi Chen
@ 2026-09-21  8:48   ` bot+bpf-ci
  0 siblings, 0 replies; 8+ messages in thread
From: bot+bpf-ci @ 2026-09-21  8:48 UTC (permalink / raw)
  To: hi, bpf
  Cc: qmo, andrii, eddyz87, ihor.solodrai, linux-kselftest, ast, andrii,
	daniel, martin.lau, eddyz87, yonghong.song, mason, ihor.solodrai

[-- Attachment #1: Type: text/plain, Size: 4356 bytes --]

> diff --git a/tools/testing/selftests/bpf/bpftool_helpers.c b/tools/testing/selftests/bpf/bpftool_helpers.c
> index c49fdd90eb033..9380b24bbf52b 100644
> --- a/tools/testing/selftests/bpf/bpftool_helpers.c
> +++ b/tools/testing/selftests/bpf/bpftool_helpers.c
> @@ -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;
> diff --git a/tools/testing/selftests/bpf/bpftool_helpers.h b/tools/testing/selftests/bpf/bpftool_helpers.h
> index dec1ba201410f..17e3be8e70730 100644
> --- a/tools/testing/selftests/bpf/bpftool_helpers.h
> +++ b/tools/testing/selftests/bpf/bpftool_helpers.h
> @@ -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);
> diff --git a/tools/testing/selftests/bpf/prog_tests/bpftool_ringbuf.c b/tools/testing/selftests/bpf/prog_tests/bpftool_ringbuf.c
> new file mode 100644
> index 0000000000000..e9724c55d1dc6
> --- /dev/null
> +++ b/tools/testing/selftests/bpf/prog_tests/bpftool_ringbuf.c

[ ... ]

> +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");
> +}

This isn't a bug, but since emit_record() also drives the
bpf_perf_event_output() path for record 3, would a buffer-neutral tag
like "record output" read better here than "ringbuf output"?

[ ... ]

> +static void test_consumer(const char *format, bool idle, int signo, bool pinned)
> +{

[ ... ]

> +	if (!idle) {
> +		/* Both prefilled records occupy 16 bytes including their headers. */
> +		if (!consumed(position, 32))
> +			goto out;

This isn't a bug, but would it read more clearly as something like "each
prefilled record occupies 16 bytes including its header", so the 32 below
follows directly from the comment?

[ ... ]

> +static void test_perf_consumer(void)
> +{

[ ... ]

> +	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") ||

This isn't a bug, but could this comment spell out that the perf callback
does not fflush() and the payload is sized so its hex output overflows
stdio's pipe buffer, so the poll() below has something to see?

[ ... ]

> diff --git a/tools/testing/selftests/bpf/progs/bpftool_ringbuf.c b/tools/testing/selftests/bpf/progs/bpftool_ringbuf.c
> new file mode 100644
> index 0000000000000..bbbc93c30a833
> --- /dev/null
> +++ b/tools/testing/selftests/bpf/progs/bpftool_ringbuf.c
> @@ -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");

This isn't a bug, but since produce() emits with BPF_F_CURRENT_CPU and
test_perf_consumer() already resizes perfbuf to libbpf_num_possible_cpus(),
would it read better to drop `__uint(max_entries, 1)` and let libbpf size
the perf event array itself, the way progs/test_perf_buffer.c and
progs/perfbuf_bench.c do?

[ ... ]


---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md

CI run summary: https://github.com/kernel-patches/bpf/actions/runs/35575071830

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH bpf-next v5 0/2] bpftool: Consume ring buffer maps with event_pipe
  2026-09-21  7:35 [PATCH bpf-next v5 0/2] bpftool: Consume ring buffer maps with event_pipe Tianyi Chen
  2026-09-21  7:35 ` [PATCH bpf-next v5 1/2] bpftool: Read " Tianyi Chen
  2026-09-21  7:35 ` [PATCH bpf-next v5 2/2] selftests/bpf: Cover bpftool ring buffer event_pipe Tianyi Chen
@ 2026-09-21  9:18 ` Quentin Monnet
  2026-09-21 22:10 ` patchwork-bot+netdevbpf
  3 siblings, 0 replies; 8+ messages in thread
From: Quentin Monnet @ 2026-09-21  9:18 UTC (permalink / raw)
  To: Tianyi Chen, bpf; +Cc: andrii, eddyz87, ihor.solodrai, linux-kselftest

2026-09-21 16:35 UTC+0900 ~ Tianyi Chen <hi@tychen.cc>
> 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

Looks good to me, thanks for this.

Acked-by: Quentin Monnet <qmo@kernel.org>

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH bpf-next v5 1/2] bpftool: Read ring buffer maps with event_pipe
  2026-09-21  7:35 ` [PATCH bpf-next v5 1/2] bpftool: Read " Tianyi Chen
  2026-09-21  8:34   ` bot+bpf-ci
@ 2026-09-21  9:23   ` Quentin Monnet
  1 sibling, 0 replies; 8+ messages in thread
From: Quentin Monnet @ 2026-09-21  9:23 UTC (permalink / raw)
  To: Tianyi Chen, bpf; +Cc: andrii, eddyz87, ihor.solodrai, linux-kselftest

2026-09-21 16:35 UTC+0900 ~ Tianyi Chen <hi@tychen.cc>
> map event_pipe only accepts perf event arrays, leaving no built-in way
> to inspect records produced through the BPF ring buffer API. Extend it
> to consume queued and live BPF_MAP_TYPE_RINGBUF records in plain or
> JSON output, retaining the existing perf event array path.
> 
> Ring buffers have a shared consumer position and no implicit CPU or
> timestamp. Document that this command consumes records rather than
> observing them passively, and reject perf-only CPU/index selectors.
> 
> A producer can keep ring_buffer__poll() busy after a stop signal, so
> return -EINTR from the record callback when stopping. Keep stdio out of
> the shared signal handler and preserve callback output errors.
> 
> Link: https://github.com/libbpf/bpftool/issues/54
> 
> Assisted-by: LLM
> Signed-off-by: Tianyi Chen <hi@tychen.cc>

Tested-by: Quentin Monnet <qmo@kernel.org>

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH bpf-next v5 0/2] bpftool: Consume ring buffer maps with event_pipe
  2026-09-21  7:35 [PATCH bpf-next v5 0/2] bpftool: Consume ring buffer maps with event_pipe Tianyi Chen
                   ` (2 preceding siblings ...)
  2026-09-21  9:18 ` [PATCH bpf-next v5 0/2] bpftool: Consume ring buffer maps with event_pipe Quentin Monnet
@ 2026-09-21 22:10 ` patchwork-bot+netdevbpf
  3 siblings, 0 replies; 8+ messages in thread
From: patchwork-bot+netdevbpf @ 2026-09-21 22:10 UTC (permalink / raw)
  To: Tianyi Chen; +Cc: bpf, qmo, andrii, eddyz87, ihor.solodrai, linux-kselftest

Hello:

This series was applied to bpf/bpf-next.git (master)
by Andrii Nakryiko <andrii@kernel.org>:

On Mon, 21 Sep 2026 16:35:54 +0900 you wrote:
> 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.
> 
> [...]

Here is the summary with links:
  - [bpf-next,v5,1/2] bpftool: Read ring buffer maps with event_pipe
    https://git.kernel.org/bpf/bpf-next/c/b5dc1b184f32
  - [bpf-next,v5,2/2] selftests/bpf: Cover bpftool ring buffer event_pipe
    https://git.kernel.org/bpf/bpf-next/c/b09784655ecc

You are awesome, thank you!
-- 
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html



^ permalink raw reply	[flat|nested] 8+ messages in thread

end of thread, other threads:[~2026-09-21 22:11 UTC | newest]

Thread overview: 8+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-21  7:35 [PATCH bpf-next v5 0/2] bpftool: Consume ring buffer maps with event_pipe Tianyi Chen
2026-09-21  7:35 ` [PATCH bpf-next v5 1/2] bpftool: Read " Tianyi Chen
2026-09-21  8:34   ` bot+bpf-ci
2026-09-21  9:23   ` Quentin Monnet
2026-09-21  7:35 ` [PATCH bpf-next v5 2/2] selftests/bpf: Cover bpftool ring buffer event_pipe Tianyi Chen
2026-09-21  8:48   ` bot+bpf-ci
2026-09-21  9:18 ` [PATCH bpf-next v5 0/2] bpftool: Consume ring buffer maps with event_pipe Quentin Monnet
2026-09-21 22:10 ` patchwork-bot+netdevbpf

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox