188886309SYosry Ahmed // SPDX-License-Identifier: GPL-2.0-only
288886309SYosry Ahmed /*
3*e0401dceSYosry Ahmed * This test makes sure BPF stats collection using rstat works correctly.
4*e0401dceSYosry Ahmed * The test uses 3 BPF progs:
5*e0401dceSYosry Ahmed * (a) counter: This BPF prog is invoked every time we attach a process to a
6*e0401dceSYosry Ahmed * cgroup and locklessly increments a percpu counter.
7*e0401dceSYosry Ahmed * The program then calls cgroup_rstat_updated() to inform rstat
8*e0401dceSYosry Ahmed * of an update on the (cpu, cgroup) pair.
9*e0401dceSYosry Ahmed *
10*e0401dceSYosry Ahmed * (b) flusher: This BPF prog is invoked when an rstat flush is ongoing, it
11*e0401dceSYosry Ahmed * aggregates all percpu counters to a total counter, and also
12*e0401dceSYosry Ahmed * propagates the changes to the ancestor cgroups.
13*e0401dceSYosry Ahmed *
14*e0401dceSYosry Ahmed * (c) dumper: This BPF prog is a cgroup_iter. It is used to output the total
15*e0401dceSYosry Ahmed * counter of a cgroup through reading a file in userspace.
16*e0401dceSYosry Ahmed *
17*e0401dceSYosry Ahmed * The test sets up a cgroup hierarchy, and the above programs. It spawns a few
18*e0401dceSYosry Ahmed * processes in the leaf cgroups and makes sure all the counters are aggregated
19*e0401dceSYosry Ahmed * correctly.
2088886309SYosry Ahmed *
2188886309SYosry Ahmed * Copyright 2022 Google LLC.
2288886309SYosry Ahmed */
2388886309SYosry Ahmed #include <asm-generic/errno.h>
2488886309SYosry Ahmed #include <errno.h>
2588886309SYosry Ahmed #include <sys/types.h>
2688886309SYosry Ahmed #include <sys/mount.h>
2788886309SYosry Ahmed #include <sys/stat.h>
2888886309SYosry Ahmed #include <unistd.h>
2988886309SYosry Ahmed
3088886309SYosry Ahmed #include <test_progs.h>
3188886309SYosry Ahmed #include <bpf/libbpf.h>
3288886309SYosry Ahmed #include <bpf/bpf.h>
3388886309SYosry Ahmed
3488886309SYosry Ahmed #include "cgroup_helpers.h"
3588886309SYosry Ahmed #include "cgroup_hierarchical_stats.skel.h"
3688886309SYosry Ahmed
3788886309SYosry Ahmed #define PAGE_SIZE 4096
3888886309SYosry Ahmed #define MB(x) (x << 20)
3988886309SYosry Ahmed
40*e0401dceSYosry Ahmed #define PROCESSES_PER_CGROUP 3
41*e0401dceSYosry Ahmed
4288886309SYosry Ahmed #define BPFFS_ROOT "/sys/fs/bpf/"
43*e0401dceSYosry Ahmed #define BPFFS_ATTACH_COUNTERS BPFFS_ROOT "attach_counters/"
4488886309SYosry Ahmed
4588886309SYosry Ahmed #define CG_ROOT_NAME "root"
4688886309SYosry Ahmed #define CG_ROOT_ID 1
4788886309SYosry Ahmed
4888886309SYosry Ahmed #define CGROUP_PATH(p, n) {.path = p"/"n, .name = n}
4988886309SYosry Ahmed
5088886309SYosry Ahmed static struct {
5188886309SYosry Ahmed const char *path, *name;
5288886309SYosry Ahmed unsigned long long id;
5388886309SYosry Ahmed int fd;
5488886309SYosry Ahmed } cgroups[] = {
5588886309SYosry Ahmed CGROUP_PATH("/", "test"),
5688886309SYosry Ahmed CGROUP_PATH("/test", "child1"),
5788886309SYosry Ahmed CGROUP_PATH("/test", "child2"),
5888886309SYosry Ahmed CGROUP_PATH("/test/child1", "child1_1"),
5988886309SYosry Ahmed CGROUP_PATH("/test/child1", "child1_2"),
6088886309SYosry Ahmed CGROUP_PATH("/test/child2", "child2_1"),
6188886309SYosry Ahmed CGROUP_PATH("/test/child2", "child2_2"),
6288886309SYosry Ahmed };
6388886309SYosry Ahmed
6488886309SYosry Ahmed #define N_CGROUPS ARRAY_SIZE(cgroups)
6588886309SYosry Ahmed #define N_NON_LEAF_CGROUPS 3
6688886309SYosry Ahmed
6788886309SYosry Ahmed static int root_cgroup_fd;
6888886309SYosry Ahmed static bool mounted_bpffs;
6988886309SYosry Ahmed
7088886309SYosry Ahmed /* reads file at 'path' to 'buf', returns 0 on success. */
read_from_file(const char * path,char * buf,size_t size)7188886309SYosry Ahmed static int read_from_file(const char *path, char *buf, size_t size)
7288886309SYosry Ahmed {
7388886309SYosry Ahmed int fd, len;
7488886309SYosry Ahmed
7588886309SYosry Ahmed fd = open(path, O_RDONLY);
7688886309SYosry Ahmed if (fd < 0)
7788886309SYosry Ahmed return fd;
7888886309SYosry Ahmed
7988886309SYosry Ahmed len = read(fd, buf, size);
8088886309SYosry Ahmed close(fd);
8188886309SYosry Ahmed if (len < 0)
8288886309SYosry Ahmed return len;
8388886309SYosry Ahmed
8488886309SYosry Ahmed buf[len] = 0;
8588886309SYosry Ahmed return 0;
8688886309SYosry Ahmed }
8788886309SYosry Ahmed
8888886309SYosry Ahmed /* mounts bpffs and mkdir for reading stats, returns 0 on success. */
setup_bpffs(void)8988886309SYosry Ahmed static int setup_bpffs(void)
9088886309SYosry Ahmed {
9188886309SYosry Ahmed int err;
9288886309SYosry Ahmed
9388886309SYosry Ahmed /* Mount bpffs */
9488886309SYosry Ahmed err = mount("bpf", BPFFS_ROOT, "bpf", 0, NULL);
9588886309SYosry Ahmed mounted_bpffs = !err;
9688886309SYosry Ahmed if (ASSERT_FALSE(err && errno != EBUSY, "mount"))
9788886309SYosry Ahmed return err;
9888886309SYosry Ahmed
9988886309SYosry Ahmed /* Create a directory to contain stat files in bpffs */
100*e0401dceSYosry Ahmed err = mkdir(BPFFS_ATTACH_COUNTERS, 0755);
10188886309SYosry Ahmed if (!ASSERT_OK(err, "mkdir"))
10288886309SYosry Ahmed return err;
10388886309SYosry Ahmed
10488886309SYosry Ahmed return 0;
10588886309SYosry Ahmed }
10688886309SYosry Ahmed
cleanup_bpffs(void)10788886309SYosry Ahmed static void cleanup_bpffs(void)
10888886309SYosry Ahmed {
10988886309SYosry Ahmed /* Remove created directory in bpffs */
110*e0401dceSYosry Ahmed ASSERT_OK(rmdir(BPFFS_ATTACH_COUNTERS), "rmdir "BPFFS_ATTACH_COUNTERS);
11188886309SYosry Ahmed
11288886309SYosry Ahmed /* Unmount bpffs, if it wasn't already mounted when we started */
11388886309SYosry Ahmed if (mounted_bpffs)
11488886309SYosry Ahmed return;
11588886309SYosry Ahmed
11688886309SYosry Ahmed ASSERT_OK(umount(BPFFS_ROOT), "unmount bpffs");
11788886309SYosry Ahmed }
11888886309SYosry Ahmed
11988886309SYosry Ahmed /* sets up cgroups, returns 0 on success. */
setup_cgroups(void)12088886309SYosry Ahmed static int setup_cgroups(void)
12188886309SYosry Ahmed {
12288886309SYosry Ahmed int i, fd, err;
12388886309SYosry Ahmed
12488886309SYosry Ahmed err = setup_cgroup_environment();
12588886309SYosry Ahmed if (!ASSERT_OK(err, "setup_cgroup_environment"))
12688886309SYosry Ahmed return err;
12788886309SYosry Ahmed
12888886309SYosry Ahmed root_cgroup_fd = get_root_cgroup();
12988886309SYosry Ahmed if (!ASSERT_GE(root_cgroup_fd, 0, "get_root_cgroup"))
13088886309SYosry Ahmed return root_cgroup_fd;
13188886309SYosry Ahmed
13288886309SYosry Ahmed for (i = 0; i < N_CGROUPS; i++) {
13388886309SYosry Ahmed fd = create_and_get_cgroup(cgroups[i].path);
13488886309SYosry Ahmed if (!ASSERT_GE(fd, 0, "create_and_get_cgroup"))
13588886309SYosry Ahmed return fd;
13688886309SYosry Ahmed
13788886309SYosry Ahmed cgroups[i].fd = fd;
13888886309SYosry Ahmed cgroups[i].id = get_cgroup_id(cgroups[i].path);
13988886309SYosry Ahmed }
14088886309SYosry Ahmed return 0;
14188886309SYosry Ahmed }
14288886309SYosry Ahmed
cleanup_cgroups(void)14388886309SYosry Ahmed static void cleanup_cgroups(void)
14488886309SYosry Ahmed {
14588886309SYosry Ahmed close(root_cgroup_fd);
14688886309SYosry Ahmed for (int i = 0; i < N_CGROUPS; i++)
14788886309SYosry Ahmed close(cgroups[i].fd);
14888886309SYosry Ahmed cleanup_cgroup_environment();
14988886309SYosry Ahmed }
15088886309SYosry Ahmed
15188886309SYosry Ahmed /* Sets up cgroup hiearchary, returns 0 on success. */
setup_hierarchy(void)15288886309SYosry Ahmed static int setup_hierarchy(void)
15388886309SYosry Ahmed {
15488886309SYosry Ahmed return setup_bpffs() || setup_cgroups();
15588886309SYosry Ahmed }
15688886309SYosry Ahmed
destroy_hierarchy(void)15788886309SYosry Ahmed static void destroy_hierarchy(void)
15888886309SYosry Ahmed {
15988886309SYosry Ahmed cleanup_cgroups();
16088886309SYosry Ahmed cleanup_bpffs();
16188886309SYosry Ahmed }
16288886309SYosry Ahmed
attach_processes(void)163*e0401dceSYosry Ahmed static int attach_processes(void)
16488886309SYosry Ahmed {
165*e0401dceSYosry Ahmed int i, j, status;
16688886309SYosry Ahmed
167*e0401dceSYosry Ahmed /* In every leaf cgroup, attach 3 processes */
16888886309SYosry Ahmed for (i = N_NON_LEAF_CGROUPS; i < N_CGROUPS; i++) {
169*e0401dceSYosry Ahmed for (j = 0; j < PROCESSES_PER_CGROUP; j++) {
17088886309SYosry Ahmed pid_t pid;
17188886309SYosry Ahmed
172*e0401dceSYosry Ahmed /* Create child and attach to cgroup */
17388886309SYosry Ahmed pid = fork();
17488886309SYosry Ahmed if (pid == 0) {
175*e0401dceSYosry Ahmed if (join_parent_cgroup(cgroups[i].path))
176*e0401dceSYosry Ahmed exit(EACCES);
177*e0401dceSYosry Ahmed exit(0);
17888886309SYosry Ahmed }
17988886309SYosry Ahmed
180*e0401dceSYosry Ahmed /* Cleanup child */
18188886309SYosry Ahmed waitpid(pid, &status, 0);
182*e0401dceSYosry Ahmed if (!ASSERT_TRUE(WIFEXITED(status), "child process exited"))
183*e0401dceSYosry Ahmed return 1;
184*e0401dceSYosry Ahmed if (!ASSERT_EQ(WEXITSTATUS(status), 0,
185*e0401dceSYosry Ahmed "child process exit code"))
186*e0401dceSYosry Ahmed return 1;
187*e0401dceSYosry Ahmed }
18888886309SYosry Ahmed }
18988886309SYosry Ahmed return 0;
19088886309SYosry Ahmed }
19188886309SYosry Ahmed
19288886309SYosry Ahmed static unsigned long long
get_attach_counter(unsigned long long cgroup_id,const char * file_name)193*e0401dceSYosry Ahmed get_attach_counter(unsigned long long cgroup_id, const char *file_name)
19488886309SYosry Ahmed {
195*e0401dceSYosry Ahmed unsigned long long attach_counter = 0, id = 0;
19688886309SYosry Ahmed static char buf[128], path[128];
19788886309SYosry Ahmed
19888886309SYosry Ahmed /* For every cgroup, read the file generated by cgroup_iter */
199*e0401dceSYosry Ahmed snprintf(path, 128, "%s%s", BPFFS_ATTACH_COUNTERS, file_name);
20088886309SYosry Ahmed if (!ASSERT_OK(read_from_file(path, buf, 128), "read cgroup_iter"))
20188886309SYosry Ahmed return 0;
20288886309SYosry Ahmed
20388886309SYosry Ahmed /* Check the output file formatting */
204*e0401dceSYosry Ahmed ASSERT_EQ(sscanf(buf, "cg_id: %llu, attach_counter: %llu\n",
205*e0401dceSYosry Ahmed &id, &attach_counter), 2, "output format");
20688886309SYosry Ahmed
20788886309SYosry Ahmed /* Check that the cgroup_id is displayed correctly */
20888886309SYosry Ahmed ASSERT_EQ(id, cgroup_id, "cgroup_id");
209*e0401dceSYosry Ahmed /* Check that the counter is non-zero */
210*e0401dceSYosry Ahmed ASSERT_GT(attach_counter, 0, "attach counter non-zero");
211*e0401dceSYosry Ahmed return attach_counter;
21288886309SYosry Ahmed }
21388886309SYosry Ahmed
check_attach_counters(void)214*e0401dceSYosry Ahmed static void check_attach_counters(void)
21588886309SYosry Ahmed {
216*e0401dceSYosry Ahmed unsigned long long attach_counters[N_CGROUPS], root_attach_counter;
21788886309SYosry Ahmed int i;
21888886309SYosry Ahmed
219*e0401dceSYosry Ahmed for (i = 0; i < N_CGROUPS; i++)
220*e0401dceSYosry Ahmed attach_counters[i] = get_attach_counter(cgroups[i].id,
22188886309SYosry Ahmed cgroups[i].name);
22288886309SYosry Ahmed
22388886309SYosry Ahmed /* Read stats for root too */
224*e0401dceSYosry Ahmed root_attach_counter = get_attach_counter(CG_ROOT_ID, CG_ROOT_NAME);
225*e0401dceSYosry Ahmed
226*e0401dceSYosry Ahmed /* Check that all leafs cgroups have an attach counter of 3 */
227*e0401dceSYosry Ahmed for (i = N_NON_LEAF_CGROUPS; i < N_CGROUPS; i++)
228*e0401dceSYosry Ahmed ASSERT_EQ(attach_counters[i], PROCESSES_PER_CGROUP,
229*e0401dceSYosry Ahmed "leaf cgroup attach counter");
23088886309SYosry Ahmed
23188886309SYosry Ahmed /* Check that child1 == child1_1 + child1_2 */
232*e0401dceSYosry Ahmed ASSERT_EQ(attach_counters[1], attach_counters[3] + attach_counters[4],
233*e0401dceSYosry Ahmed "child1_counter");
23488886309SYosry Ahmed /* Check that child2 == child2_1 + child2_2 */
235*e0401dceSYosry Ahmed ASSERT_EQ(attach_counters[2], attach_counters[5] + attach_counters[6],
236*e0401dceSYosry Ahmed "child2_counter");
23788886309SYosry Ahmed /* Check that test == child1 + child2 */
238*e0401dceSYosry Ahmed ASSERT_EQ(attach_counters[0], attach_counters[1] + attach_counters[2],
239*e0401dceSYosry Ahmed "test_counter");
24088886309SYosry Ahmed /* Check that root >= test */
241*e0401dceSYosry Ahmed ASSERT_GE(root_attach_counter, attach_counters[1], "root_counter");
24288886309SYosry Ahmed }
24388886309SYosry Ahmed
24488886309SYosry Ahmed /* Creates iter link and pins in bpffs, returns 0 on success, -errno on failure.
24588886309SYosry Ahmed */
setup_cgroup_iter(struct cgroup_hierarchical_stats * obj,int cgroup_fd,const char * file_name)24688886309SYosry Ahmed static int setup_cgroup_iter(struct cgroup_hierarchical_stats *obj,
24788886309SYosry Ahmed int cgroup_fd, const char *file_name)
24888886309SYosry Ahmed {
24988886309SYosry Ahmed DECLARE_LIBBPF_OPTS(bpf_iter_attach_opts, opts);
25088886309SYosry Ahmed union bpf_iter_link_info linfo = {};
25188886309SYosry Ahmed struct bpf_link *link;
25288886309SYosry Ahmed static char path[128];
25388886309SYosry Ahmed int err;
25488886309SYosry Ahmed
25588886309SYosry Ahmed /*
25688886309SYosry Ahmed * Create an iter link, parameterized by cgroup_fd. We only want to
25788886309SYosry Ahmed * traverse one cgroup, so set the traversal order to "self".
25888886309SYosry Ahmed */
25988886309SYosry Ahmed linfo.cgroup.cgroup_fd = cgroup_fd;
260d4ffb6f3SHao Luo linfo.cgroup.order = BPF_CGROUP_ITER_SELF_ONLY;
26188886309SYosry Ahmed opts.link_info = &linfo;
26288886309SYosry Ahmed opts.link_info_len = sizeof(linfo);
263*e0401dceSYosry Ahmed link = bpf_program__attach_iter(obj->progs.dumper, &opts);
26488886309SYosry Ahmed if (!ASSERT_OK_PTR(link, "attach_iter"))
26588886309SYosry Ahmed return -EFAULT;
26688886309SYosry Ahmed
26788886309SYosry Ahmed /* Pin the link to a bpffs file */
268*e0401dceSYosry Ahmed snprintf(path, 128, "%s%s", BPFFS_ATTACH_COUNTERS, file_name);
26988886309SYosry Ahmed err = bpf_link__pin(link, path);
27088886309SYosry Ahmed ASSERT_OK(err, "pin cgroup_iter");
27188886309SYosry Ahmed
27288886309SYosry Ahmed /* Remove the link, leaving only the ref held by the pinned file */
27388886309SYosry Ahmed bpf_link__destroy(link);
27488886309SYosry Ahmed return err;
27588886309SYosry Ahmed }
27688886309SYosry Ahmed
27788886309SYosry Ahmed /* Sets up programs for collecting stats, returns 0 on success. */
setup_progs(struct cgroup_hierarchical_stats ** skel)27888886309SYosry Ahmed static int setup_progs(struct cgroup_hierarchical_stats **skel)
27988886309SYosry Ahmed {
28088886309SYosry Ahmed int i, err;
28188886309SYosry Ahmed
28288886309SYosry Ahmed *skel = cgroup_hierarchical_stats__open_and_load();
28388886309SYosry Ahmed if (!ASSERT_OK_PTR(*skel, "open_and_load"))
28488886309SYosry Ahmed return 1;
28588886309SYosry Ahmed
28688886309SYosry Ahmed /* Attach cgroup_iter program that will dump the stats to cgroups */
28788886309SYosry Ahmed for (i = 0; i < N_CGROUPS; i++) {
28888886309SYosry Ahmed err = setup_cgroup_iter(*skel, cgroups[i].fd, cgroups[i].name);
28988886309SYosry Ahmed if (!ASSERT_OK(err, "setup_cgroup_iter"))
29088886309SYosry Ahmed return err;
29188886309SYosry Ahmed }
29288886309SYosry Ahmed
29388886309SYosry Ahmed /* Also dump stats for root */
29488886309SYosry Ahmed err = setup_cgroup_iter(*skel, root_cgroup_fd, CG_ROOT_NAME);
29588886309SYosry Ahmed if (!ASSERT_OK(err, "setup_cgroup_iter"))
29688886309SYosry Ahmed return err;
29788886309SYosry Ahmed
298*e0401dceSYosry Ahmed bpf_program__set_autoattach((*skel)->progs.dumper, false);
29988886309SYosry Ahmed err = cgroup_hierarchical_stats__attach(*skel);
30088886309SYosry Ahmed if (!ASSERT_OK(err, "attach"))
30188886309SYosry Ahmed return err;
30288886309SYosry Ahmed
30388886309SYosry Ahmed return 0;
30488886309SYosry Ahmed }
30588886309SYosry Ahmed
destroy_progs(struct cgroup_hierarchical_stats * skel)30688886309SYosry Ahmed static void destroy_progs(struct cgroup_hierarchical_stats *skel)
30788886309SYosry Ahmed {
30888886309SYosry Ahmed static char path[128];
30988886309SYosry Ahmed int i;
31088886309SYosry Ahmed
31188886309SYosry Ahmed for (i = 0; i < N_CGROUPS; i++) {
31288886309SYosry Ahmed /* Delete files in bpffs that cgroup_iters are pinned in */
313*e0401dceSYosry Ahmed snprintf(path, 128, "%s%s", BPFFS_ATTACH_COUNTERS,
31488886309SYosry Ahmed cgroups[i].name);
31588886309SYosry Ahmed ASSERT_OK(remove(path), "remove cgroup_iter pin");
31688886309SYosry Ahmed }
31788886309SYosry Ahmed
31888886309SYosry Ahmed /* Delete root file in bpffs */
319*e0401dceSYosry Ahmed snprintf(path, 128, "%s%s", BPFFS_ATTACH_COUNTERS, CG_ROOT_NAME);
32088886309SYosry Ahmed ASSERT_OK(remove(path), "remove cgroup_iter root pin");
32188886309SYosry Ahmed cgroup_hierarchical_stats__destroy(skel);
32288886309SYosry Ahmed }
32388886309SYosry Ahmed
test_cgroup_hierarchical_stats(void)32488886309SYosry Ahmed void test_cgroup_hierarchical_stats(void)
32588886309SYosry Ahmed {
32688886309SYosry Ahmed struct cgroup_hierarchical_stats *skel = NULL;
32788886309SYosry Ahmed
32888886309SYosry Ahmed if (setup_hierarchy())
32988886309SYosry Ahmed goto hierarchy_cleanup;
33088886309SYosry Ahmed if (setup_progs(&skel))
33188886309SYosry Ahmed goto cleanup;
332*e0401dceSYosry Ahmed if (attach_processes())
33388886309SYosry Ahmed goto cleanup;
334*e0401dceSYosry Ahmed check_attach_counters();
33588886309SYosry Ahmed cleanup:
33688886309SYosry Ahmed destroy_progs(skel);
33788886309SYosry Ahmed hierarchy_cleanup:
33888886309SYosry Ahmed destroy_hierarchy();
33988886309SYosry Ahmed }
340