/* 
 * Program srawread.c
 *
 * A program to benchmark raw scsi I/O performance under linux
 *
 * Eric Youngdale  -  9/13/93
 *
 * This program was written because of all of the whining on the SCSI
 * channel about poor performance of the scsi drivers.  This program basically
 * reads the specified scsi device and measures the throughput.  Note that
 * the filesystem *AND* the buffer cache are bypassed by this code, this
 * program was designed to benchmark the naked scsi drivers by themselves
 * without the need to account for the overhead of any other portion of the
 * kernel.
 *
 * This program does a series of reads of the disk, of consecutive
 * areas on the disk.  The device is first queried to determine the
 * sector size for the device, and then the series of reads is begun.
 * About 5.0 Mb is read from the device, and then the performance numbers
 * are reported.  Note that since the buffer cache is completely bypassed,
 * there is no need to be concerned about cache hits or anything.
 *
 * With my Texel double speed cdrom, I obtain about 303Kb/sec with a 4Kb
 * blocksize, and this is almost exactly the specified data rate for
 * the device.
 *
 * For my Toshiba hard disk, I obtain the following numbers (aha1542B):
 *
 * BKSZ(bytes) 	Datarate(b/sec)
 * 4096 	  673684
 * 8192 	  960600
 * 16384	 1288490
 * 32768	 1587832
 * 49152 	 1755428
 * 65536	 1761001
 * 
 * I should point out that
 * with normal sequential file reads through a filesystem, that normally
 * there will be a read-ahead of approximately 16 sectors or 8192 bytes
 * in addition to the data requested by the read.  For a 4Kb blocksize
 * filesystem, typically 12Kb are requested at one time, so
 * theoretically one can get something quite larger.  I have experimented
 * with block devices that have larger block sizes, and I have seen data
 * rates of about 1.1Mb/sec with the same disk drive using a blocksize of
 * 4Kb with ext2 and using iozone to measure performance, so I know that
 * this is real.
 *
 * The Toshiba disk has a rotational rate of 3600 RPM, so the
 * peak I/O rate corresponds to something like 57 sectors per
 * revolution.  There are 85 sectors per track on this drive
 * which would correspond to a maximum theoretical throughput
 * of 2.6Mb/sec.  Note that the Adaptec has bus on/off times that
 * it uses to ensure that it does not hog the bus.  The current
 * settings in the linux kernel are 7 microseconds on and 5
 * microseconds off.  When you multiply the theoretical maximum
 * throughput by (7/(7+5)) you get about 1.5Mb/sec.  I am not sure
 * how to factor this into the equation because there is undoubtably
 * caching somewhere.  I suppose that it is safe to say that the
 * theoretical maximum speed you will ever observe is somewhere
 * between the two numbers.
 *
 * To use this you simply compile and then use the following command:
 *
 * ./rawread /dev/sda
 *
 * to perform the benchmark.  It should do only reads, so it should be
 * safe, but you will probably need root privileges, and if this screws
 * up something for you do not come running to me.
 *
 * 
 * Changes from Harald Koenig  -  11/25/93
 *
 * - added READ_10 support for > 1GB discs
 * - using gettimeofday() for timing
 * - two new command line parameters (argv[2] and argv[3]):
 *   - argv[2] (bstart) : starting block to check different zones on ZBR discs
 *   - argv[3] (bstep)  : factor for sequential stepping, default 1
 *                        use 0 for reading always the same blocks (from cache)
 *
 */

#define USE_GETTIMEOFDAY /* please, always! gettimeofday(2) is much more accurate */
#define USE_SG_IO


#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <inttypes.h>
#include <sys/times.h>
#include <linux/hdreg.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#ifdef USE_SG_IO
# include <scsi/sg.h>
#endif

FILE *infile = NULL;
unsigned char buffer[2*64*1024+8];


int read_size=32768;

static double
  time_so_far()
{
#ifdef USE_GETTIMEOFDAY
#include <sys/time.h>
  struct timeval t1;

  gettimeofday(&t1,NULL);
  return (t1.tv_sec + t1.tv_usec*1e-6);
#else /* USE_GETTIMEOFDAY */
  clock_t        val;
  struct tms tms;


  if ((val = times(&tms)) == -1)
    perror("times");

  return ((double) val) / ((double) CLK_TCK);
#endif /* USE_GETTIMEOFDAY */
}


