xref: /openbmc/linux/tools/testing/selftests/cachestat/test_cachestat.c (revision d6b6592ac6d11eab91e6758d224eac35f4122aca)
188537aacSNhat Pham // SPDX-License-Identifier: GPL-2.0
288537aacSNhat Pham #define _GNU_SOURCE
3*91bf0634SMichael Ellerman #define __SANE_USERSPACE_TYPES__ // Use ll64
488537aacSNhat Pham 
588537aacSNhat Pham #include <stdio.h>
688537aacSNhat Pham #include <stdbool.h>
788537aacSNhat Pham #include <linux/kernel.h>
8f84f62e6SAndre Przywara #include <linux/magic.h>
988537aacSNhat Pham #include <linux/mman.h>
1088537aacSNhat Pham #include <sys/mman.h>
1188537aacSNhat Pham #include <sys/shm.h>
1288537aacSNhat Pham #include <sys/syscall.h>
13f84f62e6SAndre Przywara #include <sys/vfs.h>
1488537aacSNhat Pham #include <unistd.h>
1588537aacSNhat Pham #include <string.h>
1688537aacSNhat Pham #include <fcntl.h>
1788537aacSNhat Pham #include <errno.h>
1888537aacSNhat Pham 
1988537aacSNhat Pham #include "../kselftest.h"
2088537aacSNhat Pham 
21f84f62e6SAndre Przywara #define NR_TESTS	9
225e56982dSAndre Przywara 
2388537aacSNhat Pham static const char * const dev_files[] = {
2488537aacSNhat Pham 	"/dev/zero", "/dev/null", "/dev/urandom",
2588537aacSNhat Pham 	"/proc/version", "/proc"
2688537aacSNhat Pham };
2788537aacSNhat Pham 
print_cachestat(struct cachestat * cs)2888537aacSNhat Pham void print_cachestat(struct cachestat *cs)
2988537aacSNhat Pham {
3088537aacSNhat Pham 	ksft_print_msg(
3188537aacSNhat Pham 	"Using cachestat: Cached: %lu, Dirty: %lu, Writeback: %lu, Evicted: %lu, Recently Evicted: %lu\n",
3288537aacSNhat Pham 	cs->nr_cache, cs->nr_dirty, cs->nr_writeback,
3388537aacSNhat Pham 	cs->nr_evicted, cs->nr_recently_evicted);
3488537aacSNhat Pham }
3588537aacSNhat Pham 
write_exactly(int fd,size_t filesize)3688537aacSNhat Pham bool write_exactly(int fd, size_t filesize)
3788537aacSNhat Pham {
3888537aacSNhat Pham 	int random_fd = open("/dev/urandom", O_RDONLY);
3988537aacSNhat Pham 	char *cursor, *data;
4088537aacSNhat Pham 	int remained;
4188537aacSNhat Pham 	bool ret;
4288537aacSNhat Pham 
4388537aacSNhat Pham 	if (random_fd < 0) {
4488537aacSNhat Pham 		ksft_print_msg("Unable to access urandom.\n");
4588537aacSNhat Pham 		ret = false;
4688537aacSNhat Pham 		goto out;
4788537aacSNhat Pham 	}
4888537aacSNhat Pham 
4988537aacSNhat Pham 	data = malloc(filesize);
5088537aacSNhat Pham 	if (!data) {
5188537aacSNhat Pham 		ksft_print_msg("Unable to allocate data.\n");
5288537aacSNhat Pham 		ret = false;
5388537aacSNhat Pham 		goto close_random_fd;
5488537aacSNhat Pham 	}
5588537aacSNhat Pham 
5688537aacSNhat Pham 	remained = filesize;
5788537aacSNhat Pham 	cursor = data;
5888537aacSNhat Pham 
5988537aacSNhat Pham 	while (remained) {
6088537aacSNhat Pham 		ssize_t read_len = read(random_fd, cursor, remained);
6188537aacSNhat Pham 
6288537aacSNhat Pham 		if (read_len <= 0) {
6388537aacSNhat Pham 			ksft_print_msg("Unable to read from urandom.\n");
6488537aacSNhat Pham 			ret = false;
6588537aacSNhat Pham 			goto out_free_data;
6688537aacSNhat Pham 		}
6788537aacSNhat Pham 
6888537aacSNhat Pham 		remained -= read_len;
6988537aacSNhat Pham 		cursor += read_len;
7088537aacSNhat Pham 	}
7188537aacSNhat Pham 
7288537aacSNhat Pham 	/* write random data to fd */
7388537aacSNhat Pham 	remained = filesize;
7488537aacSNhat Pham 	cursor = data;
7588537aacSNhat Pham 	while (remained) {
7688537aacSNhat Pham 		ssize_t write_len = write(fd, cursor, remained);
7788537aacSNhat Pham 
7888537aacSNhat Pham 		if (write_len <= 0) {
7988537aacSNhat Pham 			ksft_print_msg("Unable write random data to file.\n");
8088537aacSNhat Pham 			ret = false;
8188537aacSNhat Pham 			goto out_free_data;
8288537aacSNhat Pham 		}
8388537aacSNhat Pham 
8488537aacSNhat Pham 		remained -= write_len;
8588537aacSNhat Pham 		cursor += write_len;
8688537aacSNhat Pham 	}
8788537aacSNhat Pham 
8888537aacSNhat Pham 	ret = true;
8988537aacSNhat Pham out_free_data:
9088537aacSNhat Pham 	free(data);
9188537aacSNhat Pham close_random_fd:
9288537aacSNhat Pham 	close(random_fd);
9388537aacSNhat Pham out:
9488537aacSNhat Pham 	return ret;
9588537aacSNhat Pham }
9688537aacSNhat Pham 
9788537aacSNhat Pham /*
98f84f62e6SAndre Przywara  * fsync() is implemented via noop_fsync() on tmpfs. This makes the fsync()
99f84f62e6SAndre Przywara  * test fail below, so we need to check for test file living on a tmpfs.
100f84f62e6SAndre Przywara  */
is_on_tmpfs(int fd)101f84f62e6SAndre Przywara static bool is_on_tmpfs(int fd)
102f84f62e6SAndre Przywara {
103f84f62e6SAndre Przywara 	struct statfs statfs_buf;
104f84f62e6SAndre Przywara 
105f84f62e6SAndre Przywara 	if (fstatfs(fd, &statfs_buf))
106f84f62e6SAndre Przywara 		return false;
107f84f62e6SAndre Przywara 
108f84f62e6SAndre Przywara 	return statfs_buf.f_type == TMPFS_MAGIC;
109f84f62e6SAndre Przywara }
110f84f62e6SAndre Przywara 
111f84f62e6SAndre Przywara /*
11288537aacSNhat Pham  * Open/create the file at filename, (optionally) write random data to it
11388537aacSNhat Pham  * (exactly num_pages), then test the cachestat syscall on this file.
11488537aacSNhat Pham  *
11588537aacSNhat Pham  * If test_fsync == true, fsync the file, then check the number of dirty
11688537aacSNhat Pham  * pages.
11788537aacSNhat Pham  */
test_cachestat(const char * filename,bool write_random,bool create,bool test_fsync,unsigned long num_pages,int open_flags,mode_t open_mode)118f84f62e6SAndre Przywara static int test_cachestat(const char *filename, bool write_random, bool create,
119f84f62e6SAndre Przywara 			  bool test_fsync, unsigned long num_pages,
120f84f62e6SAndre Przywara 			  int open_flags, mode_t open_mode)
12188537aacSNhat Pham {
12288537aacSNhat Pham 	size_t PS = sysconf(_SC_PAGESIZE);
12388537aacSNhat Pham 	int filesize = num_pages * PS;
124f84f62e6SAndre Przywara 	int ret = KSFT_PASS;
12588537aacSNhat Pham 	long syscall_ret;
12688537aacSNhat Pham 	struct cachestat cs;
12788537aacSNhat Pham 	struct cachestat_range cs_range = { 0, filesize };
12888537aacSNhat Pham 
12988537aacSNhat Pham 	int fd = open(filename, open_flags, open_mode);
13088537aacSNhat Pham 
13188537aacSNhat Pham 	if (fd == -1) {
13288537aacSNhat Pham 		ksft_print_msg("Unable to create/open file.\n");
133f84f62e6SAndre Przywara 		ret = KSFT_FAIL;
13488537aacSNhat Pham 		goto out;
13588537aacSNhat Pham 	} else {
13688537aacSNhat Pham 		ksft_print_msg("Create/open %s\n", filename);
13788537aacSNhat Pham 	}
13888537aacSNhat Pham 
13988537aacSNhat Pham 	if (write_random) {
14088537aacSNhat Pham 		if (!write_exactly(fd, filesize)) {
14188537aacSNhat Pham 			ksft_print_msg("Unable to access urandom.\n");
142f84f62e6SAndre Przywara 			ret = KSFT_FAIL;
14388537aacSNhat Pham 			goto out1;
14488537aacSNhat Pham 		}
14588537aacSNhat Pham 	}
14688537aacSNhat Pham 
1479b1db732SAndre Przywara 	syscall_ret = syscall(__NR_cachestat, fd, &cs_range, &cs, 0);
14888537aacSNhat Pham 
14988537aacSNhat Pham 	ksft_print_msg("Cachestat call returned %ld\n", syscall_ret);
15088537aacSNhat Pham 
15188537aacSNhat Pham 	if (syscall_ret) {
15288537aacSNhat Pham 		ksft_print_msg("Cachestat returned non-zero.\n");
153f84f62e6SAndre Przywara 		ret = KSFT_FAIL;
15488537aacSNhat Pham 		goto out1;
15588537aacSNhat Pham 
15688537aacSNhat Pham 	} else {
15788537aacSNhat Pham 		print_cachestat(&cs);
15888537aacSNhat Pham 
15988537aacSNhat Pham 		if (write_random) {
16088537aacSNhat Pham 			if (cs.nr_cache + cs.nr_evicted != num_pages) {
16188537aacSNhat Pham 				ksft_print_msg(
16288537aacSNhat Pham 					"Total number of cached and evicted pages is off.\n");
163f84f62e6SAndre Przywara 				ret = KSFT_FAIL;
16488537aacSNhat Pham 			}
16588537aacSNhat Pham 		}
16688537aacSNhat Pham 	}
16788537aacSNhat Pham 
16888537aacSNhat Pham 	if (test_fsync) {
169f84f62e6SAndre Przywara 		if (is_on_tmpfs(fd)) {
170f84f62e6SAndre Przywara 			ret = KSFT_SKIP;
171f84f62e6SAndre Przywara 		} else if (fsync(fd)) {
17288537aacSNhat Pham 			ksft_print_msg("fsync fails.\n");
173f84f62e6SAndre Przywara 			ret = KSFT_FAIL;
17488537aacSNhat Pham 		} else {
1759b1db732SAndre Przywara 			syscall_ret = syscall(__NR_cachestat, fd, &cs_range, &cs, 0);
17688537aacSNhat Pham 
17788537aacSNhat Pham 			ksft_print_msg("Cachestat call (after fsync) returned %ld\n",
17888537aacSNhat Pham 				syscall_ret);
17988537aacSNhat Pham 
18088537aacSNhat Pham 			if (!syscall_ret) {
18188537aacSNhat Pham 				print_cachestat(&cs);
18288537aacSNhat Pham 
18388537aacSNhat Pham 				if (cs.nr_dirty) {
184f84f62e6SAndre Przywara 					ret = KSFT_FAIL;
18588537aacSNhat Pham 					ksft_print_msg(
18688537aacSNhat Pham 						"Number of dirty should be zero after fsync.\n");
18788537aacSNhat Pham 				}
18888537aacSNhat Pham 			} else {
18988537aacSNhat Pham 				ksft_print_msg("Cachestat (after fsync) returned non-zero.\n");
190f84f62e6SAndre Przywara 				ret = KSFT_FAIL;
19188537aacSNhat Pham 				goto out1;
19288537aacSNhat Pham 			}
19388537aacSNhat Pham 		}
19488537aacSNhat Pham 	}
19588537aacSNhat Pham 
19688537aacSNhat Pham out1:
19788537aacSNhat Pham 	close(fd);
19888537aacSNhat Pham 
19988537aacSNhat Pham 	if (create)
20088537aacSNhat Pham 		remove(filename);
20188537aacSNhat Pham out:
20288537aacSNhat Pham 	return ret;
20388537aacSNhat Pham }
20488537aacSNhat Pham 
test_cachestat_shmem(void)20588537aacSNhat Pham bool test_cachestat_shmem(void)
20688537aacSNhat Pham {
20788537aacSNhat Pham 	size_t PS = sysconf(_SC_PAGESIZE);
20888537aacSNhat Pham 	size_t filesize = PS * 512 * 2; /* 2 2MB huge pages */
20988537aacSNhat Pham 	int syscall_ret;
21088537aacSNhat Pham 	size_t compute_len = PS * 512;
21188537aacSNhat Pham 	struct cachestat_range cs_range = { PS, compute_len };
21288537aacSNhat Pham 	char *filename = "tmpshmcstat";
21388537aacSNhat Pham 	struct cachestat cs;
21488537aacSNhat Pham 	bool ret = true;
21588537aacSNhat Pham 	unsigned long num_pages = compute_len / PS;
21688537aacSNhat Pham 	int fd = shm_open(filename, O_CREAT | O_RDWR, 0600);
21788537aacSNhat Pham 
21888537aacSNhat Pham 	if (fd < 0) {
21988537aacSNhat Pham 		ksft_print_msg("Unable to create shmem file.\n");
22088537aacSNhat Pham 		ret = false;
22188537aacSNhat Pham 		goto out;
22288537aacSNhat Pham 	}
22388537aacSNhat Pham 
22488537aacSNhat Pham 	if (ftruncate(fd, filesize)) {
22588537aacSNhat Pham 		ksft_print_msg("Unable to truncate shmem file.\n");
22688537aacSNhat Pham 		ret = false;
22788537aacSNhat Pham 		goto close_fd;
22888537aacSNhat Pham 	}
22988537aacSNhat Pham 
23088537aacSNhat Pham 	if (!write_exactly(fd, filesize)) {
23188537aacSNhat Pham 		ksft_print_msg("Unable to write to shmem file.\n");
23288537aacSNhat Pham 		ret = false;
23388537aacSNhat Pham 		goto close_fd;
23488537aacSNhat Pham 	}
23588537aacSNhat Pham 
2369b1db732SAndre Przywara 	syscall_ret = syscall(__NR_cachestat, fd, &cs_range, &cs, 0);
23788537aacSNhat Pham 
23888537aacSNhat Pham 	if (syscall_ret) {
23988537aacSNhat Pham 		ksft_print_msg("Cachestat returned non-zero.\n");
24088537aacSNhat Pham 		ret = false;
24188537aacSNhat Pham 		goto close_fd;
24288537aacSNhat Pham 	} else {
24388537aacSNhat Pham 		print_cachestat(&cs);
24488537aacSNhat Pham 		if (cs.nr_cache + cs.nr_evicted != num_pages) {
24588537aacSNhat Pham 			ksft_print_msg(
24688537aacSNhat Pham 				"Total number of cached and evicted pages is off.\n");
24788537aacSNhat Pham 			ret = false;
24888537aacSNhat Pham 		}
24988537aacSNhat Pham 	}
25088537aacSNhat Pham 
25188537aacSNhat Pham close_fd:
25288537aacSNhat Pham 	shm_unlink(filename);
25388537aacSNhat Pham out:
25488537aacSNhat Pham 	return ret;
25588537aacSNhat Pham }
25688537aacSNhat Pham 
main(void)25788537aacSNhat Pham int main(void)
25888537aacSNhat Pham {
2595e56982dSAndre Przywara 	int ret;
2605e56982dSAndre Przywara 
2615e56982dSAndre Przywara 	ksft_print_header();
2625e56982dSAndre Przywara 
2635e56982dSAndre Przywara 	ret = syscall(__NR_cachestat, -1, NULL, NULL, 0);
2645e56982dSAndre Przywara 	if (ret == -1 && errno == ENOSYS)
2655e56982dSAndre Przywara 		ksft_exit_skip("cachestat syscall not available\n");
2665e56982dSAndre Przywara 
2675e56982dSAndre Przywara 	ksft_set_plan(NR_TESTS);
2685e56982dSAndre Przywara 
2695e56982dSAndre Przywara 	if (ret == -1 && errno == EBADF) {
2705e56982dSAndre Przywara 		ksft_test_result_pass("bad file descriptor recognized\n");
2715e56982dSAndre Przywara 		ret = 0;
2725e56982dSAndre Przywara 	} else {
2735e56982dSAndre Przywara 		ksft_test_result_fail("bad file descriptor ignored\n");
2745e56982dSAndre Przywara 		ret = 1;
2755e56982dSAndre Przywara 	}
27688537aacSNhat Pham 
27788537aacSNhat Pham 	for (int i = 0; i < 5; i++) {
27888537aacSNhat Pham 		const char *dev_filename = dev_files[i];
27988537aacSNhat Pham 
28088537aacSNhat Pham 		if (test_cachestat(dev_filename, false, false, false,
281f84f62e6SAndre Przywara 			4, O_RDONLY, 0400) == KSFT_PASS)
28288537aacSNhat Pham 			ksft_test_result_pass("cachestat works with %s\n", dev_filename);
28388537aacSNhat Pham 		else {
28488537aacSNhat Pham 			ksft_test_result_fail("cachestat fails with %s\n", dev_filename);
28588537aacSNhat Pham 			ret = 1;
28688537aacSNhat Pham 		}
28788537aacSNhat Pham 	}
28888537aacSNhat Pham 
28988537aacSNhat Pham 	if (test_cachestat("tmpfilecachestat", true, true,
290f84f62e6SAndre Przywara 		false, 4, O_CREAT | O_RDWR, 0600) == KSFT_PASS)
29188537aacSNhat Pham 		ksft_test_result_pass("cachestat works with a normal file\n");
29288537aacSNhat Pham 	else {
29388537aacSNhat Pham 		ksft_test_result_fail("cachestat fails with normal file\n");
29488537aacSNhat Pham 		ret = 1;
29588537aacSNhat Pham 	}
29688537aacSNhat Pham 
297f84f62e6SAndre Przywara 	switch (test_cachestat("tmpfilecachestat", true, true,
298f84f62e6SAndre Przywara 		true, 4, O_CREAT | O_RDWR, 0600)) {
299f84f62e6SAndre Przywara 	case KSFT_FAIL:
300f84f62e6SAndre Przywara 		ksft_test_result_fail("cachestat fsync fails with normal file\n");
301f84f62e6SAndre Przywara 		ret = KSFT_FAIL;
302f84f62e6SAndre Przywara 		break;
303f84f62e6SAndre Przywara 	case KSFT_PASS:
304f84f62e6SAndre Przywara 		ksft_test_result_pass("cachestat fsync works with a normal file\n");
305f84f62e6SAndre Przywara 		break;
306f84f62e6SAndre Przywara 	case KSFT_SKIP:
307f84f62e6SAndre Przywara 		ksft_test_result_skip("tmpfilecachestat is on tmpfs\n");
308f84f62e6SAndre Przywara 		break;
309f84f62e6SAndre Przywara 	}
310f84f62e6SAndre Przywara 
31188537aacSNhat Pham 	if (test_cachestat_shmem())
31288537aacSNhat Pham 		ksft_test_result_pass("cachestat works with a shmem file\n");
31388537aacSNhat Pham 	else {
31488537aacSNhat Pham 		ksft_test_result_fail("cachestat fails with a shmem file\n");
31588537aacSNhat Pham 		ret = 1;
31688537aacSNhat Pham 	}
31788537aacSNhat Pham 
31888537aacSNhat Pham 	return ret;
31988537aacSNhat Pham }
320