/* diskrate.c */
/* derived from Tom Watson's iorate by Patrick McGehearty & George Bier
 *
 * Copyright (C) 1991, Convex Computer Corp
 * uses gettimeofday instead of time for better accuracy
 * does multiple file writes to flush first file from buffer cache
 * before measuring read rate for first file.  Performs
 * sync operations to force written files to go to disk.
 * Purpose of program is to measure throughput to disk
 * with minimum benefit of buffer cache. (see note below)
 *
 *
 */

/* to eliminate buffer cache measurements, it is recommended that
   the diskrate program perform a write leaving the written file 
   the (-l option), then umount and mount the filesystem, then do the read  
*/

#define roundup(x, y)   ((((x)+((y)-1))/(y))*(y))
#include	<stdio.h>
#include	<ctype.h>
#include	<sys/param.h>
#include	<sys/stat.h>
#include	<sys/file.h>
#include        <sys/fcntl.h> /* Milind Saraph, to keep Solaris happy */
#include	<sys/time.h>

char *malloc(), *ctime();
int	bsize;		/* the block size */
int	nbytes;		/* the total number of bytes for output, input */
int	ebytes;		/* the total number of bytes for extra files */
char  *filename = NULL;
char  *extraname = NULL;  /* the file name used for cache flushing */
int     extra=0;
int	fd1,fd2;		/* the file's file descriptor */
struct	stat	filestat;	/* the file's stat info */
struct timeval before,after,aftersync,deltat,sumtime,avetime;
long	timecnt;
long	starttime, stoptime;	/* the starting and ending times */
char	*buf = NULL;	/* the i/o buffer */
long clockoverhead;
long syncoverhead;
long deltausec;
int readflag = 0;
int writeflag = 0;
int leave = 0;

/*VARARGS1*/
p(a,b,c,d,e,f)
	char *a;
{	fprintf(stderr,a,b,c,d,e,f);	}

/*VARARGS1*/
fatal(a,b,c,d,e,f)
	char *a;
{	fprintf(stderr,a,b,c,d,e,f);	exit(3);	}

deltatime(last,first)
struct timeval *last,*first;
{
    deltat.tv_usec = last->tv_usec - first->tv_usec;
    deltat.tv_sec = last->tv_sec - first->tv_sec;
    if (deltat.tv_usec < 0)
    {   deltat.tv_usec += 1000000;
	deltat.tv_sec -= 1;
    }
}

addtime()
{
    timecnt++;
    sumtime.tv_usec += deltat.tv_usec;
    sumtime.tv_sec += deltat.tv_sec;
    if (sumtime.tv_usec >= 1000000)
    {   sumtime.tv_usec -= 1000000;
	sumtime.tv_sec += 1;
    }
}

paverate()
{
   double useconds;
   useconds = (double) sumtime.tv_sec * 1000000.0 + (double)sumtime.tv_usec;
   p("Ave Write: %.3f mbytes/sec.\n",((double) nbytes * timecnt) / useconds);
   sumtime.tv_usec = 0;
   sumtime.tv_sec = 0;
   timecnt = 0;
}

