Add a tool for testing performance of storage devices. --- /home/pjd/tmp/src/tools/tools/README Sun Mar 28 20:01:15 2004 +++ tools/tools/README Fri Aug 20 18:56:43 2004 @@ -1,4 +1,4 @@ -# $FreeBSD: src/tools/tools/README,v 1.31 2004/03/28 18:01:15 marcel Exp $ +# $FreeBSD: src/tools/tools/README,v 1.32 2004/08/20 16:56:43 pjd Exp $ This directory is for tools. @@ -37,6 +37,7 @@ pciid Generate src/share/misc/pci_vendors. portsinfo Generate list of new ports for last two weeks. prstats Generate statistics about the PR database. +raidtest Tool for testing performance of storage devices. scsi-defects Get at the primary or grown defect list of a SCSI disk. tinderbox Sample script for nightly test builds. upgrade Scripts used for upgrading an installed system. --- /dev/null Wed Sep 8 19:44:00 2004 +++ tools/tools/raidtest/Makefile Fri Aug 20 14:02:34 2004 @@ -0,0 +1,7 @@ +# $FreeBSD: src/tools/tools/raidtest/Makefile,v 1.1 2004/08/20 12:02:34 pjd Exp $ + +PROG= raidtest +NOMAN= true +WARNS?= 6 + +.include --- /dev/null Wed Sep 8 19:44:00 2004 +++ tools/tools/raidtest/README Fri Aug 20 14:02:34 2004 @@ -0,0 +1,29 @@ +[ $FreeBSD: src/tools/tools/raidtest/README,v 1.1 2004/08/20 12:02:34 pjd Exp $ ] + +This utility can be used to test performance of storage devices. +First, one need to generate file with I/O operations: + + # set mediasize=`diskinfo /dev/ | awk '{print $3}'` + # set sectorsize=`diskinfo /dev/ | awk '{print $2}'` + # raidtest genfile -s $mediasize -S $sectorsize -n 50000 + +It will generate test which contains 50000 I/O requests with random +size and random offset. Size is a multiple of sectorsize, but less than or +equal to 128kB (maxium size of I/O request). I/O request type (READ or WRITE) +is random as well. +All test data are stored in 'raidtest.data' file in current working directory. + +To run test, one should type: + + # raidtest test -d /dev/ -n 10 + +This command will read test data from 'raidtest.data' file, run 10 processes +which will be used to send requests to the given device in parallel. +When test is finished you will see statistics: + + Bytes per second: + Requests per second: + +If you compare performance of two storage devices, use the same data file! + + -- Pawel Jakub Dawidek --- /dev/null Wed Sep 8 19:44:00 2004 +++ tools/tools/raidtest/raidtest.c Fri Aug 20 14:49:09 2004 @@ -0,0 +1,418 @@ +/*- + * Copyright (c) 2004 Pawel Jakub Dawidek + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +__FBSDID("$FreeBSD: src/tools/tools/raidtest/raidtest.c,v 1.2 2004/08/20 12:49:09 pjd Exp $"); + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#define DEFAULT_DATA_FILE "raidtest.data" +#define MAX_IO_LENGTH 131072 +#define IO_TYPE_READ 0 +#define IO_TYPE_WRITE 1 + +struct ioreq { + off_t iorq_offset; + unsigned iorq_length; + unsigned iorq_type; +}; + +struct iorec { + uint64_t iorc_offset; + uint32_t iorc_length; + uint8_t iorc_type; +}; + +static void +usage(void) +{ + + fprintf(stderr, "usage: %s genfile [-frw] <-s mediasize> [-S sectorsize] <-n nrequests> [file]\n", getprogname()); + fprintf(stderr, " %s test [-rw] <-d device> [-n processes] [file]\n", getprogname()); + exit(EXIT_FAILURE); +} + +static unsigned +gen_size(unsigned secsize) +{ + unsigned maxsec; + + maxsec = MAX_IO_LENGTH / secsize; + return (secsize * ((arc4random() % maxsec) + 1)); +} + +static int +read_ioreq(int fd, struct ioreq *iorq) +{ + struct iorec iorc; + + if (read(fd, &iorc, sizeof(iorc)) != sizeof(iorc)) + return (1); + iorq->iorq_offset = le64dec(&iorc.iorc_offset); + iorq->iorq_length = le32dec(&iorc.iorc_length); + iorq->iorq_type = iorc.iorc_type; + return (0); +} + +static int +write_ioreq(int fd, struct ioreq *iorq) +{ + struct iorec iorc; + + le64enc(&iorc.iorc_offset, iorq->iorq_offset); + le32enc(&iorc.iorc_length, iorq->iorq_length); + iorc.iorc_type = iorq->iorq_type; + return (write(fd, &iorc, sizeof(iorc)) != sizeof(iorc)); +} + +static void +raidtest_genfile(int argc, char *argv[]) +{ + uintmax_t i, nreqs, mediasize, nsectors, nbytes, nrreqs, nwreqs; + unsigned secsize, maxsec; + const char *file = NULL; + struct ioreq iorq; + int ch, fd, flags, rdonly, wronly; + + nreqs = 0; + mediasize = 0; + secsize = 512; + rdonly = wronly = 0; + flags = O_WRONLY | O_CREAT | O_EXCL | O_TRUNC; + while ((ch = getopt(argc, argv, "fn:rs:S:w")) != -1) { + switch (ch) { + case 'f': + flags &= ~O_EXCL; + break; + case 'n': + errno = 0; + nreqs = strtoumax(optarg, NULL, 0); + if (errno != 0) { + err(EXIT_FAILURE, + "Invalid value for '%c' argument.", ch); + } + break; + case 'r': + rdonly = 1; + break; + case 's': + errno = 0; + mediasize = strtoumax(optarg, NULL, 0); + if (errno != 0) { + err(EXIT_FAILURE, + "Invalid value for '%c' argument.", ch); + } + break; + case 'S': + errno = 0; + secsize = strtoul(optarg, NULL, 0); + if (errno != 0) { + err(EXIT_FAILURE, + "Invalid value for '%c' argument.", ch); + } + break; + case 'w': + wronly = 1; + break; + default: + usage(); + } + } + argc -= optind; + argv += optind; + if (nreqs == 0) + errx(EXIT_FAILURE, "Option '%c' not specified.", 'n'); + if (mediasize == 0) + errx(EXIT_FAILURE, "Option '%c' not specified.", 's'); + if (rdonly && wronly) { + errx(EXIT_FAILURE, "Both '%c' and '%c' options were specified.", + 'r', 'w'); + } + if (argc == 0) + file = DEFAULT_DATA_FILE; + else if (argc == 1) + file = argv[0]; + else + usage(); + fd = open(file, flags, 0644); + if (fd < 0) + err(EXIT_FAILURE, "Cannot create '%s' file", file); + nsectors = mediasize / secsize; + nbytes = nrreqs = nwreqs = 0; + for (i = 0; i < nreqs; i++) { + /* Generate I/O request length. */ + iorq.iorq_length = gen_size(secsize); + /* Generate I/O request offset. */ + maxsec = nsectors - (iorq.iorq_length / secsize); + iorq.iorq_offset = (arc4random() % maxsec) * secsize; + /* Generate I/O request type. */ + if (rdonly) + iorq.iorq_type = IO_TYPE_READ; + else if (wronly) + iorq.iorq_type = IO_TYPE_WRITE; + else + iorq.iorq_type = arc4random() % 2; + nbytes += iorq.iorq_length; + switch (iorq.iorq_type) { + case IO_TYPE_READ: + nrreqs++; + break; + case IO_TYPE_WRITE: + nwreqs++; + break; + } + if (write_ioreq(fd, &iorq) != 0) { + unlink(file); + err(EXIT_FAILURE, "Error while writing"); + } + } + printf("File %s generated.\n", file); + printf("Number of READ requests: %ju.\n", nrreqs); + printf("Number of WRITE requests: %ju.\n", nwreqs); + printf("Number of bytes to transmit: %ju.\n", nbytes); +} + +static void +test_start(int fd, struct ioreq *iorqs, uintmax_t nreqs) +{ + unsigned char data[MAX_IO_LENGTH]; + struct ioreq *iorq; + uintmax_t n; + + for (n = 0; n < nreqs; n++) { + iorq = &iorqs[n]; + switch (iorq->iorq_type) { + case IO_TYPE_READ: + if (pread(fd, data, iorq->iorq_length, + iorq->iorq_offset) != (ssize_t)iorq->iorq_length) { + fprintf(stderr, + "%u: read(%jd, %u) failed: %s.\n", getpid(), + (intmax_t)iorq->iorq_offset, + iorq->iorq_length, strerror(errno)); + } + break; + case IO_TYPE_WRITE: + if (pwrite(fd, data, iorq->iorq_length, + iorq->iorq_offset) != (ssize_t)iorq->iorq_length) { + fprintf(stderr, + "%u: write(%jd, %u) failed: %s.\n", + getpid(), (intmax_t)iorq->iorq_offset, + iorq->iorq_length, strerror(errno)); + } + break; + default: + fprintf(stderr, "%u: Invalid request type: %u.\n", + getpid(), iorq->iorq_type); + break; + } + } +} + +static void +show_stats(long secs, uintmax_t nbytes, uintmax_t nreqs) +{ + + printf("Bytes per second: %ju\n", nbytes / secs); + printf("Requests per second: %ju\n", nreqs / secs); +} + +static void +raidtest_test(int argc, char *argv[]) +{ + uintmax_t i, nbytes, nreqs, nrreqs, nwreqs, reqs_per_proc, nstart; + const char *dev, *file = NULL; + struct timeval tstart, tend; + struct ioreq *iorqs; + unsigned nprocs; + struct stat sb; + pid_t *procs; + int ch, fdd, fdf, j, rdonly, wronly; + + dev = NULL; + nprocs = 1; + rdonly = wronly = 0; + while ((ch = getopt(argc, argv, "d:n:rvw")) != -1) { + switch (ch) { + case 'd': + dev = optarg; + break; + case 'n': + errno = 0; + nprocs = strtoul(optarg, NULL, 0); + if (errno != 0) { + err(EXIT_FAILURE, + "Invalid value for '%c' argument.", ch); + } + break; + case 'r': + rdonly = 1; + break; + case 'w': + wronly = 1; + break; + default: + usage(); + } + } + argc -= optind; + argv += optind; + if (dev == NULL) + errx(EXIT_FAILURE, "Option '%c' not specified.", 'd'); + if (nprocs < 1) + errx(EXIT_FAILURE, "Invalid number of processes"); + if (rdonly && wronly) { + errx(EXIT_FAILURE, "Both '%c' and '%c' options were specified.", + 'r', 'w'); + } + if (argc == 0) + file = DEFAULT_DATA_FILE; + else if (argc == 1) + file = argv[0]; + else + usage(); + fdf = open(file, O_RDONLY); + if (fdf < 0) + err(EXIT_FAILURE, "Cannot open '%s' file", file); + if (fstat(fdf, &sb) < 0) + err(EXIT_FAILURE, "Cannot stat '%s' file", file); + if ((sb.st_size % sizeof(struct iorec)) != 0) + err(EXIT_FAILURE, "Invalid size of '%s' file", file); + fdd = open(dev, O_RDWR | O_DIRECT); + if (fdd < 0) + err(EXIT_FAILURE, "Cannot open '%s' device", file); + procs = malloc(sizeof(pid_t) * nprocs); + if (procs == NULL) { + close(fdf); + close(fdd); + errx(EXIT_FAILURE, "Cannot allocate %u bytes of memory.", + sizeof(pid_t) * nprocs); + } + iorqs = + malloc((sb.st_size / sizeof(struct iorec)) * sizeof(struct ioreq)); + if (iorqs == NULL) { + close(fdf); + close(fdd); + free(procs); + errx(EXIT_FAILURE, "Cannot allocate %jd bytes of memory.", + (intmax_t)(sb.st_size / sizeof(struct iorec)) * + sizeof(struct ioreq)); + } + nreqs = sb.st_size / sizeof(struct iorec); + nbytes = nrreqs = nwreqs = 0; + for (i = 0; i < nreqs; i++) { + if (read_ioreq(fdf, &iorqs[i])) + err(EXIT_FAILURE, "Error while reading"); + if (rdonly) + iorqs[i].iorq_type = IO_TYPE_READ; + else if (wronly) + iorqs[i].iorq_type = IO_TYPE_WRITE; + nbytes += iorqs[i].iorq_length; + switch (iorqs[i].iorq_type) { + case IO_TYPE_READ: + nrreqs++; + break; + case IO_TYPE_WRITE: + nwreqs++; + break; + default: + fprintf(stderr, "Invalid request type: %u.\n", + iorqs[i].iorq_type); + break; + } + } + close(fdf); + printf("Read %ju requests from %s.\n", nreqs, file); + printf("Number of READ requests: %ju.\n", nrreqs); + printf("Number of WRITE requests: %ju.\n", nwreqs); + printf("Number of bytes to transmit: %ju.\n", nbytes); + printf("Number of processes: %u.\n", nprocs); + fflush(stdout); + reqs_per_proc = nreqs / nprocs; + nstart = 0; + gettimeofday(&tstart, NULL); + for (j = 0; j < (int)nprocs; j++) { + procs[i] = fork(); + switch (procs[i]) { + case 0: + free(procs); + test_start(fdd, &iorqs[nstart], reqs_per_proc); + free(iorqs); + close(fdd); + exit(EXIT_SUCCESS); + case -1: + fprintf(stderr, "Cannot create process %u: %s\n", + (unsigned)i, strerror(errno)); + for (j--; j >= 0; j--) + kill(procs[j], SIGKILL); + free(procs); + free(iorqs); + close(fdd); + exit(EXIT_FAILURE); + } + nstart += reqs_per_proc; + } + free(iorqs); + free(procs); + for (j = 0; j < (int)nprocs; j++) { + int status; + + wait(&status); + } + gettimeofday(&tend, NULL); + show_stats(tend.tv_sec - tstart.tv_sec, nbytes, nreqs); +} + +int +main(int argc, char *argv[]) +{ + + if (argc < 2) + usage(); + argc--; + argv++; + if (strcmp(argv[0], "genfile") == 0) + raidtest_genfile(argc, argv); + else if (strcmp(argv[0], "test") == 0) + raidtest_test(argc, argv); + else + usage(); + exit(EXIT_SUCCESS); +}