/* fsperf.c  -  File system performance test */

/* Written 1992 by Werner Almesberger */


#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <getopt.h>
#include <limits.h>
#include <dirent.h>
#include <sys/stat.h>
#include <sys/times.h>


#define KBYTE 1024
#define MBYTE (KBYTE*1024)


static int iterations = 10;
static int mbytes = 4;
static int entries = 100;

static char *name;
static char mbyte[MBYTE];

static clock_t stat_start;
static unsigned long stat_low,stat_high,stat_sum,stat_count;


#define CHECK(op) if (!(op)) { fflush(stdout); \
  fprintf(stderr,"\nFAILED: %s\n  (errno=%s)\n",#op,strerror(errno)); \
  exit(1); }


static void stat_init(void)
{
    stat_high = stat_sum = stat_count = 0;
    stat_low = 1 << 30;
}


static void stat_begin(void)
{
    printf(" %4d/%-4d\b\b\b\b\b\b\b\b\b\b",stat_count,iterations);
    fflush(stdout);
    stat_start = times(NULL);
}


static void stat_end(void)
{
    unsigned long interval;

    if (!stat_count++) return;
    interval = (times(NULL)-stat_start)*(1000000/HZ);
    if (interval < stat_low) stat_low = interval;
    if (interval > stat_high) stat_high = interval;
    if ((stat_sum += interval) < interval) {
	fflush(stdout);
	fprintf(stderr,"\nCounter is overflowing.\n");
	exit(1);
    }
}


static void stat_report(int file)
{
    int factor;

    factor = file ? entries : mbytes;
    printf(" %d [%d,%d] %s\n",stat_sum/(stat_count-1)/factor,stat_low/factor,
      stat_high/factor,file ? "usec/file" : "usec/MB");
}


static void pt_allocate(void)
{
    int n,i,fd;

    printf("Allocating new blocks ...");
    stat_init();
    for (n = 0; n <= iterations; n++) {
	CHECK((fd = creat(name,0644)) >= 0);
	sync();
	stat_begin();
	for (i = 0; i < mbytes; i++) CHECK(write(fd,mbyte,MBYTE) == MBYTE);
	stat_end();
	CHECK(close(fd) >= 0);
	CHECK(remove(name) >= 0);
    }
    stat_report(0);
}


static void pt_read(void)
{
    int n,i,fd;

    printf("Reading pre-allocated blocks ... (wait)\b\b\b\b\b\b\b");
    fflush(stdout);
    stat_init();
    CHECK((fd = open(name,O_RDWR | O_TRUNC | O_CREAT,0644)) >= 0);
    for (i = 0; i < mbytes; i++) CHECK(write(fd,mbyte,MBYTE) == MBYTE);
    sync();
    for (n = 0; n <= iterations; n++) {
	CHECK(lseek(fd,0,0) >= 0);
	stat_begin();
	for (i = 0; i < mbytes; i++) CHECK(read(fd,mbyte,MBYTE) == MBYTE);
	stat_end();
    }
    CHECK(close(fd) >= 0);
    CHECK(remove(name) >= 0);
    stat_report(0);
}


static void pt_read_rev(void)
{
    int n,i,fd;

    printf("Reading pre-all. blocks in rev. order ... (wait)"
      "\b\b\b\b\b\b\b");
    fflush(stdout);
    stat_init();
    CHECK((fd = open(name,O_RDWR | O_TRUNC | O_CREAT,0644)) >= 0);
    for (i = 0; i < mbytes; i++) CHECK(write(fd,mbyte,MBYTE) == MBYTE);
    sync();
    for (n = 0; n <= iterations; n++) {
	CHECK(lseek(fd,0,0) >= 0);
	stat_begin();
	for (i = mbytes*1024-1; i >= 0; i--) {
	    CHECK(lseek(fd,i*KBYTE,0) >= 0);
	    CHECK(read(fd,mbyte,KBYTE) == KBYTE);
	}
	stat_end();
    }
    CHECK(close(fd) >= 0);
    CHECK(remove(name) >= 0);
    stat_report(0);
}


