xref: /aosp_15_r20/external/ltp/testcases/kernel/controllers/memcg/memcontrol04.c (revision 49cdfc7efb34551c7342be41a7384b9c40d7cab7)
1 // SPDX-License-Identifier: GPL-2.0-only
2 /*\
3  *
4  * [Description]
5  *
6  * Conversion of the forth kself test in cgroup/test_memcontrol.c.
7  *
8  * Original description:
9  * "First, this test creates the following hierarchy:
10  * A       memory.low = 50M,  memory.max = 200M
11  * A/B     memory.low = 50M,  memory.current = 50M
12  * A/B/C   memory.low = 75M,  memory.current = 50M
13  * A/B/D   memory.low = 25M,  memory.current = 50M
14  * A/B/E   memory.low = 500M, memory.current = 0
15  * A/B/F   memory.low = 0,    memory.current = 50M
16  *
17  * Usages are pagecache
18  * Then it creates A/G and creates a significant
19  * memory pressure in it.
20  *
21  * A/B    memory.current ~= 50M
22  * A/B/C  memory.current ~= 33M
23  * A/B/D  memory.current ~= 17M
24  * A/B/E  memory.current ~= 0
25  *
26  * After that it tries to allocate more than there is unprotected
27  * memory in A available, and checks that memory.low protects
28  * pagecache even in this case."
29  *
30  * The closest thing to memory.low on V1 is soft_limit_in_bytes which
31  * uses a different mechanism and has different semantics. So we only
32  * test on V2 like the selftest. We do test on more file systems, but
33  * not tempfs becaue it can't evict the page cache without swap. Also
34  * we avoid filesystems which allocate extra memory for buffer heads.
35  *
36  * The tolerances have been increased from the self tests.
37  */
38 
39 #define _GNU_SOURCE
40 
41 #include <inttypes.h>
42 
43 #include "memcontrol_common.h"
44 
45 #define TMPDIR "mntdir"
46 
47 static struct tst_cg_group *trunk_cg[3];
48 static struct tst_cg_group *leaf_cg[4];
49 static int fd = -1;
50 
51 enum checkpoints {
52 	CHILD_IDLE
53 };
54 
55 enum trunk_cg {
56 	A,
57 	B,
58 	G
59 };
60 
61 enum leaf_cg {
62 	C,
63 	D,
64 	E,
65 	F
66 };
67 
cleanup_sub_groups(void)68 static void cleanup_sub_groups(void)
69 {
70 	size_t i;
71 
72 	for (i = ARRAY_SIZE(leaf_cg); i > 0; i--) {
73 		if (!leaf_cg[i - 1])
74 			continue;
75 
76 		leaf_cg[i - 1] = tst_cg_group_rm(leaf_cg[i - 1]);
77 	}
78 
79 	for (i = ARRAY_SIZE(trunk_cg); i > 0; i--) {
80 		if (!trunk_cg[i - 1])
81 			continue;
82 
83 		trunk_cg[i - 1] = tst_cg_group_rm(trunk_cg[i - 1]);
84 	}
85 }
86 
alloc_anon_in_child(const struct tst_cg_group * const cg,const size_t size)87 static void alloc_anon_in_child(const struct tst_cg_group *const cg,
88 				const size_t size)
89 {
90 	const pid_t pid = SAFE_FORK();
91 
92 	if (pid) {
93 		tst_reap_children();
94 		return;
95 	}
96 
97 	SAFE_CG_PRINTF(cg, "cgroup.procs", "%d", getpid());
98 
99 	tst_res(TINFO, "Child %d in %s: Allocating anon: %"PRIdPTR,
100 		getpid(), tst_cg_group_name(cg), size);
101 	alloc_anon(size);
102 
103 	exit(0);
104 }
105 
alloc_pagecache_in_child(const struct tst_cg_group * const cg,const size_t size)106 static void alloc_pagecache_in_child(const struct tst_cg_group *const cg,
107 				     const size_t size)
108 {
109 	const pid_t pid = SAFE_FORK();
110 
111 	if (pid) {
112 		tst_reap_children();
113 		return;
114 	}
115 
116 	SAFE_CG_PRINTF(cg, "cgroup.procs", "%d", getpid());
117 
118 	tst_res(TINFO, "Child %d in %s: Allocating pagecache: %"PRIdPTR,
119 		getpid(), tst_cg_group_name(cg), size);
120 	alloc_pagecache(fd, size);
121 
122 	exit(0);
123 }
124 
test_memcg_low(void)125 static void test_memcg_low(void)
126 {
127 	long c[4];
128 	unsigned int i;
129 
130 	fd = SAFE_OPEN(TMPDIR"/tmpfile", O_RDWR | O_CREAT, 0600);
131 	trunk_cg[A] = tst_cg_group_mk(tst_cg, "trunk_A");
132 
133 	SAFE_CG_SCANF(trunk_cg[A], "memory.low", "%ld", c);
134 	if (c[0]) {
135 		tst_brk(TCONF,
136 			"memory.low already set to %ld on parent group", c[0]);
137 	}
138 
139 	SAFE_CG_PRINT(trunk_cg[A], "cgroup.subtree_control", "+memory");
140 
141 	SAFE_CG_PRINT(trunk_cg[A], "memory.max", "200M");
142 	SAFE_CG_PRINT(trunk_cg[A], "memory.swap.max", "0");
143 
144 	trunk_cg[B] = tst_cg_group_mk(trunk_cg[A], "trunk_B");
145 
146 	SAFE_CG_PRINT(trunk_cg[B], "cgroup.subtree_control", "+memory");
147 
148 	trunk_cg[G] = tst_cg_group_mk(trunk_cg[A], "trunk_G");
149 
150 	for (i = 0; i < ARRAY_SIZE(leaf_cg); i++) {
151 		leaf_cg[i] = tst_cg_group_mk(trunk_cg[B],
152 						 "leaf_%c", 'C' + i);
153 
154 		if (i == E)
155 			continue;
156 
157 		alloc_pagecache_in_child(leaf_cg[i], MB(50));
158 	}
159 
160 	SAFE_CG_PRINT(trunk_cg[A], "memory.low", "50M");
161 	SAFE_CG_PRINT(trunk_cg[B], "memory.low", "50M");
162 	SAFE_CG_PRINT(leaf_cg[C], "memory.low", "75M");
163 	SAFE_CG_PRINT(leaf_cg[D], "memory.low", "25M");
164 	SAFE_CG_PRINT(leaf_cg[E], "memory.low", "500M");
165 	SAFE_CG_PRINT(leaf_cg[F], "memory.low", "0");
166 
167 	alloc_anon_in_child(trunk_cg[G], MB(148));
168 
169 	SAFE_CG_SCANF(trunk_cg[B], "memory.current", "%ld", c);
170 	TST_EXP_EXPR(values_close(c[0], MB(50), 5),
171 		     "(A/B memory.current=%ld) ~= %d", c[0], MB(50));
172 
173 	for (i = 0; i < ARRAY_SIZE(leaf_cg); i++)
174 		SAFE_CG_SCANF(leaf_cg[i], "memory.current", "%ld", c + i);
175 
176 	TST_EXP_EXPR(values_close(c[0], MB(33), 20),
177 		     "(A/B/C memory.current=%ld) ~= %d", c[C], MB(33));
178 	TST_EXP_EXPR(values_close(c[1], MB(17), 20),
179 		     "(A/B/D memory.current=%ld) ~= %d", c[D], MB(17));
180 	TST_EXP_EXPR(values_close(c[2], 0, 1),
181 		     "(A/B/E memory.current=%ld) ~= 0", c[E]);
182 	tst_res(TINFO, "A/B/F memory.current=%ld", c[F]);
183 
184 	alloc_anon_in_child(trunk_cg[G], MB(166));
185 
186 	for (i = 0; i < ARRAY_SIZE(trunk_cg); i++) {
187 		long low, oom;
188 		const char id = "ABG"[i];
189 
190 		SAFE_CG_LINES_SCANF(trunk_cg[i], "memory.events",
191 				    "low %ld", &low);
192 		SAFE_CG_LINES_SCANF(trunk_cg[i], "memory.events",
193 				    "oom %ld", &oom);
194 
195 		tst_res(TINFO, "%c: low events=%ld, oom events=%ld",
196 			id, low, oom);
197 	}
198 
199 	for (i = 0; i < ARRAY_SIZE(leaf_cg); i++) {
200 		long low, oom;
201 		const char id = 'C' + i;
202 
203 		SAFE_CG_LINES_SCANF(leaf_cg[i], "memory.events",
204 				    "low %ld", &low);
205 		SAFE_CG_LINES_SCANF(leaf_cg[i], "memory.events",
206 				    "oom %ld", &oom);
207 
208 		TST_EXP_EXPR(oom == 0, "(%c oom events=%ld) == 0", id, oom);
209 
210 		if (i < E) {
211 			TST_EXP_EXPR(low > 0,
212 				     "(%c low events=%ld) > 0", id, low);
213 		} else {
214 			TST_EXP_EXPR(low == 0,
215 				     "(%c low events=%ld) == 0", id, low);
216 		}
217 	}
218 
219 	cleanup_sub_groups();
220 	SAFE_CLOSE(fd);
221 	SAFE_UNLINK(TMPDIR"/tmpfile");
222 }
223 
cleanup(void)224 static void cleanup(void)
225 {
226 	cleanup_sub_groups();
227 	if (fd > -1)
228 		SAFE_CLOSE(fd);
229 }
230 
231 static struct tst_test test = {
232 	.cleanup = cleanup,
233 	.test_all = test_memcg_low,
234 	.mount_device = 1,
235 	.mntpoint = TMPDIR,
236 	.all_filesystems = 1,
237 	.skip_filesystems = (const char *const[]){
238 		"exfat", "vfat", "fuse", "ntfs", "tmpfs", NULL
239 	},
240 	.forks_child = 1,
241 	.needs_root = 1,
242 	.needs_checkpoints = 1,
243 	.needs_cgroup_ver = TST_CG_V2,
244 	.needs_cgroup_ctrls = (const char *const[]){ "memory", NULL },
245 	.tags = (const struct tst_tag[]) {
246 		{
247 			"known-fail",
248 			"Low events in F: https://bugzilla.suse.com/show_bug.cgi?id=1196298"
249 		},
250 		{}
251 	},
252 };
253