Subject: Re: Disk testing?
To: None <port-alpha@NetBSD.ORG, xela@MIT.EDU>
From: Matthew Jacob <mjacob@feral.com>
List: port-alpha
Date: 02/24/1998 10:10:07
Bug reports and sneers to me.


/*
 * $Id: diskex.c,v 1.8 1997/03/27 20:25:54 mjacob Exp $
 *
 * $Log: diskex.c,v $
 * Revision 1.8  1997/03/27 20:25:54  mjacob
 * cleanup, netbsd etc.
 *
 * Revision 1.7  1997/03/27 17:57:18  mjacob
 * checkpoint
 *
 * Revision 1.6  1996/06/29  20:06:33  mjacob
 * make some changes to clean up errors in randoms; limit to
 * proper 2GB SEEK limit.
 *
 * Revision 1.5  1996/06/12  05:04:35  mjacob
 * some convex stuff.
 *
 * Revision 1.4  1996/06/11  19:37:23  mjacob
 * add in a SYSV define for solaris
 *
 * Revision 1.3  1995/07/26  22:01:27  mjacob
 * make it work again w/o compile errros on sun.
 * Always use gcc -O -Wall -D__USE_FIXED_PROTOYPES__
 *
 *
 * Copyright (c) 1995-1996, Matthew Jacob
 *
 *	This software is free software; you can redistribute it and/or
 *	modify it under the terms of the GNU Library General Public
 *	License as published by the Free Software Foundation; version 2.
 *
 *	This software 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
 *	Library General Public License for more details.
 *
 *	You should have received a copy of the GNU Library General Public
 *	License along with this software; if not, write to the Free
 *	Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 *	The author may be reached via electronic communications at
 *
 *		mjacob@feral.com
 *
 *	or, via United States Postal Address
 *
 *		Matthew Jacob
 *		1831 Castro Street
 *		San Francisco, CA, 94131
 */

#ifdef convex
#include <sys/types.h>
extern int optind;
extern int getopt(int, char **, const char *);
#define	SEEK_T	off64_t
#define	SEEK	lseek64
#define	FSTAT	fstat64
#define	STAT_T	stat64_t
#else
#define	SEEK_T	off_t
#define	SEEK	lseek
#define	FSTAT	fstat
#define	STAT_T	struct stat
#endif
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <fcntl.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/param.h>
#include <sys/ioctl.h>
#ifdef sun
#ifdef	__SVR4
#include <sys/dkio.h>
#else
#include <sun/dkio.h>
extern int gettimeofday(struct timeval *, struct timezone *);
extern void bzero(char *, int);
extern int strtol(const char *, char **, int);
#endif
extern int optind;
#endif
#ifdef linux
#include <sys/ioctl.h>
#include <linux/fs.h>
#endif
#ifdef	convex
#include <sys/ioctl.h>
#include <interfaces/io_if/scsi/scsi.h>
#endif
#ifdef	__NetBSD__
#include <sys/disklabel.h>
#include <sys/dkio.h>
static struct disklabel fred;
#endif


#ifndef	O_LARGEFILE
#define	O_LARGEFILE	0
#endif
static int sequential = 1;
static int verbose = 1;
static int zflag = 0;
static int wpercent = 0;
static SEEK_T rfsize;
static SEEK_T fsize;
static SEEK_T seekbase;
static SEEK_T seeklim;
static SEEK_T cresize;
static unsigned long sampsize = 100;
static unsigned long blksize = 8192;
static char *filename = "diskex.data";
static char *buffer = NULL;

static SEEK_T szarg(char *);
static void operate(char *, int);

static char *usage =
"Usage:
diskex [-zqr] [-b bs] [-w pct] [-f filename] [-c size] [-s size] [-S sampsize]
	-z		(linux only) flush buffer caches every sampsize ops
			(more closely approximates real device speeds)

	-q		non-verbose

	-r		random vs. sequential access. Random is w.r.t.
			to blocksize. Defaults to sequential access.

	-b bs		bs is decimal size of buffer to use, specified as
			* N for bytes, Nk for kbytes, Nm for megabytes.
			Default is 8k.

	-w pct 		Writes vs. Reads. The argument pct is the
			percentage of writes vs. reads.

	-f filename	name of file to open. Defaults to the name
			\"diskex.data\".

	-c size		For the case where you wish to (re)create
			the dat file.

	-s size		Size of test area within file to exercise.
			If not specified, the entire file is used,
			modulo block size.

 	-S sampsize	Size of sample interval (default 100)

	-h		Print this message

 Examples:

	diskex -f /dev/rsd3c -s 100m -r -b 16k
	 - execute random reads in the front 100 megabytes of /dev/rsd3c
	   with a blocksize of 16kbytes.

	diskex -f /tmp/bob.data -c 25m -w 25
	 - create /tmp/bob.data (25 megabytes) and operate on it sequentially
	  with 25%% of the operations as writes.

 Output (if not using the -q flag):
	Continuous single line stream of output, e.g.,
		  read bno      8    50.4428 ops/sec    403.5 kb/sec\n";

