1933dc80eSRoman Gushchin // SPDX-License-Identifier: GPL-2.0
2933dc80eSRoman Gushchin #define _GNU_SOURCE
3933dc80eSRoman Gushchin
4933dc80eSRoman Gushchin #include <linux/limits.h>
5933dc80eSRoman Gushchin #include <fcntl.h>
6933dc80eSRoman Gushchin #include <stdio.h>
7933dc80eSRoman Gushchin #include <stdlib.h>
8933dc80eSRoman Gushchin #include <string.h>
9933dc80eSRoman Gushchin #include <sys/stat.h>
10933dc80eSRoman Gushchin #include <sys/types.h>
11933dc80eSRoman Gushchin #include <unistd.h>
12933dc80eSRoman Gushchin #include <sys/wait.h>
13933dc80eSRoman Gushchin #include <errno.h>
14933dc80eSRoman Gushchin #include <sys/sysinfo.h>
15933dc80eSRoman Gushchin #include <pthread.h>
16933dc80eSRoman Gushchin
17933dc80eSRoman Gushchin #include "../kselftest.h"
18933dc80eSRoman Gushchin #include "cgroup_util.h"
19933dc80eSRoman Gushchin
20933dc80eSRoman Gushchin
2190631e1dSRoman Gushchin /*
22de16d6e4SMichal Hocko * Memory cgroup charging is performed using percpu batches 64 pages
234bbcc5a4SJohannes Weiner * big (look at MEMCG_CHARGE_BATCH), whereas memory.stat is exact. So
244bbcc5a4SJohannes Weiner * the maximum discrepancy between charge and vmstat entries is number
25de16d6e4SMichal Hocko * of cpus multiplied by 64 pages.
2690631e1dSRoman Gushchin */
27de16d6e4SMichal Hocko #define MAX_VMSTAT_ERROR (4096 * 64 * get_nprocs())
2890631e1dSRoman Gushchin
2990631e1dSRoman Gushchin
alloc_dcache(const char * cgroup,void * arg)30933dc80eSRoman Gushchin static int alloc_dcache(const char *cgroup, void *arg)
31933dc80eSRoman Gushchin {
32933dc80eSRoman Gushchin unsigned long i;
33933dc80eSRoman Gushchin struct stat st;
34933dc80eSRoman Gushchin char buf[128];
35933dc80eSRoman Gushchin
36933dc80eSRoman Gushchin for (i = 0; i < (unsigned long)arg; i++) {
37933dc80eSRoman Gushchin snprintf(buf, sizeof(buf),
38933dc80eSRoman Gushchin "/something-non-existent-with-a-long-name-%64lu-%d",
39933dc80eSRoman Gushchin i, getpid());
40933dc80eSRoman Gushchin stat(buf, &st);
41933dc80eSRoman Gushchin }
42933dc80eSRoman Gushchin
43933dc80eSRoman Gushchin return 0;
44933dc80eSRoman Gushchin }
45933dc80eSRoman Gushchin
46933dc80eSRoman Gushchin /*
47933dc80eSRoman Gushchin * This test allocates 100000 of negative dentries with long names.
48933dc80eSRoman Gushchin * Then it checks that "slab" in memory.stat is larger than 1M.
49933dc80eSRoman Gushchin * Then it sets memory.high to 1M and checks that at least 1/2
50933dc80eSRoman Gushchin * of slab memory has been reclaimed.
51933dc80eSRoman Gushchin */
test_kmem_basic(const char * root)52933dc80eSRoman Gushchin static int test_kmem_basic(const char *root)
53933dc80eSRoman Gushchin {
54933dc80eSRoman Gushchin int ret = KSFT_FAIL;
55933dc80eSRoman Gushchin char *cg = NULL;
56933dc80eSRoman Gushchin long slab0, slab1, current;
57933dc80eSRoman Gushchin
58933dc80eSRoman Gushchin cg = cg_name(root, "kmem_basic_test");
59933dc80eSRoman Gushchin if (!cg)
60933dc80eSRoman Gushchin goto cleanup;
61933dc80eSRoman Gushchin
62933dc80eSRoman Gushchin if (cg_create(cg))
63933dc80eSRoman Gushchin goto cleanup;
64933dc80eSRoman Gushchin
65933dc80eSRoman Gushchin if (cg_run(cg, alloc_dcache, (void *)100000))
66933dc80eSRoman Gushchin goto cleanup;
67933dc80eSRoman Gushchin
68933dc80eSRoman Gushchin slab0 = cg_read_key_long(cg, "memory.stat", "slab ");
69933dc80eSRoman Gushchin if (slab0 < (1 << 20))
70933dc80eSRoman Gushchin goto cleanup;
71933dc80eSRoman Gushchin
72933dc80eSRoman Gushchin cg_write(cg, "memory.high", "1M");
73fac26502SJohannes Weiner
74fac26502SJohannes Weiner /* wait for RCU freeing */
75fac26502SJohannes Weiner sleep(1);
76fac26502SJohannes Weiner
77933dc80eSRoman Gushchin slab1 = cg_read_key_long(cg, "memory.stat", "slab ");
7860439471SLucas Karpinski if (slab1 < 0)
79933dc80eSRoman Gushchin goto cleanup;
80933dc80eSRoman Gushchin
81933dc80eSRoman Gushchin current = cg_read_long(cg, "memory.current");
8260439471SLucas Karpinski if (current < 0)
83933dc80eSRoman Gushchin goto cleanup;
84933dc80eSRoman Gushchin
85933dc80eSRoman Gushchin if (slab1 < slab0 / 2 && current < slab0 / 2)
86933dc80eSRoman Gushchin ret = KSFT_PASS;
87933dc80eSRoman Gushchin cleanup:
88933dc80eSRoman Gushchin cg_destroy(cg);
89933dc80eSRoman Gushchin free(cg);
90933dc80eSRoman Gushchin
91933dc80eSRoman Gushchin return ret;
92933dc80eSRoman Gushchin }
93933dc80eSRoman Gushchin
alloc_kmem_fn(void * arg)94933dc80eSRoman Gushchin static void *alloc_kmem_fn(void *arg)
95933dc80eSRoman Gushchin {
96933dc80eSRoman Gushchin alloc_dcache(NULL, (void *)100);
97933dc80eSRoman Gushchin return NULL;
98933dc80eSRoman Gushchin }
99933dc80eSRoman Gushchin
alloc_kmem_smp(const char * cgroup,void * arg)100933dc80eSRoman Gushchin static int alloc_kmem_smp(const char *cgroup, void *arg)
101933dc80eSRoman Gushchin {
102933dc80eSRoman Gushchin int nr_threads = 2 * get_nprocs();
103933dc80eSRoman Gushchin pthread_t *tinfo;
104933dc80eSRoman Gushchin unsigned long i;
105933dc80eSRoman Gushchin int ret = -1;
106933dc80eSRoman Gushchin
107933dc80eSRoman Gushchin tinfo = calloc(nr_threads, sizeof(pthread_t));
108933dc80eSRoman Gushchin if (tinfo == NULL)
109933dc80eSRoman Gushchin return -1;
110933dc80eSRoman Gushchin
111933dc80eSRoman Gushchin for (i = 0; i < nr_threads; i++) {
112933dc80eSRoman Gushchin if (pthread_create(&tinfo[i], NULL, &alloc_kmem_fn,
113933dc80eSRoman Gushchin (void *)i)) {
114933dc80eSRoman Gushchin free(tinfo);
115933dc80eSRoman Gushchin return -1;
116933dc80eSRoman Gushchin }
117933dc80eSRoman Gushchin }
118933dc80eSRoman Gushchin
119933dc80eSRoman Gushchin for (i = 0; i < nr_threads; i++) {
120933dc80eSRoman Gushchin ret = pthread_join(tinfo[i], NULL);
121933dc80eSRoman Gushchin if (ret)
122933dc80eSRoman Gushchin break;
123933dc80eSRoman Gushchin }
124933dc80eSRoman Gushchin
125933dc80eSRoman Gushchin free(tinfo);
126933dc80eSRoman Gushchin return ret;
127933dc80eSRoman Gushchin }
128933dc80eSRoman Gushchin
cg_run_in_subcgroups(const char * parent,int (* fn)(const char * cgroup,void * arg),void * arg,int times)129933dc80eSRoman Gushchin static int cg_run_in_subcgroups(const char *parent,
130933dc80eSRoman Gushchin int (*fn)(const char *cgroup, void *arg),
131933dc80eSRoman Gushchin void *arg, int times)
132933dc80eSRoman Gushchin {
133933dc80eSRoman Gushchin char *child;
134933dc80eSRoman Gushchin int i;
135933dc80eSRoman Gushchin
136933dc80eSRoman Gushchin for (i = 0; i < times; i++) {
137933dc80eSRoman Gushchin child = cg_name_indexed(parent, "child", i);
138933dc80eSRoman Gushchin if (!child)
139933dc80eSRoman Gushchin return -1;
140933dc80eSRoman Gushchin
141933dc80eSRoman Gushchin if (cg_create(child)) {
142933dc80eSRoman Gushchin cg_destroy(child);
143933dc80eSRoman Gushchin free(child);
144933dc80eSRoman Gushchin return -1;
145933dc80eSRoman Gushchin }
146933dc80eSRoman Gushchin
147933dc80eSRoman Gushchin if (cg_run(child, fn, NULL)) {
148933dc80eSRoman Gushchin cg_destroy(child);
149933dc80eSRoman Gushchin free(child);
150933dc80eSRoman Gushchin return -1;
151933dc80eSRoman Gushchin }
152933dc80eSRoman Gushchin
153933dc80eSRoman Gushchin cg_destroy(child);
154933dc80eSRoman Gushchin free(child);
155933dc80eSRoman Gushchin }
156933dc80eSRoman Gushchin
157933dc80eSRoman Gushchin return 0;
158933dc80eSRoman Gushchin }
159933dc80eSRoman Gushchin
160933dc80eSRoman Gushchin /*
161933dc80eSRoman Gushchin * The test creates and destroys a large number of cgroups. In each cgroup it
162933dc80eSRoman Gushchin * allocates some slab memory (mostly negative dentries) using 2 * NR_CPUS
163933dc80eSRoman Gushchin * threads. Then it checks the sanity of numbers on the parent level:
164933dc80eSRoman Gushchin * the total size of the cgroups should be roughly equal to
1657131fd7eSLucas Karpinski * anon + file + kernel + sock.
166933dc80eSRoman Gushchin */
test_kmem_memcg_deletion(const char * root)167933dc80eSRoman Gushchin static int test_kmem_memcg_deletion(const char *root)
168933dc80eSRoman Gushchin {
1697131fd7eSLucas Karpinski long current, anon, file, kernel, sock, sum;
170933dc80eSRoman Gushchin int ret = KSFT_FAIL;
171933dc80eSRoman Gushchin char *parent;
172933dc80eSRoman Gushchin
173933dc80eSRoman Gushchin parent = cg_name(root, "kmem_memcg_deletion_test");
174933dc80eSRoman Gushchin if (!parent)
175933dc80eSRoman Gushchin goto cleanup;
176933dc80eSRoman Gushchin
177933dc80eSRoman Gushchin if (cg_create(parent))
178933dc80eSRoman Gushchin goto cleanup;
179933dc80eSRoman Gushchin
180933dc80eSRoman Gushchin if (cg_write(parent, "cgroup.subtree_control", "+memory"))
181933dc80eSRoman Gushchin goto cleanup;
182933dc80eSRoman Gushchin
183933dc80eSRoman Gushchin if (cg_run_in_subcgroups(parent, alloc_kmem_smp, NULL, 100))
184933dc80eSRoman Gushchin goto cleanup;
185933dc80eSRoman Gushchin
186933dc80eSRoman Gushchin current = cg_read_long(parent, "memory.current");
187933dc80eSRoman Gushchin anon = cg_read_key_long(parent, "memory.stat", "anon ");
188933dc80eSRoman Gushchin file = cg_read_key_long(parent, "memory.stat", "file ");
1897131fd7eSLucas Karpinski kernel = cg_read_key_long(parent, "memory.stat", "kernel ");
1904bbcc5a4SJohannes Weiner sock = cg_read_key_long(parent, "memory.stat", "sock ");
1917131fd7eSLucas Karpinski if (current < 0 || anon < 0 || file < 0 || kernel < 0 || sock < 0)
192933dc80eSRoman Gushchin goto cleanup;
193933dc80eSRoman Gushchin
1947131fd7eSLucas Karpinski sum = anon + file + kernel + sock;
19590631e1dSRoman Gushchin if (abs(sum - current) < MAX_VMSTAT_ERROR) {
196933dc80eSRoman Gushchin ret = KSFT_PASS;
197933dc80eSRoman Gushchin } else {
198933dc80eSRoman Gushchin printf("memory.current = %ld\n", current);
1997131fd7eSLucas Karpinski printf("anon + file + kernel + sock = %ld\n", sum);
200933dc80eSRoman Gushchin printf("anon = %ld\n", anon);
201933dc80eSRoman Gushchin printf("file = %ld\n", file);
2027131fd7eSLucas Karpinski printf("kernel = %ld\n", kernel);
2034bbcc5a4SJohannes Weiner printf("sock = %ld\n", sock);
204933dc80eSRoman Gushchin }
205933dc80eSRoman Gushchin
206933dc80eSRoman Gushchin cleanup:
207933dc80eSRoman Gushchin cg_destroy(parent);
208933dc80eSRoman Gushchin free(parent);
209933dc80eSRoman Gushchin
210933dc80eSRoman Gushchin return ret;
211933dc80eSRoman Gushchin }
212933dc80eSRoman Gushchin
213933dc80eSRoman Gushchin /*
214933dc80eSRoman Gushchin * The test reads the entire /proc/kpagecgroup. If the operation went
215933dc80eSRoman Gushchin * successfully (and the kernel didn't panic), the test is treated as passed.
216933dc80eSRoman Gushchin */
test_kmem_proc_kpagecgroup(const char * root)217933dc80eSRoman Gushchin static int test_kmem_proc_kpagecgroup(const char *root)
218933dc80eSRoman Gushchin {
219933dc80eSRoman Gushchin unsigned long buf[128];
220933dc80eSRoman Gushchin int ret = KSFT_FAIL;
221933dc80eSRoman Gushchin ssize_t len;
222933dc80eSRoman Gushchin int fd;
223933dc80eSRoman Gushchin
224933dc80eSRoman Gushchin fd = open("/proc/kpagecgroup", O_RDONLY);
225933dc80eSRoman Gushchin if (fd < 0)
226933dc80eSRoman Gushchin return ret;
227933dc80eSRoman Gushchin
228933dc80eSRoman Gushchin do {
229933dc80eSRoman Gushchin len = read(fd, buf, sizeof(buf));
230933dc80eSRoman Gushchin } while (len > 0);
231933dc80eSRoman Gushchin
232933dc80eSRoman Gushchin if (len == 0)
233933dc80eSRoman Gushchin ret = KSFT_PASS;
234933dc80eSRoman Gushchin
235933dc80eSRoman Gushchin close(fd);
236933dc80eSRoman Gushchin return ret;
237933dc80eSRoman Gushchin }
238933dc80eSRoman Gushchin
pthread_wait_fn(void * arg)239933dc80eSRoman Gushchin static void *pthread_wait_fn(void *arg)
240933dc80eSRoman Gushchin {
241933dc80eSRoman Gushchin sleep(100);
242933dc80eSRoman Gushchin return NULL;
243933dc80eSRoman Gushchin }
244933dc80eSRoman Gushchin
spawn_1000_threads(const char * cgroup,void * arg)245933dc80eSRoman Gushchin static int spawn_1000_threads(const char *cgroup, void *arg)
246933dc80eSRoman Gushchin {
247933dc80eSRoman Gushchin int nr_threads = 1000;
248933dc80eSRoman Gushchin pthread_t *tinfo;
249933dc80eSRoman Gushchin unsigned long i;
250933dc80eSRoman Gushchin long stack;
251933dc80eSRoman Gushchin int ret = -1;
252933dc80eSRoman Gushchin
253933dc80eSRoman Gushchin tinfo = calloc(nr_threads, sizeof(pthread_t));
254933dc80eSRoman Gushchin if (tinfo == NULL)
255933dc80eSRoman Gushchin return -1;
256933dc80eSRoman Gushchin
257933dc80eSRoman Gushchin for (i = 0; i < nr_threads; i++) {
258933dc80eSRoman Gushchin if (pthread_create(&tinfo[i], NULL, &pthread_wait_fn,
259933dc80eSRoman Gushchin (void *)i)) {
260933dc80eSRoman Gushchin free(tinfo);
261933dc80eSRoman Gushchin return(-1);
262933dc80eSRoman Gushchin }
263933dc80eSRoman Gushchin }
264933dc80eSRoman Gushchin
265933dc80eSRoman Gushchin stack = cg_read_key_long(cgroup, "memory.stat", "kernel_stack ");
266933dc80eSRoman Gushchin if (stack >= 4096 * 1000)
267933dc80eSRoman Gushchin ret = 0;
268933dc80eSRoman Gushchin
269933dc80eSRoman Gushchin free(tinfo);
270933dc80eSRoman Gushchin return ret;
271933dc80eSRoman Gushchin }
272933dc80eSRoman Gushchin
273933dc80eSRoman Gushchin /*
274933dc80eSRoman Gushchin * The test spawns a process, which spawns 1000 threads. Then it checks
275933dc80eSRoman Gushchin * that memory.stat's kernel_stack is at least 1000 pages large.
276933dc80eSRoman Gushchin */
test_kmem_kernel_stacks(const char * root)277933dc80eSRoman Gushchin static int test_kmem_kernel_stacks(const char *root)
278933dc80eSRoman Gushchin {
279933dc80eSRoman Gushchin int ret = KSFT_FAIL;
280933dc80eSRoman Gushchin char *cg = NULL;
281933dc80eSRoman Gushchin
282933dc80eSRoman Gushchin cg = cg_name(root, "kmem_kernel_stacks_test");
283933dc80eSRoman Gushchin if (!cg)
284933dc80eSRoman Gushchin goto cleanup;
285933dc80eSRoman Gushchin
286933dc80eSRoman Gushchin if (cg_create(cg))
287933dc80eSRoman Gushchin goto cleanup;
288933dc80eSRoman Gushchin
289933dc80eSRoman Gushchin if (cg_run(cg, spawn_1000_threads, NULL))
290933dc80eSRoman Gushchin goto cleanup;
291933dc80eSRoman Gushchin
292933dc80eSRoman Gushchin ret = KSFT_PASS;
293933dc80eSRoman Gushchin cleanup:
294933dc80eSRoman Gushchin cg_destroy(cg);
295933dc80eSRoman Gushchin free(cg);
296933dc80eSRoman Gushchin
297933dc80eSRoman Gushchin return ret;
298933dc80eSRoman Gushchin }
299933dc80eSRoman Gushchin
300933dc80eSRoman Gushchin /*
301933dc80eSRoman Gushchin * This test sequentionally creates 30 child cgroups, allocates some
302933dc80eSRoman Gushchin * kernel memory in each of them, and deletes them. Then it checks
303933dc80eSRoman Gushchin * that the number of dying cgroups on the parent level is 0.
304933dc80eSRoman Gushchin */
test_kmem_dead_cgroups(const char * root)305933dc80eSRoman Gushchin static int test_kmem_dead_cgroups(const char *root)
306933dc80eSRoman Gushchin {
307933dc80eSRoman Gushchin int ret = KSFT_FAIL;
308933dc80eSRoman Gushchin char *parent;
309933dc80eSRoman Gushchin long dead;
310933dc80eSRoman Gushchin int i;
311933dc80eSRoman Gushchin
312933dc80eSRoman Gushchin parent = cg_name(root, "kmem_dead_cgroups_test");
313933dc80eSRoman Gushchin if (!parent)
314933dc80eSRoman Gushchin goto cleanup;
315933dc80eSRoman Gushchin
316933dc80eSRoman Gushchin if (cg_create(parent))
317933dc80eSRoman Gushchin goto cleanup;
318933dc80eSRoman Gushchin
319933dc80eSRoman Gushchin if (cg_write(parent, "cgroup.subtree_control", "+memory"))
320933dc80eSRoman Gushchin goto cleanup;
321933dc80eSRoman Gushchin
322933dc80eSRoman Gushchin if (cg_run_in_subcgroups(parent, alloc_dcache, (void *)100, 30))
323933dc80eSRoman Gushchin goto cleanup;
324933dc80eSRoman Gushchin
325933dc80eSRoman Gushchin for (i = 0; i < 5; i++) {
326933dc80eSRoman Gushchin dead = cg_read_key_long(parent, "cgroup.stat",
327933dc80eSRoman Gushchin "nr_dying_descendants ");
328933dc80eSRoman Gushchin if (dead == 0) {
329933dc80eSRoman Gushchin ret = KSFT_PASS;
330933dc80eSRoman Gushchin break;
331933dc80eSRoman Gushchin }
332933dc80eSRoman Gushchin /*
333933dc80eSRoman Gushchin * Reclaiming cgroups might take some time,
334933dc80eSRoman Gushchin * let's wait a bit and repeat.
335933dc80eSRoman Gushchin */
336933dc80eSRoman Gushchin sleep(1);
337933dc80eSRoman Gushchin }
338933dc80eSRoman Gushchin
339933dc80eSRoman Gushchin cleanup:
340933dc80eSRoman Gushchin cg_destroy(parent);
341933dc80eSRoman Gushchin free(parent);
342933dc80eSRoman Gushchin
343933dc80eSRoman Gushchin return ret;
344933dc80eSRoman Gushchin }
345933dc80eSRoman Gushchin
34690631e1dSRoman Gushchin /*
34790631e1dSRoman Gushchin * This test creates a sub-tree with 1000 memory cgroups.
34890631e1dSRoman Gushchin * Then it checks that the memory.current on the parent level
34990631e1dSRoman Gushchin * is greater than 0 and approximates matches the percpu value
35090631e1dSRoman Gushchin * from memory.stat.
35190631e1dSRoman Gushchin */
test_percpu_basic(const char * root)35290631e1dSRoman Gushchin static int test_percpu_basic(const char *root)
35390631e1dSRoman Gushchin {
35490631e1dSRoman Gushchin int ret = KSFT_FAIL;
35590631e1dSRoman Gushchin char *parent, *child;
35690631e1dSRoman Gushchin long current, percpu;
35790631e1dSRoman Gushchin int i;
35890631e1dSRoman Gushchin
35990631e1dSRoman Gushchin parent = cg_name(root, "percpu_basic_test");
36090631e1dSRoman Gushchin if (!parent)
36190631e1dSRoman Gushchin goto cleanup;
36290631e1dSRoman Gushchin
36390631e1dSRoman Gushchin if (cg_create(parent))
36490631e1dSRoman Gushchin goto cleanup;
36590631e1dSRoman Gushchin
36690631e1dSRoman Gushchin if (cg_write(parent, "cgroup.subtree_control", "+memory"))
36790631e1dSRoman Gushchin goto cleanup;
36890631e1dSRoman Gushchin
36990631e1dSRoman Gushchin for (i = 0; i < 1000; i++) {
37090631e1dSRoman Gushchin child = cg_name_indexed(parent, "child", i);
37190631e1dSRoman Gushchin if (!child)
37290631e1dSRoman Gushchin return -1;
37390631e1dSRoman Gushchin
37490631e1dSRoman Gushchin if (cg_create(child))
37590631e1dSRoman Gushchin goto cleanup_children;
37690631e1dSRoman Gushchin
37790631e1dSRoman Gushchin free(child);
37890631e1dSRoman Gushchin }
37990631e1dSRoman Gushchin
38090631e1dSRoman Gushchin current = cg_read_long(parent, "memory.current");
38190631e1dSRoman Gushchin percpu = cg_read_key_long(parent, "memory.stat", "percpu ");
38290631e1dSRoman Gushchin
38390631e1dSRoman Gushchin if (current > 0 && percpu > 0 && abs(current - percpu) <
38490631e1dSRoman Gushchin MAX_VMSTAT_ERROR)
38590631e1dSRoman Gushchin ret = KSFT_PASS;
38690631e1dSRoman Gushchin else
38790631e1dSRoman Gushchin printf("memory.current %ld\npercpu %ld\n",
38890631e1dSRoman Gushchin current, percpu);
38990631e1dSRoman Gushchin
39090631e1dSRoman Gushchin cleanup_children:
39190631e1dSRoman Gushchin for (i = 0; i < 1000; i++) {
39290631e1dSRoman Gushchin child = cg_name_indexed(parent, "child", i);
39390631e1dSRoman Gushchin cg_destroy(child);
39490631e1dSRoman Gushchin free(child);
39590631e1dSRoman Gushchin }
39690631e1dSRoman Gushchin
39790631e1dSRoman Gushchin cleanup:
39890631e1dSRoman Gushchin cg_destroy(parent);
39990631e1dSRoman Gushchin free(parent);
40090631e1dSRoman Gushchin
40190631e1dSRoman Gushchin return ret;
40290631e1dSRoman Gushchin }
40390631e1dSRoman Gushchin
404933dc80eSRoman Gushchin #define T(x) { x, #x }
405933dc80eSRoman Gushchin struct kmem_test {
406933dc80eSRoman Gushchin int (*fn)(const char *root);
407933dc80eSRoman Gushchin const char *name;
408933dc80eSRoman Gushchin } tests[] = {
409933dc80eSRoman Gushchin T(test_kmem_basic),
410933dc80eSRoman Gushchin T(test_kmem_memcg_deletion),
411933dc80eSRoman Gushchin T(test_kmem_proc_kpagecgroup),
412933dc80eSRoman Gushchin T(test_kmem_kernel_stacks),
413933dc80eSRoman Gushchin T(test_kmem_dead_cgroups),
41490631e1dSRoman Gushchin T(test_percpu_basic),
415933dc80eSRoman Gushchin };
416933dc80eSRoman Gushchin #undef T
417933dc80eSRoman Gushchin
main(int argc,char ** argv)418933dc80eSRoman Gushchin int main(int argc, char **argv)
419933dc80eSRoman Gushchin {
420933dc80eSRoman Gushchin char root[PATH_MAX];
421933dc80eSRoman Gushchin int i, ret = EXIT_SUCCESS;
422933dc80eSRoman Gushchin
423*c81b6d64STianchen Ding if (cg_find_unified_root(root, sizeof(root), NULL))
424933dc80eSRoman Gushchin ksft_exit_skip("cgroup v2 isn't mounted\n");
425933dc80eSRoman Gushchin
426933dc80eSRoman Gushchin /*
427933dc80eSRoman Gushchin * Check that memory controller is available:
428933dc80eSRoman Gushchin * memory is listed in cgroup.controllers
429933dc80eSRoman Gushchin */
430933dc80eSRoman Gushchin if (cg_read_strstr(root, "cgroup.controllers", "memory"))
431933dc80eSRoman Gushchin ksft_exit_skip("memory controller isn't available\n");
432933dc80eSRoman Gushchin
433933dc80eSRoman Gushchin if (cg_read_strstr(root, "cgroup.subtree_control", "memory"))
434933dc80eSRoman Gushchin if (cg_write(root, "cgroup.subtree_control", "+memory"))
435933dc80eSRoman Gushchin ksft_exit_skip("Failed to set memory controller\n");
436933dc80eSRoman Gushchin
437933dc80eSRoman Gushchin for (i = 0; i < ARRAY_SIZE(tests); i++) {
438933dc80eSRoman Gushchin switch (tests[i].fn(root)) {
439933dc80eSRoman Gushchin case KSFT_PASS:
440933dc80eSRoman Gushchin ksft_test_result_pass("%s\n", tests[i].name);
441933dc80eSRoman Gushchin break;
442933dc80eSRoman Gushchin case KSFT_SKIP:
443933dc80eSRoman Gushchin ksft_test_result_skip("%s\n", tests[i].name);
444933dc80eSRoman Gushchin break;
445933dc80eSRoman Gushchin default:
446933dc80eSRoman Gushchin ret = EXIT_FAILURE;
447933dc80eSRoman Gushchin ksft_test_result_fail("%s\n", tests[i].name);
448933dc80eSRoman Gushchin break;
449933dc80eSRoman Gushchin }
450933dc80eSRoman Gushchin }
451933dc80eSRoman Gushchin
452933dc80eSRoman Gushchin return ret;
453933dc80eSRoman Gushchin }
454