• 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 "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