• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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", &current);
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", &current);
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", &current);
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", &current);
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", &current);
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