Sophie

Sophie

distrib > Mandriva > 2006.0 > x86_64 > by-pkgid > 8ef79cceb58782b0a157fb931a3d75e5 > files > 3

readahead-1.1-1mdk.src.rpm

/* 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;
}