Home | History | Annotate | Download | only in readahead
      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 
     72 static void help(void)
     73 {
     74 	printf("  -s x    testfile size (default 64MB)\n");
     75 }
     76 
     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, "unexpected failure - "
     85 		 "returned value = %ld, expected: %ld",
     86 		 TEST_RETURN, expected_ret);
     87 	return 1;
     88 }
     89 
     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 
    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 
    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 
    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 
    152 static unsigned long get_cached_size(void)
    153 {
    154 	char entry[] = "Cached: %lu";
    155 	return parse_entry(meminfo_fname, entry);
    156 }
    157 
    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  */
    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 
    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 
    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 
    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 
    392 static void cleanup(void)
    393 {
    394 	unlink(testfile);
    395 	tst_rmdir();
    396 }
    397 
    398 #else /* __NR_readahead */
    399 int main(void)
    400 {
    401 	tst_brkm(TCONF, NULL, "System doesn't support __NR_readahead");
    402 }
    403 #endif
    404