• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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