Re: [PATCH bpf-next v7 4/8] bpf: Introduce cgroup iter

Alexei Starovoitov
2022-08-09T16:23:38Z

On Mon, Aug 08, 2022 at 05:56:57PM -0700, Hao Luo wrote:
> On Mon, Aug 8, 2022 at 5:19 PM Andrii Nakryiko
> <andrii.nakryiko@gmail.com> wrote:
> >
> > On Fri, Aug 5, 2022 at 2:49 PM Hao Luo <haoluo@google.com> wrote:
> > >
> > > Cgroup_iter is a type of bpf_iter. It walks over cgroups in four modes:
> > >
> > > - walking a cgroup's descendants in pre-order.
> > > - walking a cgroup's descendants in post-order.
> > > - walking a cgroup's ancestors.
> > > - process only the given cgroup.
> > >
> > > When attaching cgroup_iter, one can set a cgroup to the iter_link
> > > created from attaching. This cgroup is passed as a file descriptor
> > > or cgroup id and serves as the starting point of the walk. If no
> > > cgroup is specified, the starting point will be the root cgroup v2.
> > >
> > > For walking descendants, one can specify the order: either pre-order or
> > > post-order. For walking ancestors, the walk starts at the specified
> > > cgroup and ends at the root.
> > >
> > > One can also terminate the walk early by returning 1 from the iter
> > > program.
> > >
> > > Note that because walking cgroup hierarchy holds cgroup_mutex, the iter
> > > program is called with cgroup_mutex held.
> > >
> > > Currently only one session is supported, which means, depending on the
> > > volume of data bpf program intends to send to user space, the number
> > > of cgroups that can be walked is limited. For example, given the current
> > > buffer size is 8 * PAGE_SIZE, if the program sends 64B data for each
> > > cgroup, assuming PAGE_SIZE is 4kb, the total number of cgroups that can
> > > be walked is 512. This is a limitation of cgroup_iter. If the output
> > > data is larger than the kernel buffer size, after all data in the
> > > kernel buffer is consumed by user space, the subsequent read() syscall
> > > will signal EOPNOTSUPP. In order to work around, the user may have to
> > > update their program to reduce the volume of data sent to output. For
> > > example, skip some uninteresting cgroups. In future, we may extend
> > > bpf_iter flags to allow customizing buffer size.
> > >
> > > Acked-by: Yonghong Song <yhs@fb.com>
> > > Acked-by: Tejun Heo <tj@kernel.org>
> > > Signed-off-by: Hao Luo <haoluo@google.com>
> > > ---
> > > include/linux/bpf.h | 8 +
> > > include/uapi/linux/bpf.h | 38 +++
> > > kernel/bpf/Makefile | 3 +
> > > kernel/bpf/cgroup_iter.c | 286 ++++++++++++++++++
> > > tools/include/uapi/linux/bpf.h | 38 +++
> > > .../selftests/bpf/prog_tests/btf_dump.c | 4 +-
> > > 6 files changed, 375 insertions(+), 2 deletions(-)
> > > create mode 100644 kernel/bpf/cgroup_iter.c
> > >
> > > diff --git a/include/linux/bpf.h b/include/linux/bpf.h
> > > index 20c26aed7896..09b5c2167424 100644
> > > --- a/include/linux/bpf.h
> > > +++ b/include/linux/bpf.h
> > > @@ -48,6 +48,7 @@ struct mem_cgroup;
> > > struct module;
> > > struct bpf_func_state;
> > > struct ftrace_ops;
> > > +struct cgroup;
> > >
> > > extern struct idr btf_idr;
> > > extern spinlock_t btf_idr_lock;
> > > @@ -1730,7 +1731,14 @@ int bpf_obj_get_user(const char __user *pathname, int flags);
> > > int __init bpf_iter_ ## target(args) { return 0; }
> > >
> > > struct bpf_iter_aux_info {
> > > + /* for map_elem iter */
> > > struct bpf_map *map;
> > > +
> > > + /* for cgroup iter */
> > > + struct {
> > > + struct cgroup *start; /* starting cgroup */
> > > + int order;
> > > + } cgroup;
> > > };
> > >
> > > typedef int (*bpf_iter_attach_target_t)(struct bpf_prog *prog,
> > > diff --git a/include/uapi/linux/bpf.h b/include/uapi/linux/bpf.h
> > > index 59a217ca2dfd..4d758b2e70d6 100644
> > > --- a/include/uapi/linux/bpf.h
> > > +++ b/include/uapi/linux/bpf.h
> > > @@ -87,10 +87,37 @@ struct bpf_cgroup_storage_key {
> > > __u32 attach_type; /* program attach type (enum bpf_attach_type) */
> > > };
> > >
> > > +enum bpf_iter_order {
> > > + BPF_ITER_ORDER_DEFAULT = 0, /* default order. */
> >
> > why is this default order necessary? It just adds confusion (I had to
> > look up source code to know what is default order). I might have
> > missed some discussion, so if there is some very good reason, then
> > please document this in commit message. But I'd rather not do some
> > magical default order instead. We can set 0 to mean invalid and error
> > out, or just do SELF as the very first value (and if user forgot to
> > specify more fancy mode, they hopefully will quickly discover this in
> > their testing).
> >
>
> PRE/POST/UP are tree-specific orders. SELF applies on all iters and
> yields only a single object. How does task_iter express a non-self
> order? By non-self, I mean something like "I don't care about the
> order, just scan _all_ the objects". And this "don't care" order, IMO,
> may be the common case. I don't think everyone cares about walking
> order for tasks. The DEFAULT is intentionally put at the first value,
> so that if users don't care about order, they don't have to specify
> this field.
>
> If that sounds valid, maybe using "UNSPEC" instead of "DEFAULT" is better?

I agree with Andrii.
This:
+ if (order == BPF_ITER_ORDER_DEFAULT)
+ order = BPF_ITER_DESCENDANTS_PRE;

looks like an arbitrary choice.
imo
BPF_ITER_DESCENDANTS_PRE = 0,
would have been more obvious. No need to dig into definition of "default".

UNSPEC = 0
is fine too if we want user to always be conscious about the order
and the kernel will error if that field is not initialized.
That would be my preference, since it will match the rest of uapi/bpf.h

I applied the first 3 patches to ease respin.
Thanks!