1 // SPDX-License-Identifier: GPL-2.0
2 /*\
3 *
4 * [Description]
5 *
6 * Conversion of second kself test in cgroup/test_memcontrol.c.
7 *
8 * Original description:
9 * "This test creates a memory cgroup, allocates some anonymous memory
10 * and some pagecache and check memory.current and some memory.stat
11 * values."
12 *
13 * Note that the V1 rss and cache counters were renamed to anon and
14 * file in V2. Besides error reporting, this test differs from the
15 * kselftest in the following ways:
16 *
17 * . It supports V1.
18 * . It writes instead of reads to fill the page cache. Because no
19 * pages were allocated on tmpfs.
20 * . It runs on most filesystems available
21 * . On EXFAT and extN we change the margin of error between all and file
22 * memory to 50%. Because these allocate non-page-cache memory during writes.
23 */
24 #define _GNU_SOURCE
25
26 #include <stdlib.h>
27 #include <stdio.h>
28
29 #include "tst_test.h"
30 #include "tst_cgroup.h"
31
32 #define TMPDIR "mntdir"
33 #define MB(x) (x << 20)
34
35 static size_t page_size;
36 static const struct tst_cgroup_group *cg_test;
37 static struct tst_cgroup_group *cg_child;
38 static int fd;
39 static int file_to_all_error = 10;
40
41 /*
42 * Checks if two given values differ by less than err% of their sum.
43 */
values_close(const ssize_t a,const ssize_t b,const ssize_t err)44 static inline int values_close(const ssize_t a,
45 const ssize_t b,
46 const ssize_t err)
47 {
48 return 100 * labs(a - b) <= (a + b) * err;
49 }
50
alloc_anon_50M_check(void)51 static void alloc_anon_50M_check(void)
52 {
53 const ssize_t size = MB(50);
54 char *buf, *ptr;
55 ssize_t anon, current;
56 const char *const anon_key_fmt =
57 TST_CGROUP_VER_IS_V1(cg_test, "memory") ? "rss %zd" : "anon %zd";
58
59 buf = SAFE_MALLOC(size);
60 for (ptr = buf; ptr < buf + size; ptr += page_size)
61 *ptr = 0;
62
63 SAFE_CGROUP_SCANF(cg_child, "memory.current", "%zd", ¤t);
64 TST_EXP_EXPR(current >= size,
65 "(memory.current=%zd) >= (size=%zd)", current, size);
66
67 SAFE_CGROUP_LINES_SCANF(cg_child, "memory.stat", anon_key_fmt, &anon);
68
69 TST_EXP_EXPR(anon > 0, "(memory.stat.anon=%zd) > 0", anon);
70 TST_EXP_EXPR(values_close(size, current, 3),
71 "(size=%zd) ~= (memory.stat.anon=%zd)", size, current);
72 TST_EXP_EXPR(values_close(anon, current, 3),
73 "(memory.current=%zd) ~= (memory.stat.anon=%zd)",
74 current, anon);
75 }
76
alloc_pagecache(const int fd,size_t size)77 static void alloc_pagecache(const int fd, size_t size)
78 {
79 char buf[BUFSIZ];
80 size_t i;
81
82 for (i = 0; i < size; i += sizeof(buf))
83 SAFE_WRITE(1, fd, buf, sizeof(buf));
84 }
85
alloc_pagecache_50M_check(void)86 static void alloc_pagecache_50M_check(void)
87 {
88 const size_t size = MB(50);
89 size_t current, file;
90 const char *const file_key_fmt =
91 TST_CGROUP_VER_IS_V1(cg_test, "memory") ? "cache %zd" : "file %zd";
92
93 TEST(open(TMPDIR"/tmpfile", O_RDWR | O_CREAT, 0600));
94
95 if (TST_RET < 0) {
96 if (TST_ERR == EOPNOTSUPP)
97 tst_brk(TCONF, "O_TMPFILE not supported by FS");
98
99 tst_brk(TBROK | TTERRNO,
100 "open(%s, O_TMPFILE | O_RDWR | O_EXCL", TMPDIR"/.");
101 }
102 fd = TST_RET;
103
104 SAFE_CGROUP_SCANF(cg_child, "memory.current", "%zu", ¤t);
105 tst_res(TINFO, "Created temp file: memory.current=%zu", current);
106
107 alloc_pagecache(fd, size);
108
109 SAFE_CGROUP_SCANF(cg_child, "memory.current", "%zu", ¤t);
110 TST_EXP_EXPR(current >= size,
111 "(memory.current=%zu) >= (size=%zu)", current, size);
112
113 SAFE_CGROUP_LINES_SCANF(cg_child, "memory.stat", file_key_fmt, &file);
114 TST_EXP_EXPR(file > 0, "(memory.stat.file=%zd) > 0", file);
115
116 TST_EXP_EXPR(values_close(file, current, file_to_all_error),
117 "(memory.current=%zd) ~= (memory.stat.file=%zd)",
118 current, file);
119
120 SAFE_CLOSE(fd);
121 }
122
test_memcg_current(unsigned int n)123 static void test_memcg_current(unsigned int n)
124 {
125 size_t current;
126
127 cg_child = tst_cgroup_group_mk(cg_test, "child");
128 SAFE_CGROUP_SCANF(cg_child, "memory.current", "%zu", ¤t);
129 TST_EXP_EXPR(current == 0, "(current=%zu) == 0", current);
130
131 if (!SAFE_FORK()) {
132 SAFE_CGROUP_PRINTF(cg_child, "cgroup.procs", "%d", getpid());
133
134 SAFE_CGROUP_SCANF(cg_child, "memory.current", "%zu", ¤t);
135 tst_res(TINFO, "Added proc to memcg: memory.current=%zu",
136 current);
137
138 if (!n)
139 alloc_anon_50M_check();
140 else
141 alloc_pagecache_50M_check();
142 } else {
143 tst_reap_children();
144 cg_child = tst_cgroup_group_rm(cg_child);
145 }
146 }
147
setup(void)148 static void setup(void)
149 {
150 page_size = SAFE_SYSCONF(_SC_PAGESIZE);
151
152 tst_cgroup_require("memory", NULL);
153 cg_test = tst_cgroup_get_test_group();
154
155 switch (tst_fs_type(TMPDIR)) {
156 case TST_EXFAT_MAGIC:
157 case TST_EXT234_MAGIC:
158 file_to_all_error = 50;
159 break;
160 }
161 }
162
cleanup(void)163 static void cleanup(void)
164 {
165 if (cg_child)
166 cg_child = tst_cgroup_group_rm(cg_child);
167
168 tst_cgroup_cleanup();
169 }
170
171 static struct tst_test test = {
172 .setup = setup,
173 .cleanup = cleanup,
174 .tcnt = 2,
175 .test = test_memcg_current,
176 .mount_device = 1,
177 .dev_min_size = 256,
178 .mntpoint = TMPDIR,
179 .all_filesystems = 1,
180 .forks_child = 1,
181 .needs_root = 1,
182 };
183