| // SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) |
| // Copyright (c) 2021 Facebook |
| // Copyright (c) 2021 Google |
| #include "vmlinux.h" |
| #include <bpf/bpf_helpers.h> |
| #include <bpf/bpf_tracing.h> |
| #include <bpf/bpf_core_read.h> |
| |
| #define MAX_LEVELS 10 // max cgroup hierarchy level: arbitrary |
| #define MAX_EVENTS 32 // max events per cgroup: arbitrary |
| |
| // NOTE: many of map and global data will be modified before loading |
| // from the userspace (perf tool) using the skeleton helpers. |
| |
| // single set of global perf events to measure |
| struct { |
| __uint(type, BPF_MAP_TYPE_PERF_EVENT_ARRAY); |
| __uint(key_size, sizeof(__u32)); |
| __uint(value_size, sizeof(int)); |
| __uint(max_entries, 1); |
| } events SEC(".maps"); |
| |
| // from cgroup id to event index |
| struct { |
| __uint(type, BPF_MAP_TYPE_HASH); |
| __uint(key_size, sizeof(__u64)); |
| __uint(value_size, sizeof(__u32)); |
| __uint(max_entries, 1); |
| } cgrp_idx SEC(".maps"); |
| |
| // per-cpu event snapshots to calculate delta |
| struct { |
| __uint(type, BPF_MAP_TYPE_PERCPU_ARRAY); |
| __uint(key_size, sizeof(__u32)); |
| __uint(value_size, sizeof(struct bpf_perf_event_value)); |
| } prev_readings SEC(".maps"); |
| |
| // aggregated event values for each cgroup (per-cpu) |
| // will be read from the user-space |
| struct { |
| __uint(type, BPF_MAP_TYPE_PERCPU_ARRAY); |
| __uint(key_size, sizeof(__u32)); |
| __uint(value_size, sizeof(struct bpf_perf_event_value)); |
| } cgrp_readings SEC(".maps"); |
| |
| /* new kernel cgroup definition */ |
| struct cgroup___new { |
| int level; |
| struct cgroup *ancestors[]; |
| } __attribute__((preserve_access_index)); |
| |
| /* old kernel cgroup definition */ |
| struct cgroup___old { |
| int level; |
| u64 ancestor_ids[]; |
| } __attribute__((preserve_access_index)); |
| |
| const volatile __u32 num_events = 1; |
| const volatile __u32 num_cpus = 1; |
| |
| int enabled = 0; |
| int use_cgroup_v2 = 0; |
| int perf_subsys_id = -1; |
| |
| static inline __u64 get_cgroup_v1_ancestor_id(struct cgroup *cgrp, int level) |
| { |
| /* recast pointer to capture new type for compiler */ |
| struct cgroup___new *cgrp_new = (void *)cgrp; |
| |
| if (bpf_core_field_exists(cgrp_new->ancestors)) { |
| return BPF_CORE_READ(cgrp_new, ancestors[level], kn, id); |
| } else { |
| /* recast pointer to capture old type for compiler */ |
| struct cgroup___old *cgrp_old = (void *)cgrp; |
| |
| return BPF_CORE_READ(cgrp_old, ancestor_ids[level]); |
| } |
| } |
| |
| static inline int get_cgroup_v1_idx(__u32 *cgrps, int size) |
| { |
| struct task_struct *p = (void *)bpf_get_current_task(); |
| struct cgroup *cgrp; |
| register int i = 0; |
| __u32 *elem; |
| int level; |
| int cnt; |
| |
| if (perf_subsys_id == -1) { |
| #if __has_builtin(__builtin_preserve_enum_value) |
| perf_subsys_id = bpf_core_enum_value(enum cgroup_subsys_id, |
| perf_event_cgrp_id); |
| #else |
| perf_subsys_id = perf_event_cgrp_id; |
| #endif |
| } |
| cgrp = BPF_CORE_READ(p, cgroups, subsys[perf_subsys_id], cgroup); |
| level = BPF_CORE_READ(cgrp, level); |
| |
| for (cnt = 0; i < MAX_LEVELS; i++) { |
| __u64 cgrp_id; |
| |
| if (i > level) |
| break; |
| |
| // convert cgroup-id to a map index |
| cgrp_id = get_cgroup_v1_ancestor_id(cgrp, i); |
| elem = bpf_map_lookup_elem(&cgrp_idx, &cgrp_id); |
| if (!elem) |
| continue; |
| |
| cgrps[cnt++] = *elem; |
| if (cnt == size) |
| break; |
| } |
| |
| return cnt; |
| } |
| |
| static inline int get_cgroup_v2_idx(__u32 *cgrps, int size) |
| { |
| register int i = 0; |
| __u32 *elem; |
| int cnt; |
| |
| for (cnt = 0; i < MAX_LEVELS; i++) { |
| __u64 cgrp_id = bpf_get_current_ancestor_cgroup_id(i); |
| |
| if (cgrp_id == 0) |
| break; |
| |
| // convert cgroup-id to a map index |
| elem = bpf_map_lookup_elem(&cgrp_idx, &cgrp_id); |
| if (!elem) |
| continue; |
| |
| cgrps[cnt++] = *elem; |
| if (cnt == size) |
| break; |
| } |
| |
| return cnt; |
| } |
| |
| static int bperf_cgroup_count(void) |
| { |
| register __u32 idx = 0; // to have it in a register to pass BPF verifier |
| register int c = 0; |
| struct bpf_perf_event_value val, delta, *prev_val, *cgrp_val; |
| __u32 cpu = bpf_get_smp_processor_id(); |
| __u32 cgrp_idx[MAX_LEVELS]; |
| int cgrp_cnt; |
| __u32 key, cgrp; |
| long err; |
| |
| if (use_cgroup_v2) |
| cgrp_cnt = get_cgroup_v2_idx(cgrp_idx, MAX_LEVELS); |
| else |
| cgrp_cnt = get_cgroup_v1_idx(cgrp_idx, MAX_LEVELS); |
| |
| for ( ; idx < MAX_EVENTS; idx++) { |
| if (idx == num_events) |
| break; |
| |
| // XXX: do not pass idx directly (for verifier) |
| key = idx; |
| // this is per-cpu array for diff |
| prev_val = bpf_map_lookup_elem(&prev_readings, &key); |
| if (!prev_val) { |
| val.counter = val.enabled = val.running = 0; |
| bpf_map_update_elem(&prev_readings, &key, &val, BPF_ANY); |
| |
| prev_val = bpf_map_lookup_elem(&prev_readings, &key); |
| if (!prev_val) |
| continue; |
| } |
| |
| // read from global perf_event array |
| key = idx * num_cpus + cpu; |
| err = bpf_perf_event_read_value(&events, key, &val, sizeof(val)); |
| if (err) |
| continue; |
| |
| if (enabled) { |
| delta.counter = val.counter - prev_val->counter; |
| delta.enabled = val.enabled - prev_val->enabled; |
| delta.running = val.running - prev_val->running; |
| |
| for (c = 0; c < MAX_LEVELS; c++) { |
| if (c == cgrp_cnt) |
| break; |
| |
| cgrp = cgrp_idx[c]; |
| |
| // aggregate the result by cgroup |
| key = cgrp * num_events + idx; |
| cgrp_val = bpf_map_lookup_elem(&cgrp_readings, &key); |
| if (cgrp_val) { |
| cgrp_val->counter += delta.counter; |
| cgrp_val->enabled += delta.enabled; |
| cgrp_val->running += delta.running; |
| } else { |
| bpf_map_update_elem(&cgrp_readings, &key, |
| &delta, BPF_ANY); |
| } |
| } |
| } |
| |
| *prev_val = val; |
| } |
| return 0; |
| } |
| |
| // This will be attached to cgroup-switches event for each cpu |
| SEC("perf_event") |
| int BPF_PROG(on_cgrp_switch) |
| { |
| return bperf_cgroup_count(); |
| } |
| |
| SEC("raw_tp/sched_switch") |
| int BPF_PROG(trigger_read) |
| { |
| return bperf_cgroup_count(); |
| } |
| |
| char LICENSE[] SEC("license") = "Dual BSD/GPL"; |