Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

selftests: add selftests for cachestat

Test cachestat on a newly created file, /dev/ files, /proc/ files and a
directory. Also test on a shmem file (which can also be tested with
huge pages since tmpfs supports huge pages).

[colin.i.king@gmail.com: fix spelling mistake "trucate" -> "truncate"]
Link: https://lkml.kernel.org/r/20230505110855.2493457-1-colin.i.king@gmail.com
[mpe@ellerman.id.au: avoid excessive stack allocation]
Link: https://lkml.kernel.org/r/877ctfa6yv.fsf@mail.lhotse
Link: https://lkml.kernel.org/r/20230503013608.2431726-4-nphamcs@gmail.com
Signed-off-by: Nhat Pham <nphamcs@gmail.com>
Signed-off-by: Colin Ian King <colin.i.king@gmail.com>
Signed-off-by: Michael Ellerman <mpe@ellerman.id.au>
Acked-by: Johannes Weiner <hannes@cmpxchg.org>
Cc: Brian Foster <bfoster@redhat.com>
Cc: Matthew Wilcox (Oracle) <willy@infradead.org>
Cc: Michael Kerrisk <mtk.manpages@gmail.com>
Cc: Colin Ian King <colin.i.king@gmail.com>
Cc: Michael Ellerman <mpe@ellerman.id.au>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>

authored by

Nhat Pham and committed by
Andrew Morton
88537aac 946e697c

