Commit | Line | Data |
---|---|---|
84092dbc RG |
1 | /* SPDX-License-Identifier: GPL-2.0 */ |
2 | #define _GNU_SOURCE | |
3 | ||
4 | #include <linux/limits.h> | |
5 | #include <fcntl.h> | |
6 | #include <stdio.h> | |
7 | #include <stdlib.h> | |
8 | #include <string.h> | |
9 | #include <sys/stat.h> | |
10 | #include <sys/types.h> | |
11 | #include <unistd.h> | |
12 | ||
13 | #include "../kselftest.h" | |
14 | #include "cgroup_util.h" | |
15 | ||
16 | /* | |
17 | * This test creates two nested cgroups with and without enabling | |
18 | * the memory controller. | |
19 | */ | |
20 | static int test_memcg_subtree_control(const char *root) | |
21 | { | |
22 | char *parent, *child, *parent2, *child2; | |
23 | int ret = KSFT_FAIL; | |
24 | char buf[PAGE_SIZE]; | |
25 | ||
26 | /* Create two nested cgroups with the memory controller enabled */ | |
27 | parent = cg_name(root, "memcg_test_0"); | |
28 | child = cg_name(root, "memcg_test_0/memcg_test_1"); | |
29 | if (!parent || !child) | |
30 | goto cleanup; | |
31 | ||
32 | if (cg_create(parent)) | |
33 | goto cleanup; | |
34 | ||
35 | if (cg_write(parent, "cgroup.subtree_control", "+memory")) | |
36 | goto cleanup; | |
37 | ||
38 | if (cg_create(child)) | |
39 | goto cleanup; | |
40 | ||
41 | if (cg_read_strstr(child, "cgroup.controllers", "memory")) | |
42 | goto cleanup; | |
43 | ||
44 | /* Create two nested cgroups without enabling memory controller */ | |
45 | parent2 = cg_name(root, "memcg_test_1"); | |
46 | child2 = cg_name(root, "memcg_test_1/memcg_test_1"); | |
47 | if (!parent2 || !child2) | |
48 | goto cleanup; | |
49 | ||
50 | if (cg_create(parent2)) | |
51 | goto cleanup; | |
52 | ||
53 | if (cg_create(child2)) | |
54 | goto cleanup; | |
55 | ||
56 | if (cg_read(child2, "cgroup.controllers", buf, sizeof(buf))) | |
57 | goto cleanup; | |
58 | ||
59 | if (!cg_read_strstr(child2, "cgroup.controllers", "memory")) | |
60 | goto cleanup; | |
61 | ||
62 | ret = KSFT_PASS; | |
63 | ||
64 | cleanup: | |
65 | cg_destroy(child); | |
66 | cg_destroy(parent); | |
67 | free(parent); | |
68 | free(child); | |
69 | ||
70 | cg_destroy(child2); | |
71 | cg_destroy(parent2); | |
72 | free(parent2); | |
73 | free(child2); | |
74 | ||
75 | return ret; | |
76 | } | |
77 | ||
78 | static int alloc_anon_50M_check(const char *cgroup, void *arg) | |
79 | { | |
80 | size_t size = MB(50); | |
81 | char *buf, *ptr; | |
82 | long anon, current; | |
83 | int ret = -1; | |
84 | ||
85 | buf = malloc(size); | |
86 | for (ptr = buf; ptr < buf + size; ptr += PAGE_SIZE) | |
87 | *ptr = 0; | |
88 | ||
89 | current = cg_read_long(cgroup, "memory.current"); | |
90 | if (current < size) | |
91 | goto cleanup; | |
92 | ||
93 | if (!values_close(size, current, 3)) | |
94 | goto cleanup; | |
95 | ||
96 | anon = cg_read_key_long(cgroup, "memory.stat", "anon "); | |
97 | if (anon < 0) | |
98 | goto cleanup; | |
99 | ||
100 | if (!values_close(anon, current, 3)) | |
101 | goto cleanup; | |
102 | ||
103 | ret = 0; | |
104 | cleanup: | |
105 | free(buf); | |
106 | return ret; | |
107 | } | |
108 | ||
109 | static int alloc_pagecache_50M_check(const char *cgroup, void *arg) | |
110 | { | |
111 | size_t size = MB(50); | |
112 | int ret = -1; | |
113 | long current, file; | |
114 | int fd; | |
115 | ||
116 | fd = get_temp_fd(); | |
117 | if (fd < 0) | |
118 | return -1; | |
119 | ||
120 | if (alloc_pagecache(fd, size)) | |
121 | goto cleanup; | |
122 | ||
123 | current = cg_read_long(cgroup, "memory.current"); | |
124 | if (current < size) | |
125 | goto cleanup; | |
126 | ||
127 | file = cg_read_key_long(cgroup, "memory.stat", "file "); | |
128 | if (file < 0) | |
129 | goto cleanup; | |
130 | ||
131 | if (!values_close(file, current, 10)) | |
132 | goto cleanup; | |
133 | ||
134 | ret = 0; | |
135 | ||
136 | cleanup: | |
137 | close(fd); | |
138 | return ret; | |
139 | } | |
140 | ||
141 | /* | |
142 | * This test create a memory cgroup, allocates | |
143 | * some anonymous memory and some pagecache | |
144 | * and check memory.current and some memory.stat values. | |
145 | */ | |
146 | static int test_memcg_current(const char *root) | |
147 | { | |
148 | int ret = KSFT_FAIL; | |
149 | long current; | |
150 | char *memcg; | |
151 | ||
152 | memcg = cg_name(root, "memcg_test"); | |
153 | if (!memcg) | |
154 | goto cleanup; | |
155 | ||
156 | if (cg_create(memcg)) | |
157 | goto cleanup; | |
158 | ||
159 | current = cg_read_long(memcg, "memory.current"); | |
160 | if (current != 0) | |
161 | goto cleanup; | |
162 | ||
163 | if (cg_run(memcg, alloc_anon_50M_check, NULL)) | |
164 | goto cleanup; | |
165 | ||
166 | if (cg_run(memcg, alloc_pagecache_50M_check, NULL)) | |
167 | goto cleanup; | |
168 | ||
169 | ret = KSFT_PASS; | |
170 | ||
171 | cleanup: | |
172 | cg_destroy(memcg); | |
173 | free(memcg); | |
174 | ||
175 | return ret; | |
176 | } | |
177 | ||
178 | static int alloc_pagecache_50M(const char *cgroup, void *arg) | |
179 | { | |
180 | int fd = (long)arg; | |
181 | ||
182 | return alloc_pagecache(fd, MB(50)); | |
183 | } | |
184 | ||
185 | static int alloc_pagecache_50M_noexit(const char *cgroup, void *arg) | |
186 | { | |
187 | int fd = (long)arg; | |
188 | int ppid = getppid(); | |
189 | ||
190 | if (alloc_pagecache(fd, MB(50))) | |
191 | return -1; | |
192 | ||
193 | while (getppid() == ppid) | |
194 | sleep(1); | |
195 | ||
196 | return 0; | |
197 | } | |
198 | ||
199 | /* | |
200 | * First, this test creates the following hierarchy: | |
201 | * A memory.min = 50M, memory.max = 200M | |
202 | * A/B memory.min = 50M, memory.current = 50M | |
203 | * A/B/C memory.min = 75M, memory.current = 50M | |
204 | * A/B/D memory.min = 25M, memory.current = 50M | |
205 | * A/B/E memory.min = 500M, memory.current = 0 | |
206 | * A/B/F memory.min = 0, memory.current = 50M | |
207 | * | |
208 | * Usages are pagecache, but the test keeps a running | |
209 | * process in every leaf cgroup. | |
210 | * Then it creates A/G and creates a significant | |
211 | * memory pressure in it. | |
212 | * | |
213 | * A/B memory.current ~= 50M | |
214 | * A/B/C memory.current ~= 33M | |
215 | * A/B/D memory.current ~= 17M | |
216 | * A/B/E memory.current ~= 0 | |
217 | * | |
218 | * After that it tries to allocate more than there is | |
219 | * unprotected memory in A available, and checks | |
220 | * checks that memory.min protects pagecache even | |
221 | * in this case. | |
222 | */ | |
223 | static int test_memcg_min(const char *root) | |
224 | { | |
225 | int ret = KSFT_FAIL; | |
226 | char *parent[3] = {NULL}; | |
227 | char *children[4] = {NULL}; | |
228 | long c[4]; | |
229 | int i, attempts; | |
230 | int fd; | |
231 | ||
232 | fd = get_temp_fd(); | |
233 | if (fd < 0) | |
234 | goto cleanup; | |
235 | ||
236 | parent[0] = cg_name(root, "memcg_test_0"); | |
237 | if (!parent[0]) | |
238 | goto cleanup; | |
239 | ||
240 | parent[1] = cg_name(parent[0], "memcg_test_1"); | |
241 | if (!parent[1]) | |
242 | goto cleanup; | |
243 | ||
244 | parent[2] = cg_name(parent[0], "memcg_test_2"); | |
245 | if (!parent[2]) | |
246 | goto cleanup; | |
247 | ||
248 | if (cg_create(parent[0])) | |
249 | goto cleanup; | |
250 | ||
251 | if (cg_read_long(parent[0], "memory.min")) { | |
252 | ret = KSFT_SKIP; | |
253 | goto cleanup; | |
254 | } | |
255 | ||
256 | if (cg_write(parent[0], "cgroup.subtree_control", "+memory")) | |
257 | goto cleanup; | |
258 | ||
259 | if (cg_write(parent[0], "memory.max", "200M")) | |
260 | goto cleanup; | |
261 | ||
262 | if (cg_write(parent[0], "memory.swap.max", "0")) | |
263 | goto cleanup; | |
264 | ||
265 | if (cg_create(parent[1])) | |
266 | goto cleanup; | |
267 | ||
268 | if (cg_write(parent[1], "cgroup.subtree_control", "+memory")) | |
269 | goto cleanup; | |
270 | ||
271 | if (cg_create(parent[2])) | |
272 | goto cleanup; | |
273 | ||
274 | for (i = 0; i < ARRAY_SIZE(children); i++) { | |
275 | children[i] = cg_name_indexed(parent[1], "child_memcg", i); | |
276 | if (!children[i]) | |
277 | goto cleanup; | |
278 | ||
279 | if (cg_create(children[i])) | |
280 | goto cleanup; | |
281 | ||
282 | if (i == 2) | |
283 | continue; | |
284 | ||
285 | cg_run_nowait(children[i], alloc_pagecache_50M_noexit, | |
286 | (void *)(long)fd); | |
287 | } | |
288 | ||
289 | if (cg_write(parent[0], "memory.min", "50M")) | |
290 | goto cleanup; | |
291 | if (cg_write(parent[1], "memory.min", "50M")) | |
292 | goto cleanup; | |
293 | if (cg_write(children[0], "memory.min", "75M")) | |
294 | goto cleanup; | |
295 | if (cg_write(children[1], "memory.min", "25M")) | |
296 | goto cleanup; | |
297 | if (cg_write(children[2], "memory.min", "500M")) | |
298 | goto cleanup; | |
299 | if (cg_write(children[3], "memory.min", "0")) | |
300 | goto cleanup; | |
301 | ||
302 | attempts = 0; | |
303 | while (!values_close(cg_read_long(parent[1], "memory.current"), | |
304 | MB(150), 3)) { | |
305 | if (attempts++ > 5) | |
306 | break; | |
307 | sleep(1); | |
308 | } | |
309 | ||
310 | if (cg_run(parent[2], alloc_anon, (void *)MB(148))) | |
311 | goto cleanup; | |
312 | ||
313 | if (!values_close(cg_read_long(parent[1], "memory.current"), MB(50), 3)) | |
314 | goto cleanup; | |
315 | ||
316 | for (i = 0; i < ARRAY_SIZE(children); i++) | |
317 | c[i] = cg_read_long(children[i], "memory.current"); | |
318 | ||
319 | if (!values_close(c[0], MB(33), 10)) | |
320 | goto cleanup; | |
321 | ||
322 | if (!values_close(c[1], MB(17), 10)) | |
323 | goto cleanup; | |
324 | ||
325 | if (!values_close(c[2], 0, 1)) | |
326 | goto cleanup; | |
327 | ||
328 | if (!cg_run(parent[2], alloc_anon, (void *)MB(170))) | |
329 | goto cleanup; | |
330 | ||
331 | if (!values_close(cg_read_long(parent[1], "memory.current"), MB(50), 3)) | |
332 | goto cleanup; | |
333 | ||
334 | ret = KSFT_PASS; | |
335 | ||
336 | cleanup: | |
337 | for (i = ARRAY_SIZE(children) - 1; i >= 0; i--) { | |
338 | if (!children[i]) | |
339 | continue; | |
340 | ||
341 | cg_destroy(children[i]); | |
342 | free(children[i]); | |
343 | } | |
344 | ||
345 | for (i = ARRAY_SIZE(parent) - 1; i >= 0; i--) { | |
346 | if (!parent[i]) | |
347 | continue; | |
348 | ||
349 | cg_destroy(parent[i]); | |
350 | free(parent[i]); | |
351 | } | |
352 | close(fd); | |
353 | return ret; | |
354 | } | |
355 | ||
356 | /* | |
357 | * First, this test creates the following hierarchy: | |
358 | * A memory.low = 50M, memory.max = 200M | |
359 | * A/B memory.low = 50M, memory.current = 50M | |
360 | * A/B/C memory.low = 75M, memory.current = 50M | |
361 | * A/B/D memory.low = 25M, memory.current = 50M | |
362 | * A/B/E memory.low = 500M, memory.current = 0 | |
363 | * A/B/F memory.low = 0, memory.current = 50M | |
364 | * | |
365 | * Usages are pagecache. | |
366 | * Then it creates A/G an creates a significant | |
367 | * memory pressure in it. | |
368 | * | |
369 | * Then it checks actual memory usages and expects that: | |
370 | * A/B memory.current ~= 50M | |
371 | * A/B/ memory.current ~= 33M | |
372 | * A/B/D memory.current ~= 17M | |
373 | * A/B/E memory.current ~= 0 | |
374 | * | |
375 | * After that it tries to allocate more than there is | |
376 | * unprotected memory in A available, | |
377 | * and checks low and oom events in memory.events. | |
378 | */ | |
379 | static int test_memcg_low(const char *root) | |
380 | { | |
381 | int ret = KSFT_FAIL; | |
382 | char *parent[3] = {NULL}; | |
383 | char *children[4] = {NULL}; | |
384 | long low, oom; | |
385 | long c[4]; | |
386 | int i; | |
387 | int fd; | |
388 | ||
389 | fd = get_temp_fd(); | |
390 | if (fd < 0) | |
391 | goto cleanup; | |
392 | ||
393 | parent[0] = cg_name(root, "memcg_test_0"); | |
394 | if (!parent[0]) | |
395 | goto cleanup; | |
396 | ||
397 | parent[1] = cg_name(parent[0], "memcg_test_1"); | |
398 | if (!parent[1]) | |
399 | goto cleanup; | |
400 | ||
401 | parent[2] = cg_name(parent[0], "memcg_test_2"); | |
402 | if (!parent[2]) | |
403 | goto cleanup; | |
404 | ||
405 | if (cg_create(parent[0])) | |
406 | goto cleanup; | |
407 | ||
408 | if (cg_read_long(parent[0], "memory.low")) | |
409 | goto cleanup; | |
410 | ||
411 | if (cg_write(parent[0], "cgroup.subtree_control", "+memory")) | |
412 | goto cleanup; | |
413 | ||
414 | if (cg_write(parent[0], "memory.max", "200M")) | |
415 | goto cleanup; | |
416 | ||
417 | if (cg_write(parent[0], "memory.swap.max", "0")) | |
418 | goto cleanup; | |
419 | ||
420 | if (cg_create(parent[1])) | |
421 | goto cleanup; | |
422 | ||
423 | if (cg_write(parent[1], "cgroup.subtree_control", "+memory")) | |
424 | goto cleanup; | |
425 | ||
426 | if (cg_create(parent[2])) | |
427 | goto cleanup; | |
428 | ||
429 | for (i = 0; i < ARRAY_SIZE(children); i++) { | |
430 | children[i] = cg_name_indexed(parent[1], "child_memcg", i); | |
431 | if (!children[i]) | |
432 | goto cleanup; | |
433 | ||
434 | if (cg_create(children[i])) | |
435 | goto cleanup; | |
436 | ||
437 | if (i == 2) | |
438 | continue; | |
439 | ||
440 | if (cg_run(children[i], alloc_pagecache_50M, (void *)(long)fd)) | |
441 | goto cleanup; | |
442 | } | |
443 | ||
444 | if (cg_write(parent[0], "memory.low", "50M")) | |
445 | goto cleanup; | |
446 | if (cg_write(parent[1], "memory.low", "50M")) | |
447 | goto cleanup; | |
448 | if (cg_write(children[0], "memory.low", "75M")) | |
449 | goto cleanup; | |
450 | if (cg_write(children[1], "memory.low", "25M")) | |
451 | goto cleanup; | |
452 | if (cg_write(children[2], "memory.low", "500M")) | |
453 | goto cleanup; | |
454 | if (cg_write(children[3], "memory.low", "0")) | |
455 | goto cleanup; | |
456 | ||
457 | if (cg_run(parent[2], alloc_anon, (void *)MB(148))) | |
458 | goto cleanup; | |
459 | ||
460 | if (!values_close(cg_read_long(parent[1], "memory.current"), MB(50), 3)) | |
461 | goto cleanup; | |
462 | ||
463 | for (i = 0; i < ARRAY_SIZE(children); i++) | |
464 | c[i] = cg_read_long(children[i], "memory.current"); | |
465 | ||
466 | if (!values_close(c[0], MB(33), 10)) | |
467 | goto cleanup; | |
468 | ||
469 | if (!values_close(c[1], MB(17), 10)) | |
470 | goto cleanup; | |
471 | ||
472 | if (!values_close(c[2], 0, 1)) | |
473 | goto cleanup; | |
474 | ||
475 | if (cg_run(parent[2], alloc_anon, (void *)MB(166))) { | |
476 | fprintf(stderr, | |
477 | "memory.low prevents from allocating anon memory\n"); | |
478 | goto cleanup; | |
479 | } | |
480 | ||
481 | for (i = 0; i < ARRAY_SIZE(children); i++) { | |
482 | oom = cg_read_key_long(children[i], "memory.events", "oom "); | |
483 | low = cg_read_key_long(children[i], "memory.events", "low "); | |
484 | ||
485 | if (oom) | |
486 | goto cleanup; | |
487 | if (i < 2 && low <= 0) | |
488 | goto cleanup; | |
489 | if (i >= 2 && low) | |
490 | goto cleanup; | |
491 | } | |
492 | ||
493 | ret = KSFT_PASS; | |
494 | ||
495 | cleanup: | |
496 | for (i = ARRAY_SIZE(children) - 1; i >= 0; i--) { | |
497 | if (!children[i]) | |
498 | continue; | |
499 | ||
500 | cg_destroy(children[i]); | |
501 | free(children[i]); | |
502 | } | |
503 | ||
504 | for (i = ARRAY_SIZE(parent) - 1; i >= 0; i--) { | |
505 | if (!parent[i]) | |
506 | continue; | |
507 | ||
508 | cg_destroy(parent[i]); | |
509 | free(parent[i]); | |
510 | } | |
511 | close(fd); | |
512 | return ret; | |
513 | } | |
514 | ||
515 | static int alloc_pagecache_max_30M(const char *cgroup, void *arg) | |
516 | { | |
517 | size_t size = MB(50); | |
518 | int ret = -1; | |
519 | long current; | |
520 | int fd; | |
521 | ||
522 | fd = get_temp_fd(); | |
523 | if (fd < 0) | |
524 | return -1; | |
525 | ||
526 | if (alloc_pagecache(fd, size)) | |
527 | goto cleanup; | |
528 | ||
529 | current = cg_read_long(cgroup, "memory.current"); | |
530 | if (current <= MB(29) || current > MB(30)) | |
531 | goto cleanup; | |
532 | ||
533 | ret = 0; | |
534 | ||
535 | cleanup: | |
536 | close(fd); | |
537 | return ret; | |
538 | ||
539 | } | |
540 | ||
541 | /* | |
542 | * This test checks that memory.high limits the amount of | |
543 | * memory which can be consumed by either anonymous memory | |
544 | * or pagecache. | |
545 | */ | |
546 | static int test_memcg_high(const char *root) | |
547 | { | |
548 | int ret = KSFT_FAIL; | |
549 | char *memcg; | |
550 | long high; | |
551 | ||
552 | memcg = cg_name(root, "memcg_test"); | |
553 | if (!memcg) | |
554 | goto cleanup; | |
555 | ||
556 | if (cg_create(memcg)) | |
557 | goto cleanup; | |
558 | ||
559 | if (cg_read_strcmp(memcg, "memory.high", "max\n")) | |
560 | goto cleanup; | |
561 | ||
562 | if (cg_write(memcg, "memory.swap.max", "0")) | |
563 | goto cleanup; | |
564 | ||
565 | if (cg_write(memcg, "memory.high", "30M")) | |
566 | goto cleanup; | |
567 | ||
568 | if (cg_run(memcg, alloc_anon, (void *)MB(100))) | |
569 | goto cleanup; | |
570 | ||
571 | if (!cg_run(memcg, alloc_pagecache_50M_check, NULL)) | |
572 | goto cleanup; | |
573 | ||
574 | if (cg_run(memcg, alloc_pagecache_max_30M, NULL)) | |
575 | goto cleanup; | |
576 | ||
577 | high = cg_read_key_long(memcg, "memory.events", "high "); | |
578 | if (high <= 0) | |
579 | goto cleanup; | |
580 | ||
581 | ret = KSFT_PASS; | |
582 | ||
583 | cleanup: | |
584 | cg_destroy(memcg); | |
585 | free(memcg); | |
586 | ||
587 | return ret; | |
588 | } | |
589 | ||
590 | /* | |
591 | * This test checks that memory.max limits the amount of | |
592 | * memory which can be consumed by either anonymous memory | |
593 | * or pagecache. | |
594 | */ | |
595 | static int test_memcg_max(const char *root) | |
596 | { | |
597 | int ret = KSFT_FAIL; | |
598 | char *memcg; | |
599 | long current, max; | |
600 | ||
601 | memcg = cg_name(root, "memcg_test"); | |
602 | if (!memcg) | |
603 | goto cleanup; | |
604 | ||
605 | if (cg_create(memcg)) | |
606 | goto cleanup; | |
607 | ||
608 | if (cg_read_strcmp(memcg, "memory.max", "max\n")) | |
609 | goto cleanup; | |
610 | ||
611 | if (cg_write(memcg, "memory.swap.max", "0")) | |
612 | goto cleanup; | |
613 | ||
614 | if (cg_write(memcg, "memory.max", "30M")) | |
615 | goto cleanup; | |
616 | ||
617 | /* Should be killed by OOM killer */ | |
618 | if (!cg_run(memcg, alloc_anon, (void *)MB(100))) | |
619 | goto cleanup; | |
620 | ||
621 | if (cg_run(memcg, alloc_pagecache_max_30M, NULL)) | |
622 | goto cleanup; | |
623 | ||
624 | current = cg_read_long(memcg, "memory.current"); | |
625 | if (current > MB(30) || !current) | |
626 | goto cleanup; | |
627 | ||
628 | max = cg_read_key_long(memcg, "memory.events", "max "); | |
629 | if (max <= 0) | |
630 | goto cleanup; | |
631 | ||
632 | ret = KSFT_PASS; | |
633 | ||
634 | cleanup: | |
635 | cg_destroy(memcg); | |
636 | free(memcg); | |
637 | ||
638 | return ret; | |
639 | } | |
640 | ||
478b2784 MR |
641 | static int alloc_anon_50M_check_swap(const char *cgroup, void *arg) |
642 | { | |
643 | long mem_max = (long)arg; | |
644 | size_t size = MB(50); | |
645 | char *buf, *ptr; | |
646 | long mem_current, swap_current; | |
647 | int ret = -1; | |
648 | ||
649 | buf = malloc(size); | |
650 | for (ptr = buf; ptr < buf + size; ptr += PAGE_SIZE) | |
651 | *ptr = 0; | |
652 | ||
653 | mem_current = cg_read_long(cgroup, "memory.current"); | |
654 | if (!mem_current || !values_close(mem_current, mem_max, 3)) | |
655 | goto cleanup; | |
656 | ||
657 | swap_current = cg_read_long(cgroup, "memory.swap.current"); | |
658 | if (!swap_current || | |
659 | !values_close(mem_current + swap_current, size, 3)) | |
660 | goto cleanup; | |
661 | ||
662 | ret = 0; | |
663 | cleanup: | |
664 | free(buf); | |
665 | return ret; | |
666 | } | |
667 | ||
668 | /* | |
669 | * This test checks that memory.swap.max limits the amount of | |
670 | * anonymous memory which can be swapped out. | |
671 | */ | |
672 | static int test_memcg_swap_max(const char *root) | |
673 | { | |
674 | int ret = KSFT_FAIL; | |
675 | char *memcg; | |
676 | long max; | |
677 | ||
678 | if (!is_swap_enabled()) | |
679 | return KSFT_SKIP; | |
680 | ||
681 | memcg = cg_name(root, "memcg_test"); | |
682 | if (!memcg) | |
683 | goto cleanup; | |
684 | ||
685 | if (cg_create(memcg)) | |
686 | goto cleanup; | |
687 | ||
688 | if (cg_read_long(memcg, "memory.swap.current")) { | |
689 | ret = KSFT_SKIP; | |
690 | goto cleanup; | |
691 | } | |
692 | ||
693 | if (cg_read_strcmp(memcg, "memory.max", "max\n")) | |
694 | goto cleanup; | |
695 | ||
696 | if (cg_read_strcmp(memcg, "memory.swap.max", "max\n")) | |
697 | goto cleanup; | |
698 | ||
699 | if (cg_write(memcg, "memory.swap.max", "30M")) | |
700 | goto cleanup; | |
701 | ||
702 | if (cg_write(memcg, "memory.max", "30M")) | |
703 | goto cleanup; | |
704 | ||
705 | /* Should be killed by OOM killer */ | |
706 | if (!cg_run(memcg, alloc_anon, (void *)MB(100))) | |
707 | goto cleanup; | |
708 | ||
709 | if (cg_read_key_long(memcg, "memory.events", "oom ") != 1) | |
710 | goto cleanup; | |
711 | ||
712 | if (cg_read_key_long(memcg, "memory.events", "oom_kill ") != 1) | |
713 | goto cleanup; | |
714 | ||
715 | if (cg_run(memcg, alloc_anon_50M_check_swap, (void *)MB(30))) | |
716 | goto cleanup; | |
717 | ||
718 | max = cg_read_key_long(memcg, "memory.events", "max "); | |
719 | if (max <= 0) | |
720 | goto cleanup; | |
721 | ||
722 | ret = KSFT_PASS; | |
723 | ||
724 | cleanup: | |
725 | cg_destroy(memcg); | |
726 | free(memcg); | |
727 | ||
728 | return ret; | |
729 | } | |
730 | ||
84092dbc RG |
731 | /* |
732 | * This test disables swapping and tries to allocate anonymous memory | |
733 | * up to OOM. Then it checks for oom and oom_kill events in | |
734 | * memory.events. | |
735 | */ | |
736 | static int test_memcg_oom_events(const char *root) | |
737 | { | |
738 | int ret = KSFT_FAIL; | |
739 | char *memcg; | |
740 | ||
741 | memcg = cg_name(root, "memcg_test"); | |
742 | if (!memcg) | |
743 | goto cleanup; | |
744 | ||
745 | if (cg_create(memcg)) | |
746 | goto cleanup; | |
747 | ||
748 | if (cg_write(memcg, "memory.max", "30M")) | |
749 | goto cleanup; | |
750 | ||
751 | if (cg_write(memcg, "memory.swap.max", "0")) | |
752 | goto cleanup; | |
753 | ||
754 | if (!cg_run(memcg, alloc_anon, (void *)MB(100))) | |
755 | goto cleanup; | |
756 | ||
757 | if (cg_read_strcmp(memcg, "cgroup.procs", "")) | |
758 | goto cleanup; | |
759 | ||
760 | if (cg_read_key_long(memcg, "memory.events", "oom ") != 1) | |
761 | goto cleanup; | |
762 | ||
763 | if (cg_read_key_long(memcg, "memory.events", "oom_kill ") != 1) | |
764 | goto cleanup; | |
765 | ||
766 | ret = KSFT_PASS; | |
767 | ||
768 | cleanup: | |
769 | cg_destroy(memcg); | |
770 | free(memcg); | |
771 | ||
772 | return ret; | |
773 | } | |
774 | ||
775 | #define T(x) { x, #x } | |
776 | struct memcg_test { | |
777 | int (*fn)(const char *root); | |
778 | const char *name; | |
779 | } tests[] = { | |
780 | T(test_memcg_subtree_control), | |
781 | T(test_memcg_current), | |
782 | T(test_memcg_min), | |
783 | T(test_memcg_low), | |
784 | T(test_memcg_high), | |
785 | T(test_memcg_max), | |
786 | T(test_memcg_oom_events), | |
478b2784 | 787 | T(test_memcg_swap_max), |
84092dbc RG |
788 | }; |
789 | #undef T | |
790 | ||
791 | int main(int argc, char **argv) | |
792 | { | |
793 | char root[PATH_MAX]; | |
794 | int i, ret = EXIT_SUCCESS; | |
795 | ||
796 | if (cg_find_unified_root(root, sizeof(root))) | |
797 | ksft_exit_skip("cgroup v2 isn't mounted\n"); | |
798 | ||
799 | /* | |
800 | * Check that memory controller is available: | |
801 | * memory is listed in cgroup.controllers | |
802 | */ | |
803 | if (cg_read_strstr(root, "cgroup.controllers", "memory")) | |
804 | ksft_exit_skip("memory controller isn't available\n"); | |
805 | ||
806 | for (i = 0; i < ARRAY_SIZE(tests); i++) { | |
807 | switch (tests[i].fn(root)) { | |
808 | case KSFT_PASS: | |
809 | ksft_test_result_pass("%s\n", tests[i].name); | |
810 | break; | |
811 | case KSFT_SKIP: | |
812 | ksft_test_result_skip("%s\n", tests[i].name); | |
813 | break; | |
814 | default: | |
815 | ret = EXIT_FAILURE; | |
816 | ksft_test_result_fail("%s\n", tests[i].name); | |
817 | break; | |
818 | } | |
819 | } | |
820 | ||
821 | return ret; | |
822 | } |