main(argc,argv)		/* diskrate - Measure the i/o rate of a system */
char **argv;
{
  int	filecnt;	/* count of number -f arguments */
  extern int optind;
  extern char *optarg;
  int c;

  sumtime.tv_usec = 0;
  sumtime.tv_sec = 0;
  timecnt = 0;
  filecnt = 0;
  extra = 0;
  readflag = 0;
  writeflag = 0;

  /* set up some reasonable defaults */
  bsize = 64 * 1024;
  nbytes = 32 * 1024 * 1024;
  ebytes = 32 * 1024 * 1024;
  extraname = "/tmp/diskratex";

  while ((c = getopt(argc, argv, "b:e:f:n:s:wrl")) != EOF)
    switch (c) {
    case 'b':	bsize = atoi(optarg);
		while ( isdigit(*optarg)) ++optarg;
		if ((*optarg == 'k') || (*optarg == 'K')) bsize *= 1024;
		if ((*optarg == 'm') || (*optarg == 'M')) bsize *= 1024*1024;
		break;
    case 'n':	nbytes = atoi(optarg);
		while ( isdigit(*optarg)) ++optarg;
		if ((*optarg == 'k') || (*optarg == 'K')) nbytes *= 1024;
		if ((*optarg == 'm') || (*optarg == 'M'))nbytes *= 1024 * 1024;
		break;
    case 'f':
		filename = optarg;
		break;
    case 'e':	extra = 1;  /* the extra file has been made obsolete,
							   use mount/umount instead */
		extraname = optarg;
		break;
    case 'r':	readflag = 1;  /* only do read */
		break;
    case 'R':	readflag = 1;  /* only do read */
		break;
    case 'w':	writeflag = 1; /* only do writes */
		break;
    case 'W':	writeflag = 1; /* only do writes */
		break;
    case 's':	ebytes = atoi(optarg);
		while ( isdigit(*optarg)) ++optarg;
		if ((*optarg == 'k') || (*optarg == 'K')) ebytes *= 1024;
		if ((*optarg == 'm') || (*optarg == 'M'))ebytes *= 1024 * 1024;
		break;
	case 'l':   leave = 1;
		break;
	case 'L':	leave = 1;
		break;
    default:	printusage();
    }
	if (filename == NULL)
	{			
		fprintf(stderr,"-f filename missing\n\n");
		printusage();
	}
    findclocktime();
    findsynctime();

    setup();
    runit();
}

printusage()
{
    fprintf(stderr,
    "Usage: diskrate [-n filesize] [-b writeblocksize] -f filename\n");
    fprintf(stderr,
 "                [-e extrafile] [-s extrafilesize] [-r -w -l]\n");
	fprintf(stderr,"\t\t-r only do a read\n");
	fprintf(stderr,"\t\t-w only do writes\n");
	fprintf(stderr,"\t\t-l leave file after write\n");
    exit(2);
}

findclocktime()
{
  register int i;
  long sumt,cnt;
  sumt = 0; cnt = 0;
  measureclock();   /* cache clock code */
  for ( i = 0; i < 100; i++)
  {
    measureclock();
    if (deltausec < 100 ) /* omit heavy overhead times */
	{ sumt += deltausec; cnt++;}
  }
  if (cnt == 0) clockoverhead = 20; /* special case, should never occur */
  else clockoverhead = sumt/cnt;
}

measureclock()
{
  (void) gettimeofday(&before, (struct timezone *) 0);
  (void) gettimeofday(&after, (struct timezone *) 0);
  after.tv_usec -= before.tv_usec;
  after.tv_sec -= before.tv_sec;
  deltausec = after.tv_sec * 1000000 + after.tv_usec;
}

findsynctime()
{
  register int i;
  long sumt,cnt;
  sumt = 0; cnt = 0;
  measuresync();   /* cache sync code */
  for ( i = 0; i < 20; i++)
  {
    measuresync();
    if (deltausec < 10000 ) /* omit heavy overhead times */
	{ sumt += deltausec; cnt++;}
  }
  if (cnt == 0) syncoverhead = 300; /* special case, should never occur */
  else syncoverhead = sumt/cnt;
}


measuresync()
{
  (void) gettimeofday(&before, (struct timezone *) 0);
  sync(); sync();
  (void) gettimeofday(&after, (struct timezone *) 0);
  after.tv_usec -= before.tv_usec;
  after.tv_sec -= before.tv_sec;
  deltausec = after.tv_sec * 1000000 + after.tv_usec;
}