+287
+7
MAINTAINERS
··· 4486 4486 F: Documentation/filesystems/caching/cachefiles.rst 4487 4487 F: fs/cachefiles/ 4488 4488 4489 + CACHESTAT: PAGE CACHE STATS FOR A FILE 4490 + M: Nhat Pham <nphamcs@gmail.com> 4491 + M: Johannes Weiner <hannes@cmpxchg.org> 4492 + L: linux-mm@kvack.org 4493 + S: Maintained 4494 + F: tools/testing/selftests/cachestat/test_cachestat.c 4495 + 4489 4496 CADENCE MIPI-CSI2 BRIDGES 4490 4497 M: Maxime Ripard <mripard@kernel.org> 4491 4498 L: linux-media@vger.kernel.org
+1
tools/testing/selftests/Makefile
··· 4 4 TARGETS += arm64 5 5 TARGETS += bpf 6 6 TARGETS += breakpoints 7 + TARGETS += cachestat 7 8 TARGETS += capabilities 8 9 TARGETS += cgroup 9 10 TARGETS += clone3
+2
tools/testing/selftests/cachestat/.gitignore
··· 1 + # SPDX-License-Identifier: GPL-2.0-only 2 + test_cachestat
+8
tools/testing/selftests/cachestat/Makefile
··· 1 + # SPDX-License-Identifier: GPL-2.0 2 + TEST_GEN_PROGS := test_cachestat 3 + 4 + CFLAGS += $(KHDR_INCLUDES) 5 + CFLAGS += -Wall 6 + CFLAGS += -lrt 7 + 8 + include ../lib.mk
+269
tools/testing/selftests/cachestat/test_cachestat.c
··· 1 + // SPDX-License-Identifier: GPL-2.0 2 + #define _GNU_SOURCE 3 + 4 + #include <stdio.h> 5 + #include <stdbool.h> 6 + #include <linux/kernel.h> 7 + #include <linux/mman.h> 8 + #include <sys/mman.h> 9 + #include <sys/shm.h> 10 + #include <sys/syscall.h> 11 + #include <unistd.h> 12 + #include <string.h> 13 + #include <fcntl.h> 14 + #include <errno.h> 15 + 16 + #include "../kselftest.h" 17 + 18 + static const char * const dev_files[] = { 19 + "/dev/zero", "/dev/null", "/dev/urandom", 20 + "/proc/version", "/proc" 21 + }; 22 + static const int cachestat_nr = 451; 23 + 24 + void print_cachestat(struct cachestat *cs) 25 + { 26 + ksft_print_msg( 27 + "Using cachestat: Cached: %lu, Dirty: %lu, Writeback: %lu, Evicted: %lu, Recently Evicted: %lu\n", 28 + cs->nr_cache, cs->nr_dirty, cs->nr_writeback, 29 + cs->nr_evicted, cs->nr_recently_evicted); 30 + } 31 + 32 + bool write_exactly(int fd, size_t filesize) 33 + { 34 + int random_fd = open("/dev/urandom", O_RDONLY); 35 + char *cursor, *data; 36 + int remained; 37 + bool ret; 38 + 39 + if (random_fd < 0) { 40 + ksft_print_msg("Unable to access urandom.\n"); 41 + ret = false; 42 + goto out; 43 + } 44 + 45 + data = malloc(filesize); 46 + if (!data) { 47 + ksft_print_msg("Unable to allocate data.\n"); 48 + ret = false; 49 + goto close_random_fd; 50 + } 51 + 52 + remained = filesize; 53 + cursor = data; 54 + 55 + while (remained) { 56 + ssize_t read_len = read(random_fd, cursor, remained); 57 + 58 + if (read_len <= 0) { 59 + ksft_print_msg("Unable to read from urandom.\n"); 60 + ret = false; 61 + goto out_free_data; 62 + } 63 + 64 + remained -= read_len; 65 + cursor += read_len; 66 + } 67 + 68 + /* write random data to fd */ 69 + remained = filesize; 70 + cursor = data; 71 + while (remained) { 72 + ssize_t write_len = write(fd, cursor, remained); 73 + 74 + if (write_len <= 0) { 75 + ksft_print_msg("Unable write random data to file.\n"); 76 + ret = false; 77 + goto out_free_data; 78 + } 79 + 80 + remained -= write_len; 81 + cursor += write_len; 82 + } 83 + 84 + ret = true; 85 + out_free_data: 86 + free(data); 87 + close_random_fd: 88 + close(random_fd); 89 + out: 90 + return ret; 91 + } 92 + 93 + /* 94 + * Open/create the file at filename, (optionally) write random data to it 95 + * (exactly num_pages), then test the cachestat syscall on this file. 96 + * 97 + * If test_fsync == true, fsync the file, then check the number of dirty 98 + * pages. 99 + */ 100 + bool test_cachestat(const char *filename, bool write_random, bool create, 101 + bool test_fsync, unsigned long num_pages, int open_flags, 102 + mode_t open_mode) 103 + { 104 + size_t PS = sysconf(_SC_PAGESIZE); 105 + int filesize = num_pages * PS; 106 + bool ret = true; 107 + long syscall_ret; 108 + struct cachestat cs; 109 + struct cachestat_range cs_range = { 0, filesize }; 110 + 111 + int fd = open(filename, open_flags, open_mode); 112 + 113 + if (fd == -1) { 114 + ksft_print_msg("Unable to create/open file.\n"); 115 + ret = false; 116 + goto out; 117 + } else { 118 + ksft_print_msg("Create/open %s\n", filename); 119 + } 120 + 121 + if (write_random) { 122 + if (!write_exactly(fd, filesize)) { 123 + ksft_print_msg("Unable to access urandom.\n"); 124 + ret = false; 125 + goto out1; 126 + } 127 + } 128 + 129 + syscall_ret = syscall(cachestat_nr, fd, &cs_range, &cs, 0); 130 + 131 + ksft_print_msg("Cachestat call returned %ld\n", syscall_ret); 132 + 133 + if (syscall_ret) { 134 + ksft_print_msg("Cachestat returned non-zero.\n"); 135 + ret = false; 136 + goto out1; 137 + 138 + } else { 139 + print_cachestat(&cs); 140 + 141 + if (write_random) { 142 + if (cs.nr_cache + cs.nr_evicted != num_pages) { 143 + ksft_print_msg( 144 + "Total number of cached and evicted pages is off.\n"); 145 + ret = false; 146 + } 147 + } 148 + } 149 + 150 + if (test_fsync) { 151 + if (fsync(fd)) { 152 + ksft_print_msg("fsync fails.\n"); 153 + ret = false; 154 + } else { 155 + syscall_ret = syscall(cachestat_nr, fd, &cs_range, &cs, 0); 156 + 157 + ksft_print_msg("Cachestat call (after fsync) returned %ld\n", 158 + syscall_ret); 159 + 160 + if (!syscall_ret) { 161 + print_cachestat(&cs); 162 + 163 + if (cs.nr_dirty) { 164 + ret = false; 165 + ksft_print_msg( 166 + "Number of dirty should be zero after fsync.\n"); 167 + } 168 + } else { 169 + ksft_print_msg("Cachestat (after fsync) returned non-zero.\n"); 170 + ret = false; 171 + goto out1; 172 + } 173 + } 174 + } 175 + 176 + out1: 177 + close(fd); 178 + 179 + if (create) 180 + remove(filename); 181 + out: 182 + return ret; 183 + } 184 + 185 + bool test_cachestat_shmem(void) 186 + { 187 + size_t PS = sysconf(_SC_PAGESIZE); 188 + size_t filesize = PS * 512 * 2; /* 2 2MB huge pages */ 189 + int syscall_ret; 190 + size_t compute_len = PS * 512; 191 + struct cachestat_range cs_range = { PS, compute_len }; 192 + char *filename = "tmpshmcstat"; 193 + struct cachestat cs; 194 + bool ret = true; 195 + unsigned long num_pages = compute_len / PS; 196 + int fd = shm_open(filename, O_CREAT | O_RDWR, 0600); 197 + 198 + if (fd < 0) { 199 + ksft_print_msg("Unable to create shmem file.\n"); 200 + ret = false; 201 + goto out; 202 + } 203 + 204 + if (ftruncate(fd, filesize)) { 205 + ksft_print_msg("Unable to truncate shmem file.\n"); 206 + ret = false; 207 + goto close_fd; 208 + } 209 + 210 + if (!write_exactly(fd, filesize)) { 211 + ksft_print_msg("Unable to write to shmem file.\n"); 212 + ret = false; 213 + goto close_fd; 214 + } 215 + 216 + syscall_ret = syscall(cachestat_nr, fd, &cs_range, &cs, 0); 217 + 218 + if (syscall_ret) { 219 + ksft_print_msg("Cachestat returned non-zero.\n"); 220 + ret = false; 221 + goto close_fd; 222 + } else { 223 + print_cachestat(&cs); 224 + if (cs.nr_cache + cs.nr_evicted != num_pages) { 225 + ksft_print_msg( 226 + "Total number of cached and evicted pages is off.\n"); 227 + ret = false; 228 + } 229 + } 230 + 231 + close_fd: 232 + shm_unlink(filename); 233 + out: 234 + return ret; 235 + } 236 + 237 + int main(void) 238 + { 239 + int ret = 0; 240 + 241 + for (int i = 0; i < 5; i++) { 242 + const char *dev_filename = dev_files[i]; 243 + 244 + if (test_cachestat(dev_filename, false, false, false, 245 + 4, O_RDONLY, 0400)) 246 + ksft_test_result_pass("cachestat works with %s\n", dev_filename); 247 + else { 248 + ksft_test_result_fail("cachestat fails with %s\n", dev_filename); 249 + ret = 1; 250 + } 251 + } 252 + 253 + if (test_cachestat("tmpfilecachestat", true, true, 254 + true, 4, O_CREAT | O_RDWR, 0400 | 0600)) 255 + ksft_test_result_pass("cachestat works with a normal file\n"); 256 + else { 257 + ksft_test_result_fail("cachestat fails with normal file\n"); 258 + ret = 1; 259 + } 260 + 261 + if (test_cachestat_shmem()) 262 + ksft_test_result_pass("cachestat works with a shmem file\n"); 263 + else { 264 + ksft_test_result_fail("cachestat fails with a shmem file\n"); 265 + ret = 1; 266 + } 267 + 268 + return ret; 269 + }