int
main(int a, char **v)
{
    extern char *optarg;
    STAT_T st;
    int c, fd;

    srand((int)(time((time_t *) 0)/getpid()));

    while ((c = getopt(a, v, "hzqrb:w:f:s:c:S:")) != -1) {
	switch (c) {
	case 'z':
	    zflag = 1;
	    break;
	case 'q':
	    verbose = 0;
	    break;
	case 'r':
	    sequential = 0;
	    break;
	case 'S':
	    sampsize = (unsigned long) szarg(optarg);
	    break;
	case 'b':
	    blksize = (unsigned long) szarg(optarg);
	    if (blksize & (blksize - 1)) {
		fprintf(stderr, "%s: blksize must be a power of two\n", *v);
		exit(1);
	    }
	    break;
	case 'w':
	    wpercent = atoi(optarg);
	    if (wpercent < 0 || wpercent > 100) {
		fprintf(stderr, "%s: bad -w value (%d)\n", *v, wpercent);
		exit(1);
	    }
	    break;
	case 'f':
	    filename  = optarg;
	    break;
	case 's':
	    fsize = szarg(optarg);
	    break;
	case 'c':
	    cresize = szarg(optarg);
	    break;
	case 'h':
	default:
	    fprintf(stderr, usage);
	    exit(1);
	}
    }
    if (optind < a) {
	fprintf(stderr, "More options?\n");;
	exit(1);
    }

    buffer = malloc(blksize);
    if (buffer == NULL) {
	perror("malloc");
	exit(1);
    }

    /*
     * Open the file..
     */
    if (cresize != (SEEK_T) 0) {
	fd = open(filename, O_CREAT|O_TRUNC|O_WRONLY|O_LARGEFILE, 0666);
	if (fd < 0) {
	    perror(filename);
	    exit(1);
	}
	while (cresize > 0) {
	    int amt = cresize > blksize? blksize: cresize;
	    if (write(fd, buffer, (int) amt) != (int) amt) {
		perror(filename);
		exit(1);
	    }
	    cresize -= (SEEK_T) amt;
	}
	(void) close(fd);
    }
    fd = open(filename, O_RDWR, 0666);
    if (fd < 0) {
	perror(filename);
	exit(1);
    }
    /*
     * See if it is a device rather than a 'regular' file...
     */
    if (FSTAT(fd, &st) < 0) {
	perror("fstat");
	exit(1);
    }
    if (S_ISBLK(st.st_mode)) {
#if	defined(__NetBSD__)
	char *makeraw(char *);
	int rfd, part;

	if ((rfd = open(makeraw(filename), O_RDONLY, 0666)) < 0) {
	    printf("Can't deal with cooked devices\n");
	    exit(1);
	}
	if (ioctl(rfd, DIOCGDINFO, (caddr_t) &fred) < 0) {
	    perror("DIOCGDINFO_raw");
	    exit(1);
	}
	seekbase = (wpercent == 0) ? 0 : 8192;
	part = filename[strlen(filename) - 1] - 'a';
	rfsize = seeklim =
	    ((SEEK_T) fred.d_partitions[part].p_size) * (SEEK_T) DEV_BSIZE;
	(void) close(rfd);
#elif	defined(sun)
	struct dk_allmap x;
	char *makeraw(char *);
	int rfd, part;

	if ((rfd = open(makeraw(filename), O_RDONLY, 0666)) < 0) {
	    printf("Can't deal with cooked devices\n");
	    exit(1);
	}
#if	defined(__svr4__)
	part = filename[strlen(filename) - 1] - '0';
#else
	part = filename[strlen(filename) - 1] - 'a';
#endif
	if (ioctl(rfd, DKIOCGAPART, (caddr_t) &x) < 0) {
	    perror("DKIOCGAPART_raw");
	    exit(1);
	}
	seekbase = (wpercent == 0) ? 0 : 8192;
	rfsize = seeklim =
	    ((SEEK_T) x.dka_map[part].dkl_nblk) * (SEEK_T) DEV_BSIZE;
	(void) close(rfd);
#elif	defined(linux)
	if (ioctl(fd, BLKGETSIZE, (caddr_t) &seeklim) < 0) {
	    perror("BLKGETSIZE");
	    exit(1);
	}
	seeklim <<= 9;
	rfsize = seeklim;
#else
	rfsize = seeklim = (SEEK_T) 1;
#endif
    } else if (S_ISCHR(st.st_mode)) {
#if	defined(__NetBSD__)
	int part;

	if (ioctl(fd, DIOCGDINFO, (caddr_t) &fred) < 0) {
	    perror("DIOCGDINFO");
	    exit(1);
	}
	seekbase = (wpercent == 0) ? 0 : 8192;
	part = filename[strlen(filename) - 1] - 'a';
	rfsize = seeklim =
	    ((SEEK_T) fred.d_partitions[part].p_size) * (SEEK_T) DEV_BSIZE;
#elif	defined(sun)
	struct dk_allmap x;
	int part;

	if (blksize < DEV_BSIZE) {
	    fprintf(stderr, "%s: block size must be at least %d bytes on "
		    "raw device\n", *v, DEV_BSIZE);
	    exit(1);
	}
#if	defined(__svr4__)
	part = filename[strlen(filename) - 1] - '0';
#else
	part = filename[strlen(filename) - 1] - 'a';
#endif
	if (ioctl(fd,  DKIOCGAPART, (caddr_t) &x) < 0) {
	    perror("DKIOCGAPART");
	    exit(1);
	}
	seekbase = (wpercent == 0) ? 0 : 8192;
	rfsize = seeklim =
	    ((SEEK_T) x.dka_map[part].dkl_nblk) * (SEEK_T) DEV_BSIZE;
#elif	defined(convex)
	struct topology top;
	seeklim = 0;
	if (ioctl(fd, SIOC_READ_TOPOLOGY, (caddr_t)&top) >= 0) {
		seeklim = (SEEK_T) top.partition[st.st_rdev & 0xf].size *
		    (SEEK_T) DEV_BSIZE;
	}
	rfsize = seeklim;
#else
	rfsize = seeklim = (SEEK_T) 1;
#endif
    } else {
	rfsize = seeklim = st.st_size;
    }

    if (seeklim < (SEEK_T) 0) {
	printf("%s too big for lseek(2) call\n", filename);
	exit(1);
    }
    if (fsize) {
	if (fsize > seeklim)
	    fsize = seeklim;
	else if (seeklim > fsize)
	    seeklim = fsize;
    } else {
	fsize = seeklim;
    }

    seeklim &= ~(blksize-1);
    if (seeklim < seekbase) {
	fprintf(stderr, "%s: botch, seeklim (%ld) < seekbase (%ld)\n",
		*v, seeklim, seekbase);
	exit(1);
    }
    if (verbose) {
	long long fs, rs, sb, sl;
	fs = (long long) fsize;
	rs = (long long) rfsize;
	sb = (long long) seekbase;
	sl = (long long) seeklim;
	fprintf(stdout, "%s: fsize %lx%08lx (real %lx%08lx) Seek base "
		" %lx%08lx Seek lim %lx%08lx\n", *v,
		(long) (fs >> 32LL), (long) (fs & 0xFFFFFFFF),
		(long) (rs >> 32LL), (long) (rs & 0xFFFFFFFF),
		(long) (sb >> 32LL), (long) (sb & 0xFFFFFFFF),
		(long) (sl >> 32LL), (long) (sl & 0xFFFFFFFF));
	fprintf(stdout, "%s: file %s bs %ld seq %d pct writes: %d\n",
		*v, filename, blksize, sequential, wpercent);
    }
    operate(*v, fd);
    exit(0);
}

