File Index
Files and directories
Sign in
FILE

test_cachestat.c

File preview with syntax highlighting.

linux-6.19.14/tools/testing/selftests/cachestat/test_cachestat.c C
  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
 23static const char * const dev_files[] = {
 24	"/dev/zero", "/dev/null", "/dev/urandom",
 25	"/proc/version", "/proc"
 26};
 27
 28void 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
 36enum file_type {
 37	FILE_MMAP,
 38	FILE_SHMEM
 39};
 40
 41bool 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;
 94out_free_data:
 95	free(data);
 96close_random_fd:
 97	close(random_fd);
 98out:
 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 */
106static 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 */
123static 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
201out1:
202	close(fd);
203
204	if (create)
205		remove(filename);
206out:
207	return ret;
208}
209const 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
222bool 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", *map;
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		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
291close_fd:
292	shm_unlink(filename);
293out:
294	return ret;
295}
296
297int 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}
~/ Read-only file preview.