summaryrefslogtreecommitdiffstats
path: root/src/VBox/ValidationKit/utils/storage/IoPerf.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/VBox/ValidationKit/utils/storage/IoPerf.cpp')
-rw-r--r--src/VBox/ValidationKit/utils/storage/IoPerf.cpp1405
1 files changed, 1405 insertions, 0 deletions
diff --git a/src/VBox/ValidationKit/utils/storage/IoPerf.cpp b/src/VBox/ValidationKit/utils/storage/IoPerf.cpp
new file mode 100644
index 00000000..db3b7211
--- /dev/null
+++ b/src/VBox/ValidationKit/utils/storage/IoPerf.cpp
@@ -0,0 +1,1405 @@
+/* $Id: IoPerf.cpp $ */
+/** @file
+ * IoPerf - Storage I/O Performance Benchmark.
+ */
+
+/*
+ * Copyright (C) 2019-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/err.h>
+#include <iprt/dir.h>
+#include <iprt/file.h>
+#include <iprt/getopt.h>
+#include <iprt/initterm.h>
+#include <iprt/ioqueue.h>
+#include <iprt/list.h>
+#include <iprt/mem.h>
+#include <iprt/message.h>
+#include <iprt/param.h>
+#include <iprt/path.h>
+#include <iprt/process.h>
+#include <iprt/rand.h>
+#include <iprt/string.h>
+#include <iprt/stream.h>
+#include <iprt/system.h>
+#include <iprt/test.h>
+#include <iprt/time.h>
+#include <iprt/thread.h>
+#include <iprt/zero.h>
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+
+/** Size multiplier for the random data buffer to seek around. */
+#define IOPERF_RAND_DATA_BUF_FACTOR 3
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+
+/** Forward declaration of the master. */
+typedef struct IOPERFMASTER *PIOPERFMASTER;
+
+/**
+ * I/O perf supported tests.
+ */
+typedef enum IOPERFTEST
+{
+ /** Invalid test handle. */
+ IOPERFTEST_INVALID = 0,
+ /** The test was disabled. */
+ IOPERFTEST_DISABLED,
+ IOPERFTEST_FIRST_WRITE,
+ IOPERFTEST_SEQ_READ,
+ IOPERFTEST_SEQ_WRITE,
+ IOPERFTEST_REV_READ,
+ IOPERFTEST_REV_WRITE,
+ IOPERFTEST_RND_READ,
+ IOPERFTEST_RND_WRITE,
+ IOPERFTEST_SEQ_READWRITE,
+ IOPERFTEST_RND_READWRITE,
+ /** Special shutdown test which lets the workers exit, must be LAST. */
+ IOPERFTEST_SHUTDOWN,
+ IOPERFTEST_32BIT_HACK = 0x7fffffff
+} IOPERFTEST;
+
+
+/**
+ * I/O perf test set preparation method.
+ */
+typedef enum IOPERFTESTSETPREP
+{
+ IOPERFTESTSETPREP_INVALID = 0,
+ /** Just create the file and don't set any sizes. */
+ IOPERFTESTSETPREP_JUST_CREATE,
+ /** Standard RTFileSetSize() call which might create a sparse file. */
+ IOPERFTESTSETPREP_SET_SZ,
+ /** Uses RTFileSetAllocationSize() to ensure storage is allocated for the file. */
+ IOPERFTESTSETPREP_SET_ALLOC_SZ,
+ /** 32bit hack. */
+ IOPERFTESTSETPREP_32BIT_HACK = 0x7fffffff
+} IOPERFTESTSETPREP;
+
+
+/**
+ * Statistics values for a single request kept around until the
+ * test completed for statistics collection.
+ */
+typedef struct IOPERFREQSTAT
+{
+ /** Start timestamp for the request. */
+ uint64_t tsStart;
+ /** Completion timestamp for the request. */
+ uint64_t tsComplete;
+} IOPERFREQSTAT;
+/** Pointer to a request statistics record. */
+typedef IOPERFREQSTAT *PIOPERFREQSTAT;
+
+
+/**
+ * I/O perf request.
+ */
+typedef struct IOPERFREQ
+{
+ /** Request operation code. */
+ RTIOQUEUEOP enmOp;
+ /** Start offset. */
+ uint64_t offXfer;
+ /** Transfer size for the request. */
+ size_t cbXfer;
+ /** The buffer used for the transfer. */
+ void *pvXfer;
+ /** This is the statically assigned destination buffer for read requests for this request. */
+ void *pvXferRead;
+ /** Size of the read buffer. */
+ size_t cbXferRead;
+ /** Pointer to statistics record. */
+ PIOPERFREQSTAT pStats;
+} IOPERFREQ;
+/** Pointer to an I/O perf request. */
+typedef IOPERFREQ *PIOPERFREQ;
+/** Pointer to a constant I/O perf request. */
+typedef const IOPERFREQ *PCIOPERFREQ;
+
+
+/**
+ * I/O perf job data.
+ */
+typedef struct IOPERFJOB
+{
+ /** Pointer to the master if multiple jobs are running. */
+ PIOPERFMASTER pMaster;
+ /** Job ID. */
+ uint32_t idJob;
+ /** The test this job is executing. */
+ volatile IOPERFTEST enmTest;
+ /** The thread executing the job. */
+ RTTHREAD hThread;
+ /** The I/O queue for the job. */
+ RTIOQUEUE hIoQueue;
+ /** The file path used. */
+ char *pszFilename;
+ /** The handle to use for the I/O queue. */
+ RTHANDLE Hnd;
+ /** Multi event semaphore to synchronise with other jobs. */
+ RTSEMEVENTMULTI hSemEvtMultiRendezvous;
+ /** The test set size. */
+ uint64_t cbTestSet;
+ /** Size of one I/O block. */
+ size_t cbIoBlock;
+ /** Maximum number of requests to queue. */
+ uint32_t cReqsMax;
+ /** Pointer to the array of request specific data. */
+ PIOPERFREQ paIoReqs;
+ /** Page aligned chunk of memory assigned as read buffers for the individual requests. */
+ void *pvIoReqReadBuf;
+ /** Size of the read memory buffer. */
+ size_t cbIoReqReadBuf;
+ /** Random number generator used. */
+ RTRAND hRand;
+ /** The random data buffer used for writes. */
+ uint8_t *pbRandWrite;
+ /** Size of the random write buffer in 512 byte blocks. */
+ uint32_t cRandWriteBlocks512B;
+ /** Chance in percent to get a write. */
+ unsigned uWriteChance;
+ /** Flag whether to verify read data. */
+ bool fVerifyReads;
+ /** Start timestamp. */
+ uint64_t tsStart;
+ /** End timestamp. for the job. */
+ uint64_t tsFinish;
+ /** Number of request statistic records. */
+ uint32_t cReqStats;
+ /** Index of the next free statistics record to use. */
+ uint32_t idxReqStatNext;
+ /** Array of request statistic records for the whole test. */
+ PIOPERFREQSTAT paReqStats;
+ /** Test dependent data. */
+ union
+ {
+ /** Sequential read write. */
+ uint64_t offNextSeq;
+ /** Data for random acess. */
+ struct
+ {
+ /** Number of valid entries in the bitmap. */
+ uint32_t cBlocks;
+ /** Pointer to the bitmap marking accessed blocks. */
+ uint8_t *pbMapAccessed;
+ /** Number of unaccessed blocks. */
+ uint32_t cBlocksLeft;
+ } Rnd;
+ } Tst;
+} IOPERFJOB;
+/** Pointer to an I/O Perf job. */
+typedef IOPERFJOB *PIOPERFJOB;
+
+
+/**
+ * I/O perf master instance coordinating the job execution.
+ */
+typedef struct IOPERFMASTER
+{
+ /** Event semaphore. */
+ /** Number of jobs. */
+ uint32_t cJobs;
+ /** Job instances, variable in size. */
+ IOPERFJOB aJobs[1];
+} IOPERFMASTER;
+
+
+enum
+{
+ kCmdOpt_First = 128,
+
+ kCmdOpt_FirstWrite = kCmdOpt_First,
+ kCmdOpt_NoFirstWrite,
+ kCmdOpt_SeqRead,
+ kCmdOpt_NoSeqRead,
+ kCmdOpt_SeqWrite,
+ kCmdOpt_NoSeqWrite,
+ kCmdOpt_RndRead,
+ kCmdOpt_NoRndRead,
+ kCmdOpt_RndWrite,
+ kCmdOpt_NoRndWrite,
+ kCmdOpt_RevRead,
+ kCmdOpt_NoRevRead,
+ kCmdOpt_RevWrite,
+ kCmdOpt_NoRevWrite,
+ kCmdOpt_SeqReadWrite,
+ kCmdOpt_NoSeqReadWrite,
+ kCmdOpt_RndReadWrite,
+ kCmdOpt_NoRndReadWrite,
+
+ kCmdOpt_End
+};
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** Command line parameters */
+static const RTGETOPTDEF g_aCmdOptions[] =
+{
+ { "--dir", 'd', RTGETOPT_REQ_STRING },
+ { "--relative-dir", 'r', RTGETOPT_REQ_NOTHING },
+
+ { "--jobs", 'j', RTGETOPT_REQ_UINT32 },
+ { "--io-engine", 'i', RTGETOPT_REQ_STRING },
+ { "--test-set-size", 's', RTGETOPT_REQ_UINT64 },
+ { "--block-size", 'b', RTGETOPT_REQ_UINT32 },
+ { "--maximum-requests", 'm', RTGETOPT_REQ_UINT32 },
+ { "--verify-reads", 'y', RTGETOPT_REQ_BOOL },
+ { "--use-cache", 'c', RTGETOPT_REQ_BOOL },
+
+ { "--first-write", kCmdOpt_FirstWrite, RTGETOPT_REQ_NOTHING },
+ { "--no-first-write", kCmdOpt_NoFirstWrite, RTGETOPT_REQ_NOTHING },
+ { "--seq-read", kCmdOpt_SeqRead, RTGETOPT_REQ_NOTHING },
+ { "--no-seq-read", kCmdOpt_NoSeqRead, RTGETOPT_REQ_NOTHING },
+ { "--seq-write", kCmdOpt_SeqWrite, RTGETOPT_REQ_NOTHING },
+ { "--no-seq-write", kCmdOpt_NoSeqWrite, RTGETOPT_REQ_NOTHING },
+ { "--rnd-read", kCmdOpt_RndRead, RTGETOPT_REQ_NOTHING },
+ { "--no-rnd-read", kCmdOpt_NoRndRead, RTGETOPT_REQ_NOTHING },
+ { "--rnd-write", kCmdOpt_RndWrite, RTGETOPT_REQ_NOTHING },
+ { "--no-rnd-write", kCmdOpt_NoRndWrite, RTGETOPT_REQ_NOTHING },
+ { "--rev-read", kCmdOpt_RevRead, RTGETOPT_REQ_NOTHING },
+ { "--no-rev-read", kCmdOpt_NoRevRead, RTGETOPT_REQ_NOTHING },
+ { "--rev-write", kCmdOpt_RevWrite, RTGETOPT_REQ_NOTHING },
+ { "--no-rev-write", kCmdOpt_NoRevWrite, RTGETOPT_REQ_NOTHING },
+ { "--seq-read-write", kCmdOpt_SeqReadWrite, RTGETOPT_REQ_NOTHING },
+ { "--no-seq-read-write", kCmdOpt_NoSeqReadWrite, RTGETOPT_REQ_NOTHING },
+ { "--rnd-read-write", kCmdOpt_RndReadWrite, RTGETOPT_REQ_NOTHING },
+ { "--no-rnd-read-write", kCmdOpt_NoRndReadWrite, RTGETOPT_REQ_NOTHING },
+
+ { "--quiet", 'q', RTGETOPT_REQ_NOTHING },
+ { "--verbose", 'v', RTGETOPT_REQ_NOTHING },
+ { "--version", 'V', RTGETOPT_REQ_NOTHING },
+ { "--help", 'h', RTGETOPT_REQ_NOTHING } /* for Usage() */
+};
+
+/** The test handle. */
+static RTTEST g_hTest;
+/** Verbosity level. */
+static uint32_t g_uVerbosity = 0;
+/** Selected I/O engine for the tests, NULL means pick best default one. */
+static const char *g_pszIoEngine = NULL;
+/** Number of jobs to run concurrently. */
+static uint32_t g_cJobs = 1;
+/** Size of each test set (file) in bytes. */
+static uint64_t g_cbTestSet = _2G;
+/** Block size for each request. */
+static size_t g_cbIoBlock = _4K;
+/** Maximum number of concurrent requests for each job. */
+static uint32_t g_cReqsMax = 16;
+/** Flag whether to open the file without caching enabled. */
+static bool g_fNoCache = true;
+/** Write chance for mixed read/write tests. */
+static unsigned g_uWriteChance = 50;
+/** Flag whether to verify read data. */
+static bool g_fVerifyReads = true;
+
+/** @name Configured tests, this must match the IOPERFTEST order.
+ * @{ */
+static IOPERFTEST g_aenmTests[] =
+{
+ IOPERFTEST_DISABLED, /** @< The invalid test value is disabled of course. */
+ IOPERFTEST_DISABLED,
+ IOPERFTEST_FIRST_WRITE,
+ IOPERFTEST_SEQ_READ,
+ IOPERFTEST_SEQ_WRITE,
+ IOPERFTEST_REV_READ,
+ IOPERFTEST_REV_WRITE,
+ IOPERFTEST_RND_READ,
+ IOPERFTEST_RND_WRITE,
+ IOPERFTEST_SEQ_READWRITE,
+ IOPERFTEST_RND_READWRITE,
+ IOPERFTEST_SHUTDOWN
+};
+/** The test index being selected next. */
+static uint32_t g_idxTest = 2;
+/** @} */
+
+/** Set if g_szDir and friends are path relative to CWD rather than absolute. */
+static bool g_fRelativeDir = false;
+/** The length of g_szDir. */
+static size_t g_cchDir;
+
+/** The test directory (absolute). This will always have a trailing slash. */
+static char g_szDir[RTPATH_BIG_MAX];
+
+
+/*********************************************************************************************************************************
+* Tests *
+*********************************************************************************************************************************/
+
+
+/**
+ * Selects the next test to run.
+ *
+ * @return Next test to run.
+ */
+static IOPERFTEST ioPerfJobTestSelectNext()
+{
+ AssertReturn(g_idxTest < RT_ELEMENTS(g_aenmTests), IOPERFTEST_SHUTDOWN);
+
+ while ( g_idxTest < RT_ELEMENTS(g_aenmTests)
+ && g_aenmTests[g_idxTest] == IOPERFTEST_DISABLED)
+ g_idxTest++;
+
+ AssertReturn(g_idxTest < RT_ELEMENTS(g_aenmTests), IOPERFTEST_SHUTDOWN);
+
+ return g_aenmTests[g_idxTest++];
+}
+
+
+/**
+ * Returns the I/O queue operation for the next request.
+ *
+ * @returns I/O queue operation enum.
+ * @param pJob The job data for the current worker.
+ */
+static RTIOQUEUEOP ioPerfJobTestGetIoQOp(PIOPERFJOB pJob)
+{
+ switch (pJob->enmTest)
+ {
+ case IOPERFTEST_FIRST_WRITE:
+ case IOPERFTEST_SEQ_WRITE:
+ case IOPERFTEST_REV_WRITE:
+ case IOPERFTEST_RND_WRITE:
+ return RTIOQUEUEOP_WRITE;
+
+ case IOPERFTEST_SEQ_READ:
+ case IOPERFTEST_RND_READ:
+ case IOPERFTEST_REV_READ:
+ return RTIOQUEUEOP_READ;
+
+ case IOPERFTEST_SEQ_READWRITE:
+ case IOPERFTEST_RND_READWRITE:
+ {
+ uint32_t uRnd = RTRandAdvU32Ex(pJob->hRand, 0, 100);
+ return (uRnd < pJob->uWriteChance) ? RTIOQUEUEOP_WRITE : RTIOQUEUEOP_READ;
+ }
+
+ default:
+ AssertMsgFailed(("Invalid/unknown test selected: %d\n", pJob->enmTest));
+ break;
+ }
+
+ return RTIOQUEUEOP_INVALID;
+}
+
+
+/**
+ * Returns the offset to use for the next request.
+ *
+ * @returns Offset to use.
+ * @param pJob The job data for the current worker.
+ */
+static uint64_t ioPerfJobTestGetOffsetNext(PIOPERFJOB pJob)
+{
+ uint64_t offNext = 0;
+
+ switch (pJob->enmTest)
+ {
+ case IOPERFTEST_FIRST_WRITE:
+ case IOPERFTEST_SEQ_WRITE:
+ case IOPERFTEST_SEQ_READ:
+ case IOPERFTEST_SEQ_READWRITE:
+ offNext = pJob->Tst.offNextSeq;
+ pJob->Tst.offNextSeq += pJob->cbIoBlock;
+ break;
+ case IOPERFTEST_REV_WRITE:
+ case IOPERFTEST_REV_READ:
+ offNext = pJob->Tst.offNextSeq;
+ if (pJob->Tst.offNextSeq == 0)
+ pJob->Tst.offNextSeq = pJob->cbTestSet;
+ else
+ pJob->Tst.offNextSeq -= pJob->cbIoBlock;
+ break;
+ case IOPERFTEST_RND_WRITE:
+ case IOPERFTEST_RND_READ:
+ case IOPERFTEST_RND_READWRITE:
+ {
+ int idx = -1;
+
+ idx = ASMBitFirstClear(pJob->Tst.Rnd.pbMapAccessed, pJob->Tst.Rnd.cBlocks);
+
+ /* In case this is the last request we don't need to search further. */
+ if (pJob->Tst.Rnd.cBlocksLeft > 1)
+ {
+ int idxIo;
+ idxIo = RTRandAdvU32Ex(pJob->hRand, idx, pJob->Tst.Rnd.cBlocks - 1);
+
+ /*
+ * If the bit is marked free use it, otherwise search for the next free bit
+ * and if that doesn't work use the first free bit.
+ */
+ if (ASMBitTest(pJob->Tst.Rnd.pbMapAccessed, idxIo))
+ {
+ idxIo = ASMBitNextClear(pJob->Tst.Rnd.pbMapAccessed, pJob->Tst.Rnd.cBlocks, idxIo);
+ if (idxIo != -1)
+ idx = idxIo;
+ }
+ else
+ idx = idxIo;
+ }
+
+ Assert(idx != -1);
+ offNext = (uint64_t)idx * pJob->cbIoBlock;
+ pJob->Tst.Rnd.cBlocksLeft--;
+ ASMBitSet(pJob->Tst.Rnd.pbMapAccessed, idx);
+ break;
+ }
+ default:
+ AssertMsgFailed(("Invalid/unknown test selected: %d\n", pJob->enmTest));
+ break;
+ }
+
+ return offNext;
+}
+
+
+/**
+ * Returns a pointer to the write buffer with random data for the given offset which
+ * is predictable for data verification.
+ *
+ * @returns Pointer to I/O block sized data buffer with random data.
+ * @param pJob The job data for the current worker.
+ * @param off The offset to get the buffer for.
+ */
+static void *ioPerfJobTestGetWriteBufForOffset(PIOPERFJOB pJob, uint64_t off)
+{
+ /*
+ * Dividing the file into 512 byte blocks so buffer pointers are at least
+ * 512 byte aligned to work with async I/O on some platforms (Linux and O_DIRECT for example).
+ */
+ uint64_t uBlock = off / 512;
+ uint32_t idxBuf = uBlock % pJob->cRandWriteBlocks512B;
+ return pJob->pbRandWrite + idxBuf * 512;
+}
+
+
+/**
+ * Initialize the given request for submission.
+ *
+ * @param pJob The job data for the current worker.
+ * @param pIoReq The request to initialize.
+ */
+static void ioPerfJobTestReqInit(PIOPERFJOB pJob, PIOPERFREQ pIoReq)
+{
+ pIoReq->enmOp = ioPerfJobTestGetIoQOp(pJob);
+ pIoReq->offXfer = ioPerfJobTestGetOffsetNext(pJob);
+ pIoReq->cbXfer = pJob->cbIoBlock;
+ if (pIoReq->enmOp == RTIOQUEUEOP_READ)
+ pIoReq->pvXfer = pIoReq->pvXferRead;
+ else if (pIoReq->enmOp == RTIOQUEUEOP_WRITE)
+ pIoReq->pvXfer = ioPerfJobTestGetWriteBufForOffset(pJob, pIoReq->offXfer);
+ else /* Flush */
+ pIoReq->pvXfer = NULL;
+
+ Assert(pJob->idxReqStatNext < pJob->cReqStats);
+ if (RT_LIKELY(pJob->idxReqStatNext < pJob->cReqStats))
+ {
+ pIoReq->pStats = &pJob->paReqStats[pJob->idxReqStatNext++];
+ pIoReq->pStats->tsStart = RTTimeNanoTS();
+ }
+ else
+ pIoReq->pStats = NULL;
+}
+
+
+/**
+ * Returns a stringified version of the test given.
+ *
+ * @returns Pointer to string representation of the test.
+ * @param enmTest The test to stringify.
+ */
+static const char *ioPerfJobTestStringify(IOPERFTEST enmTest)
+{
+ switch (enmTest)
+ {
+ case IOPERFTEST_FIRST_WRITE:
+ return "FirstWrite";
+ case IOPERFTEST_SEQ_WRITE:
+ return "SequentialWrite";
+ case IOPERFTEST_SEQ_READ:
+ return "SequentialRead";
+ case IOPERFTEST_REV_WRITE:
+ return "ReverseWrite";
+ case IOPERFTEST_REV_READ:
+ return "ReverseRead";
+ case IOPERFTEST_RND_WRITE:
+ return "RandomWrite";
+ case IOPERFTEST_RND_READ:
+ return "RandomRead";
+ case IOPERFTEST_SEQ_READWRITE:
+ return "SequentialReadWrite";
+ case IOPERFTEST_RND_READWRITE:
+ return "RandomReadWrite";
+ default:
+ AssertMsgFailed(("Invalid/unknown test selected: %d\n", enmTest));
+ break;
+ }
+
+ return "INVALID_TEST";
+}
+
+
+/**
+ * Initializes the test state for the current test.
+ *
+ * @returns IPRT status code.
+ * @param pJob The job data for the current worker.
+ */
+static int ioPerfJobTestInit(PIOPERFJOB pJob)
+{
+ int rc = VINF_SUCCESS;
+
+ pJob->idxReqStatNext = 0;
+
+ switch (pJob->enmTest)
+ {
+ case IOPERFTEST_FIRST_WRITE:
+ case IOPERFTEST_SEQ_WRITE:
+ case IOPERFTEST_SEQ_READ:
+ case IOPERFTEST_SEQ_READWRITE:
+ pJob->Tst.offNextSeq = 0;
+ break;
+ case IOPERFTEST_REV_WRITE:
+ case IOPERFTEST_REV_READ:
+ pJob->Tst.offNextSeq = pJob->cbTestSet - pJob->cbIoBlock;
+ break;
+ case IOPERFTEST_RND_WRITE:
+ case IOPERFTEST_RND_READ:
+ case IOPERFTEST_RND_READWRITE:
+ {
+ pJob->Tst.Rnd.cBlocks = (uint32_t)( pJob->cbTestSet / pJob->cbIoBlock
+ + (pJob->cbTestSet % pJob->cbIoBlock ? 1 : 0));
+ pJob->Tst.Rnd.cBlocksLeft = pJob->Tst.Rnd.cBlocks;
+ pJob->Tst.Rnd.pbMapAccessed = (uint8_t *)RTMemAllocZ( pJob->Tst.Rnd.cBlocks / 8
+ + ((pJob->Tst.Rnd.cBlocks % 8)
+ ? 1
+ : 0));
+ if (!pJob->Tst.Rnd.pbMapAccessed)
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+ default:
+ AssertMsgFailed(("Invalid/unknown test selected: %d\n", pJob->enmTest));
+ break;
+ }
+
+ pJob->tsStart = RTTimeNanoTS();
+ return rc;
+}
+
+
+/**
+ * Frees allocated resources specific for the current test.
+ *
+ * @param pJob The job data for the current worker.
+ */
+static void ioPerfJobTestFinish(PIOPERFJOB pJob)
+{
+ pJob->tsFinish = RTTimeNanoTS();
+
+ switch (pJob->enmTest)
+ {
+ case IOPERFTEST_FIRST_WRITE:
+ case IOPERFTEST_SEQ_WRITE:
+ case IOPERFTEST_SEQ_READ:
+ case IOPERFTEST_REV_WRITE:
+ case IOPERFTEST_REV_READ:
+ case IOPERFTEST_SEQ_READWRITE:
+ break; /* Nothing to do. */
+
+ case IOPERFTEST_RND_WRITE:
+ case IOPERFTEST_RND_READ:
+ case IOPERFTEST_RND_READWRITE:
+ RTMemFree(pJob->Tst.Rnd.pbMapAccessed);
+ break;
+ default:
+ AssertMsgFailed(("Invalid/unknown test selected: %d\n", pJob->enmTest));
+ break;
+ }
+}
+
+
+/**
+ * Returns whether the current test is done with submitting new requests (reached test set size).
+ *
+ * @returns True when the test has submitted all required requests, false if there are still requests required
+ */
+static bool ioPerfJobTestIsDone(PIOPERFJOB pJob)
+{
+ switch (pJob->enmTest)
+ {
+ case IOPERFTEST_FIRST_WRITE:
+ case IOPERFTEST_SEQ_WRITE:
+ case IOPERFTEST_SEQ_READ:
+ case IOPERFTEST_REV_WRITE:
+ case IOPERFTEST_REV_READ:
+ case IOPERFTEST_SEQ_READWRITE:
+ return pJob->Tst.offNextSeq == pJob->cbTestSet;
+ case IOPERFTEST_RND_WRITE:
+ case IOPERFTEST_RND_READ:
+ case IOPERFTEST_RND_READWRITE:
+ return pJob->Tst.Rnd.cBlocksLeft == 0;
+ break;
+ default:
+ AssertMsgFailed(("Invalid/unknown test selected: %d\n", pJob->enmTest));
+ break;
+ }
+
+ return true;
+}
+
+
+/**
+ * The test I/O loop pumping I/O.
+ *
+ * @returns IPRT status code.
+ * @param pJob The job data for the current worker.
+ */
+static int ioPerfJobTestIoLoop(PIOPERFJOB pJob)
+{
+ int rc = ioPerfJobTestInit(pJob);
+ if (RT_SUCCESS(rc))
+ {
+ /* Allocate the completion event array. */
+ uint32_t cReqsQueued = 0;
+ PRTIOQUEUECEVT paIoQCEvt = (PRTIOQUEUECEVT)RTMemAllocZ(pJob->cReqsMax * sizeof(RTIOQUEUECEVT));
+ if (RT_LIKELY(paIoQCEvt))
+ {
+ /* Queue requests up to the maximum. */
+ while ( (cReqsQueued < pJob->cReqsMax)
+ && !ioPerfJobTestIsDone(pJob)
+ && RT_SUCCESS(rc))
+ {
+ PIOPERFREQ pReq = &pJob->paIoReqs[cReqsQueued];
+ ioPerfJobTestReqInit(pJob, pReq);
+ RTTESTI_CHECK_RC(RTIoQueueRequestPrepare(pJob->hIoQueue, &pJob->Hnd, pReq->enmOp,
+ pReq->offXfer, pReq->pvXfer, pReq->cbXfer, 0 /*fReqFlags*/,
+ pReq), VINF_SUCCESS);
+ cReqsQueued++;
+ }
+
+ /* Commit the prepared requests. */
+ if ( RT_SUCCESS(rc)
+ && cReqsQueued)
+ {
+ RTTESTI_CHECK_RC(RTIoQueueCommit(pJob->hIoQueue), VINF_SUCCESS);
+ }
+
+ /* Enter wait loop and process completed requests. */
+ while ( RT_SUCCESS(rc)
+ && cReqsQueued)
+ {
+ uint32_t cCEvtCompleted = 0;
+
+ RTTESTI_CHECK_RC(RTIoQueueEvtWait(pJob->hIoQueue, paIoQCEvt, pJob->cReqsMax, 1 /*cMinWait*/,
+ &cCEvtCompleted, 0 /*fFlags*/), VINF_SUCCESS);
+ if (RT_SUCCESS(rc))
+ {
+ uint32_t cReqsThisQueued = 0;
+
+ /* Process any completed event and continue to fill the queue as long as there is stuff to do. */
+ for (uint32_t i = 0; i < cCEvtCompleted; i++)
+ {
+ PIOPERFREQ pReq = (PIOPERFREQ)paIoQCEvt[i].pvUser;
+
+ if (RT_SUCCESS(paIoQCEvt[i].rcReq))
+ {
+ Assert(paIoQCEvt[i].cbXfered == pReq->cbXfer);
+
+ if (pReq->pStats)
+ pReq->pStats->tsComplete = RTTimeNanoTS();
+
+ if ( pJob->fVerifyReads
+ && pReq->enmOp == RTIOQUEUEOP_READ)
+ {
+ const void *pvBuf = ioPerfJobTestGetWriteBufForOffset(pJob, pReq->offXfer);
+ if (memcmp(pReq->pvXferRead, pvBuf, pReq->cbXfer))
+ {
+ if (g_uVerbosity > 1)
+ RTTestIFailed("IoPerf: Corrupted data detected by read at offset %#llu (sz: %zu)", pReq->offXfer, pReq->cbXfer);
+ else
+ RTTestIErrorInc();
+ }
+ }
+
+ if (!ioPerfJobTestIsDone(pJob))
+ {
+ ioPerfJobTestReqInit(pJob, pReq);
+ RTTESTI_CHECK_RC(RTIoQueueRequestPrepare(pJob->hIoQueue, &pJob->Hnd, pReq->enmOp,
+ pReq->offXfer, pReq->pvXfer, pReq->cbXfer, 0 /*fReqFlags*/,
+ pReq), VINF_SUCCESS);
+ cReqsThisQueued++;
+ }
+ else
+ cReqsQueued--;
+ }
+ else
+ RTTestIErrorInc();
+ }
+
+ if ( cReqsThisQueued
+ && RT_SUCCESS(rc))
+ {
+ RTTESTI_CHECK_RC(RTIoQueueCommit(pJob->hIoQueue), VINF_SUCCESS);
+ }
+ }
+ }
+
+ RTMemFree(paIoQCEvt);
+ }
+
+ ioPerfJobTestFinish(pJob);
+ }
+
+ return rc;
+}
+
+
+/**
+ * Calculates the statistic values for the given job after a
+ * test finished.
+ *
+ * @param pJob The job data.
+ */
+static void ioPerfJobStats(PIOPERFJOB pJob)
+{
+ const char *pszTest = ioPerfJobTestStringify(pJob->enmTest);
+ uint64_t nsJobRuntime = pJob->tsFinish - pJob->tsStart;
+ RTTestIValueF(nsJobRuntime, RTTESTUNIT_NS, "%s/Job/%RU32/Runtime", pszTest, pJob->idJob);
+
+ uint64_t *paReqRuntimeNs = (uint64_t *)RTMemAllocZ(pJob->cReqStats * sizeof(uint64_t));
+ if (RT_LIKELY(paReqRuntimeNs))
+ {
+ /* Calculate runtimes for each request first. */
+ for (uint32_t i = 0; i < pJob->cReqStats; i++)
+ {
+ PIOPERFREQSTAT pStat = &pJob->paReqStats[i];
+ paReqRuntimeNs[i] = pStat->tsComplete - pStat->tsStart;
+ }
+
+ /* Get average bandwidth for the job. */
+ RTTestIValueF((uint64_t)((double)pJob->cbTestSet / ((double)nsJobRuntime / RT_NS_1SEC)),
+ RTTESTUNIT_BYTES_PER_SEC, "%s/Job/%RU32/AvgBandwidth", pszTest, pJob->idJob);
+
+ RTTestIValueF((uint64_t)(pJob->cReqStats / ((double)nsJobRuntime / RT_NS_1SEC)),
+ RTTESTUNIT_OCCURRENCES_PER_SEC, "%s/Job/%RU32/AvgIops", pszTest, pJob->idJob);
+
+ /* Calculate the average latency for the requests. */
+ uint64_t uLatency = 0;
+ for (uint32_t i = 0; i < pJob->cReqStats; i++)
+ uLatency += paReqRuntimeNs[i];
+ RTTestIValueF(uLatency / pJob->cReqStats, RTTESTUNIT_NS, "%s/Job/%RU32/AvgLatency", pszTest, pJob->idJob);
+
+ RTMemFree(paReqRuntimeNs);
+ }
+ else
+ RTTestIErrorInc();
+}
+
+
+/**
+ * Synchronizes with the other jobs and waits for the current test to execute.
+ *
+ * @returns IPRT status.
+ * @param pJob The job data for the current worker.
+ */
+static int ioPerfJobSync(PIOPERFJOB pJob)
+{
+ if (pJob->pMaster)
+ {
+ /* Enter the rendezvous semaphore. */
+ int rc = VINF_SUCCESS;
+
+ return rc;
+ }
+
+ /* Single threaded run, collect the results from our current test and select the next test. */
+ /** @todo Results and statistics collection. */
+ pJob->enmTest = ioPerfJobTestSelectNext();
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * I/O perf job main work loop.
+ *
+ * @returns IPRT status code.
+ * @param pJob The job data for the current worker.
+ */
+static int ioPerfJobWorkLoop(PIOPERFJOB pJob)
+{
+ int rc = VINF_SUCCESS;
+
+ for (;;)
+ {
+ /* Synchronize with the other jobs and the master. */
+ rc = ioPerfJobSync(pJob);
+ if (RT_FAILURE(rc))
+ break;
+
+ if (pJob->enmTest == IOPERFTEST_SHUTDOWN)
+ break;
+
+ rc = ioPerfJobTestIoLoop(pJob);
+ if (RT_FAILURE(rc))
+ break;
+
+ /*
+ * Do the statistics here for a single job run,
+ * the master will do this for each job and combined statistics
+ * otherwise.
+ */
+ if (!pJob->pMaster)
+ ioPerfJobStats(pJob);
+ }
+
+ return rc;
+}
+
+
+/**
+ * Job thread entry point.
+ */
+static DECLCALLBACK(int) ioPerfJobThread(RTTHREAD hThrdSelf, void *pvUser)
+{
+ RT_NOREF(hThrdSelf);
+
+ PIOPERFJOB pJob = (PIOPERFJOB)pvUser;
+ return ioPerfJobWorkLoop(pJob);
+}
+
+
+/**
+ * Prepares the test set by laying out the files and filling them with data.
+ *
+ * @returns IPRT status code.
+ * @param pJob The job to initialize.
+ */
+static int ioPerfJobTestSetPrep(PIOPERFJOB pJob)
+{
+ int rc = RTRandAdvCreateParkMiller(&pJob->hRand);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTRandAdvSeed(pJob->hRand, RTTimeNanoTS());
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Create a random data buffer for writes, we'll use multiple of the I/O block size to
+ * be able to seek in the buffer quite a bit to make the file content as random as possible
+ * to avoid mechanisms like compression or deduplication for now which can influence storage
+ * benchmarking unpredictably.
+ */
+ pJob->cRandWriteBlocks512B = (uint32_t)(((IOPERF_RAND_DATA_BUF_FACTOR - 1) * pJob->cbIoBlock) / 512);
+ pJob->pbRandWrite = (uint8_t *)RTMemPageAllocZ(IOPERF_RAND_DATA_BUF_FACTOR * pJob->cbIoBlock);
+ if (RT_LIKELY(pJob->pbRandWrite))
+ {
+ RTRandAdvBytes(pJob->hRand, pJob->pbRandWrite, IOPERF_RAND_DATA_BUF_FACTOR * pJob->cbIoBlock);
+
+ /* Write the content here if the first write test is disabled. */
+ if (g_aenmTests[IOPERFTEST_FIRST_WRITE] == IOPERFTEST_DISABLED)
+ {
+ for (uint64_t off = 0; off < pJob->cbTestSet && RT_SUCCESS(rc); off += pJob->cbIoBlock)
+ {
+ void *pvWrite = ioPerfJobTestGetWriteBufForOffset(pJob, off);
+ rc = RTFileWriteAt(pJob->Hnd.u.hFile, off, pvWrite, pJob->cbIoBlock, NULL);
+ }
+ }
+
+ if (RT_SUCCESS(rc))
+ return rc;
+
+ RTMemPageFree(pJob->pbRandWrite, IOPERF_RAND_DATA_BUF_FACTOR * pJob->cbIoBlock);
+ }
+ }
+ RTRandAdvDestroy(pJob->hRand);
+ }
+
+ return rc;
+}
+
+
+/**
+ * Initializes the given job instance.
+ *
+ * @returns IPRT status code.
+ * @param pJob The job to initialize.
+ * @param pMaster The coordination master if any.
+ * @param idJob ID of the job.
+ * @param pszIoEngine I/O queue engine for this job, NULL for best default.
+ * @param pszTestDir The test directory to create the file in - requires a slash a the end.
+ * @param enmPrepMethod Test set preparation method to use.
+ * @param cbTestSet Size of the test set ofr this job.
+ * @param cbIoBlock I/O block size for the given job.
+ * @param cReqsMax Maximum number of concurrent requests for this job.
+ * @param uWriteChance The write chance for mixed read/write tests.
+ * @param fVerifyReads Flag whether to verify read data.
+ */
+static int ioPerfJobInit(PIOPERFJOB pJob, PIOPERFMASTER pMaster, uint32_t idJob,
+ const char *pszIoEngine, const char *pszTestDir,
+ IOPERFTESTSETPREP enmPrepMethod,
+ uint64_t cbTestSet, size_t cbIoBlock, uint32_t cReqsMax,
+ unsigned uWriteChance, bool fVerifyReads)
+{
+ pJob->pMaster = pMaster;
+ pJob->idJob = idJob;
+ pJob->enmTest = IOPERFTEST_INVALID;
+ pJob->hThread = NIL_RTTHREAD;
+ pJob->Hnd.enmType = RTHANDLETYPE_FILE;
+ pJob->cbTestSet = cbTestSet;
+ pJob->cbIoBlock = cbIoBlock;
+ pJob->cReqsMax = cReqsMax;
+ pJob->cbIoReqReadBuf = cReqsMax * cbIoBlock;
+ pJob->uWriteChance = uWriteChance;
+ pJob->fVerifyReads = fVerifyReads;
+ pJob->cReqStats = (uint32_t)(pJob->cbTestSet / pJob->cbIoBlock + ((pJob->cbTestSet % pJob->cbIoBlock) ? 1 : 0));
+ pJob->idxReqStatNext = 0;
+
+ int rc = VINF_SUCCESS;
+ pJob->paIoReqs = (PIOPERFREQ)RTMemAllocZ(cReqsMax * sizeof(IOPERFREQ));
+ if (RT_LIKELY(pJob->paIoReqs))
+ {
+ pJob->paReqStats = (PIOPERFREQSTAT)RTMemAllocZ(pJob->cReqStats * sizeof(IOPERFREQSTAT));
+ if (RT_LIKELY(pJob->paReqStats))
+ {
+ pJob->pvIoReqReadBuf = RTMemPageAlloc(pJob->cbIoReqReadBuf);
+ if (RT_LIKELY(pJob->pvIoReqReadBuf))
+ {
+ uint8_t *pbReadBuf = (uint8_t *)pJob->pvIoReqReadBuf;
+
+ for (uint32_t i = 0; i < cReqsMax; i++)
+ {
+ pJob->paIoReqs[i].pvXferRead = pbReadBuf;
+ pJob->paIoReqs[i].cbXferRead = cbIoBlock;
+ pbReadBuf += cbIoBlock;
+ }
+
+ /* Create the file. */
+ pJob->pszFilename = RTStrAPrintf2("%sioperf-%u.file", pszTestDir, idJob);
+ if (RT_LIKELY(pJob->pszFilename))
+ {
+ uint32_t fOpen = RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE | RTFILE_O_READWRITE | RTFILE_O_ASYNC_IO;
+ if (g_fNoCache)
+ fOpen |= RTFILE_O_NO_CACHE;
+ rc = RTFileOpen(&pJob->Hnd.u.hFile, pJob->pszFilename, fOpen);
+ if (RT_SUCCESS(rc))
+ {
+ switch (enmPrepMethod)
+ {
+ case IOPERFTESTSETPREP_JUST_CREATE:
+ break;
+ case IOPERFTESTSETPREP_SET_SZ:
+ rc = RTFileSetSize(pJob->Hnd.u.hFile, pJob->cbTestSet);
+ break;
+ case IOPERFTESTSETPREP_SET_ALLOC_SZ:
+ rc = RTFileSetAllocationSize(pJob->Hnd.u.hFile, pJob->cbTestSet, RTFILE_ALLOC_SIZE_F_DEFAULT);
+ break;
+ default:
+ AssertMsgFailed(("Invalid file preparation method: %d\n", enmPrepMethod));
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ rc = ioPerfJobTestSetPrep(pJob);
+ if (RT_SUCCESS(rc))
+ {
+ /* Create I/O queue. */
+ PCRTIOQUEUEPROVVTABLE pIoQProv = NULL;
+ if (!pszIoEngine)
+ pIoQProv = RTIoQueueProviderGetBestForHndType(RTHANDLETYPE_FILE);
+ else
+ pIoQProv = RTIoQueueProviderGetById(pszIoEngine);
+
+ if (RT_LIKELY(pIoQProv))
+ {
+ rc = RTIoQueueCreate(&pJob->hIoQueue, pIoQProv, 0 /*fFlags*/, cReqsMax, cReqsMax);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTIoQueueHandleRegister(pJob->hIoQueue, &pJob->Hnd);
+ if (RT_SUCCESS(rc))
+ {
+ /* Spin up the worker thread. */
+ if (pMaster)
+ rc = RTThreadCreateF(&pJob->hThread, ioPerfJobThread, pJob, 0,
+ RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, "ioperf-%u", idJob);
+
+ if (RT_SUCCESS(rc))
+ return VINF_SUCCESS;
+ }
+ }
+ }
+ else
+ rc = VERR_NOT_SUPPORTED;
+ }
+
+ RTRandAdvDestroy(pJob->hRand);
+ }
+
+ RTFileClose(pJob->Hnd.u.hFile);
+ RTFileDelete(pJob->pszFilename);
+ }
+
+ RTStrFree(pJob->pszFilename);
+ }
+ else
+ rc = VERR_NO_STR_MEMORY;
+
+ RTMemPageFree(pJob->pvIoReqReadBuf, pJob->cbIoReqReadBuf);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ RTMemFree(pJob->paReqStats);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ return rc;
+}
+
+
+/**
+ * Teardown a job instance and free all associated resources.
+ *
+ * @returns IPRT status code.
+ * @param pJob The job to teardown.
+ */
+static int ioPerfJobTeardown(PIOPERFJOB pJob)
+{
+ if (pJob->pMaster)
+ {
+ int rc = RTThreadWait(pJob->hThread, RT_INDEFINITE_WAIT, NULL);
+ AssertRC(rc); RT_NOREF(rc);
+ }
+
+ RTIoQueueHandleDeregister(pJob->hIoQueue, &pJob->Hnd);
+ RTIoQueueDestroy(pJob->hIoQueue);
+ RTRandAdvDestroy(pJob->hRand);
+ RTMemPageFree(pJob->pbRandWrite, IOPERF_RAND_DATA_BUF_FACTOR * pJob->cbIoBlock);
+ RTFileClose(pJob->Hnd.u.hFile);
+ RTFileDelete(pJob->pszFilename);
+ RTStrFree(pJob->pszFilename);
+ RTMemPageFree(pJob->pvIoReqReadBuf, pJob->cbIoReqReadBuf);
+ RTMemFree(pJob->paIoReqs);
+ RTMemFree(pJob->paReqStats);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Single job testing entry point.
+ *
+ * @returns IPRT status code.
+ */
+static int ioPerfDoTestSingle(void)
+{
+ IOPERFJOB Job;
+
+ int rc = ioPerfJobInit(&Job, NULL, 0, g_pszIoEngine,
+ g_szDir, IOPERFTESTSETPREP_SET_SZ,
+ g_cbTestSet, g_cbIoBlock, g_cReqsMax,
+ g_uWriteChance, g_fVerifyReads);
+ if (RT_SUCCESS(rc))
+ {
+ rc = ioPerfJobWorkLoop(&Job);
+ if (RT_SUCCESS(rc))
+ {
+ rc = ioPerfJobTeardown(&Job);
+ AssertRC(rc); RT_NOREF(rc);
+ }
+ }
+
+ return rc;
+}
+
+
+/**
+ * Multi job testing entry point.
+ *
+ * @returns IPRT status code.
+ */
+static int ioPerfDoTestMulti(void)
+{
+ return VERR_NOT_IMPLEMENTED;
+}
+
+
+/**
+ * Display the usage to @a pStrm.
+ */
+static void Usage(PRTSTREAM pStrm)
+{
+ char szExec[RTPATH_MAX];
+ RTStrmPrintf(pStrm, "usage: %s <-d <testdir>> [options]\n",
+ RTPathFilename(RTProcGetExecutablePath(szExec, sizeof(szExec))));
+ RTStrmPrintf(pStrm, "\n");
+ RTStrmPrintf(pStrm, "options: \n");
+
+ for (unsigned i = 0; i < RT_ELEMENTS(g_aCmdOptions); i++)
+ {
+ char szHelp[80];
+ const char *pszHelp;
+ switch (g_aCmdOptions[i].iShort)
+ {
+ case 'd': pszHelp = "The directory to use for testing. default: CWD/fstestdir"; break;
+ case 'r': pszHelp = "Don't abspath test dir (good for deep dirs). default: disabled"; break;
+ case 'y': pszHelp = "Flag whether to verify read data. default: enabled"; break;
+ case 'c': pszHelp = "Flag whether to use the filesystem cache. default: disabled"; break;
+ case 'v': pszHelp = "More verbose execution."; break;
+ case 'q': pszHelp = "Quiet execution."; break;
+ case 'h': pszHelp = "Displays this help and exit"; break;
+ case 'V': pszHelp = "Displays the program revision"; break;
+ default:
+ if (g_aCmdOptions[i].iShort >= kCmdOpt_First)
+ {
+ if (RTStrStartsWith(g_aCmdOptions[i].pszLong, "--no-"))
+ RTStrPrintf(szHelp, sizeof(szHelp), "Disables the '%s' test.", g_aCmdOptions[i].pszLong + 5);
+ else
+ RTStrPrintf(szHelp, sizeof(szHelp), "Enables the '%s' test.", g_aCmdOptions[i].pszLong + 2);
+ pszHelp = szHelp;
+ }
+ else
+ pszHelp = "Option undocumented";
+ break;
+ }
+ if ((unsigned)g_aCmdOptions[i].iShort < 127U)
+ {
+ char szOpt[64];
+ RTStrPrintf(szOpt, sizeof(szOpt), "%s, -%c", g_aCmdOptions[i].pszLong, g_aCmdOptions[i].iShort);
+ RTStrmPrintf(pStrm, " %-19s %s\n", szOpt, pszHelp);
+ }
+ else
+ RTStrmPrintf(pStrm, " %-19s %s\n", g_aCmdOptions[i].pszLong, pszHelp);
+ }
+}
+
+
+int main(int argc, char *argv[])
+{
+ /*
+ * Init IPRT and globals.
+ */
+ int rc = RTTestInitAndCreate("IoPerf", &g_hTest);
+ if (rc)
+ return rc;
+
+ /*
+ * Default values.
+ */
+ char szDefaultDir[32];
+ const char *pszDir = szDefaultDir;
+ RTStrPrintf(szDefaultDir, sizeof(szDefaultDir), "ioperfdir-%u" RTPATH_SLASH_STR, RTProcSelf());
+
+ RTGETOPTUNION ValueUnion;
+ RTGETOPTSTATE GetState;
+ RTGetOptInit(&GetState, argc, argv, g_aCmdOptions, RT_ELEMENTS(g_aCmdOptions), 1, 0 /* fFlags */);
+ while ((rc = RTGetOpt(&GetState, &ValueUnion)) != 0)
+ {
+ switch (rc)
+ {
+ case 'd':
+ pszDir = ValueUnion.psz;
+ break;
+
+ case 'r':
+ g_fRelativeDir = true;
+ break;
+
+ case 'i':
+ g_pszIoEngine = ValueUnion.psz;
+ break;
+
+ case 's':
+ g_cbTestSet = ValueUnion.u64;
+ break;
+
+ case 'b':
+ g_cbIoBlock = ValueUnion.u32;
+ break;
+
+ case 'm':
+ g_cReqsMax = ValueUnion.u32;
+ break;
+
+ case 'y':
+ g_fVerifyReads = ValueUnion.f;
+ break;
+
+ case 'c':
+ g_fNoCache = !ValueUnion.f;
+ break;
+
+ case kCmdOpt_FirstWrite:
+ g_aenmTests[IOPERFTEST_FIRST_WRITE] = IOPERFTEST_FIRST_WRITE;
+ break;
+ case kCmdOpt_NoFirstWrite:
+ g_aenmTests[IOPERFTEST_FIRST_WRITE] = IOPERFTEST_DISABLED;
+ break;
+ case kCmdOpt_SeqRead:
+ g_aenmTests[IOPERFTEST_SEQ_READ] = IOPERFTEST_SEQ_READ;
+ break;
+ case kCmdOpt_NoSeqRead:
+ g_aenmTests[IOPERFTEST_SEQ_READ] = IOPERFTEST_DISABLED;
+ break;
+ case kCmdOpt_SeqWrite:
+ g_aenmTests[IOPERFTEST_SEQ_WRITE] = IOPERFTEST_SEQ_WRITE;
+ break;
+ case kCmdOpt_NoSeqWrite:
+ g_aenmTests[IOPERFTEST_SEQ_WRITE] = IOPERFTEST_DISABLED;
+ break;
+ case kCmdOpt_RndRead:
+ g_aenmTests[IOPERFTEST_RND_READ] = IOPERFTEST_RND_READ;
+ break;
+ case kCmdOpt_NoRndRead:
+ g_aenmTests[IOPERFTEST_RND_READ] = IOPERFTEST_DISABLED;
+ break;
+ case kCmdOpt_RndWrite:
+ g_aenmTests[IOPERFTEST_RND_WRITE] = IOPERFTEST_RND_WRITE;
+ break;
+ case kCmdOpt_NoRndWrite:
+ g_aenmTests[IOPERFTEST_RND_WRITE] = IOPERFTEST_DISABLED;
+ break;
+ case kCmdOpt_RevRead:
+ g_aenmTests[IOPERFTEST_REV_READ] = IOPERFTEST_REV_READ;
+ break;
+ case kCmdOpt_NoRevRead:
+ g_aenmTests[IOPERFTEST_REV_READ] = IOPERFTEST_DISABLED;
+ break;
+ case kCmdOpt_RevWrite:
+ g_aenmTests[IOPERFTEST_REV_WRITE] = IOPERFTEST_REV_WRITE;
+ break;
+ case kCmdOpt_NoRevWrite:
+ g_aenmTests[IOPERFTEST_REV_WRITE] = IOPERFTEST_DISABLED;
+ break;
+ case kCmdOpt_SeqReadWrite:
+ g_aenmTests[IOPERFTEST_SEQ_READWRITE] = IOPERFTEST_SEQ_READWRITE;
+ break;
+ case kCmdOpt_NoSeqReadWrite:
+ g_aenmTests[IOPERFTEST_SEQ_READWRITE] = IOPERFTEST_DISABLED;
+ break;
+ case kCmdOpt_RndReadWrite:
+ g_aenmTests[IOPERFTEST_RND_READWRITE] = IOPERFTEST_RND_READWRITE;
+ break;
+ case kCmdOpt_NoRndReadWrite:
+ g_aenmTests[IOPERFTEST_RND_READWRITE] = IOPERFTEST_DISABLED;
+ break;
+
+ case 'q':
+ g_uVerbosity = 0;
+ break;
+
+ case 'v':
+ g_uVerbosity++;
+ break;
+
+ case 'h':
+ Usage(g_pStdOut);
+ return RTEXITCODE_SUCCESS;
+
+ case 'V':
+ {
+ char szRev[] = "$Revision: 157380 $";
+ szRev[RT_ELEMENTS(szRev) - 2] = '\0';
+ RTPrintf(RTStrStrip(strchr(szRev, ':') + 1));
+ return RTEXITCODE_SUCCESS;
+ }
+
+ default:
+ return RTGetOptPrintError(rc, &ValueUnion);
+ }
+ }
+
+ /*
+ * Populate g_szDir.
+ */
+ if (!g_fRelativeDir)
+ rc = RTPathAbs(pszDir, g_szDir, sizeof(g_szDir));
+ else
+ rc = RTStrCopy(g_szDir, sizeof(g_szDir), pszDir);
+ if (RT_FAILURE(rc))
+ {
+ RTTestFailed(g_hTest, "%s(%s) failed: %Rrc\n", g_fRelativeDir ? "RTStrCopy" : "RTAbsPath", pszDir, rc);
+ return RTTestSummaryAndDestroy(g_hTest);
+ }
+ RTPathEnsureTrailingSeparator(g_szDir, sizeof(g_szDir));
+ g_cchDir = strlen(g_szDir);
+
+ /*
+ * Create the test directory with an 'empty' subdirectory under it,
+ * execute the tests, and remove directory when done.
+ */
+ RTTestBanner(g_hTest);
+ if (!RTPathExists(g_szDir))
+ {
+ /* The base dir: */
+ rc = RTDirCreate(g_szDir, 0755,
+ RTDIRCREATE_FLAGS_NOT_CONTENT_INDEXED_DONT_SET | RTDIRCREATE_FLAGS_NOT_CONTENT_INDEXED_NOT_CRITICAL);
+ if (RT_SUCCESS(rc))
+ {
+ RTTestIPrintf(RTTESTLVL_ALWAYS, "Test dir: %s\n", g_szDir);
+
+ if (g_cJobs == 1)
+ rc = ioPerfDoTestSingle();
+ else
+ rc = ioPerfDoTestMulti();
+
+ g_szDir[g_cchDir] = '\0';
+ rc = RTDirRemoveRecursive(g_szDir, RTDIRRMREC_F_CONTENT_AND_DIR | (g_fRelativeDir ? RTDIRRMREC_F_NO_ABS_PATH : 0));
+ if (RT_FAILURE(rc))
+ RTTestFailed(g_hTest, "RTDirRemoveRecursive(%s,) -> %Rrc\n", g_szDir, rc);
+ }
+ else
+ RTTestFailed(g_hTest, "RTDirCreate(%s) -> %Rrc\n", g_szDir, rc);
+ }
+ else
+ RTTestFailed(g_hTest, "Test directory already exists: %s\n", g_szDir);
+
+ return RTTestSummaryAndDestroy(g_hTest);
+}
+