xref: /openbmc/linux/tools/testing/selftests/cgroup/test_kmem.c (revision b181f7029bd71238ac2754ce7052dffd69432085)
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