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 "lapi/syscalls.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 (4u * 1024u)
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: %zu", 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 = SAFE_OPEN(cleanup, fname, O_RDONLY);
215
216 if (do_readahead) {
217 cached_start = get_cached_size();
218 do {
219 TEST(readahead(fd, offset, fsize - offset));
220 if (TEST_RETURN != 0) {
221 check_ret(0);
222 break;
223 }
224
225 /* estimate max readahead size based on first call */
226 if (!max_ra_estimate) {
227 *cached = get_cached_size();
228 if (*cached > cached_start) {
229 max_ra_estimate = (1024 *
230 (*cached - cached_start));
231 tst_resm(TINFO, "max ra estimate: %lu",
232 max_ra_estimate);
233 }
234 max_ra_estimate = MAX(max_ra_estimate,
235 MIN_SANE_READAHEAD);
236 }
237
238 i++;
239 offset += max_ra_estimate;
240 } while ((size_t)offset < fsize);
241 tst_resm(TINFO, "readahead calls made: %zu", i);
242 *cached = get_cached_size();
243
244 /* offset of file shouldn't change after readahead */
245 offset = lseek(fd, 0, SEEK_CUR);
246 if (offset == (off_t) - 1)
247 tst_brkm(TBROK | TERRNO, cleanup, "lseek failed");
248 if (offset == 0)
249 tst_resm(TPASS, "offset is still at 0 as expected");
250 else
251 tst_resm(TFAIL, "offset has changed to: %lu", offset);
252 }
253
254 if (gettimeofday(&now, NULL) == -1)
255 tst_brkm(TBROK | TERRNO, cleanup, "gettimeofday failed");
256 time_start_usec = now.tv_sec * 1000000 + now.tv_usec;
257 read_bytes_start = get_bytes_read();
258
259 p = mmap(NULL, fsize, PROT_READ, MAP_SHARED | MAP_POPULATE, fd, 0);
260 if (p == MAP_FAILED)
261 tst_brkm(TBROK | TERRNO, cleanup, "mmap failed");
262
263 /* for old kernels, where MAP_POPULATE doesn't work, touch each page */
264 tmp = 0;
265 for (i = 0; i < fsize; i += pagesize)
266 tmp = tmp ^ p[i];
267 /* prevent gcc from optimizing out loop above */
268 if (tmp != 0)
269 tst_brkm(TBROK, NULL, "This line should not be reached");
270
271 if (!do_readahead)
272 *cached = get_cached_size();
273
274 SAFE_MUNMAP(cleanup, p, fsize);
275
276 *read_bytes = get_bytes_read() - read_bytes_start;
277 if (gettimeofday(&now, NULL) == -1)
278 tst_brkm(TBROK | TERRNO, cleanup, "gettimeofday failed");
279 time_end_usec = now.tv_sec * 1000000 + now.tv_usec;
280 *usec = time_end_usec - time_start_usec;
281
282 SAFE_CLOSE(cleanup, fd);
283 }
284
test_readahead(void)285 static void test_readahead(void)
286 {
287 unsigned long read_bytes, read_bytes_ra;
288 long usec, usec_ra;
289 unsigned long cached_max, cached_low, cached, cached_ra;
290 char proc_io_fname[128];
291 sprintf(proc_io_fname, "/proc/%u/io", getpid());
292
293 /* find out how much can cache hold if we read whole file */
294 read_testfile(0, testfile, testfile_size, &read_bytes, &usec, &cached);
295 cached_max = get_cached_size();
296 sync();
297 drop_caches();
298 cached_low = get_cached_size();
299 cached_max = cached_max - cached_low;
300
301 tst_resm(TINFO, "read_testfile(0)");
302 read_testfile(0, testfile, testfile_size, &read_bytes, &usec, &cached);
303 if (cached > cached_low)
304 cached = cached - cached_low;
305 else
306 cached = 0;
307
308 sync();
309 drop_caches();
310 cached_low = get_cached_size();
311 tst_resm(TINFO, "read_testfile(1)");
312 read_testfile(1, testfile, testfile_size, &read_bytes_ra,
313 &usec_ra, &cached_ra);
314 if (cached_ra > cached_low)
315 cached_ra = cached_ra - cached_low;
316 else
317 cached_ra = 0;
318
319 tst_resm(TINFO, "read_testfile(0) took: %ld usec", usec);
320 tst_resm(TINFO, "read_testfile(1) took: %ld usec", usec_ra);
321 if (has_file(proc_io_fname, 0)) {
322 tst_resm(TINFO, "read_testfile(0) read: %ld bytes", read_bytes);
323 tst_resm(TINFO, "read_testfile(1) read: %ld bytes",
324 read_bytes_ra);
325 /* actual number of read bytes depends on total RAM */
326 if (read_bytes_ra < read_bytes)
327 tst_resm(TPASS, "readahead saved some I/O");
328 else
329 tst_resm(TFAIL, "readahead failed to save any I/O");
330 } else {
331 tst_resm(TCONF, "Your system doesn't have /proc/pid/io,"
332 " unable to determine read bytes during test");
333 }
334
335 tst_resm(TINFO, "cache can hold at least: %ld kB", cached_max);
336 tst_resm(TINFO, "read_testfile(0) used cache: %ld kB", cached);
337 tst_resm(TINFO, "read_testfile(1) used cache: %ld kB", cached_ra);
338
339 if (cached_max * 1024 >= testfile_size) {
340 /*
341 * if cache can hold ~testfile_size then cache increase
342 * for readahead should be at least testfile_size/2
343 */
344 if (cached_ra * 1024 > testfile_size / 2)
345 tst_resm(TPASS, "using cache as expected");
346 else
347 tst_resm(TWARN, "using less cache than expected");
348 } else {
349 tst_resm(TCONF, "Page cache on your system is too small "
350 "to hold whole testfile.");
351 }
352 }
353
main(int argc,char * argv[])354 int main(int argc, char *argv[])
355 {
356 int lc;
357
358 tst_parse_opts(argc, argv, options, help);
359
360 if (opt_fsize)
361 testfile_size = atoi(opt_fsizestr);
362
363 setup();
364 for (lc = 0; TEST_LOOPING(lc); lc++) {
365 tst_count = 0;
366 test_readahead();
367 }
368 cleanup();
369 tst_exit();
370 }
371
setup(void)372 static void setup(void)
373 {
374 tst_require_root();
375 tst_tmpdir();
376 TEST_PAUSE;
377
378 has_file(drop_caches_fname, 1);
379 has_file(meminfo_fname, 1);
380
381 /* check if readahead is supported */
382 ltp_syscall(__NR_readahead, 0, 0, 0);
383
384 pagesize = getpagesize();
385 create_testfile();
386 }
387
cleanup(void)388 static void cleanup(void)
389 {
390 unlink(testfile);
391 tst_rmdir();
392 }
393
394 #else /* __NR_readahead */
main(void)395 int main(void)
396 {
397 tst_brkm(TCONF, NULL, "System doesn't support __NR_readahead");
398 }
399 #endif
400