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