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