static void pt_write(void)
{
    int n,i,fd;

    printf("Writing pre-allocated blocks ... (wait)\b\b\b\b\b\b\b");
    fflush(stdout);
    stat_init();
    CHECK((fd = creat(name,0644)) >= 0);
    for (i = 0; i < mbytes; i++) CHECK(write(fd,mbyte,MBYTE) == MBYTE);
    for (n = 0; n <= iterations; n++) {
	printf(" (wait)   \b\b\b\b\b\b\b\b\b\b");
        fflush(stdout);
	CHECK(lseek(fd,0,0) >= 0);
	sync();
	stat_begin();
	for (i = 0; i < mbytes; i++) CHECK(write(fd,mbyte,MBYTE) == MBYTE);
	stat_end();
    }
    CHECK(close(fd) >= 0);
    CHECK(remove(name) >= 0);
    stat_report(0);
}


static void pt_create(void)
{
    int n,i,fd;
    char fname[10];

    printf("File creation ...");
    stat_init();
    CHECK(mkdir(name,0755) >= 0);
    CHECK(chdir(name) >= 0);
    for (n = 0; n <= iterations; n++) {
	sync();
	stat_begin();
	for (i = 0; i < entries; i++) {
	    sprintf(fname,"%d",i);
	    CHECK((fd = creat(fname,0644)) >= 0);
	    CHECK(close(fd) >= 0);
	}
	stat_end();
	printf(" (wait)   \b\b\b\b\b\b\b\b\b\b");
        fflush(stdout);
	for (i = 0; i < entries; i++) {
	    sprintf(fname,"%d",i);
	    CHECK(unlink(fname) >= 0);
	}
    }
    CHECK(chdir("/") >= 0);
    CHECK(rmdir(name) >= 0);
    stat_report(1);
}


static void pt_lookup(void)
{
    int n,i,fd;
    char fname[10];
    struct stat st;

    printf("Directory lookups ... (wait)\b\b\b\b\b\b\b");
    fflush(stdout);
    stat_init();
    CHECK(mkdir(name,0755) >= 0);
    CHECK(chdir(name) >= 0);
    for (i = 0; i < entries; i++) {
	sprintf(fname,"%d",i);
	CHECK((fd = creat(fname,0644)) >= 0);
	CHECK(close(fd) >= 0);
    }
    sync();
    for (n = 0; n <= iterations; n++) {
	stat_begin();
	for (i = 0; i < entries; i++) {
	    sprintf(fname,"%d",i);
	    CHECK(stat(fname,&st) >= 0);
	}
	stat_end();
    }
    printf(" (wait)   \b\b\b\b\b\b\b\b\b\b");
    fflush(stdout);
    for (i = 0; i < entries; i++) {
	sprintf(fname,"%d",i);
	CHECK(unlink(fname) >= 0);
    }
    CHECK(chdir("/") >= 0);
    CHECK(rmdir(name) >= 0);
    stat_report(1);
}


static void pt_lookup_rev(void)
{
    int n,i,fd;
    char fname[10];
    struct stat st;

    printf("Directory lookups in reverse order ... (wait)\b\b\b\b\b\b\b");
    fflush(stdout);
    stat_init();
    CHECK(mkdir(name,0755) >= 0);
    CHECK(chdir(name) >= 0);
    for (i = 0; i < entries; i++) {
	sprintf(fname,"%d",i);
	CHECK((fd = creat(fname,0644)) >= 0);
	CHECK(close(fd) >= 0);
    }
    sync();
    for (n = 0; n <= iterations; n++) {
	stat_begin();
	for (i = entries-1; i >= 0; i--) {
	    sprintf(fname,"%d",i);
	    CHECK(stat(fname,&st) >= 0);
	}
	stat_end();
    }
    printf(" (wait)   \b\b\b\b\b\b\b\b\b\b");
    fflush(stdout);
    for (i = 0; i < entries; i++) {
	sprintf(fname,"%d",i);
	CHECK(unlink(fname) >= 0);
    }
    CHECK(chdir("/") >= 0);
    CHECK(rmdir(name) >= 0);
    stat_report(1);
}


