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 "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 
     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 | TTERRNO, "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: %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  */
    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 
    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 
    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 
    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 
    388 static void cleanup(void)
    389 {
    390 	unlink(testfile);
    391 	tst_rmdir();
    392 }
    393 
    394 #else /* __NR_readahead */
    395 int main(void)
    396 {
    397 	tst_brkm(TCONF, NULL, "System doesn't support __NR_readahead");
    398 }
    399 #endif
    400