340 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3 * This test makes sure BPF stats collection using rstat works correctly.4 * The test uses 3 BPF progs:5 * (a) counter: This BPF prog is invoked every time we attach a process to a6 * cgroup and locklessly increments a percpu counter.7 * The program then calls cgroup_rstat_updated() to inform rstat8 * of an update on the (cpu, cgroup) pair.9 *10 * (b) flusher: This BPF prog is invoked when an rstat flush is ongoing, it11 * aggregates all percpu counters to a total counter, and also12 * propagates the changes to the ancestor cgroups.13 *14 * (c) dumper: This BPF prog is a cgroup_iter. It is used to output the total15 * counter of a cgroup through reading a file in userspace.16 *17 * The test sets up a cgroup hierarchy, and the above programs. It spawns a few18 * processes in the leaf cgroups and makes sure all the counters are aggregated19 * correctly.20 *21 * Copyright 2022 Google LLC.22 */23#include <asm-generic/errno.h>24#include <errno.h>25#include <sys/types.h>26#include <sys/mount.h>27#include <sys/stat.h>28#include <unistd.h>29 30#include <test_progs.h>31#include <bpf/libbpf.h>32#include <bpf/bpf.h>33 34#include "cgroup_helpers.h"35#include "cgroup_hierarchical_stats.skel.h"36 37#define PAGE_SIZE 409638#define MB(x) (x << 20)39 40#define PROCESSES_PER_CGROUP 341 42#define BPFFS_ROOT "/sys/fs/bpf/"43#define BPFFS_ATTACH_COUNTERS BPFFS_ROOT "attach_counters/"44 45#define CG_ROOT_NAME "root"46#define CG_ROOT_ID 147 48#define CGROUP_PATH(p, n) {.path = p"/"n, .name = n}49 50static struct {51 const char *path, *name;52 unsigned long long id;53 int fd;54} cgroups[] = {55 CGROUP_PATH("/", "test"),56 CGROUP_PATH("/test", "child1"),57 CGROUP_PATH("/test", "child2"),58 CGROUP_PATH("/test/child1", "child1_1"),59 CGROUP_PATH("/test/child1", "child1_2"),60 CGROUP_PATH("/test/child2", "child2_1"),61 CGROUP_PATH("/test/child2", "child2_2"),62};63 64#define N_CGROUPS ARRAY_SIZE(cgroups)65#define N_NON_LEAF_CGROUPS 366 67static int root_cgroup_fd;68static bool mounted_bpffs;69 70/* reads file at 'path' to 'buf', returns 0 on success. */71static int read_from_file(const char *path, char *buf, size_t size)72{73 int fd, len;74 75 fd = open(path, O_RDONLY);76 if (fd < 0)77 return fd;78 79 len = read(fd, buf, size);80 close(fd);81 if (len < 0)82 return len;83 84 buf[len] = 0;85 return 0;86}87 88/* mounts bpffs and mkdir for reading stats, returns 0 on success. */89static int setup_bpffs(void)90{91 int err;92 93 /* Mount bpffs */94 err = mount("bpf", BPFFS_ROOT, "bpf", 0, NULL);95 mounted_bpffs = !err;96 if (ASSERT_FALSE(err && errno != EBUSY, "mount"))97 return err;98 99 /* Create a directory to contain stat files in bpffs */100 err = mkdir(BPFFS_ATTACH_COUNTERS, 0755);101 if (!ASSERT_OK(err, "mkdir"))102 return err;103 104 return 0;105}106 107static void cleanup_bpffs(void)108{109 /* Remove created directory in bpffs */110 ASSERT_OK(rmdir(BPFFS_ATTACH_COUNTERS), "rmdir "BPFFS_ATTACH_COUNTERS);111 112 /* Unmount bpffs, if it wasn't already mounted when we started */113 if (mounted_bpffs)114 return;115 116 ASSERT_OK(umount(BPFFS_ROOT), "unmount bpffs");117}118 119/* sets up cgroups, returns 0 on success. */120static int setup_cgroups(void)121{122 int i, fd, err;123 124 err = setup_cgroup_environment();125 if (!ASSERT_OK(err, "setup_cgroup_environment"))126 return err;127 128 root_cgroup_fd = get_root_cgroup();129 if (!ASSERT_GE(root_cgroup_fd, 0, "get_root_cgroup"))130 return root_cgroup_fd;131 132 for (i = 0; i < N_CGROUPS; i++) {133 fd = create_and_get_cgroup(cgroups[i].path);134 if (!ASSERT_GE(fd, 0, "create_and_get_cgroup"))135 return fd;136 137 cgroups[i].fd = fd;138 cgroups[i].id = get_cgroup_id(cgroups[i].path);139 }140 return 0;141}142 143static void cleanup_cgroups(void)144{145 close(root_cgroup_fd);146 for (int i = 0; i < N_CGROUPS; i++)147 close(cgroups[i].fd);148 cleanup_cgroup_environment();149}150 151/* Sets up cgroup hiearchary, returns 0 on success. */152static int setup_hierarchy(void)153{154 return setup_bpffs() || setup_cgroups();155}156 157static void destroy_hierarchy(void)158{159 cleanup_cgroups();160 cleanup_bpffs();161}162 163static int attach_processes(void)164{165 int i, j, status;166 167 /* In every leaf cgroup, attach 3 processes */168 for (i = N_NON_LEAF_CGROUPS; i < N_CGROUPS; i++) {169 for (j = 0; j < PROCESSES_PER_CGROUP; j++) {170 pid_t pid;171 172 /* Create child and attach to cgroup */173 pid = fork();174 if (pid == 0) {175 if (join_parent_cgroup(cgroups[i].path))176 exit(EACCES);177 exit(0);178 }179 180 /* Cleanup child */181 waitpid(pid, &status, 0);182 if (!ASSERT_TRUE(WIFEXITED(status), "child process exited"))183 return 1;184 if (!ASSERT_EQ(WEXITSTATUS(status), 0,185 "child process exit code"))186 return 1;187 }188 }189 return 0;190}191 192static unsigned long long193get_attach_counter(unsigned long long cgroup_id, const char *file_name)194{195 unsigned long long attach_counter = 0, id = 0;196 static char buf[128], path[128];197 198 /* For every cgroup, read the file generated by cgroup_iter */199 snprintf(path, 128, "%s%s", BPFFS_ATTACH_COUNTERS, file_name);200 if (!ASSERT_OK(read_from_file(path, buf, 128), "read cgroup_iter"))201 return 0;202 203 /* Check the output file formatting */204 ASSERT_EQ(sscanf(buf, "cg_id: %llu, attach_counter: %llu\n",205 &id, &attach_counter), 2, "output format");206 207 /* Check that the cgroup_id is displayed correctly */208 ASSERT_EQ(id, cgroup_id, "cgroup_id");209 /* Check that the counter is non-zero */210 ASSERT_GT(attach_counter, 0, "attach counter non-zero");211 return attach_counter;212}213 214static void check_attach_counters(void)215{216 unsigned long long attach_counters[N_CGROUPS], root_attach_counter;217 int i;218 219 for (i = 0; i < N_CGROUPS; i++)220 attach_counters[i] = get_attach_counter(cgroups[i].id,221 cgroups[i].name);222 223 /* Read stats for root too */224 root_attach_counter = get_attach_counter(CG_ROOT_ID, CG_ROOT_NAME);225 226 /* Check that all leafs cgroups have an attach counter of 3 */227 for (i = N_NON_LEAF_CGROUPS; i < N_CGROUPS; i++)228 ASSERT_EQ(attach_counters[i], PROCESSES_PER_CGROUP,229 "leaf cgroup attach counter");230 231 /* Check that child1 == child1_1 + child1_2 */232 ASSERT_EQ(attach_counters[1], attach_counters[3] + attach_counters[4],233 "child1_counter");234 /* Check that child2 == child2_1 + child2_2 */235 ASSERT_EQ(attach_counters[2], attach_counters[5] + attach_counters[6],236 "child2_counter");237 /* Check that test == child1 + child2 */238 ASSERT_EQ(attach_counters[0], attach_counters[1] + attach_counters[2],239 "test_counter");240 /* Check that root >= test */241 ASSERT_GE(root_attach_counter, attach_counters[1], "root_counter");242}243 244/* Creates iter link and pins in bpffs, returns 0 on success, -errno on failure.245 */246static int setup_cgroup_iter(struct cgroup_hierarchical_stats *obj,247 int cgroup_fd, const char *file_name)248{249 DECLARE_LIBBPF_OPTS(bpf_iter_attach_opts, opts);250 union bpf_iter_link_info linfo = {};251 struct bpf_link *link;252 static char path[128];253 int err;254 255 /*256 * Create an iter link, parameterized by cgroup_fd. We only want to257 * traverse one cgroup, so set the traversal order to "self".258 */259 linfo.cgroup.cgroup_fd = cgroup_fd;260 linfo.cgroup.order = BPF_CGROUP_ITER_SELF_ONLY;261 opts.link_info = &linfo;262 opts.link_info_len = sizeof(linfo);263 link = bpf_program__attach_iter(obj->progs.dumper, &opts);264 if (!ASSERT_OK_PTR(link, "attach_iter"))265 return -EFAULT;266 267 /* Pin the link to a bpffs file */268 snprintf(path, 128, "%s%s", BPFFS_ATTACH_COUNTERS, file_name);269 err = bpf_link__pin(link, path);270 ASSERT_OK(err, "pin cgroup_iter");271 272 /* Remove the link, leaving only the ref held by the pinned file */273 bpf_link__destroy(link);274 return err;275}276 277/* Sets up programs for collecting stats, returns 0 on success. */278static int setup_progs(struct cgroup_hierarchical_stats **skel)279{280 int i, err;281 282 *skel = cgroup_hierarchical_stats__open_and_load();283 if (!ASSERT_OK_PTR(*skel, "open_and_load"))284 return 1;285 286 /* Attach cgroup_iter program that will dump the stats to cgroups */287 for (i = 0; i < N_CGROUPS; i++) {288 err = setup_cgroup_iter(*skel, cgroups[i].fd, cgroups[i].name);289 if (!ASSERT_OK(err, "setup_cgroup_iter"))290 return err;291 }292 293 /* Also dump stats for root */294 err = setup_cgroup_iter(*skel, root_cgroup_fd, CG_ROOT_NAME);295 if (!ASSERT_OK(err, "setup_cgroup_iter"))296 return err;297 298 bpf_program__set_autoattach((*skel)->progs.dumper, false);299 err = cgroup_hierarchical_stats__attach(*skel);300 if (!ASSERT_OK(err, "attach"))301 return err;302 303 return 0;304}305 306static void destroy_progs(struct cgroup_hierarchical_stats *skel)307{308 static char path[128];309 int i;310 311 for (i = 0; i < N_CGROUPS; i++) {312 /* Delete files in bpffs that cgroup_iters are pinned in */313 snprintf(path, 128, "%s%s", BPFFS_ATTACH_COUNTERS,314 cgroups[i].name);315 ASSERT_OK(remove(path), "remove cgroup_iter pin");316 }317 318 /* Delete root file in bpffs */319 snprintf(path, 128, "%s%s", BPFFS_ATTACH_COUNTERS, CG_ROOT_NAME);320 ASSERT_OK(remove(path), "remove cgroup_iter root pin");321 cgroup_hierarchical_stats__destroy(skel);322}323 324void test_cgroup_hierarchical_stats(void)325{326 struct cgroup_hierarchical_stats *skel = NULL;327 328 if (setup_hierarchy())329 goto hierarchy_cleanup;330 if (setup_progs(&skel))331 goto cleanup;332 if (attach_processes())333 goto cleanup;334 check_attach_counters();335cleanup:336 destroy_progs(skel);337hierarchy_cleanup:338 destroy_hierarchy();339}340