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