static SEEK_T
szarg(char *n)
{
    register int shift = 0;
    register char *q = n;

    while (*q != (char) 0)
	q++;
    q--;

    if (*q == 'b' || *q == 'B')
	q--;

    if (*q == 'k' || *q == 'K') {
	shift = 10;
	*q = 0;
    } else if (*q == 'm' || *q == 'M') {
	shift = 20;
	*q = 0;
    } else if (*q == 'g' || *q == 'G') {
	shift = 30;
	*q = 0;
    }
    return ((SEEK_T) strtol((const char *)n, (char **) NULL, 0) << shift);
}

static void
operate(char *pname, int fd)
{
    register int i;
    register unsigned long offset, etime;
    char *optype;
    unsigned long *opvec;
    struct timeval st, et;
    register double opsec, kbsec;

    offset = seekbase;
    kbsec = opsec = 0;

    if (gettimeofday(&st, (struct timezone *) 0) < 0) {
	perror("gettimeofday");
	exit(1);
	/* NOTREACHED */
    }
    opvec = (unsigned long *) malloc(sampsize * sizeof (unsigned long));
    if (opvec == NULL) {
	fprintf(stderr, "%s: unable to malloc opvec for sampsize %ld\n",
	    pname, sampsize);
	exit(1);
	/* NOTREACHED */
    }
    optype = (char *) malloc(sampsize * sizeof (char));
    if (optype == NULL) {
	fprintf(stderr, "%s: unable to malloc optype for sampsize %ld\n",
	    pname, sampsize);
	exit(1);
	/* NOTREACHED */
    }

#if	defined(sun) && defined(__svr4__)
    setbuf(stdout, NULL);
#endif
    for (;;) {
	for (i = 0; i < sampsize; i++) {
	    if (sequential) {
		if (offset + blksize > seeklim)
		    offset = seekbase;
		opvec[i] = offset;
		offset += blksize;
	    } else {
		do {
		    opvec[i] = rand() % seeklim;
		} while (opvec[i] < seekbase || opvec[i] >= seeklim - blksize);
	    }
	    if ((float) i < (((float) wpercent / 100.0) * (float) sampsize))
		optype[i] = 1;
	    else
		optype[i] = 0;
	}

	for (i = 0; i < sampsize; i++) {
	    if (SEEK(fd, (off_t) opvec[i], 0) < 0) {
		fprintf(stderr, "%s: SEEK fails, errno %d\n", pname, errno);
		return;
	    }
	    if (optype[i]) {
		if (write(fd, buffer, blksize) != blksize) {
		    perror("\nwrite");
		    return;
		}
	    } else {
		if (read(fd, buffer, blksize) != blksize) {
		    perror("\nread");
		    return;
		}
	    }
	    if (verbose) {
		printf("%6s bno %6ld %10.4f ops/sec %8.1f kb/sec\r",
		       optype[i]? "write" : "read", opvec[i] / blksize,
		       opsec, kbsec);
	    }
	}
	if (gettimeofday(&et, (struct timezone *) 0) < 0) {
	    perror("\ngettimeofday");
	    exit(1);
	}
	etime = et.tv_sec - st.tv_sec;
	if (et.tv_usec < st.tv_usec) {
	    etime--;
	    etime *= 1000000;
	    etime += (et.tv_usec + 1000000 - st.tv_usec);
	} else {
	    etime *= 1000000;
	    etime += (et.tv_usec - st.tv_usec);
	}
	opsec = (double) etime / (double) 1000000.0;
	kbsec = ((blksize * (double) sampsize) / opsec) / 1024.0;
	opsec = (double) sampsize / (double) opsec;
	st = et;
#if	defined(linux)
	if (zflag)
	    (void) ioctl(fd, BLKFLSBUF, 0);
#endif
    }
}

