From: "Alexei Starovoitov" <alexei.starovoitov@gmail.com>
To: "Tianyi Chen" <hi@tychen.cc>, <bpf@vger.kernel.org>
Cc: <qmo@kernel.org>, <andrii@kernel.org>, <eddyz87@gmail.com>,
<ihor.solodrai@linux.dev>, <linux-kselftest@vger.kernel.org>,
<skokovmaksimevg@gmail.com>, <daniel@iogearbox.net>
Subject: Re: [PATCH bpf-next v10 3/4] bpftool: Add recursive map dumping
Date: Wed, 04 Nov 2026 02:28:28 +0000 [thread overview]
Message-ID: <DMM2LU4OE14Q.15KT5S3KNYPHX@gmail.com> (raw)
In-Reply-To: <8f2668ed4cd086f5fe3abb7ad0ca35066999ec49.1793732336.git.hi@tychen.cc>
On Tue, Nov 03, 2026 at 11:01 AM Tianyi Chen <hi@tychen.cc> wrote:
> Holding every discovered inner-map FD open would make descriptor use
> grow with the number of maps and could exhaust RLIMIT_NOFILE. Keep the
> selected map FDs open, queue distinct inner map IDs, and open, dump and
> close each queued map in turn. This needs only one additional map FD.
> The tradeoff is deferred ID resolution: concurrently removed inner
> maps can disappear before they are opened, so the dump is not atomic.
This is overkill.
The kernel has one level of map-in-map. bpf_map_meta_alloc() rejects
an inner map that has inner maps. Nothing recurses here.
Lets dump the inner map at the place that prints its id:
open it by id, dump it, close it. That's one more fd too.
These go away: struct map_dump_ctx, the hashmap, pending_ids,
plain_btf_wtr, the pass that records the selected maps,
the second loop in do_dump().
The selected fds don't have to stay open.
this patch inner map in its entry
------------------ ----------------------- ------------------------
json output array of map objects, same entries as without
the reader joins by id 'recursive', plus
"inner_map"
from id to open all maps in front of it nothing
in the queue are dumped
inner map is gone the whole dump fails error in this entry,
like a failed lookup
shared inner map printed once printed in every entry
Inner maps get replaced on a live system. That's what map-in-map is for.
The longer the queue the more likely the dump fails.
The last row is the price. I think it's fine.
Something like this (not even compile tested):
diff --git a/tools/bpf/bpftool/Documentation/bpftool-map.rst b/tools/bpf/bpftool/Documentation/bpftool-map.rst
index e4ed7e7..0f0b1d9 100644
--- a/tools/bpf/bpftool/Documentation/bpftool-map.rst
+++ b/tools/bpf/bpftool/Documentation/bpftool-map.rst
@@ -29,7 +29,7 @@ MAP COMMANDS
| **bpftool** **map create** *FILE* **type** *TYPE* **key** *KEY_SIZE* **value** *VALUE_SIZE* \
| **entries** *MAX_ENTRIES* **name** *NAME* [**flags** *FLAGS*] [**inner_map** *MAP*] \
| [**offload_dev** *NAME*]
-| **bpftool** **map dump** *MAP*
+| **bpftool** **map dump** *MAP* [**recursive**]
| **bpftool** **map update** *MAP* [**key** *DATA*] [**value** *VALUE*] [*UPDATE_FLAGS*]
| **bpftool** **map lookup** *MAP* [**key** *DATA*]
| **bpftool** **map getnext** *MAP* [**key** *DATA*]
@@ -87,10 +87,13 @@ bpftool map create *FILE* type *TYPE* key *KEY_SIZE* value *VALUE_SIZE* entries
Keyword **offload_dev** expects a network interface name, and is used to
request hardware offload for the map.
-bpftool map dump *MAP*
+bpftool map dump *MAP* [recursive]
Dump all entries in a given *MAP*. In case of **name**, *MAP* may match
several maps which will all be dumped.
+ With **recursive**, an entry of an **array_of_maps** or a **hash_of_maps**
+ also has the dump of the inner map that it refers to.
+
bpftool map update *MAP* [key *DATA*] [value *VALUE*] [*UPDATE_FLAGS*]
Update map entry for a given *KEY*.
diff --git a/tools/bpf/bpftool/bash-completion/bpftool b/tools/bpf/bpftool/bash-completion/bpftool
index a28af31..78fdb93 100644
--- a/tools/bpf/bpftool/bash-completion/bpftool
+++ b/tools/bpf/bpftool/bash-completion/bpftool
@@ -718,6 +718,9 @@ _bpftool()
return 0
;;
*)
+ if [[ $command == dump && $cword -eq 5 ]]; then
+ COMPREPLY=( $( compgen -W 'recursive' -- "$cur" ) )
+ fi
return 0
;;
esac
diff --git a/tools/bpf/bpftool/map.c b/tools/bpf/bpftool/map.c
index 4b65edf..0b3968b 100644
--- a/tools/bpf/bpftool/map.c
+++ b/tools/bpf/bpftool/map.c
@@ -22,6 +22,9 @@
#include "main.h"
static struct hashmap *map_table;
+static bool recursive_dump;
+
+static void dump_inner_map(__u32 id);
static bool map_is_per_cpu(__u32 type)
{
@@ -140,9 +143,14 @@ static void print_entry_json(struct bpf_map_info *info, unsigned char *key,
print_hex_data_json(key, info->key_size);
jsonw_name(json_wtr, "value");
print_hex_data_json(value, info->value_size);
- if (map_is_map_of_maps(info->type))
+ if (map_is_map_of_maps(info->type)) {
jsonw_uint_field(json_wtr, "inner_map_id",
*(unsigned int *)value);
+ if (recursive_dump) {
+ jsonw_name(json_wtr, "inner_map");
+ dump_inner_map(*(unsigned int *)value);
+ }
+ }
if (btf) {
struct btf_dumper d = {
.btf = btf,
@@ -273,6 +281,8 @@ static void print_entry_plain(struct bpf_map_info *info, unsigned char *key,
}
printf("\n");
+ if (recursive_dump && map_is_map_of_maps(info->type))
+ dump_inner_map(*(unsigned int *)value);
} else {
unsigned int step;
int i;
@@ -929,6 +939,36 @@ exit_free:
return err;
}
+/* The kernel has one level of map-in-map. An inner map has no inner maps. */
+static void dump_inner_map(__u32 id)
+{
+ LIBBPF_OPTS(bpf_get_fd_by_id_opts, opts, .open_flags = BPF_F_RDONLY);
+ struct bpf_map_info info = {};
+ json_writer_t *btf_wtr = NULL;
+ __u32 len = sizeof(info);
+ int fd;
+
+ fd = bpf_map_get_fd_by_id_opts(id, &opts);
+ if (fd < 0) {
+ p_err("can't get map by id (%u): %s", id, strerror(errno));
+ return;
+ }
+ if (bpf_map_get_info_by_fd(fd, &info, &len)) {
+ p_err("can't get map info: %s", strerror(errno));
+ goto out;
+ }
+
+ if (!json_output && info.btf_id)
+ btf_wtr = get_btf_writer();
+
+ map_dump(fd, &info, json_output ? json_wtr : btf_wtr, true);
+
+ if (btf_wtr)
+ jsonw_destroy(&btf_wtr);
+out:
+ close(fd);
+}
+
static int do_dump(int argc, char **argv)
{
json_writer_t *wtr = NULL, *btf_wtr = NULL;
@@ -938,8 +978,8 @@ static int do_dump(int argc, char **argv)
int *fds = NULL;
int err = -1;
- if (argc != 2)
- usage();
+ if (!REQ_ARGS(2))
+ return -1;
fds = malloc(sizeof(int));
if (!fds) {
@@ -950,6 +990,16 @@ static int do_dump(int argc, char **argv)
if (nb_fds < 1)
goto exit_free;
+ while (argc > 0) {
+ if (is_prefix(*argv, "recursive")) {
+ NEXT_ARG();
+ recursive_dump = true;
+ } else {
+ p_err("expected 'recursive', got: '%s'", *argv);
+ goto exit_close;
+ }
+ }
+
if (json_output) {
wtr = json_wtr;
} else {
@@ -994,6 +1044,7 @@ exit_close:
exit_free:
free(fds);
free_btf_vmlinux();
+ recursive_dump = false;
return err;
}
@@ -1502,7 +1553,7 @@ static int do_help(int argc, char **argv)
" %1$s %2$s create FILE type TYPE key KEY_SIZE value VALUE_SIZE \\\n"
" entries MAX_ENTRIES name NAME [flags FLAGS] \\\n"
" [inner_map MAP] [offload_dev NAME]\n"
- " %1$s %2$s dump MAP\n"
+ " %1$s %2$s dump MAP [recursive]\n"
" %1$s %2$s update MAP [key DATA] [value VALUE] [UPDATE_FLAGS]\n"
" %1$s %2$s lookup MAP [key DATA]\n"
" %1$s %2$s getnext MAP [key DATA]\n"
next prev parent reply other threads:[~2026-11-04 2:28 UTC|newest]
Thread overview: 8+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-03 19:01 [PATCH bpf-next v10 0/4] bpftool: Add recursive map dumping Tianyi Chen
2026-11-03 19:01 ` [PATCH bpf-next v10 1/4] bpftool: Reset map info before each query in show and dump loops Tianyi Chen
2026-11-03 19:01 ` [PATCH bpf-next v10 2/4] selftests/bpf: Check bpftool map info across mixed BTF maps Tianyi Chen
2026-11-04 2:27 ` Alexei Starovoitov
2026-11-03 19:01 ` [PATCH bpf-next v10 3/4] bpftool: Add recursive map dumping Tianyi Chen
2026-11-04 2:28 ` Alexei Starovoitov [this message]
2026-11-03 19:01 ` [PATCH bpf-next v10 4/4] selftests/bpf: Cover recursive bpftool map dumps Tianyi Chen
2026-11-03 19:38 ` bot+bpf-ci
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=DMM2LU4OE14Q.15KT5S3KNYPHX@gmail.com \
--to=alexei.starovoitov@gmail.com \
--cc=andrii@kernel.org \
--cc=bpf@vger.kernel.org \
--cc=daniel@iogearbox.net \
--cc=eddyz87@gmail.com \
--cc=hi@tychen.cc \
--cc=ihor.solodrai@linux.dev \
--cc=linux-kselftest@vger.kernel.org \
--cc=qmo@kernel.org \
--cc=skokovmaksimevg@gmail.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.