// SPDX-License-Identifier: GPL-2.0-only /* * Copyright 2022 Google LLC. */ #include "vmlinux.h" #include <bpf/bpf_helpers.h> #include <bpf/bpf_tracing.h> #include <bpf/bpf_core_read.h> char _license[] SEC("license") = "GPL"; struct percpu_attach_counter { /* Previous percpu state, to figure out if we have new updates */ __u64 prev; /* Current percpu state */ __u64 state; }; struct attach_counter { /* State propagated through children, pending aggregation */ __u64 pending; /* Total state, including all cpus and all children */ __u64 state; }; struct { __uint(type, BPF_MAP_TYPE_PERCPU_HASH); __uint(max_entries, 1024); __type(key, __u64); __type(value, struct percpu_attach_counter); } percpu_attach_counters SEC(".maps"); struct { __uint(type, BPF_MAP_TYPE_HASH); __uint(max_entries, 1024); __type(key, __u64); __type(value, struct attach_counter); } attach_counters SEC(".maps"); extern void cgroup_rstat_updated(struct cgroup *cgrp, int cpu) __ksym; extern void cgroup_rstat_flush(struct cgroup *cgrp) __ksym; static uint64_t cgroup_id(struct cgroup *cgrp) { return cgrp->kn->id; } static int create_percpu_attach_counter(__u64 cg_id, __u64 state) { struct percpu_attach_counter pcpu_init = {.state = state, .prev = 0}; return bpf_map_update_elem(&percpu_attach_counters, &cg_id, &pcpu_init, BPF_NOEXIST); } static int create_attach_counter(__u64 cg_id, __u64 state, __u64 pending) { struct attach_counter init = {.state = state, .pending = pending}; return bpf_map_update_elem(&attach_counters, &cg_id, &init, BPF_NOEXIST); } SEC("fentry/cgroup_attach_task") int BPF_PROG(counter, struct cgroup *dst_cgrp, struct task_struct *leader, bool threadgroup) { __u64 cg_id = cgroup_id(dst_cgrp); struct percpu_attach_counter *pcpu_counter = bpf_map_lookup_elem( &percpu_attach_counters, &cg_id); if (pcpu_counter) pcpu_counter->state += 1; else if (create_percpu_attach_counter(cg_id, 1)) return 0; cgroup_rstat_updated(dst_cgrp, bpf_get_smp_processor_id()); return 0; } SEC("fentry/bpf_rstat_flush") int BPF_PROG(flusher, struct cgroup *cgrp, struct cgroup *parent, int cpu) { struct percpu_attach_counter *pcpu_counter; struct attach_counter *total_counter, *parent_counter; __u64 cg_id = cgroup_id(cgrp); __u64 parent_cg_id = parent ? cgroup_id(parent) : 0; __u64 state; __u64 delta = 0; /* Add CPU changes on this level since the last flush */ pcpu_counter = bpf_map_lookup_percpu_elem(&percpu_attach_counters, &cg_id, cpu); if (pcpu_counter) { state = pcpu_counter->state; delta += state - pcpu_counter->prev; pcpu_counter->prev = state; } total_counter = bpf_map_lookup_elem(&attach_counters, &cg_id); if (!total_counter) { if (create_attach_counter(cg_id, delta, 0)) return 0; goto update_parent; } /* Collect pending stats from subtree */ if (total_counter->pending) { delta += total_counter->pending; total_counter->pending = 0; } /* Propagate changes to this cgroup's total */ total_counter->state += delta; update_parent: /* Skip if there are no changes to propagate, or no parent */ if (!delta || !parent_cg_id) return 0; /* Propagate changes to cgroup's parent */ parent_counter = bpf_map_lookup_elem(&attach_counters, &parent_cg_id); if (parent_counter) parent_counter->pending += delta; else create_attach_counter(parent_cg_id, 0, delta); return 0; } SEC("iter.s/cgroup") int BPF_PROG(dumper, struct bpf_iter_meta *meta, struct cgroup *cgrp) { struct seq_file *seq = meta->seq; struct attach_counter *total_counter; __u64 cg_id = cgrp ? cgroup_id(cgrp) : 0; /* Do nothing for the terminal call */ if (!cg_id) return 1; /* Flush the stats to make sure we get the most updated numbers */ cgroup_rstat_flush(cgrp); total_counter = bpf_map_lookup_elem(&attach_counters, &cg_id); if (!total_counter) { BPF_SEQ_PRINTF(seq, "cg_id: %llu, attach_counter: 0\n", cg_id); } else { BPF_SEQ_PRINTF(seq, "cg_id: %llu, attach_counter: %llu\n", cg_id, total_counter->state); } return 0; }