int main( int argc, char *argv[] )
{
  int b,bstart=0,bstep=1;
  int c=1,verbose=0;
  int status, i;
  unsigned char *cmd;
  unsigned int capacity, sectorsize;
  int this_count, block;
  int rate;
  double starttime, endtime;
  int read_len = 10;			/* length of read command */
  int fua = 0;				/* "force unit access" for READ(10): don't read from cache but from media */
#ifdef USE_SG_IO
  sg_io_hdr_t sgbuf;
  int legacy_ioctl=0;
#endif

  while (argc>c && argv[c][0] == '-') {
    for(i=1; i < strlen(argv[c]); i++)
      switch (argv[c][i]) {
	case 'v': verbose = 1; break;
#ifdef USE_SG_IO
	case 'i': legacy_ioctl = 1; break;
#endif
	case 'f': fua = 1; break;
	case '6': read_len = 6; break;
	default: goto error;
      }
    c++;
  }
  if (argc>c) {
    infile = fopen( argv[c], "r" );
    if (!infile) {
      perror(argv[c]);
      return 2;
    }
  }
  if (argc>c+1) bstart = atoi(argv[c+1]);
  if (argc>c+2) bstep  = atoi(argv[c+2]);

  if (infile == NULL || argc==1 || argc>c+3) {
error:
    printf("usage: sraw [-v]%s scsi-device [ bstart [ bstep ] ]\n",
#ifdef USE_SG_IO
	    "[-i]"
#else
	    ""
#endif
	    );
    return 1;
  }

  memset(buffer, 0, sizeof(buffer));

  *( (int *)  buffer )		= 0;	/* length of input data */
  *( ((int *) buffer) + 1 )	= read_size;	/* length of output buffer */

  cmd = (unsigned char *) ( ((int *) buffer) + 2 );
  
  cmd[0] = 0x25;			/* READ CAPICTY (10 bytes)*/
  cmd[1] = 0x00;			/* b7-5: lun=0, b4-1: reserved, b0: reladdr=0 */
  /* cmd[2..5] = 0x00;			   logical block address = 0 */
  /* cmd[6..8] = 0x00;			   (reserved), cmd[8].b0=PMI(0) */
  /* cmd[9] = 0x00;			   control */

#ifdef USE_SG_IO
  if (! legacy_ioctl) {
    memset(&sgbuf, 0, sizeof(sgbuf));
    sgbuf.interface_id = 'S';		/* SCSI Generic Interface */
    sgbuf.dxfer_direction = SG_DXFER_FROM_DEV;
    sgbuf.cmd_len = 10;
    sgbuf.cmdp = cmd;
    sgbuf.dxfer_len = 8;		/* send back 8 bytes of data (capacity, sectorsize) */
    sgbuf.dxferp = cmd;
    sgbuf.timeout = 2000;
    status = ioctl( fileno(infile), SG_IO, &sgbuf );
  }
  else
#endif
  status = ioctl( fileno(infile), 1 /* SCSI_IOCTL_SEND_COMMAND */, buffer );
  if (status < 0) {
    perror("ioctl");
    return 3;
  }
  capacity = (buffer[8] << 24) | (buffer[9] << 16)  | (buffer[10] << 8) | buffer[11];
  sectorsize = (buffer[12] << 24) | (buffer[13] << 16)  | (buffer[14] << 8) | buffer[15];
  if (verbose)
    printf("Size %llu bytes, sectorsize %u\n", ((uint64_t)capacity) * sectorsize, sectorsize);

  do{
/*
	  for (i=0; i<10*1024; i++)
	  {
		  buffer[i] = 0;
	  }
*/
	  block = 0;
	  this_count = read_size / sectorsize;
	  starttime = time_so_far();
	  do{
		  *( (int *)  buffer )		= 0;	/* length of input data */
		  *( ((int *) buffer) + 1 )	= read_size;	/* length of output buffer */
		  
		  cmd = (unsigned char *) ( ((int *) buffer) + 2 );
		  
		  b = bstart + bstep * block;
		  if (read_len == 10) {
		    cmd[0] = 0x28;			/* read_10 */
		    cmd[1] = 0x00;
		    if (fua)
		      cmd[1] |= 0x08;
		    cmd[2] = (b >> 24) & 0xff;
		    cmd[3] = (b >> 16) & 0xff;
		    cmd[4] = (b >>  8) & 0xff;
		    cmd[5] =  b        & 0xff;
		    cmd[6] = 0;
		    cmd[7] = (this_count >> 8) & 0xff; /* transfer length */
		    cmd[8] =  this_count       & 0xff;
		    cmd[9] = 0x00; /* control */
		  }
		  else {
		    cmd[0] = 0x08;			/* read_6 */
		    cmd[1] = (b >> 16) & 0x1f;
		    cmd[2] = (b >> 8) & 0xff;
		    cmd[3] = b & 0xff;
		    cmd[4] = this_count;
		    cmd[5] = 0x00;
		  }
#ifdef USE_SG_IO
		  if (! legacy_ioctl) {
		    memset(&sgbuf, 0, sizeof(sgbuf));
		    sgbuf.interface_id = 'S';		/* SCSI Generic Interface */
		    sgbuf.dxfer_direction = SG_DXFER_FROM_DEV;
		    sgbuf.cmd_len = read_len;
		    sgbuf.cmdp = cmd;
		    sgbuf.dxfer_len = read_size;
		    sgbuf.dxferp = cmd;
		    sgbuf.timeout = 2000;
		    status = ioctl( fileno(infile), SG_IO, &sgbuf );
		  }
		  else
#endif
		  status = ioctl( fileno(infile), 3 /* SCSI_IOCTL_BENCHMARK_COMMAND */, buffer );
		  if (status < 0) {
		    if (verbose)
		      printf("ioctl: %s\n", strerror(errno));
		    else
		      printf("(%d) ", status);
		  }
		  block += this_count;
	  } while(block < (10000 / (sectorsize >> 9)));
	  endtime = time_so_far() - starttime;
	  rate = (block * sectorsize) / endtime;
	  if (!verbose)
	    printf("%6d   %10.4f  %6d    %8d \n",
		 read_size, endtime, block, rate);
	  else
	    printf("Blocksize %d, time elapsed %1.4f seconds, %d blocks.  Throughput = %d bytes/sec\n",
		 read_size, endtime, block, rate);

	  read_size += 4096;
  } while(read_size <= 2*64*1024);
	  
  return 0;
}

/*  end  */

