/* readahead -- read in advance a list of files, adding them to the page cache * * Copyright (C) 2005 Ziga Mahkovec <ziga.mahkovec@klika.si> * Based on a previous version by Arjan van de Ven <arjanv@redhat.com> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Usage: readahead FILE * FILE contains a newline-delimited list of files to preload */ #include <unistd.h> #include <stdio.h> #include <string.h> #include <errno.h> #include <time.h> #include <fcntl.h> #include <sys/stat.h> #include <sys/time.h> #include <ext2fs/ext2fs.h> #include <blkid/blkid.h> #define MAXPATHLEN 1024 #define MAXFILES 32768 #define MAXDEVICES 32 /* Block group hit threshold (triggering complete inode table scan) */ #define GROUP_HIT_THRESH 128 #define IO_MGR unix_io_manager #define TIMEDIFF(t1, t2) ((t2.tv_sec-t1.tv_sec) * 1000 + \ (t2.tv_usec-t1.tv_usec) / 1000) struct device { dev_t st_dev; char *name; ext2_filsys fs; blk_t *group_hits; }; struct file { char* path; off_t size; blk_t block; }; /* Compare files by path. */ static int comp_files_path(const void *f1, const void *f2) { struct file *fb1 = (struct file *)f1; struct file *fb2 = (struct file *)f2; return (strcmp(fb1->path, fb2->path)); } /* Compare files by first block. */ static int comp_files_block(const void *f1, const void *f2) { struct file *fb1 = (struct file *)f1; struct file *fb2 = (struct file *)f2; return (int)(fb1->block - fb2->block); } /* Get the device the specified file is on. */ struct device * get_file_device(dev_t dev, struct device *devices, int *count) { int i; ext2_filsys fs = NULL; for (i=0; i<*count; i++) { if (devices[i].st_dev == dev) return &devices[i]; } /* Create new device */ if (*count == MAXDEVICES) { fprintf(stderr, "Max device count reached\n"); return NULL; } devices[i].st_dev = dev; devices[i].name = blkid_devno_to_devname(dev); if (ext2fs_open(devices[i].name, 0, 0, 0, IO_MGR, &fs) || !fs) return NULL; else { unsigned int j, n_groups; devices[i].fs = fs; n_groups = fs->super->s_blocks_count / fs->super->s_blocks_per_group + 1; devices[i].group_hits = (blk_t *)malloc(sizeof(blk_t) * n_groups); for (j=0; j<n_groups; j++) devices[i].group_hits[j] = 0; (*count)++; return &devices[i]; } } /* Get the first data block of the specified file. */ int get_first_block(ext2_filsys fs, blk_t *blocknr, e2_blkcnt_t blockcnt, blk_t ref_block, int ref_offset, void *private) { struct file* f = (struct file *)private; f->block = *blocknr; return BLOCK_ABORT; } /* Read the entire inode table of the specified block group. */ void preload_inodes(ext2_filsys fs, int group) { ext2_inode_scan e2iscan = NULL; struct ext2_inode inode; ext2_ino_t ino = 0; int c_group; struct timeval t1, t2; if (ext2fs_open_inode_scan(fs, 0, &e2iscan) || !e2iscan) { fprintf(stderr, "ext2fs_open_inode_scan failed\n"); return; } if (ext2fs_inode_scan_goto_blockgroup(e2iscan, group)) { fprintf(stderr, "ext2fs_open_inode_scan() failed\n"); ext2fs_close_inode_scan(e2iscan); return; } c_group = group; gettimeofday(&t1, NULL); while (c_group == group && !ext2fs_get_next_inode(e2iscan, &ino, &inode)) { c_group = ext2fs_group_of_ino(fs, ino); } ext2fs_close_inode_scan(e2iscan); gettimeofday(&t2, NULL); } /* * Two modes are available for processing files. If do_read is not set, the * file will be stat-ed (possibly preloading the block group's entire inode * table) and the first block will be set. If do_read is set, readahead(2) * will be called. */ void process_file(struct file *file, struct device *devices, int *dev_count, int do_read) { int fd = -1; struct stat stat_buf; if (!file || !file->path) return; fd = open(file->path, O_RDONLY); if (fd < 0 || fstat(fd, &stat_buf) < 0) goto out; if (!S_ISREG(stat_buf.st_mode)) goto out; /* not a regular file */ if (!gnu_dev_major(stat_buf.st_dev)) goto out; /* mounted over NFS */ file->size = stat_buf.st_size; if (!do_read) { struct device *dev = get_file_device(stat_buf.st_dev, devices, dev_count); if (!dev || !dev->fs) goto out; int group = ext2fs_group_of_ino(dev->fs, stat_buf.st_ino); if (dev->group_hits[group] == GROUP_HIT_THRESH) { /* * We got enough hits in this block group. Preload all * inodes. */ preload_inodes(dev->fs, group); } dev->group_hits[group]++; /* Store the first data block (for sorting later) */ ext2fs_block_iterate2(dev->fs, stat_buf.st_ino, 0, NULL, get_first_block, (void *)file); } else { /* Populate the page cache */ readahead(fd, (loff_t)0, stat_buf.st_size); } out: if (fd >= 0) close(fd); } int main(int argc, char **argv) { int i; struct device devices[16]; int dev_count = 0; char path[MAXPATHLEN]; struct file files[MAXFILES]; int file_count = 0; off_t total_size = 0; struct timeval t1, t2; if (argc < 2) { fprintf(stderr, "usage: %s <file.list> ...\n", argv[0]); exit(1); } gettimeofday(&t1, NULL); /* Iterate through the file lists */ for (i=1; i<argc; i++) { FILE *fin = fopen(argv[i], "r"); if (fin == NULL) { fprintf(stderr, "%s\n", strerror(errno)); continue; } while (fgets(path, MAXPATHLEN, fin) != NULL) { int len = strlen(path); if (len == 0) continue; if (path[len - 1] == '\n') path[--len] = '\0'; files[file_count].path = (char *)malloc(sizeof(char) * (len + 1)); strcpy(files[file_count].path, path); files[file_count].block = -1; files[file_count].size = 0; file_count++; if (file_count == MAXFILES) { fprintf(stderr, "Max file count reached\n"); i = argc; break; } } fclose(fin); } /* * Stat each file. Sorting by path should offer least directory block * seeking. */ qsort(files, file_count, sizeof(struct file), comp_files_path); for (i=0; i<file_count; i++) { process_file(&files[i], devices, &dev_count, 0); total_size += files[i].size; } /* Readahead files. Sort by first data block. */ qsort(files, file_count, sizeof(struct file), comp_files_block); for (i=0; i<file_count; i++) if (files[i].block > 0) process_file(&files[i], NULL, NULL, 1); /* Clean up */ for (i=0; i<file_count; i++) { free(files[i].path); } for (i=0; i<dev_count; i++) { if (devices[i].name) free(devices[i].name); if (devices[i].group_hits) free(devices[i].group_hits); if (devices[i].fs) ext2fs_close(devices[i].fs); } gettimeofday(&t2, NULL); printf("Preloaded %d files (%ld KB) in %ld ms\n", file_count, total_size / 1024, TIMEDIFF(t1, t2)); return 0; }