1*6bce6ddbSJP Kobryn // SPDX-License-Identifier: GPL-2.0 2*6bce6ddbSJP Kobryn /* Copyright (c) 2025 Meta Platforms, Inc. and affiliates. */ 3*6bce6ddbSJP Kobryn #include <test_progs.h> 4*6bce6ddbSJP Kobryn #include <bpf/libbpf.h> 5*6bce6ddbSJP Kobryn #include <bpf/btf.h> 6*6bce6ddbSJP Kobryn #include <fcntl.h> 7*6bce6ddbSJP Kobryn #include <sys/mman.h> 8*6bce6ddbSJP Kobryn #include <unistd.h> 9*6bce6ddbSJP Kobryn #include "cgroup_helpers.h" 10*6bce6ddbSJP Kobryn #include "cgroup_iter_memcg.h" 11*6bce6ddbSJP Kobryn #include "cgroup_iter_memcg.skel.h" 12*6bce6ddbSJP Kobryn 13*6bce6ddbSJP Kobryn static int read_stats(struct bpf_link *link) 14*6bce6ddbSJP Kobryn { 15*6bce6ddbSJP Kobryn int fd, ret = 0; 16*6bce6ddbSJP Kobryn ssize_t bytes; 17*6bce6ddbSJP Kobryn 18*6bce6ddbSJP Kobryn fd = bpf_iter_create(bpf_link__fd(link)); 19*6bce6ddbSJP Kobryn if (!ASSERT_OK_FD(fd, "bpf_iter_create")) 20*6bce6ddbSJP Kobryn return 1; 21*6bce6ddbSJP Kobryn 22*6bce6ddbSJP Kobryn /* 23*6bce6ddbSJP Kobryn * Invoke iter program by reading from its fd. We're not expecting any 24*6bce6ddbSJP Kobryn * data to be written by the bpf program so the result should be zero. 25*6bce6ddbSJP Kobryn * Results will be read directly through the custom data section 26*6bce6ddbSJP Kobryn * accessible through skel->data_query.memcg_query. 27*6bce6ddbSJP Kobryn */ 28*6bce6ddbSJP Kobryn bytes = read(fd, NULL, 0); 29*6bce6ddbSJP Kobryn if (!ASSERT_EQ(bytes, 0, "read fd")) 30*6bce6ddbSJP Kobryn ret = 1; 31*6bce6ddbSJP Kobryn 32*6bce6ddbSJP Kobryn close(fd); 33*6bce6ddbSJP Kobryn return ret; 34*6bce6ddbSJP Kobryn } 35*6bce6ddbSJP Kobryn 36*6bce6ddbSJP Kobryn static void test_anon(struct bpf_link *link, struct memcg_query *memcg_query) 37*6bce6ddbSJP Kobryn { 38*6bce6ddbSJP Kobryn void *map; 39*6bce6ddbSJP Kobryn size_t len; 40*6bce6ddbSJP Kobryn 41*6bce6ddbSJP Kobryn len = sysconf(_SC_PAGESIZE) * 1024; 42*6bce6ddbSJP Kobryn 43*6bce6ddbSJP Kobryn /* 44*6bce6ddbSJP Kobryn * Increase memcg anon usage by mapping and writing 45*6bce6ddbSJP Kobryn * to a new anon region. 46*6bce6ddbSJP Kobryn */ 47*6bce6ddbSJP Kobryn map = mmap(NULL, len, PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); 48*6bce6ddbSJP Kobryn if (!ASSERT_NEQ(map, MAP_FAILED, "mmap anon")) 49*6bce6ddbSJP Kobryn return; 50*6bce6ddbSJP Kobryn 51*6bce6ddbSJP Kobryn memset(map, 1, len); 52*6bce6ddbSJP Kobryn 53*6bce6ddbSJP Kobryn if (!ASSERT_OK(read_stats(link), "read stats")) 54*6bce6ddbSJP Kobryn goto cleanup; 55*6bce6ddbSJP Kobryn 56*6bce6ddbSJP Kobryn ASSERT_GT(memcg_query->nr_anon_mapped, 0, "final anon mapped val"); 57*6bce6ddbSJP Kobryn 58*6bce6ddbSJP Kobryn cleanup: 59*6bce6ddbSJP Kobryn munmap(map, len); 60*6bce6ddbSJP Kobryn } 61*6bce6ddbSJP Kobryn 62*6bce6ddbSJP Kobryn static void test_file(struct bpf_link *link, struct memcg_query *memcg_query) 63*6bce6ddbSJP Kobryn { 64*6bce6ddbSJP Kobryn void *map; 65*6bce6ddbSJP Kobryn size_t len; 66*6bce6ddbSJP Kobryn char *path; 67*6bce6ddbSJP Kobryn int fd; 68*6bce6ddbSJP Kobryn 69*6bce6ddbSJP Kobryn len = sysconf(_SC_PAGESIZE) * 1024; 70*6bce6ddbSJP Kobryn path = "/tmp/test_cgroup_iter_memcg"; 71*6bce6ddbSJP Kobryn 72*6bce6ddbSJP Kobryn /* 73*6bce6ddbSJP Kobryn * Increase memcg file usage by creating and writing 74*6bce6ddbSJP Kobryn * to a mapped file. 75*6bce6ddbSJP Kobryn */ 76*6bce6ddbSJP Kobryn fd = open(path, O_CREAT | O_RDWR, 0644); 77*6bce6ddbSJP Kobryn if (!ASSERT_OK_FD(fd, "open fd")) 78*6bce6ddbSJP Kobryn return; 79*6bce6ddbSJP Kobryn if (!ASSERT_OK(ftruncate(fd, len), "ftruncate")) 80*6bce6ddbSJP Kobryn goto cleanup_fd; 81*6bce6ddbSJP Kobryn 82*6bce6ddbSJP Kobryn map = mmap(NULL, len, PROT_WRITE, MAP_SHARED, fd, 0); 83*6bce6ddbSJP Kobryn if (!ASSERT_NEQ(map, MAP_FAILED, "mmap file")) 84*6bce6ddbSJP Kobryn goto cleanup_fd; 85*6bce6ddbSJP Kobryn 86*6bce6ddbSJP Kobryn memset(map, 1, len); 87*6bce6ddbSJP Kobryn 88*6bce6ddbSJP Kobryn if (!ASSERT_OK(read_stats(link), "read stats")) 89*6bce6ddbSJP Kobryn goto cleanup_map; 90*6bce6ddbSJP Kobryn 91*6bce6ddbSJP Kobryn ASSERT_GT(memcg_query->nr_file_pages, 0, "final file value"); 92*6bce6ddbSJP Kobryn ASSERT_GT(memcg_query->nr_file_mapped, 0, "final file mapped value"); 93*6bce6ddbSJP Kobryn 94*6bce6ddbSJP Kobryn cleanup_map: 95*6bce6ddbSJP Kobryn munmap(map, len); 96*6bce6ddbSJP Kobryn cleanup_fd: 97*6bce6ddbSJP Kobryn close(fd); 98*6bce6ddbSJP Kobryn unlink(path); 99*6bce6ddbSJP Kobryn } 100*6bce6ddbSJP Kobryn 101*6bce6ddbSJP Kobryn static void test_shmem(struct bpf_link *link, struct memcg_query *memcg_query) 102*6bce6ddbSJP Kobryn { 103*6bce6ddbSJP Kobryn size_t len; 104*6bce6ddbSJP Kobryn int fd; 105*6bce6ddbSJP Kobryn 106*6bce6ddbSJP Kobryn len = sysconf(_SC_PAGESIZE) * 1024; 107*6bce6ddbSJP Kobryn 108*6bce6ddbSJP Kobryn /* 109*6bce6ddbSJP Kobryn * Increase memcg shmem usage by creating and writing 110*6bce6ddbSJP Kobryn * to a shmem object. 111*6bce6ddbSJP Kobryn */ 112*6bce6ddbSJP Kobryn fd = shm_open("/tmp_shmem", O_CREAT | O_RDWR, 0644); 113*6bce6ddbSJP Kobryn if (!ASSERT_OK_FD(fd, "shm_open")) 114*6bce6ddbSJP Kobryn return; 115*6bce6ddbSJP Kobryn 116*6bce6ddbSJP Kobryn if (!ASSERT_OK(fallocate(fd, 0, 0, len), "fallocate")) 117*6bce6ddbSJP Kobryn goto cleanup; 118*6bce6ddbSJP Kobryn 119*6bce6ddbSJP Kobryn if (!ASSERT_OK(read_stats(link), "read stats")) 120*6bce6ddbSJP Kobryn goto cleanup; 121*6bce6ddbSJP Kobryn 122*6bce6ddbSJP Kobryn ASSERT_GT(memcg_query->nr_shmem, 0, "final shmem value"); 123*6bce6ddbSJP Kobryn 124*6bce6ddbSJP Kobryn cleanup: 125*6bce6ddbSJP Kobryn close(fd); 126*6bce6ddbSJP Kobryn shm_unlink("/tmp_shmem"); 127*6bce6ddbSJP Kobryn } 128*6bce6ddbSJP Kobryn 129*6bce6ddbSJP Kobryn #define NR_PIPES 64 130*6bce6ddbSJP Kobryn static void test_kmem(struct bpf_link *link, struct memcg_query *memcg_query) 131*6bce6ddbSJP Kobryn { 132*6bce6ddbSJP Kobryn int fds[NR_PIPES][2], i; 133*6bce6ddbSJP Kobryn 134*6bce6ddbSJP Kobryn /* 135*6bce6ddbSJP Kobryn * Increase kmem value by creating pipes which will allocate some 136*6bce6ddbSJP Kobryn * kernel buffers. 137*6bce6ddbSJP Kobryn */ 138*6bce6ddbSJP Kobryn for (i = 0; i < NR_PIPES; i++) { 139*6bce6ddbSJP Kobryn if (!ASSERT_OK(pipe(fds[i]), "pipe")) 140*6bce6ddbSJP Kobryn goto cleanup; 141*6bce6ddbSJP Kobryn } 142*6bce6ddbSJP Kobryn 143*6bce6ddbSJP Kobryn if (!ASSERT_OK(read_stats(link), "read stats")) 144*6bce6ddbSJP Kobryn goto cleanup; 145*6bce6ddbSJP Kobryn 146*6bce6ddbSJP Kobryn ASSERT_GT(memcg_query->memcg_kmem, 0, "kmem value"); 147*6bce6ddbSJP Kobryn 148*6bce6ddbSJP Kobryn cleanup: 149*6bce6ddbSJP Kobryn for (i = i - 1; i >= 0; i--) { 150*6bce6ddbSJP Kobryn close(fds[i][0]); 151*6bce6ddbSJP Kobryn close(fds[i][1]); 152*6bce6ddbSJP Kobryn } 153*6bce6ddbSJP Kobryn } 154*6bce6ddbSJP Kobryn 155*6bce6ddbSJP Kobryn static void test_pgfault(struct bpf_link *link, struct memcg_query *memcg_query) 156*6bce6ddbSJP Kobryn { 157*6bce6ddbSJP Kobryn void *map; 158*6bce6ddbSJP Kobryn size_t len; 159*6bce6ddbSJP Kobryn 160*6bce6ddbSJP Kobryn len = sysconf(_SC_PAGESIZE) * 1024; 161*6bce6ddbSJP Kobryn 162*6bce6ddbSJP Kobryn /* Create region to use for triggering a page fault. */ 163*6bce6ddbSJP Kobryn map = mmap(NULL, len, PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); 164*6bce6ddbSJP Kobryn if (!ASSERT_NEQ(map, MAP_FAILED, "mmap anon")) 165*6bce6ddbSJP Kobryn return; 166*6bce6ddbSJP Kobryn 167*6bce6ddbSJP Kobryn /* Trigger page fault. */ 168*6bce6ddbSJP Kobryn memset(map, 1, len); 169*6bce6ddbSJP Kobryn 170*6bce6ddbSJP Kobryn if (!ASSERT_OK(read_stats(link), "read stats")) 171*6bce6ddbSJP Kobryn goto cleanup; 172*6bce6ddbSJP Kobryn 173*6bce6ddbSJP Kobryn ASSERT_GT(memcg_query->pgfault, 0, "final pgfault val"); 174*6bce6ddbSJP Kobryn 175*6bce6ddbSJP Kobryn cleanup: 176*6bce6ddbSJP Kobryn munmap(map, len); 177*6bce6ddbSJP Kobryn } 178*6bce6ddbSJP Kobryn 179*6bce6ddbSJP Kobryn void test_cgroup_iter_memcg(void) 180*6bce6ddbSJP Kobryn { 181*6bce6ddbSJP Kobryn char *cgroup_rel_path = "/cgroup_iter_memcg_test"; 182*6bce6ddbSJP Kobryn struct cgroup_iter_memcg *skel; 183*6bce6ddbSJP Kobryn struct bpf_link *link; 184*6bce6ddbSJP Kobryn int cgroup_fd; 185*6bce6ddbSJP Kobryn 186*6bce6ddbSJP Kobryn cgroup_fd = cgroup_setup_and_join(cgroup_rel_path); 187*6bce6ddbSJP Kobryn if (!ASSERT_OK_FD(cgroup_fd, "cgroup_setup_and_join")) 188*6bce6ddbSJP Kobryn return; 189*6bce6ddbSJP Kobryn 190*6bce6ddbSJP Kobryn skel = cgroup_iter_memcg__open_and_load(); 191*6bce6ddbSJP Kobryn if (!ASSERT_OK_PTR(skel, "cgroup_iter_memcg__open_and_load")) 192*6bce6ddbSJP Kobryn goto cleanup_cgroup_fd; 193*6bce6ddbSJP Kobryn 194*6bce6ddbSJP Kobryn DECLARE_LIBBPF_OPTS(bpf_iter_attach_opts, opts); 195*6bce6ddbSJP Kobryn union bpf_iter_link_info linfo = { 196*6bce6ddbSJP Kobryn .cgroup.cgroup_fd = cgroup_fd, 197*6bce6ddbSJP Kobryn .cgroup.order = BPF_CGROUP_ITER_SELF_ONLY, 198*6bce6ddbSJP Kobryn }; 199*6bce6ddbSJP Kobryn opts.link_info = &linfo; 200*6bce6ddbSJP Kobryn opts.link_info_len = sizeof(linfo); 201*6bce6ddbSJP Kobryn 202*6bce6ddbSJP Kobryn link = bpf_program__attach_iter(skel->progs.cgroup_memcg_query, &opts); 203*6bce6ddbSJP Kobryn if (!ASSERT_OK_PTR(link, "bpf_program__attach_iter")) 204*6bce6ddbSJP Kobryn goto cleanup_skel; 205*6bce6ddbSJP Kobryn 206*6bce6ddbSJP Kobryn if (test__start_subtest("cgroup_iter_memcg__anon")) 207*6bce6ddbSJP Kobryn test_anon(link, &skel->data_query->memcg_query); 208*6bce6ddbSJP Kobryn if (test__start_subtest("cgroup_iter_memcg__shmem")) 209*6bce6ddbSJP Kobryn test_shmem(link, &skel->data_query->memcg_query); 210*6bce6ddbSJP Kobryn if (test__start_subtest("cgroup_iter_memcg__file")) 211*6bce6ddbSJP Kobryn test_file(link, &skel->data_query->memcg_query); 212*6bce6ddbSJP Kobryn if (test__start_subtest("cgroup_iter_memcg__kmem")) 213*6bce6ddbSJP Kobryn test_kmem(link, &skel->data_query->memcg_query); 214*6bce6ddbSJP Kobryn if (test__start_subtest("cgroup_iter_memcg__pgfault")) 215*6bce6ddbSJP Kobryn test_pgfault(link, &skel->data_query->memcg_query); 216*6bce6ddbSJP Kobryn 217*6bce6ddbSJP Kobryn bpf_link__destroy(link); 218*6bce6ddbSJP Kobryn cleanup_skel: 219*6bce6ddbSJP Kobryn cgroup_iter_memcg__destroy(skel); 220*6bce6ddbSJP Kobryn cleanup_cgroup_fd: 221*6bce6ddbSJP Kobryn close(cgroup_fd); 222*6bce6ddbSJP Kobryn cleanup_cgroup_environment(); 223*6bce6ddbSJP Kobryn } 224