xref: /linux/tools/testing/selftests/cachestat/test_cachestat.c (revision 0074281bb6316108e0cff094bd4db78ab3eee236)
1 // SPDX-License-Identifier: GPL-2.0
2 #define _GNU_SOURCE
3 #define __SANE_USERSPACE_TYPES__ // Use ll64
4 
5 #include <stdio.h>
6 #include <stdbool.h>
7 #include <linux/kernel.h>
8 #include <linux/magic.h>
9 #include <linux/mman.h>
10 #include <sys/mman.h>
11 #include <sys/shm.h>
12 #include <sys/syscall.h>
13 #include <sys/vfs.h>
14 #include <unistd.h>
15 #include <string.h>
16 #include <fcntl.h>
17 #include <errno.h>
18 
19 #include "../kselftest.h"
20 
21 #define NR_TESTS	9
22 
23 static const char * const dev_files[] = {
24 	"/dev/zero", "/dev/null", "/dev/urandom",
25 	"/proc/version", "/proc"
26 };
27 
print_cachestat(struct cachestat * cs)28 void print_cachestat(struct cachestat *cs)
29 {
30 	ksft_print_msg(
31 	"Using cachestat: Cached: %llu, Dirty: %llu, Writeback: %llu, Evicted: %llu, Recently Evicted: %llu\n",
32 	cs->nr_cache, cs->nr_dirty, cs->nr_writeback,
33 	cs->nr_evicted, cs->nr_recently_evicted);
34 }
35 
36 enum file_type {
37 	FILE_MMAP,
38 	FILE_SHMEM
39 };
40 
write_exactly(int fd,size_t filesize)41 bool write_exactly(int fd, size_t filesize)
42 {
43 	int random_fd = open("/dev/urandom", O_RDONLY);
44 	char *cursor, *data;
45 	int remained;
46 	bool ret;
47 
48 	if (random_fd < 0) {
49 		ksft_print_msg("Unable to access urandom.\n");
50 		ret = false;
51 		goto out;
52 	}
53 
54 	data = malloc(filesize);
55 	if (!data) {
56 		ksft_print_msg("Unable to allocate data.\n");
57 		ret = false;
58 		goto close_random_fd;
59 	}
60 
61 	remained = filesize;
62 	cursor = data;
63 
64 	while (remained) {
65 		ssize_t read_len = read(random_fd, cursor, remained);
66 
67 		if (read_len <= 0) {
68 			ksft_print_msg("Unable to read from urandom.\n");
69 			ret = false;
70 			goto out_free_data;
71 		}
72 
73 		remained -= read_len;
74 		cursor += read_len;
75 	}
76 
77 	/* write random data to fd */
78 	remained = filesize;
79 	cursor = data;
80 	while (remained) {
81 		ssize_t write_len = write(fd, cursor, remained);
82 
83 		if (write_len <= 0) {
84 			ksft_print_msg("Unable write random data to file.\n");
85 			ret = false;
86 			goto out_free_data;
87 		}
88 
89 		remained -= write_len;
90 		cursor += write_len;
91 	}
92 
93 	ret = true;
94 out_free_data:
95 	free(data);
96 close_random_fd:
97 	close(random_fd);
98 out:
99 	return ret;
100 }
101 
102 /*
103  * fsync() is implemented via noop_fsync() on tmpfs. This makes the fsync()
104  * test fail below, so we need to check for test file living on a tmpfs.
105  */
is_on_tmpfs(int fd)106 static bool is_on_tmpfs(int fd)
107 {
108 	struct statfs statfs_buf;
109 
110 	if (fstatfs(fd, &statfs_buf))
111 		return false;
112 
113 	return statfs_buf.f_type == TMPFS_MAGIC;
114 }
115 
116 /*
117  * Open/create the file at filename, (optionally) write random data to it
118  * (exactly num_pages), then test the cachestat syscall on this file.
119  *
120  * If test_fsync == true, fsync the file, then check the number of dirty
121  * pages.
122  */
test_cachestat(const char * filename,bool write_random,bool create,bool test_fsync,unsigned long num_pages,int open_flags,mode_t open_mode)123 static int test_cachestat(const char *filename, bool write_random, bool create,
124 			  bool test_fsync, unsigned long num_pages,
125 			  int open_flags, mode_t open_mode)
126 {
127 	size_t PS = sysconf(_SC_PAGESIZE);
128 	int filesize = num_pages * PS;
129 	int ret = KSFT_PASS;
130 	long syscall_ret;
131 	struct cachestat cs;
132 	struct cachestat_range cs_range = { 0, filesize };
133 
134 	int fd = open(filename, open_flags, open_mode);
135 
136 	if (fd == -1) {
137 		ksft_print_msg("Unable to create/open file.\n");
138 		ret = KSFT_FAIL;
139 		goto out;
140 	} else {
141 		ksft_print_msg("Create/open %s\n", filename);
142 	}
143 
144 	if (write_random) {
145 		if (!write_exactly(fd, filesize)) {
146 			ksft_print_msg("Unable to access urandom.\n");
147 			ret = KSFT_FAIL;
148 			goto out1;
149 		}
150 	}
151 
152 	syscall_ret = syscall(__NR_cachestat, fd, &cs_range, &cs, 0);
153 
154 	ksft_print_msg("Cachestat call returned %ld\n", syscall_ret);
155 
156 	if (syscall_ret) {
157 		ksft_print_msg("Cachestat returned non-zero.\n");
158 		ret = KSFT_FAIL;
159 		goto out1;
160 
161 	} else {
162 		print_cachestat(&cs);
163 
164 		if (write_random) {
165 			if (cs.nr_cache + cs.nr_evicted != num_pages) {
166 				ksft_print_msg(
167 					"Total number of cached and evicted pages is off.\n");
168 				ret = KSFT_FAIL;
169 			}
170 		}
171 	}
172 
173 	if (test_fsync) {
174 		if (is_on_tmpfs(fd)) {
175 			ret = KSFT_SKIP;
176 		} else if (fsync(fd)) {
177 			ksft_print_msg("fsync fails.\n");
178 			ret = KSFT_FAIL;
179 		} else {
180 			syscall_ret = syscall(__NR_cachestat, fd, &cs_range, &cs, 0);
181 
182 			ksft_print_msg("Cachestat call (after fsync) returned %ld\n",
183 				syscall_ret);
184 
185 			if (!syscall_ret) {
186 				print_cachestat(&cs);
187 
188 				if (cs.nr_dirty) {
189 					ret = KSFT_FAIL;
190 					ksft_print_msg(
191 						"Number of dirty should be zero after fsync.\n");
192 				}
193 			} else {
194 				ksft_print_msg("Cachestat (after fsync) returned non-zero.\n");
195 				ret = KSFT_FAIL;
196 				goto out1;
197 			}
198 		}
199 	}
200 
201 out1:
202 	close(fd);
203 
204 	if (create)
205 		remove(filename);
206 out:
207 	return ret;
208 }
file_type_str(enum file_type type)209 const char *file_type_str(enum file_type type)
210 {
211 	switch (type) {
212 	case FILE_SHMEM:
213 		return "shmem";
214 	case FILE_MMAP:
215 		return "mmap";
216 	default:
217 		return "unknown";
218 	}
219 }
220 
221 
run_cachestat_test(enum file_type type)222 bool run_cachestat_test(enum file_type type)
223 {
224 	size_t PS = sysconf(_SC_PAGESIZE);
225 	size_t filesize = PS * 512 * 2; /* 2 2MB huge pages */
226 	int syscall_ret;
227 	size_t compute_len = PS * 512;
228 	struct cachestat_range cs_range = { PS, compute_len };
229 	char *filename = "tmpshmcstat";
230 	struct cachestat cs;
231 	bool ret = true;
232 	int fd;
233 	unsigned long num_pages = compute_len / PS;
234 	if (type == FILE_SHMEM)
235 		fd = shm_open(filename, O_CREAT | O_RDWR, 0600);
236 	else
237 		fd = open(filename, O_RDWR | O_CREAT | O_TRUNC, 0666);
238 
239 	if (fd < 0) {
240 		ksft_print_msg("Unable to create %s file.\n",
241 				file_type_str(type));
242 		ret = false;
243 		goto out;
244 	}
245 
246 	if (ftruncate(fd, filesize)) {
247 		ksft_print_msg("Unable to truncate %s file.\n",file_type_str(type));
248 		ret = false;
249 		goto close_fd;
250 	}
251 	switch (type) {
252 	case FILE_SHMEM:
253 		if (!write_exactly(fd, filesize)) {
254 			ksft_print_msg("Unable to write to file.\n");
255 			ret = false;
256 			goto close_fd;
257 		}
258 		break;
259 	case FILE_MMAP:
260 		char *map = mmap(NULL, filesize, PROT_READ | PROT_WRITE,
261 				 MAP_SHARED, fd, 0);
262 
263 		if (map == MAP_FAILED) {
264 			ksft_print_msg("mmap failed.\n");
265 			ret = false;
266 			goto close_fd;
267 		}
268 		for (int i = 0; i < filesize; i++)
269 			map[i] = 'A';
270 		break;
271 	default:
272 		ksft_print_msg("Unsupported file type.\n");
273 		ret = false;
274 		goto close_fd;
275 	}
276 	syscall_ret = syscall(__NR_cachestat, fd, &cs_range, &cs, 0);
277 
278 	if (syscall_ret) {
279 		ksft_print_msg("Cachestat returned non-zero.\n");
280 		ret = false;
281 		goto close_fd;
282 	} else {
283 		print_cachestat(&cs);
284 		if (cs.nr_cache + cs.nr_evicted != num_pages) {
285 			ksft_print_msg(
286 				"Total number of cached and evicted pages is off.\n");
287 			ret = false;
288 		}
289 	}
290 
291 close_fd:
292 	shm_unlink(filename);
293 out:
294 	return ret;
295 }
296 
main(void)297 int main(void)
298 {
299 	int ret;
300 
301 	ksft_print_header();
302 
303 	ret = syscall(__NR_cachestat, -1, NULL, NULL, 0);
304 	if (ret == -1 && errno == ENOSYS)
305 		ksft_exit_skip("cachestat syscall not available\n");
306 
307 	ksft_set_plan(NR_TESTS);
308 
309 	if (ret == -1 && errno == EBADF) {
310 		ksft_test_result_pass("bad file descriptor recognized\n");
311 		ret = 0;
312 	} else {
313 		ksft_test_result_fail("bad file descriptor ignored\n");
314 		ret = 1;
315 	}
316 
317 	for (int i = 0; i < 5; i++) {
318 		const char *dev_filename = dev_files[i];
319 
320 		if (test_cachestat(dev_filename, false, false, false,
321 			4, O_RDONLY, 0400) == KSFT_PASS)
322 			ksft_test_result_pass("cachestat works with %s\n", dev_filename);
323 		else {
324 			ksft_test_result_fail("cachestat fails with %s\n", dev_filename);
325 			ret = 1;
326 		}
327 	}
328 
329 	if (test_cachestat("tmpfilecachestat", true, true,
330 		false, 4, O_CREAT | O_RDWR, 0600) == KSFT_PASS)
331 		ksft_test_result_pass("cachestat works with a normal file\n");
332 	else {
333 		ksft_test_result_fail("cachestat fails with normal file\n");
334 		ret = 1;
335 	}
336 
337 	switch (test_cachestat("tmpfilecachestat", true, true,
338 		true, 4, O_CREAT | O_RDWR, 0600)) {
339 	case KSFT_FAIL:
340 		ksft_test_result_fail("cachestat fsync fails with normal file\n");
341 		ret = KSFT_FAIL;
342 		break;
343 	case KSFT_PASS:
344 		ksft_test_result_pass("cachestat fsync works with a normal file\n");
345 		break;
346 	case KSFT_SKIP:
347 		ksft_test_result_skip("tmpfilecachestat is on tmpfs\n");
348 		break;
349 	}
350 
351 	if (run_cachestat_test(FILE_SHMEM))
352 		ksft_test_result_pass("cachestat works with a shmem file\n");
353 	else {
354 		ksft_test_result_fail("cachestat fails with a shmem file\n");
355 		ret = 1;
356 	}
357 
358 	if (run_cachestat_test(FILE_MMAP))
359 		ksft_test_result_pass("cachestat works with a mmap file\n");
360 	else {
361 		ksft_test_result_fail("cachestat fails with a mmap file\n");
362 		ret = 1;
363 	}
364 	return ret;
365 }
366