1 /*
2 * Copyright (C) 2012 Linux Test Project, Inc.
3 *
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of version 2 of the GNU General Public
6 * License as published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it would be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
11 *
12 * Further, this software is distributed without any warranty that it
13 * is free of the rightful claim of any third person regarding
14 * infringement or the like. Any license provided herein, whether
15 * implied or otherwise, applies only to this software file. Patent
16 * licenses, if any, provided herein do not apply to combinations of
17 * this program with other software, or any other product whatsoever.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write the Free Software
21 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
22 * 02110-1301, USA.
23 */
24
25 /*
26 * functional test for readahead() syscall
27 *
28 * This test is measuring effects of readahead syscall.
29 * It mmaps/reads a test file with and without prior call to readahead.
30 *
31 */
32 #define _GNU_SOURCE
33 #include <sys/types.h>
34 #include <sys/syscall.h>
35 #include <sys/mman.h>
36 #include <sys/stat.h>
37 #include <sys/types.h>
38 #include <sys/time.h>
39 #include <errno.h>
40 #include <stdio.h>
41 #include <stdlib.h>
42 #include <stdint.h>
43 #include <unistd.h>
44 #include <fcntl.h>
45 #include "config.h"
46 #include "test.h"
47 #include "safe_macros.h"
48 #include "linux_syscall_numbers.h"
49
50 char *TCID = "readahead02";
51 int TST_TOTAL = 1;
52
53 #if defined(__NR_readahead)
54 static const char testfile[] = "testfile";
55 static const char drop_caches_fname[] = "/proc/sys/vm/drop_caches";
56 static const char meminfo_fname[] = "/proc/meminfo";
57 static size_t testfile_size = 64 * 1024 * 1024;
58 static int opt_fsize;
59 static char *opt_fsizestr;
60 static int pagesize;
61
62 #define MIN_SANE_READAHEAD (4 * 1024)
63
64 option_t options[] = {
65 {"s:", &opt_fsize, &opt_fsizestr},
66 {NULL, NULL, NULL}
67 };
68
69 static void setup(void);
70 static void cleanup(void);
71
help(void)72 static void help(void)
73 {
74 printf(" -s x testfile size (default 64MB)\n");
75 }
76
check_ret(long expected_ret)77 static int check_ret(long expected_ret)
78 {
79 if (expected_ret == TEST_RETURN) {
80 tst_resm(TPASS, "expected ret success - "
81 "returned value = %ld", TEST_RETURN);
82 return 0;
83 }
84 tst_resm(TFAIL | TTERRNO, "unexpected failure - "
85 "returned value = %ld, expected: %ld",
86 TEST_RETURN, expected_ret);
87 return 1;
88 }
89
has_file(const char * fname,int required)90 static int has_file(const char *fname, int required)
91 {
92 int ret;
93 struct stat buf;
94 ret = stat(fname, &buf);
95 if (ret == -1) {
96 if (errno == ENOENT)
97 if (required)
98 tst_brkm(TCONF, cleanup, "%s not available",
99 fname);
100 else
101 return 0;
102 else
103 tst_brkm(TBROK | TERRNO, cleanup, "stat %s", fname);
104 }
105 return 1;
106 }
107
drop_caches(void)108 static void drop_caches(void)
109 {
110 int ret;
111 FILE *f;
112
113 f = fopen(drop_caches_fname, "w");
114 if (f) {
115 ret = fprintf(f, "1");
116 fclose(f);
117 if (ret < 1)
118 tst_brkm(TBROK, cleanup, "Failed to drop caches");
119 } else {
120 tst_brkm(TBROK, cleanup, "Failed to open drop_caches");
121 }
122 }
123
parse_entry(const char * fname,const char * entry)124 static unsigned long parse_entry(const char *fname, const char *entry)
125 {
126 FILE *f;
127 long value = -1;
128 int ret;
129 char *line = NULL;
130 size_t linelen;
131
132 f = fopen(fname, "r");
133 if (f) {
134 do {
135 ret = getline(&line, &linelen, f);
136 if (sscanf(line, entry, &value) == 1)
137 break;
138 } while (ret != -1);
139 fclose(f);
140 }
141 return value;
142 }
143
get_bytes_read(void)144 static unsigned long get_bytes_read(void)
145 {
146 char fname[128];
147 char entry[] = "read_bytes: %lu";
148 sprintf(fname, "/proc/%u/io", getpid());
149 return parse_entry(fname, entry);
150 }
151
get_cached_size(void)152 static unsigned long get_cached_size(void)
153 {
154 char entry[] = "Cached: %lu";
155 return parse_entry(meminfo_fname, entry);
156 }
157
create_testfile(void)158 static void create_testfile(void)
159 {
160 FILE *f;
161 char *tmp;
162 size_t i;
163
164 tst_resm(TINFO, "creating test file of size: %ld", testfile_size);
165 tmp = SAFE_MALLOC(cleanup, pagesize);
166
167 /* round to page size */
168 testfile_size = testfile_size & ~((long)pagesize - 1);
169
170 f = fopen(testfile, "w");
171 if (!f) {
172 free(tmp);
173 tst_brkm(TBROK | TERRNO, cleanup, "Failed to create %s",
174 testfile);
175 }
176
177 for (i = 0; i < testfile_size; i += pagesize)
178 if (fwrite(tmp, pagesize, 1, f) < 1) {
179 free(tmp);
180 tst_brkm(TBROK, cleanup, "Failed to create %s",
181 testfile);
182 }
183 fflush(f);
184 fsync(fileno(f));
185 fclose(f);
186 free(tmp);
187 }
188
189
190 /* read_testfile - mmap testfile and read every page.
191 * This functions measures how many I/O and time it takes to fully
192 * read contents of test file.
193 *
194 * @do_readahead: call readahead prior to reading file content?
195 * @fname: name of file to test
196 * @fsize: how many bytes to read/mmap
197 * @read_bytes: returns difference of bytes read, parsed from /proc/<pid>/io
198 * @usec: returns how many microsecond it took to go over fsize bytes
199 * @cached: returns cached kB from /proc/meminfo
200 */
read_testfile(int do_readahead,const char * fname,size_t fsize,unsigned long * read_bytes,long * usec,unsigned long * cached)201 static void read_testfile(int do_readahead, const char *fname, size_t fsize,
202 unsigned long *read_bytes, long *usec,
203 unsigned long *cached)
204 {
205 int fd;
206 size_t i = 0;
207 long read_bytes_start;
208 unsigned char *p, tmp;
209 unsigned long time_start_usec, time_end_usec;
210 unsigned long cached_start, max_ra_estimate = 0;
211 off_t offset = 0;
212 struct timeval now;
213
214 fd = open(fname, O_RDONLY);
215 if (fd < 0)
216 tst_brkm(TBROK | TERRNO, cleanup, "Failed to open %s", fname);
217
218 if (do_readahead) {
219 cached_start = get_cached_size();
220 do {
221 TEST(readahead(fd, offset, fsize - offset));
222 if (TEST_RETURN != 0) {
223 check_ret(0);
224 break;
225 }
226
227 /* estimate max readahead size based on first call */
228 if (!max_ra_estimate) {
229 *cached = get_cached_size();
230 if (*cached > cached_start) {
231 max_ra_estimate = (1024 *
232 (*cached - cached_start));
233 tst_resm(TINFO, "max ra estimate: %lu",
234 max_ra_estimate);
235 }
236 max_ra_estimate = MAX(max_ra_estimate,
237 MIN_SANE_READAHEAD);
238 }
239
240 i++;
241 offset += max_ra_estimate;
242 } while ((size_t)offset < fsize);
243 tst_resm(TINFO, "readahead calls made: %zu", i);
244 *cached = get_cached_size();
245
246 /* offset of file shouldn't change after readahead */
247 offset = lseek(fd, 0, SEEK_CUR);
248 if (offset == (off_t) - 1)
249 tst_brkm(TBROK | TERRNO, cleanup, "lseek failed");
250 if (offset == 0)
251 tst_resm(TPASS, "offset is still at 0 as expected");
252 else
253 tst_resm(TFAIL, "offset has changed to: %lu", offset);
254 }
255
256 if (gettimeofday(&now, NULL) == -1)
257 tst_brkm(TBROK | TERRNO, cleanup, "gettimeofday failed");
258 time_start_usec = now.tv_sec * 1000000 + now.tv_usec;
259 read_bytes_start = get_bytes_read();
260
261 p = mmap(NULL, fsize, PROT_READ, MAP_SHARED | MAP_POPULATE, fd, 0);
262 if (p == MAP_FAILED)
263 tst_brkm(TBROK | TERRNO, cleanup, "mmap failed");
264
265 /* for old kernels, where MAP_POPULATE doesn't work, touch each page */
266 tmp = 0;
267 for (i = 0; i < fsize; i += pagesize)
268 tmp = tmp ^ p[i];
269 /* prevent gcc from optimizing out loop above */
270 if (tmp != 0)
271 tst_brkm(TBROK, NULL, "This line should not be reached");
272
273 if (!do_readahead)
274 *cached = get_cached_size();
275
276 if (munmap(p, fsize) == -1)
277 tst_brkm(TBROK | TERRNO, cleanup, "munmap failed");
278
279 *read_bytes = get_bytes_read() - read_bytes_start;
280 if (gettimeofday(&now, NULL) == -1)
281 tst_brkm(TBROK | TERRNO, cleanup, "gettimeofday failed");
282 time_end_usec = now.tv_sec * 1000000 + now.tv_usec;
283 *usec = time_end_usec - time_start_usec;
284
285 if (close(fd) == -1)
286 tst_brkm(TBROK | TERRNO, cleanup, "close failed");
287 }
288
test_readahead(void)289 static void test_readahead(void)
290 {
291 unsigned long read_bytes, read_bytes_ra;
292 long usec, usec_ra;
293 unsigned long cached_max, cached_low, cached, cached_ra;
294 char proc_io_fname[128];
295 sprintf(proc_io_fname, "/proc/%u/io", getpid());
296
297 /* find out how much can cache hold if we read whole file */
298 read_testfile(0, testfile, testfile_size, &read_bytes, &usec, &cached);
299 cached_max = get_cached_size();
300 sync();
301 drop_caches();
302 cached_low = get_cached_size();
303 cached_max = cached_max - cached_low;
304
305 tst_resm(TINFO, "read_testfile(0)");
306 read_testfile(0, testfile, testfile_size, &read_bytes, &usec, &cached);
307 if (cached > cached_low)
308 cached = cached - cached_low;
309 else
310 cached = 0;
311
312 sync();
313 drop_caches();
314 cached_low = get_cached_size();
315 tst_resm(TINFO, "read_testfile(1)");
316 read_testfile(1, testfile, testfile_size, &read_bytes_ra,
317 &usec_ra, &cached_ra);
318 if (cached_ra > cached_low)
319 cached_ra = cached_ra - cached_low;
320 else
321 cached_ra = 0;
322
323 tst_resm(TINFO, "read_testfile(0) took: %ld usec", usec);
324 tst_resm(TINFO, "read_testfile(1) took: %ld usec", usec_ra);
325 if (has_file(proc_io_fname, 0)) {
326 tst_resm(TINFO, "read_testfile(0) read: %ld bytes", read_bytes);
327 tst_resm(TINFO, "read_testfile(1) read: %ld bytes",
328 read_bytes_ra);
329 /* actual number of read bytes depends on total RAM */
330 if (read_bytes_ra < read_bytes)
331 tst_resm(TPASS, "readahead saved some I/O");
332 else
333 tst_resm(TFAIL, "readahead failed to save any I/O");
334 } else {
335 tst_resm(TCONF, "Your system doesn't have /proc/pid/io,"
336 " unable to determine read bytes during test");
337 }
338
339 tst_resm(TINFO, "cache can hold at least: %ld kB", cached_max);
340 tst_resm(TINFO, "read_testfile(0) used cache: %ld kB", cached);
341 tst_resm(TINFO, "read_testfile(1) used cache: %ld kB", cached_ra);
342
343 if (cached_max * 1024 >= testfile_size) {
344 /*
345 * if cache can hold ~testfile_size then cache increase
346 * for readahead should be at least testfile_size/2
347 */
348 if (cached_ra * 1024 > testfile_size / 2)
349 tst_resm(TPASS, "using cache as expected");
350 else
351 tst_resm(TWARN, "using less cache than expected");
352 } else {
353 tst_resm(TCONF, "Page cache on your system is too small "
354 "to hold whole testfile.");
355 }
356 }
357
main(int argc,char * argv[])358 int main(int argc, char *argv[])
359 {
360 int lc;
361
362 tst_parse_opts(argc, argv, options, help);
363
364 if (opt_fsize)
365 testfile_size = atoi(opt_fsizestr);
366
367 setup();
368 for (lc = 0; TEST_LOOPING(lc); lc++) {
369 tst_count = 0;
370 test_readahead();
371 }
372 cleanup();
373 tst_exit();
374 }
375
setup(void)376 static void setup(void)
377 {
378 tst_require_root();
379 tst_tmpdir();
380 TEST_PAUSE;
381
382 has_file(drop_caches_fname, 1);
383 has_file(meminfo_fname, 1);
384
385 /* check if readahead is supported */
386 ltp_syscall(__NR_readahead, 0, 0, 0);
387
388 pagesize = getpagesize();
389 create_testfile();
390 }
391
cleanup(void)392 static void cleanup(void)
393 {
394 unlink(testfile);
395 tst_rmdir();
396 }
397
398 #else /* __NR_readahead */
main(void)399 int main(void)
400 {
401 tst_brkm(TCONF, NULL, "System doesn't support __NR_readahead");
402 }
403 #endif
404