Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
torvalds
GitHub Repository: torvalds/linux
Path: blob/master/tools/testing/selftests/cachestat/test_cachestat.c
26285 views
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
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
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
*/
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
*/
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
}
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
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
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
367