#if	defined(sun) || defined(convex) || defined(__NetBSD__)
#include <memory.h>
char *
makeraw(char *fn)
{
    register char *ns, *p;
    
    ns = malloc(strlen(fn) + 2);
    memset(ns, 0, strlen(fn) + 2);

#if	defined(sun) && defined(__svr4__)
    if (strncmp(fn, "/dev/dsk", 8) == 0) {
	strcpy(ns, "/dev/rdsk");
	strcat(ns, fn+8);
	return (ns);
    } else if (strncmp(fn, "dsk", 3) == 0) {
	strcpy(ns, "rdsk");
	strcat(ns, fn+3);
	return (ns);
    }
#endif
    p = strrchr(fn, '/');
    if (p == (char *) 0) {
	ns[0] = 'r';
	(void) strcpy(&ns[1], fn);
    } else {
	p++;
	(void) strncpy(ns, fn, p - fn);
	(void) strcat(ns, "r");
	(void) strcat(ns, p);
    }
    return (ns);
}
#endif
/*
 * ---------------------------------------------------------------------------
 * Local variables:
 * c-indent-level: 4
 * c-brace-imaginary-offset: 0
 * c-brace-offset: -4
 * c-argdecl-indent: 4
 * c-label-offset: -4
 * c-continued-statement-offset: 4
 * c-continued-brace-offset: 0
 * End:
 */