static void pt_readdir(void)
{
    int n,i,fd;
    char fname[10];
    DIR *dp;

    printf("Directory reads ... (wait)\b\b\b\b\b\b\b");
    fflush(stdout);
    stat_init();
    CHECK(mkdir(name,0755) >= 0);
    CHECK(chdir(name) >= 0);
    for (i = 0; i < entries; i++) {
	sprintf(fname,"%d",i);
	CHECK((fd = creat(fname,0644)) >= 0);
	CHECK(close(fd) >= 0);
    }
    sync();
    for (n = 0; n <= iterations; n++) {
	stat_begin();
	CHECK((dp = opendir(".")) != NULL);
	while (readdir(dp));
	CHECK(closedir(dp) >= 0);
	stat_end();
    }
    printf(" (wait)   \b\b\b\b\b\b\b\b\b\b");
    fflush(stdout);
    for (i = 0; i < entries; i++) {
	sprintf(fname,"%d",i);
	CHECK(unlink(fname) >= 0);
    }
    CHECK(chdir("/") >= 0);
    CHECK(rmdir(name) >= 0);
    stat_report(1);
}


static void pt_unlink(void)
{
    int n,i,fd;
    char fname[10];

    printf("File deletion ...");
    stat_init();
    CHECK(mkdir(name,0755) >= 0);
    CHECK(chdir(name) >= 0);
    for (n = 0; n <= iterations; n++) {
	printf(" (wait)   \b\b\b\b\b\b\b\b\b\b");
        fflush(stdout);
	for (i = 0; i < entries; i++) {
	    sprintf(fname,"%d",i);
	    CHECK((fd = creat(fname,0644)) >= 0);
	    CHECK(close(fd) >= 0);
	}
	sync();
	stat_begin();
	for (i = 0; i < entries; i++) {
	    sprintf(fname,"%d",i);
	    CHECK(unlink(fname) >= 0);
	}
	stat_end();
    }
    CHECK(chdir("/") >= 0);
    CHECK(rmdir(name) >= 0);
    stat_report(1);
}


static void usage(char *self)
{
    fprintf(stderr,"usage: %s [ -i iterations ] [ -e entries ] [ -m megabytes "
      "] path\n",self);
    exit(1);
}


static int number(char *num)
{
    int number;
    char *end;

    number = strtol(num,&end,0);
    if (!end || !*end) return number;
    fprintf(stderr,"Not a number: \"%s\"\n",num);
    exit(1);
    return 0; /* not reached */
}


int main(int argc,char **argv)
{
    char *self;
    int c;

    self = argv[0];
    while ((c = getopt(argc,argv,"i:e:m:")) != -1)
	switch (c) {
	    case 'i':
		iterations = number(optarg);
		break;
	    case 'e':
		entries = number(optarg);
		break;
	    case 'm':
		mbytes = number(optarg);
		break;
	    case '?':
		usage(self);
	}
    if (optind != argc-1) usage(self);
    name = argv[optind];
    memset(mbyte,'X',MBYTE);
    printf("File system performance test.\n\n");
    printf("  Iterations per test  %d\n",iterations);
    printf("  File size            %d MB\n",mbytes);
    printf("  Directory entries    %d\n\n",entries);
    (void) rmdir(name);
    (void) unlink(name);
    if (mbytes) {
	pt_allocate();
	pt_read();
	pt_read_rev();
	pt_write();
    }
    if (entries) {
	pt_create();
	pt_lookup();
	pt_lookup_rev();
	pt_readdir();
	pt_unlink();
    }
    return 0;
}