setup()
{
	register long i;
	fd1 = open(filename, O_CREAT|O_RDWR, 0666);
	if (fd1 < 0) fatal("Can't open/create %s.\n", filename);
	if (fstat(fd1, &filestat) < 0) fatal("Can't stat %s.\n", filename);
	if (bsize == 0) bsize = filestat.st_blksize;	 /* file sys bsize */
	nbytes = roundup (nbytes,bsize);		/* no odd endings */

	if(extra != 0)
	{
	    fd2 = open(extraname, O_CREAT|O_RDWR, 0666);
	    if (fd2 < 0) fatal("Can't open/create %s.\n", extraname);
	    if (fstat(fd2, &filestat) < 0)
		fatal("Can't stat %s.\n", extraname);
	}

	/* get an i/o buffer */
	if (buf == NULL) buf = malloc((unsigned)bsize);
	if (buf == NULL) fatal("Can't allocate %d byte buffer.\n", bsize);
	for (i=0; i < bsize; i++) buf[i] = i;
}

runit()
{
	register i;

	if (readflag == 0)
	{
	    /* write measurement */
	    dowrite(fd1);
	}

	if(extra != 0) quickwrite(fd2);

	/* read measurement */

	if (writeflag == 0)
	    doread(fd1);

	(void)close(fd1);
	if (leave == 0 && readflag == 0 )
	{
	    (void)unlink(filename);
	}
	if(extra != 0)
	{
	    (void)close(fd2);
	    (void)unlink(extraname);
	}
	return 0;
}


dowrite(fd)
int fd;
{
    register int i;
    double usecs;
    sync();    /* insure disk cache is empty at start of measurement */
    sleep(2);  /* give disk buffer time to clear */
    sync();    /* complete clearing of disk cache */
    (void)gettimeofday(&before, (struct timezone *) 0); /* get start */
    for (i=nbytes; i > 0; i -= bsize)
	if (write(fd,buf,bsize) != bsize)
	{    
		perror("write");
		exit(9);
	}
    (void)gettimeofday(&after, (struct timezone *) 0);
    /* force file write */
    fsync(fd);
    (void)gettimeofday(&aftersync, (struct timezone *) 0);

    deltatime(&after,&before);  /* compute rate before cache flush */

    usecs = (double)deltat.tv_sec * 1000000.0
		 + (double)(deltat.tv_usec - clockoverhead);
	p("Chars written: %-10d\n",nbytes);
    if (usecs <= 0.0)
		p("Buffered Write: 0.0  seconds      ");
    else	p("Buffered Write: %6.3f mbytes/sec. ",
		((double) nbytes / usecs) );

    deltatime(&aftersync,&before);	/* compute rate after cache flush */
    usecs = (double)deltat.tv_sec * 1000000.0
		 + (double)(deltat.tv_usec - clockoverhead);
    if (usecs <= 0.0)
		p("Flushed Write: 0.0  seconds\n");
    else
    {   
		addtime();
		p("Flushed Write: %6.3f mbytes/sec.\n",
		((double) nbytes / usecs ) );
    }
}

quickwrite(fd)
int fd;
{
    register int i;
    double usecs;
    for (i=ebytes; i > 0; i -= bsize)
	if (write(fd,buf,bsize) != bsize)
	{    
		perror("write");
		exit(9);
	}
    /* force file write */
    fsync(fd);
}


doread(fd)
int fd;
{
    register int i;
    double usecs;

    sync();    /* insure disk cache is empty at start of measurement */
    sleep(2);  /* give disk buffer time to clear */

    (void)lseek(fd,0,0);		/* rewind */

    (void)gettimeofday(&before, (struct timezone *) 0);
    for (i=nbytes; i > 0; i -= bsize)
		if (read(fd,buf,bsize) != bsize)
		{	
			perror("read"); 
			exit(9);
		}

    (void)gettimeofday(&after, (struct timezone *) 0);

    deltatime(&after,&before);
    usecs = (double)deltat.tv_sec * 1000000.0
		 + (double)(deltat.tv_usec - clockoverhead);
	p("Chars Read: %-10d\n",nbytes);
    if (usecs <= 0.0)
	p("Sorry, read took 0 time- time has stopped.\n");
    else
	p("Read Rate: %6.3f mbytes/sec.\n",
		((double) nbytes / usecs) );

}

