VirtualBox

source: vbox/trunk/src/VBox/ValidationKit/utils/storage/IoPerf.cpp@ 82799

Last change on this file since 82799 was 80251, checked in by vboxsync, 6 years ago

ValidationKit/IoPerf: Collapse the tests into a single test when running to avoid running into to deep sub test depth errors when running the storage benchmark testcase which is already hitting the limit of 10 nested tests configured

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 49.2 KB
Line 
1/* $Id: IoPerf.cpp 80251 2019-08-13 12:53:12Z vboxsync $ */
2/** @file
3 * IoPerf - Storage I/O Performance Benchmark.
4 */
5
6/*
7 * Copyright (C) 2019 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 *
17 * The contents of this file may alternatively be used under the terms
18 * of the Common Development and Distribution License Version 1.0
19 * (CDDL) only, as it comes in the "COPYING.CDDL" file of the
20 * VirtualBox OSE distribution, in which case the provisions of the
21 * CDDL are applicable instead of those of the GPL.
22 *
23 * You may elect to license modified versions of this file under the
24 * terms and conditions of either the GPL or the CDDL or both.
25 */
26
27
28/*********************************************************************************************************************************
29* Header Files *
30*********************************************************************************************************************************/
31#include <iprt/asm.h>
32#include <iprt/assert.h>
33#include <iprt/err.h>
34#include <iprt/dir.h>
35#include <iprt/file.h>
36#include <iprt/getopt.h>
37#include <iprt/initterm.h>
38#include <iprt/ioqueue.h>
39#include <iprt/list.h>
40#include <iprt/mem.h>
41#include <iprt/message.h>
42#include <iprt/param.h>
43#include <iprt/path.h>
44#include <iprt/process.h>
45#include <iprt/rand.h>
46#include <iprt/string.h>
47#include <iprt/stream.h>
48#include <iprt/system.h>
49#include <iprt/test.h>
50#include <iprt/time.h>
51#include <iprt/thread.h>
52#include <iprt/zero.h>
53
54
55/*********************************************************************************************************************************
56* Defined Constants And Macros *
57*********************************************************************************************************************************/
58
59/** Size multiplier for the random data buffer to seek around. */
60#define IOPERF_RAND_DATA_BUF_FACTOR 3
61
62
63/*********************************************************************************************************************************
64* Structures and Typedefs *
65*********************************************************************************************************************************/
66
67/** Forward declaration of the master. */
68typedef struct IOPERFMASTER *PIOPERFMASTER;
69
70/**
71 * I/O perf supported tests.
72 */
73typedef enum IOPERFTEST
74{
75 /** Invalid test handle. */
76 IOPERFTEST_INVALID = 0,
77 /** The test was disabled. */
78 IOPERFTEST_DISABLED,
79 IOPERFTEST_FIRST_WRITE,
80 IOPERFTEST_SEQ_READ,
81 IOPERFTEST_SEQ_WRITE,
82 IOPERFTEST_REV_READ,
83 IOPERFTEST_REV_WRITE,
84 IOPERFTEST_RND_READ,
85 IOPERFTEST_RND_WRITE,
86 IOPERFTEST_SEQ_READWRITE,
87 IOPERFTEST_RND_READWRITE,
88 /** Special shutdown test which lets the workers exit, must be LAST. */
89 IOPERFTEST_SHUTDOWN,
90 IOPERFTEST_32BIT_HACK = 0x7fffffff
91} IOPERFTEST;
92
93
94/**
95 * I/O perf test set preparation method.
96 */
97typedef enum IOPERFTESTSETPREP
98{
99 IOPERFTESTSETPREP_INVALID = 0,
100 /** Just create the file and don't set any sizes. */
101 IOPERFTESTSETPREP_JUST_CREATE,
102 /** Standard RTFileSetSize() call which might create a sparse file. */
103 IOPERFTESTSETPREP_SET_SZ,
104 /** Uses RTFileSetAllocationSize() to ensure storage is allocated for the file. */
105 IOPERFTESTSETPREP_SET_ALLOC_SZ,
106 /** 32bit hack. */
107 IOPERFTESTSETPREP_32BIT_HACK = 0x7fffffff
108} IOPERFTESTSETPREP;
109
110
111/**
112 * Statistics values for a single request kept around until the
113 * test completed for statistics collection.
114 */
115typedef struct IOPERFREQSTAT
116{
117 /** Start timestamp for the request. */
118 uint64_t tsStart;
119 /** Completion timestamp for the request. */
120 uint64_t tsComplete;
121} IOPERFREQSTAT;
122/** Pointer to a request statistics record. */
123typedef IOPERFREQSTAT *PIOPERFREQSTAT;
124
125
126/**
127 * I/O perf request.
128 */
129typedef struct IOPERFREQ
130{
131 /** Request operation code. */
132 RTIOQUEUEOP enmOp;
133 /** Start offset. */
134 uint64_t offXfer;
135 /** Transfer size for the request. */
136 size_t cbXfer;
137 /** The buffer used for the transfer. */
138 void *pvXfer;
139 /** This is the statically assigned destination buffer for read requests for this request. */
140 void *pvXferRead;
141 /** Size of the read buffer. */
142 size_t cbXferRead;
143 /** Pointer to statistics record. */
144 PIOPERFREQSTAT pStats;
145} IOPERFREQ;
146/** Pointer to an I/O perf request. */
147typedef IOPERFREQ *PIOPERFREQ;
148/** Pointer to a constant I/O perf request. */
149typedef const IOPERFREQ *PCIOPERFREQ;
150
151
152/**
153 * I/O perf job data.
154 */
155typedef struct IOPERFJOB
156{
157 /** Pointer to the master if multiple jobs are running. */
158 PIOPERFMASTER pMaster;
159 /** Job ID. */
160 uint32_t idJob;
161 /** The test this job is executing. */
162 volatile IOPERFTEST enmTest;
163 /** The thread executing the job. */
164 RTTHREAD hThread;
165 /** The I/O queue for the job. */
166 RTIOQUEUE hIoQueue;
167 /** The file path used. */
168 char *pszFilename;
169 /** The handle to use for the I/O queue. */
170 RTHANDLE Hnd;
171 /** Multi event semaphore to synchronise with other jobs. */
172 RTSEMEVENTMULTI hSemEvtMultiRendezvous;
173 /** The test set size. */
174 uint64_t cbTestSet;
175 /** Size of one I/O block. */
176 size_t cbIoBlock;
177 /** Maximum number of requests to queue. */
178 uint32_t cReqsMax;
179 /** Pointer to the array of request specific data. */
180 PIOPERFREQ paIoReqs;
181 /** Page aligned chunk of memory assigned as read buffers for the individual requests. */
182 void *pvIoReqReadBuf;
183 /** Size of the read memory buffer. */
184 size_t cbIoReqReadBuf;
185 /** Random number generator used. */
186 RTRAND hRand;
187 /** The random data buffer used for writes. */
188 uint8_t *pbRandWrite;
189 /** Size of the random write buffer in 512 byte blocks. */
190 uint32_t cRandWriteBlocks512B;
191 /** Chance in percent to get a write. */
192 unsigned uWriteChance;
193 /** Flag whether to verify read data. */
194 bool fVerifyReads;
195 /** Start timestamp. */
196 uint64_t tsStart;
197 /** End timestamp. for the job. */
198 uint64_t tsFinish;
199 /** Number of request statistic records. */
200 uint32_t cReqStats;
201 /** Index of the next free statistics record to use. */
202 uint32_t idxReqStatNext;
203 /** Array of request statistic records for the whole test. */
204 PIOPERFREQSTAT paReqStats;
205 /** Test dependent data. */
206 union
207 {
208 /** Sequential read write. */
209 uint64_t offNextSeq;
210 /** Data for random acess. */
211 struct
212 {
213 /** Number of valid entries in the bitmap. */
214 uint32_t cBlocks;
215 /** Pointer to the bitmap marking accessed blocks. */
216 uint8_t *pbMapAccessed;
217 /** Number of unaccessed blocks. */
218 uint32_t cBlocksLeft;
219 } Rnd;
220 } Tst;
221} IOPERFJOB;
222/** Pointer to an I/O Perf job. */
223typedef IOPERFJOB *PIOPERFJOB;
224
225
226/**
227 * I/O perf master instance coordinating the job execution.
228 */
229typedef struct IOPERFMASTER
230{
231 /** Event semaphore. */
232 /** Number of jobs. */
233 uint32_t cJobs;
234 /** Job instances, variable in size. */
235 IOPERFJOB aJobs[1];
236} IOPERFMASTER;
237
238
239enum
240{
241 kCmdOpt_First = 128,
242
243 kCmdOpt_FirstWrite = kCmdOpt_First,
244 kCmdOpt_NoFirstWrite,
245 kCmdOpt_SeqRead,
246 kCmdOpt_NoSeqRead,
247 kCmdOpt_SeqWrite,
248 kCmdOpt_NoSeqWrite,
249 kCmdOpt_RndRead,
250 kCmdOpt_NoRndRead,
251 kCmdOpt_RndWrite,
252 kCmdOpt_NoRndWrite,
253 kCmdOpt_RevRead,
254 kCmdOpt_NoRevRead,
255 kCmdOpt_RevWrite,
256 kCmdOpt_NoRevWrite,
257 kCmdOpt_SeqReadWrite,
258 kCmdOpt_NoSeqReadWrite,
259 kCmdOpt_RndReadWrite,
260 kCmdOpt_NoRndReadWrite,
261
262 kCmdOpt_End
263};
264
265
266/*********************************************************************************************************************************
267* Global Variables *
268*********************************************************************************************************************************/
269/** Command line parameters */
270static const RTGETOPTDEF g_aCmdOptions[] =
271{
272 { "--dir", 'd', RTGETOPT_REQ_STRING },
273 { "--relative-dir", 'r', RTGETOPT_REQ_NOTHING },
274
275 { "--jobs", 'j', RTGETOPT_REQ_UINT32 },
276 { "--io-engine", 'i', RTGETOPT_REQ_STRING },
277 { "--test-set-size", 's', RTGETOPT_REQ_UINT64 },
278 { "--block-size", 'b', RTGETOPT_REQ_UINT32 },
279 { "--maximum-requests", 'm', RTGETOPT_REQ_UINT32 },
280 { "--verify-reads", 'y', RTGETOPT_REQ_BOOL },
281 { "--use-cache", 'c', RTGETOPT_REQ_BOOL },
282
283 { "--first-write", kCmdOpt_FirstWrite, RTGETOPT_REQ_NOTHING },
284 { "--no-first-write", kCmdOpt_NoFirstWrite, RTGETOPT_REQ_NOTHING },
285 { "--seq-read", kCmdOpt_SeqRead, RTGETOPT_REQ_NOTHING },
286 { "--no-seq-read", kCmdOpt_NoSeqRead, RTGETOPT_REQ_NOTHING },
287 { "--seq-write", kCmdOpt_SeqWrite, RTGETOPT_REQ_NOTHING },
288 { "--no-seq-write", kCmdOpt_NoSeqWrite, RTGETOPT_REQ_NOTHING },
289 { "--rnd-read", kCmdOpt_RndRead, RTGETOPT_REQ_NOTHING },
290 { "--no-rnd-read", kCmdOpt_NoRndRead, RTGETOPT_REQ_NOTHING },
291 { "--rnd-write", kCmdOpt_RndWrite, RTGETOPT_REQ_NOTHING },
292 { "--no-rnd-write", kCmdOpt_NoRndWrite, RTGETOPT_REQ_NOTHING },
293 { "--rev-read", kCmdOpt_RevRead, RTGETOPT_REQ_NOTHING },
294 { "--no-rev-read", kCmdOpt_NoRevRead, RTGETOPT_REQ_NOTHING },
295 { "--rev-write", kCmdOpt_RevWrite, RTGETOPT_REQ_NOTHING },
296 { "--no-rev-write", kCmdOpt_NoRevWrite, RTGETOPT_REQ_NOTHING },
297 { "--seq-read-write", kCmdOpt_SeqReadWrite, RTGETOPT_REQ_NOTHING },
298 { "--no-seq-read-write", kCmdOpt_NoSeqReadWrite, RTGETOPT_REQ_NOTHING },
299 { "--rnd-read-write", kCmdOpt_RndReadWrite, RTGETOPT_REQ_NOTHING },
300 { "--no-rnd-read-write", kCmdOpt_NoRndReadWrite, RTGETOPT_REQ_NOTHING },
301
302 { "--quiet", 'q', RTGETOPT_REQ_NOTHING },
303 { "--verbose", 'v', RTGETOPT_REQ_NOTHING },
304 { "--version", 'V', RTGETOPT_REQ_NOTHING },
305 { "--help", 'h', RTGETOPT_REQ_NOTHING } /* for Usage() */
306};
307
308/** The test handle. */
309static RTTEST g_hTest;
310/** Verbosity level. */
311static uint32_t g_uVerbosity = 0;
312/** Selected I/O engine for the tests, NULL means pick best default one. */
313static const char *g_pszIoEngine = NULL;
314/** Number of jobs to run concurrently. */
315static uint32_t g_cJobs = 1;
316/** Size of each test set (file) in bytes. */
317static uint64_t g_cbTestSet = _2G;
318/** Block size for each request. */
319static size_t g_cbIoBlock = _4K;
320/** Maximum number of concurrent requests for each job. */
321static uint32_t g_cReqsMax = 16;
322/** Flag whether to open the file without caching enabled. */
323static bool g_fNoCache = true;
324/** Write chance for mixed read/write tests. */
325static unsigned g_uWriteChance = 50;
326/** Flag whether to verify read data. */
327static bool g_fVerifyReads = true;
328
329/** @name Configured tests, this must match the IOPERFTEST order.
330 * @{ */
331static IOPERFTEST g_aenmTests[] =
332{
333 IOPERFTEST_DISABLED, /** @< The invalid test value is disabled of course. */
334 IOPERFTEST_DISABLED,
335 IOPERFTEST_FIRST_WRITE,
336 IOPERFTEST_SEQ_READ,
337 IOPERFTEST_SEQ_WRITE,
338 IOPERFTEST_REV_READ,
339 IOPERFTEST_REV_WRITE,
340 IOPERFTEST_RND_READ,
341 IOPERFTEST_RND_WRITE,
342 IOPERFTEST_SEQ_READWRITE,
343 IOPERFTEST_RND_READWRITE,
344 IOPERFTEST_SHUTDOWN
345};
346/** The test index being selected next. */
347static uint32_t g_idxTest = 2;
348/** @} */
349
350/** Set if g_szDir and friends are path relative to CWD rather than absolute. */
351static bool g_fRelativeDir = false;
352/** The length of g_szDir. */
353static size_t g_cchDir;
354
355/** The test directory (absolute). This will always have a trailing slash. */
356static char g_szDir[RTPATH_BIG_MAX];
357
358
359/*********************************************************************************************************************************
360* Tests *
361*********************************************************************************************************************************/
362
363
364/**
365 * Selects the next test to run.
366 *
367 * @return Next test to run.
368 */
369static IOPERFTEST ioPerfJobTestSelectNext()
370{
371 AssertReturn(g_idxTest < RT_ELEMENTS(g_aenmTests), IOPERFTEST_SHUTDOWN);
372
373 while ( g_idxTest < RT_ELEMENTS(g_aenmTests)
374 && g_aenmTests[g_idxTest] == IOPERFTEST_DISABLED)
375 g_idxTest++;
376
377 AssertReturn(g_idxTest < RT_ELEMENTS(g_aenmTests), IOPERFTEST_SHUTDOWN);
378
379 return g_aenmTests[g_idxTest++];
380}
381
382
383/**
384 * Returns the I/O queue operation for the next request.
385 *
386 * @returns I/O queue operation enum.
387 * @param pJob The job data for the current worker.
388 */
389static RTIOQUEUEOP ioPerfJobTestGetIoQOp(PIOPERFJOB pJob)
390{
391 switch (pJob->enmTest)
392 {
393 case IOPERFTEST_FIRST_WRITE:
394 case IOPERFTEST_SEQ_WRITE:
395 case IOPERFTEST_SEQ_READ:
396 case IOPERFTEST_REV_WRITE:
397 case IOPERFTEST_RND_WRITE:
398 return RTIOQUEUEOP_WRITE;
399
400 case IOPERFTEST_RND_READ:
401 case IOPERFTEST_REV_READ:
402 return RTIOQUEUEOP_READ;
403
404 case IOPERFTEST_SEQ_READWRITE:
405 case IOPERFTEST_RND_READWRITE:
406 {
407 uint32_t uRnd = RTRandAdvU32Ex(pJob->hRand, 0, 100);
408 return (uRnd < pJob->uWriteChance) ? RTIOQUEUEOP_WRITE : RTIOQUEUEOP_READ;
409 }
410
411 default:
412 AssertMsgFailed(("Invalid/unknown test selected: %d\n", pJob->enmTest));
413 break;
414 }
415
416 return RTIOQUEUEOP_INVALID;
417}
418
419
420/**
421 * Returns the offset to use for the next request.
422 *
423 * @returns Offset to use.
424 * @param pJob The job data for the current worker.
425 */
426static uint64_t ioPerfJobTestGetOffsetNext(PIOPERFJOB pJob)
427{
428 uint64_t offNext = 0;
429
430 switch (pJob->enmTest)
431 {
432 case IOPERFTEST_FIRST_WRITE:
433 case IOPERFTEST_SEQ_WRITE:
434 case IOPERFTEST_SEQ_READ:
435 case IOPERFTEST_SEQ_READWRITE:
436 offNext = pJob->Tst.offNextSeq;
437 pJob->Tst.offNextSeq += pJob->cbIoBlock;
438 break;
439 case IOPERFTEST_REV_WRITE:
440 case IOPERFTEST_REV_READ:
441 offNext = pJob->Tst.offNextSeq;
442 if (pJob->Tst.offNextSeq == 0)
443 pJob->Tst.offNextSeq = pJob->cbTestSet;
444 else
445 pJob->Tst.offNextSeq -= pJob->cbIoBlock;
446 break;
447 case IOPERFTEST_RND_WRITE:
448 case IOPERFTEST_RND_READ:
449 case IOPERFTEST_RND_READWRITE:
450 {
451 int idx = -1;
452
453 idx = ASMBitFirstClear(pJob->Tst.Rnd.pbMapAccessed, pJob->Tst.Rnd.cBlocks);
454
455 /* In case this is the last request we don't need to search further. */
456 if (pJob->Tst.Rnd.cBlocksLeft > 1)
457 {
458 int idxIo;
459 idxIo = RTRandAdvU32Ex(pJob->hRand, idx, pJob->Tst.Rnd.cBlocks - 1);
460
461 /*
462 * If the bit is marked free use it, otherwise search for the next free bit
463 * and if that doesn't work use the first free bit.
464 */
465 if (ASMBitTest(pJob->Tst.Rnd.pbMapAccessed, idxIo))
466 {
467 idxIo = ASMBitNextClear(pJob->Tst.Rnd.pbMapAccessed, pJob->Tst.Rnd.cBlocks, idxIo);
468 if (idxIo != -1)
469 idx = idxIo;
470 }
471 else
472 idx = idxIo;
473 }
474
475 Assert(idx != -1);
476 offNext = (uint64_t)idx * pJob->cbIoBlock;
477 pJob->Tst.Rnd.cBlocksLeft--;
478 ASMBitSet(pJob->Tst.Rnd.pbMapAccessed, idx);
479 break;
480 }
481 default:
482 AssertMsgFailed(("Invalid/unknown test selected: %d\n", pJob->enmTest));
483 break;
484 }
485
486 return offNext;
487}
488
489
490/**
491 * Returns a pointer to the write buffer with random data for the given offset which
492 * is predictable for data verification.
493 *
494 * @returns Pointer to I/O block sized data buffer with random data.
495 * @param pJob The job data for the current worker.
496 * @param off The offset to get the buffer for.
497 */
498static void *ioPerfJobTestGetWriteBufForOffset(PIOPERFJOB pJob, uint64_t off)
499{
500 /*
501 * Dividing the file into 512 byte blocks so buffer pointers are at least
502 * 512 byte aligned to work with async I/O on some platforms (Linux and O_DIRECT for example).
503 */
504 uint64_t uBlock = off / 512;
505 uint32_t idxBuf = uBlock % pJob->cRandWriteBlocks512B;
506 return pJob->pbRandWrite + idxBuf * 512;
507}
508
509
510/**
511 * Initialize the given request for submission.
512 *
513 * @returns nothing.
514 * @param pJob The job data for the current worker.
515 * @param pIoReq The request to initialize.
516 */
517static void ioPerfJobTestReqInit(PIOPERFJOB pJob, PIOPERFREQ pIoReq)
518{
519 pIoReq->enmOp = ioPerfJobTestGetIoQOp(pJob);
520 pIoReq->offXfer = ioPerfJobTestGetOffsetNext(pJob);
521 pIoReq->cbXfer = pJob->cbIoBlock;
522 if (pIoReq->enmOp == RTIOQUEUEOP_READ)
523 pIoReq->pvXfer = pIoReq->pvXferRead;
524 else if (pIoReq->enmOp == RTIOQUEUEOP_WRITE)
525 pIoReq->pvXfer = ioPerfJobTestGetWriteBufForOffset(pJob, pIoReq->offXfer);
526 else /* Flush */
527 pIoReq->pvXfer = NULL;
528
529 Assert(pJob->idxReqStatNext < pJob->cReqStats);
530 if (RT_LIKELY(pJob->idxReqStatNext < pJob->cReqStats))
531 {
532 pIoReq->pStats = &pJob->paReqStats[pJob->idxReqStatNext++];
533 pIoReq->pStats->tsStart = RTTimeNanoTS();
534 }
535 else
536 pIoReq->pStats = NULL;
537}
538
539
540/**
541 * Returns a stringified version of the test given.
542 *
543 * @returns Pointer to string representation of the test.
544 * @param enmTest The test to stringify.
545 */
546static const char *ioPerfJobTestStringify(IOPERFTEST enmTest)
547{
548 switch (enmTest)
549 {
550 case IOPERFTEST_FIRST_WRITE:
551 return "FirstWrite";
552 case IOPERFTEST_SEQ_WRITE:
553 return "SequentialWrite";
554 case IOPERFTEST_SEQ_READ:
555 return "SequentialRead";
556 case IOPERFTEST_REV_WRITE:
557 return "ReverseWrite";
558 case IOPERFTEST_REV_READ:
559 return "ReverseRead";
560 case IOPERFTEST_RND_WRITE:
561 return "RandomWrite";
562 case IOPERFTEST_RND_READ:
563 return "RandomRead";
564 case IOPERFTEST_SEQ_READWRITE:
565 return "SequentialReadWrite";
566 case IOPERFTEST_RND_READWRITE:
567 return "RandomReadWrite";
568 default:
569 AssertMsgFailed(("Invalid/unknown test selected: %d\n", enmTest));
570 break;
571 }
572
573 return "INVALID_TEST";
574}
575
576
577/**
578 * Initializes the test state for the current test.
579 *
580 * @returns IPRT status code.
581 * @param pJob The job data for the current worker.
582 */
583static int ioPerfJobTestInit(PIOPERFJOB pJob)
584{
585 int rc = VINF_SUCCESS;
586
587 pJob->idxReqStatNext = 0;
588
589 switch (pJob->enmTest)
590 {
591 case IOPERFTEST_FIRST_WRITE:
592 case IOPERFTEST_SEQ_WRITE:
593 case IOPERFTEST_SEQ_READ:
594 case IOPERFTEST_SEQ_READWRITE:
595 pJob->Tst.offNextSeq = 0;
596 break;
597 case IOPERFTEST_REV_WRITE:
598 case IOPERFTEST_REV_READ:
599 pJob->Tst.offNextSeq = pJob->cbTestSet - pJob->cbIoBlock;
600 break;
601 case IOPERFTEST_RND_WRITE:
602 case IOPERFTEST_RND_READ:
603 case IOPERFTEST_RND_READWRITE:
604 {
605 pJob->Tst.Rnd.cBlocks = (uint32_t)( pJob->cbTestSet / pJob->cbIoBlock
606 + (pJob->cbTestSet % pJob->cbIoBlock ? 1 : 0));
607 pJob->Tst.Rnd.cBlocksLeft = pJob->Tst.Rnd.cBlocks;
608 pJob->Tst.Rnd.pbMapAccessed = (uint8_t *)RTMemAllocZ( pJob->Tst.Rnd.cBlocks / 8
609 + ((pJob->Tst.Rnd.cBlocks % 8)
610 ? 1
611 : 0));
612 if (!pJob->Tst.Rnd.pbMapAccessed)
613 rc = VERR_NO_MEMORY;
614 break;
615 }
616 default:
617 AssertMsgFailed(("Invalid/unknown test selected: %d\n", pJob->enmTest));
618 break;
619 }
620
621 pJob->tsStart = RTTimeNanoTS();
622 return rc;
623}
624
625
626/**
627 * Frees allocated resources specific for the current test.
628 *
629 * @returns nothing.
630 * @param pJob The job data for the current worker.
631 */
632static void ioPerfJobTestFinish(PIOPERFJOB pJob)
633{
634 pJob->tsFinish = RTTimeNanoTS();
635
636 switch (pJob->enmTest)
637 {
638 case IOPERFTEST_FIRST_WRITE:
639 case IOPERFTEST_SEQ_WRITE:
640 case IOPERFTEST_SEQ_READ:
641 case IOPERFTEST_REV_WRITE:
642 case IOPERFTEST_REV_READ:
643 case IOPERFTEST_SEQ_READWRITE:
644 break; /* Nothing to do. */
645
646 case IOPERFTEST_RND_WRITE:
647 case IOPERFTEST_RND_READ:
648 case IOPERFTEST_RND_READWRITE:
649 RTMemFree(pJob->Tst.Rnd.pbMapAccessed);
650 break;
651 default:
652 AssertMsgFailed(("Invalid/unknown test selected: %d\n", pJob->enmTest));
653 break;
654 }
655}
656
657
658/**
659 * Returns whether the current test is done with submitting new requests (reached test set size).
660 *
661 * @returns True when the test has submitted all required requests, false if there are still requests required
662 */
663static bool ioPerfJobTestIsDone(PIOPERFJOB pJob)
664{
665 switch (pJob->enmTest)
666 {
667 case IOPERFTEST_FIRST_WRITE:
668 case IOPERFTEST_SEQ_WRITE:
669 case IOPERFTEST_SEQ_READ:
670 case IOPERFTEST_REV_WRITE:
671 case IOPERFTEST_REV_READ:
672 case IOPERFTEST_SEQ_READWRITE:
673 return pJob->Tst.offNextSeq == pJob->cbTestSet;
674 case IOPERFTEST_RND_WRITE:
675 case IOPERFTEST_RND_READ:
676 case IOPERFTEST_RND_READWRITE:
677 return pJob->Tst.Rnd.cBlocksLeft == 0;
678 break;
679 default:
680 AssertMsgFailed(("Invalid/unknown test selected: %d\n", pJob->enmTest));
681 break;
682 }
683
684 return true;
685}
686
687
688/**
689 * The test I/O loop pumping I/O.
690 *
691 * @returns IPRT status code.
692 * @param pJob The job data for the current worker.
693 */
694static int ioPerfJobTestIoLoop(PIOPERFJOB pJob)
695{
696 int rc = ioPerfJobTestInit(pJob);
697 if (RT_SUCCESS(rc))
698 {
699 /* Allocate the completion event array. */
700 uint32_t cReqsQueued = 0;
701 PRTIOQUEUECEVT paIoQCEvt = (PRTIOQUEUECEVT)RTMemAllocZ(pJob->cReqsMax * sizeof(RTIOQUEUECEVT));
702 if (RT_LIKELY(paIoQCEvt))
703 {
704 /* Queue requests up to the maximum. */
705 while ( (cReqsQueued < pJob->cReqsMax)
706 && !ioPerfJobTestIsDone(pJob)
707 && RT_SUCCESS(rc))
708 {
709 PIOPERFREQ pReq = &pJob->paIoReqs[cReqsQueued];
710 ioPerfJobTestReqInit(pJob, pReq);
711 RTTESTI_CHECK_RC(RTIoQueueRequestPrepare(pJob->hIoQueue, &pJob->Hnd, pReq->enmOp,
712 pReq->offXfer, pReq->pvXfer, pReq->cbXfer, 0 /*fReqFlags*/,
713 pReq), VINF_SUCCESS);
714 cReqsQueued++;
715 }
716
717 /* Commit the prepared requests. */
718 if ( RT_SUCCESS(rc)
719 && cReqsQueued)
720 {
721 RTTESTI_CHECK_RC(RTIoQueueCommit(pJob->hIoQueue), VINF_SUCCESS);
722 }
723
724 /* Enter wait loop and process completed requests. */
725 while ( RT_SUCCESS(rc)
726 && cReqsQueued)
727 {
728 uint32_t cCEvtCompleted = 0;
729
730 RTTESTI_CHECK_RC(RTIoQueueEvtWait(pJob->hIoQueue, paIoQCEvt, pJob->cReqsMax, 1 /*cMinWait*/,
731 &cCEvtCompleted, 0 /*fFlags*/), VINF_SUCCESS);
732 if (RT_SUCCESS(rc))
733 {
734 uint32_t cReqsThisQueued = 0;
735
736 /* Process any completed event and continue to fill the queue as long as there is stuff to do. */
737 for (uint32_t i = 0; i < cCEvtCompleted; i++)
738 {
739 PIOPERFREQ pReq = (PIOPERFREQ)paIoQCEvt[i].pvUser;
740
741 if (RT_SUCCESS(paIoQCEvt[i].rcReq))
742 {
743 Assert(paIoQCEvt[i].cbXfered == pReq->cbXfer);
744
745 if (pReq->pStats)
746 pReq->pStats->tsComplete = RTTimeNanoTS();
747
748 if ( pJob->fVerifyReads
749 && pReq->enmOp == RTIOQUEUEOP_READ)
750 {
751 const void *pvBuf = ioPerfJobTestGetWriteBufForOffset(pJob, pReq->offXfer);
752 if (memcmp(pReq->pvXferRead, pvBuf, pReq->cbXfer))
753 {
754 if (g_uVerbosity > 1)
755 RTTestIFailed("IoPerf: Corrupted data detected by read at offset %#llu (sz: %zu)", pReq->offXfer, pReq->cbXfer);
756 else
757 RTTestIErrorInc();
758 }
759 }
760
761 if (!ioPerfJobTestIsDone(pJob))
762 {
763 ioPerfJobTestReqInit(pJob, pReq);
764 RTTESTI_CHECK_RC(RTIoQueueRequestPrepare(pJob->hIoQueue, &pJob->Hnd, pReq->enmOp,
765 pReq->offXfer, pReq->pvXfer, pReq->cbXfer, 0 /*fReqFlags*/,
766 pReq), VINF_SUCCESS);
767 cReqsThisQueued++;
768 }
769 else
770 cReqsQueued--;
771 }
772 else
773 RTTestIErrorInc();
774 }
775
776 if ( cReqsThisQueued
777 && RT_SUCCESS(rc))
778 {
779 RTTESTI_CHECK_RC(RTIoQueueCommit(pJob->hIoQueue), VINF_SUCCESS);
780 }
781 }
782 }
783
784 RTMemFree(paIoQCEvt);
785 }
786
787 ioPerfJobTestFinish(pJob);
788 }
789
790 return rc;
791}
792
793
794/**
795 * Calculates the statistic values for the given job after a
796 * test finished.
797 *
798 * @returns nothing.
799 * @param pJob The job data.
800 */
801static void ioPerfJobStats(PIOPERFJOB pJob)
802{
803 const char *pszTest = ioPerfJobTestStringify(pJob->enmTest);
804 uint64_t nsJobRuntime = pJob->tsFinish - pJob->tsStart;
805 RTTestIValueF(nsJobRuntime, RTTESTUNIT_NS, "%s/Job/%RU32/Runtime", pszTest, pJob->idJob);
806
807 uint64_t *paReqRuntimeNs = (uint64_t *)RTMemAllocZ(pJob->cReqStats * sizeof(uint64_t));
808 if (RT_LIKELY(paReqRuntimeNs))
809 {
810 /* Calculate runtimes for each request first. */
811 for (uint32_t i = 0; i < pJob->cReqStats; i++)
812 {
813 PIOPERFREQSTAT pStat = &pJob->paReqStats[i];
814 paReqRuntimeNs[i] = pStat->tsComplete - pStat->tsStart;
815 }
816
817 /* Get average bandwidth for the job. */
818 RTTestIValueF((uint64_t)(pJob->cbTestSet / ((double)nsJobRuntime / RT_NS_1SEC)),
819 RTTESTUNIT_BYTES_PER_SEC, "%s/Job/%RU32/AvgBandwidth", pszTest, pJob->idJob);
820
821 RTTestIValueF((uint64_t)(pJob->cReqStats / ((double)nsJobRuntime / RT_NS_1SEC)),
822 RTTESTUNIT_OCCURRENCES_PER_SEC, "%s/Job/%RU32/AvgIops", pszTest, pJob->idJob);
823
824 /* Calculate the average latency for the requests. */
825 uint64_t uLatency = 0;
826 for (uint32_t i = 0; i < pJob->cReqStats; i++)
827 uLatency += paReqRuntimeNs[i];
828 RTTestIValueF(uLatency / pJob->cReqStats, RTTESTUNIT_NS, "%s/Job/%RU32/AvgLatency", pszTest, pJob->idJob);
829
830 RTMemFree(paReqRuntimeNs);
831 }
832 else
833 RTTestIErrorInc();
834}
835
836
837/**
838 * Synchronizes with the other jobs and waits for the current test to execute.
839 *
840 * @returns IPRT status.
841 * @param pJob The job data for the current worker.
842 */
843static int ioPerfJobSync(PIOPERFJOB pJob)
844{
845 if (pJob->pMaster)
846 {
847 /* Enter the rendezvous semaphore. */
848 int rc = VINF_SUCCESS;
849
850 return rc;
851 }
852
853 /* Single threaded run, collect the results from our current test and select the next test. */
854 /** @todo Results and statistics collection. */
855 pJob->enmTest = ioPerfJobTestSelectNext();
856 return VINF_SUCCESS;
857}
858
859
860/**
861 * I/O perf job main work loop.
862 *
863 * @returns IPRT status code.
864 * @param pJob The job data for the current worker.
865 */
866static int ioPerfJobWorkLoop(PIOPERFJOB pJob)
867{
868 int rc = VINF_SUCCESS;
869
870 for (;;)
871 {
872 /* Synchronize with the other jobs and the master. */
873 rc = ioPerfJobSync(pJob);
874 if (RT_FAILURE(rc))
875 break;
876
877 if (pJob->enmTest == IOPERFTEST_SHUTDOWN)
878 break;
879
880 rc = ioPerfJobTestIoLoop(pJob);
881 if (RT_FAILURE(rc))
882 break;
883
884 /*
885 * Do the statistics here for a single job run,
886 * the master will do this for each job and combined statistics
887 * otherwise.
888 */
889 if (!pJob->pMaster)
890 ioPerfJobStats(pJob);
891 }
892
893 return rc;
894}
895
896
897/**
898 * Job thread entry point.
899 */
900static DECLCALLBACK(int) ioPerfJobThread(RTTHREAD hThrdSelf, void *pvUser)
901{
902 RT_NOREF(hThrdSelf);
903
904 PIOPERFJOB pJob = (PIOPERFJOB)pvUser;
905 return ioPerfJobWorkLoop(pJob);
906}
907
908
909/**
910 * Prepares the test set by laying out the files and filling them with data.
911 *
912 * @returns IPRT status code.
913 * @param pJob The job to initialize.
914 */
915static int ioPerfJobTestSetPrep(PIOPERFJOB pJob)
916{
917 int rc = RTRandAdvCreateParkMiller(&pJob->hRand);
918 if (RT_SUCCESS(rc))
919 {
920 rc = RTRandAdvSeed(pJob->hRand, RTTimeNanoTS());
921 if (RT_SUCCESS(rc))
922 {
923 /*
924 * Create a random data buffer for writes, we'll use multiple of the I/O block size to
925 * be able to seek in the buffer quite a bit to make the file content as random as possible
926 * to avoid mechanisms like compression or deduplication for now which can influence storage
927 * benchmarking unpredictably.
928 */
929 pJob->cRandWriteBlocks512B = (uint32_t)(((IOPERF_RAND_DATA_BUF_FACTOR - 1) * pJob->cbIoBlock) / 512);
930 pJob->pbRandWrite = (uint8_t *)RTMemPageAllocZ(IOPERF_RAND_DATA_BUF_FACTOR * pJob->cbIoBlock);
931 if (RT_LIKELY(pJob->pbRandWrite))
932 {
933 RTRandAdvBytes(pJob->hRand, pJob->pbRandWrite, IOPERF_RAND_DATA_BUF_FACTOR * pJob->cbIoBlock);
934
935 /* Write the content here if the first write test is disabled. */
936 if (g_aenmTests[IOPERFTEST_FIRST_WRITE] == IOPERFTEST_DISABLED)
937 {
938 for (uint64_t off = 0; off < pJob->cbTestSet && RT_SUCCESS(rc); off += pJob->cbIoBlock)
939 {
940 void *pvWrite = ioPerfJobTestGetWriteBufForOffset(pJob, off);
941 rc = RTFileWriteAt(pJob->Hnd.u.hFile, off, pvWrite, pJob->cbIoBlock, NULL);
942 }
943 }
944
945 if (RT_SUCCESS(rc))
946 return rc;
947
948 RTMemPageFree(pJob->pbRandWrite, IOPERF_RAND_DATA_BUF_FACTOR * pJob->cbIoBlock);
949 }
950 }
951 RTRandAdvDestroy(pJob->hRand);
952 }
953
954 return rc;
955}
956
957
958/**
959 * Initializes the given job instance.
960 *
961 * @returns IPRT status code.
962 * @param pJob The job to initialize.
963 * @param pMaster The coordination master if any.
964 * @param idJob ID of the job.
965 * @param pszIoEngine I/O queue engine for this job, NULL for best default.
966 * @param pszTestDir The test directory to create the file in - requires a slash a the end.
967 * @param enmPrepMethod Test set preparation method to use.
968 * @param cbTestSet Size of the test set ofr this job.
969 * @param cbIoBlock I/O block size for the given job.
970 * @param cReqsMax Maximum number of concurrent requests for this job.
971 * @param uWriteChance The write chance for mixed read/write tests.
972 * @param fVerifyReads Flag whether to verify read data.
973 */
974static int ioPerfJobInit(PIOPERFJOB pJob, PIOPERFMASTER pMaster, uint32_t idJob,
975 const char *pszIoEngine, const char *pszTestDir,
976 IOPERFTESTSETPREP enmPrepMethod,
977 uint64_t cbTestSet, size_t cbIoBlock, uint32_t cReqsMax,
978 unsigned uWriteChance, bool fVerifyReads)
979{
980 pJob->pMaster = pMaster;
981 pJob->idJob = idJob;
982 pJob->enmTest = IOPERFTEST_INVALID;
983 pJob->hThread = NIL_RTTHREAD;
984 pJob->Hnd.enmType = RTHANDLETYPE_FILE;
985 pJob->cbTestSet = cbTestSet;
986 pJob->cbIoBlock = cbIoBlock;
987 pJob->cReqsMax = cReqsMax;
988 pJob->cbIoReqReadBuf = cReqsMax * cbIoBlock;
989 pJob->uWriteChance = uWriteChance;
990 pJob->fVerifyReads = fVerifyReads;
991 pJob->cReqStats = (uint32_t)(pJob->cbTestSet / pJob->cbIoBlock + ((pJob->cbTestSet % pJob->cbIoBlock) ? 1 : 0));
992 pJob->idxReqStatNext = 0;
993
994 int rc = VINF_SUCCESS;
995 pJob->paIoReqs = (PIOPERFREQ)RTMemAllocZ(cReqsMax * sizeof(IOPERFREQ));
996 if (RT_LIKELY(pJob->paIoReqs))
997 {
998 pJob->paReqStats = (PIOPERFREQSTAT)RTMemAllocZ(pJob->cReqStats * sizeof(IOPERFREQSTAT));
999 if (RT_LIKELY(pJob->paReqStats))
1000 {
1001 pJob->pvIoReqReadBuf = RTMemPageAlloc(pJob->cbIoReqReadBuf);
1002 if (RT_LIKELY(pJob->pvIoReqReadBuf))
1003 {
1004 uint8_t *pbReadBuf = (uint8_t *)pJob->pvIoReqReadBuf;
1005
1006 for (uint32_t i = 0; i < cReqsMax; i++)
1007 {
1008 pJob->paIoReqs[i].pvXferRead = pbReadBuf;
1009 pJob->paIoReqs[i].cbXferRead = cbIoBlock;
1010 pbReadBuf += cbIoBlock;
1011 }
1012
1013 /* Create the file. */
1014 pJob->pszFilename = RTStrAPrintf2("%sioperf-%u.file", pszTestDir, idJob);
1015 if (RT_LIKELY(pJob->pszFilename))
1016 {
1017 uint32_t fOpen = RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE | RTFILE_O_READWRITE | RTFILE_O_ASYNC_IO;
1018 if (g_fNoCache)
1019 fOpen |= RTFILE_O_NO_CACHE;
1020 rc = RTFileOpen(&pJob->Hnd.u.hFile, pJob->pszFilename, fOpen);
1021 if (RT_SUCCESS(rc))
1022 {
1023 switch (enmPrepMethod)
1024 {
1025 case IOPERFTESTSETPREP_JUST_CREATE:
1026 break;
1027 case IOPERFTESTSETPREP_SET_SZ:
1028 rc = RTFileSetSize(pJob->Hnd.u.hFile, pJob->cbTestSet);
1029 break;
1030 case IOPERFTESTSETPREP_SET_ALLOC_SZ:
1031 rc = RTFileSetAllocationSize(pJob->Hnd.u.hFile, pJob->cbTestSet, RTFILE_ALLOC_SIZE_F_DEFAULT);
1032 break;
1033 default:
1034 AssertMsgFailed(("Invalid file preparation method: %d\n", enmPrepMethod));
1035 }
1036
1037 if (RT_SUCCESS(rc))
1038 {
1039 rc = ioPerfJobTestSetPrep(pJob);
1040 if (RT_SUCCESS(rc))
1041 {
1042 /* Create I/O queue. */
1043 PCRTIOQUEUEPROVVTABLE pIoQProv = NULL;
1044 if (!pszIoEngine)
1045 pIoQProv = RTIoQueueProviderGetBestForHndType(RTHANDLETYPE_FILE);
1046 else
1047 pIoQProv = RTIoQueueProviderGetById(pszIoEngine);
1048
1049 if (RT_LIKELY(pIoQProv))
1050 {
1051 rc = RTIoQueueCreate(&pJob->hIoQueue, pIoQProv, 0 /*fFlags*/, cReqsMax, cReqsMax);
1052 if (RT_SUCCESS(rc))
1053 {
1054 rc = RTIoQueueHandleRegister(pJob->hIoQueue, &pJob->Hnd);
1055 if (RT_SUCCESS(rc))
1056 {
1057 /* Spin up the worker thread. */
1058 if (pMaster)
1059 rc = RTThreadCreateF(&pJob->hThread, ioPerfJobThread, pJob, 0,
1060 RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, "ioperf-%u", idJob);
1061
1062 if (RT_SUCCESS(rc))
1063 return VINF_SUCCESS;
1064 }
1065 }
1066 }
1067 else
1068 rc = VERR_NOT_SUPPORTED;
1069 }
1070
1071 RTRandAdvDestroy(pJob->hRand);
1072 }
1073
1074 RTFileClose(pJob->Hnd.u.hFile);
1075 RTFileDelete(pJob->pszFilename);
1076 }
1077
1078 RTStrFree(pJob->pszFilename);
1079 }
1080 else
1081 rc = VERR_NO_STR_MEMORY;
1082
1083 RTMemPageFree(pJob->pvIoReqReadBuf, pJob->cbIoReqReadBuf);
1084 }
1085 else
1086 rc = VERR_NO_MEMORY;
1087
1088 RTMemFree(pJob->paReqStats);
1089 }
1090 else
1091 rc = VERR_NO_MEMORY;
1092 }
1093 else
1094 rc = VERR_NO_MEMORY;
1095
1096 return rc;
1097}
1098
1099
1100/**
1101 * Teardown a job instance and free all associated resources.
1102 *
1103 * @returns IPRT status code.
1104 * @param pJob The job to teardown.
1105 */
1106static int ioPerfJobTeardown(PIOPERFJOB pJob)
1107{
1108 if (pJob->pMaster)
1109 {
1110 int rc = RTThreadWait(pJob->hThread, RT_INDEFINITE_WAIT, NULL);
1111 AssertRC(rc); RT_NOREF(rc);
1112 }
1113
1114 RTIoQueueHandleDeregister(pJob->hIoQueue, &pJob->Hnd);
1115 RTIoQueueDestroy(pJob->hIoQueue);
1116 RTRandAdvDestroy(pJob->hRand);
1117 RTMemPageFree(pJob->pbRandWrite, IOPERF_RAND_DATA_BUF_FACTOR * pJob->cbIoBlock);
1118 RTFileClose(pJob->Hnd.u.hFile);
1119 RTFileDelete(pJob->pszFilename);
1120 RTStrFree(pJob->pszFilename);
1121 RTMemPageFree(pJob->pvIoReqReadBuf, pJob->cbIoReqReadBuf);
1122 RTMemFree(pJob->paIoReqs);
1123 RTMemFree(pJob->paReqStats);
1124 return VINF_SUCCESS;
1125}
1126
1127
1128/**
1129 * Single job testing entry point.
1130 *
1131 * @returns IPRT status code.
1132 */
1133static int ioPerfDoTestSingle(void)
1134{
1135 IOPERFJOB Job;
1136
1137 int rc = ioPerfJobInit(&Job, NULL, 0, g_pszIoEngine,
1138 g_szDir, IOPERFTESTSETPREP_SET_SZ,
1139 g_cbTestSet, g_cbIoBlock, g_cReqsMax,
1140 g_uWriteChance, g_fVerifyReads);
1141 if (RT_SUCCESS(rc))
1142 {
1143 rc = ioPerfJobWorkLoop(&Job);
1144 if (RT_SUCCESS(rc))
1145 {
1146 rc = ioPerfJobTeardown(&Job);
1147 AssertRC(rc); RT_NOREF(rc);
1148 }
1149 }
1150
1151 return rc;
1152}
1153
1154
1155/**
1156 * Multi job testing entry point.
1157 *
1158 * @returns IPRT status code.
1159 */
1160static int ioPerfDoTestMulti(void)
1161{
1162 return VERR_NOT_IMPLEMENTED;
1163}
1164
1165
1166/**
1167 * Display the usage to @a pStrm.
1168 */
1169static void Usage(PRTSTREAM pStrm)
1170{
1171 char szExec[RTPATH_MAX];
1172 RTStrmPrintf(pStrm, "usage: %s <-d <testdir>> [options]\n",
1173 RTPathFilename(RTProcGetExecutablePath(szExec, sizeof(szExec))));
1174 RTStrmPrintf(pStrm, "\n");
1175 RTStrmPrintf(pStrm, "options: \n");
1176
1177 for (unsigned i = 0; i < RT_ELEMENTS(g_aCmdOptions); i++)
1178 {
1179 char szHelp[80];
1180 const char *pszHelp;
1181 switch (g_aCmdOptions[i].iShort)
1182 {
1183 case 'd': pszHelp = "The directory to use for testing. default: CWD/fstestdir"; break;
1184 case 'r': pszHelp = "Don't abspath test dir (good for deep dirs). default: disabled"; break;
1185 case 'y': pszHelp = "Flag whether to verify read data. default: enabled"; break;
1186 case 'c': pszHelp = "Flag whether to use the filesystem cache. default: disabled"; break;
1187 case 'v': pszHelp = "More verbose execution."; break;
1188 case 'q': pszHelp = "Quiet execution."; break;
1189 case 'h': pszHelp = "Displays this help and exit"; break;
1190 case 'V': pszHelp = "Displays the program revision"; break;
1191 default:
1192 if (g_aCmdOptions[i].iShort >= kCmdOpt_First)
1193 {
1194 if (RTStrStartsWith(g_aCmdOptions[i].pszLong, "--no-"))
1195 RTStrPrintf(szHelp, sizeof(szHelp), "Disables the '%s' test.", g_aCmdOptions[i].pszLong + 5);
1196 else
1197 RTStrPrintf(szHelp, sizeof(szHelp), "Enables the '%s' test.", g_aCmdOptions[i].pszLong + 2);
1198 pszHelp = szHelp;
1199 }
1200 else
1201 pszHelp = "Option undocumented";
1202 break;
1203 }
1204 if ((unsigned)g_aCmdOptions[i].iShort < 127U)
1205 {
1206 char szOpt[64];
1207 RTStrPrintf(szOpt, sizeof(szOpt), "%s, -%c", g_aCmdOptions[i].pszLong, g_aCmdOptions[i].iShort);
1208 RTStrmPrintf(pStrm, " %-19s %s\n", szOpt, pszHelp);
1209 }
1210 else
1211 RTStrmPrintf(pStrm, " %-19s %s\n", g_aCmdOptions[i].pszLong, pszHelp);
1212 }
1213}
1214
1215
1216int main(int argc, char *argv[])
1217{
1218 /*
1219 * Init IPRT and globals.
1220 */
1221 int rc = RTTestInitAndCreate("IoPerf", &g_hTest);
1222 if (rc)
1223 return rc;
1224
1225 /*
1226 * Default values.
1227 */
1228 char szDefaultDir[32];
1229 const char *pszDir = szDefaultDir;
1230 RTStrPrintf(szDefaultDir, sizeof(szDefaultDir), "ioperfdir-%u" RTPATH_SLASH_STR, RTProcSelf());
1231
1232 RTGETOPTUNION ValueUnion;
1233 RTGETOPTSTATE GetState;
1234 RTGetOptInit(&GetState, argc, argv, g_aCmdOptions, RT_ELEMENTS(g_aCmdOptions), 1, 0 /* fFlags */);
1235 while ((rc = RTGetOpt(&GetState, &ValueUnion)) != 0)
1236 {
1237 switch (rc)
1238 {
1239 case 'd':
1240 pszDir = ValueUnion.psz;
1241 break;
1242
1243 case 'r':
1244 g_fRelativeDir = true;
1245 break;
1246
1247 case 'i':
1248 g_pszIoEngine = ValueUnion.psz;
1249 break;
1250
1251 case 's':
1252 g_cbTestSet = ValueUnion.u64;
1253 break;
1254
1255 case 'b':
1256 g_cbIoBlock = ValueUnion.u32;
1257 break;
1258
1259 case 'm':
1260 g_cReqsMax = ValueUnion.u32;
1261 break;
1262
1263 case 'y':
1264 g_fVerifyReads = ValueUnion.f;
1265 break;
1266
1267 case 'c':
1268 g_fNoCache = !ValueUnion.f;
1269 break;
1270
1271 case kCmdOpt_FirstWrite:
1272 g_aenmTests[IOPERFTEST_FIRST_WRITE] = IOPERFTEST_FIRST_WRITE;
1273 break;
1274 case kCmdOpt_NoFirstWrite:
1275 g_aenmTests[IOPERFTEST_FIRST_WRITE] = IOPERFTEST_DISABLED;
1276 break;
1277 case kCmdOpt_SeqRead:
1278 g_aenmTests[IOPERFTEST_SEQ_READ] = IOPERFTEST_SEQ_READ;
1279 break;
1280 case kCmdOpt_NoSeqRead:
1281 g_aenmTests[IOPERFTEST_SEQ_READ] = IOPERFTEST_DISABLED;
1282 break;
1283 case kCmdOpt_SeqWrite:
1284 g_aenmTests[IOPERFTEST_SEQ_WRITE] = IOPERFTEST_SEQ_WRITE;
1285 break;
1286 case kCmdOpt_NoSeqWrite:
1287 g_aenmTests[IOPERFTEST_SEQ_WRITE] = IOPERFTEST_DISABLED;
1288 break;
1289 case kCmdOpt_RndRead:
1290 g_aenmTests[IOPERFTEST_RND_READ] = IOPERFTEST_RND_READ;
1291 break;
1292 case kCmdOpt_NoRndRead:
1293 g_aenmTests[IOPERFTEST_RND_READ] = IOPERFTEST_DISABLED;
1294 break;
1295 case kCmdOpt_RndWrite:
1296 g_aenmTests[IOPERFTEST_RND_WRITE] = IOPERFTEST_RND_WRITE;
1297 break;
1298 case kCmdOpt_NoRndWrite:
1299 g_aenmTests[IOPERFTEST_RND_WRITE] = IOPERFTEST_DISABLED;
1300 break;
1301 case kCmdOpt_RevRead:
1302 g_aenmTests[IOPERFTEST_REV_READ] = IOPERFTEST_REV_READ;
1303 break;
1304 case kCmdOpt_NoRevRead:
1305 g_aenmTests[IOPERFTEST_REV_READ] = IOPERFTEST_DISABLED;
1306 break;
1307 case kCmdOpt_RevWrite:
1308 g_aenmTests[IOPERFTEST_REV_WRITE] = IOPERFTEST_REV_WRITE;
1309 break;
1310 case kCmdOpt_NoRevWrite:
1311 g_aenmTests[IOPERFTEST_REV_WRITE] = IOPERFTEST_DISABLED;
1312 break;
1313 case kCmdOpt_SeqReadWrite:
1314 g_aenmTests[IOPERFTEST_SEQ_READWRITE] = IOPERFTEST_SEQ_READWRITE;
1315 break;
1316 case kCmdOpt_NoSeqReadWrite:
1317 g_aenmTests[IOPERFTEST_SEQ_READWRITE] = IOPERFTEST_DISABLED;
1318 break;
1319 case kCmdOpt_RndReadWrite:
1320 g_aenmTests[IOPERFTEST_RND_READWRITE] = IOPERFTEST_RND_READWRITE;
1321 break;
1322 case kCmdOpt_NoRndReadWrite:
1323 g_aenmTests[IOPERFTEST_RND_READWRITE] = IOPERFTEST_DISABLED;
1324 break;
1325
1326 case 'q':
1327 g_uVerbosity = 0;
1328 break;
1329
1330 case 'v':
1331 g_uVerbosity++;
1332 break;
1333
1334 case 'h':
1335 Usage(g_pStdOut);
1336 return RTEXITCODE_SUCCESS;
1337
1338 case 'V':
1339 {
1340 char szRev[] = "$Revision: 80251 $";
1341 szRev[RT_ELEMENTS(szRev) - 2] = '\0';
1342 RTPrintf(RTStrStrip(strchr(szRev, ':') + 1));
1343 return RTEXITCODE_SUCCESS;
1344 }
1345
1346 default:
1347 return RTGetOptPrintError(rc, &ValueUnion);
1348 }
1349 }
1350
1351 /*
1352 * Populate g_szDir.
1353 */
1354 if (!g_fRelativeDir)
1355 rc = RTPathAbs(pszDir, g_szDir, sizeof(g_szDir));
1356 else
1357 rc = RTStrCopy(g_szDir, sizeof(g_szDir), pszDir);
1358 if (RT_FAILURE(rc))
1359 {
1360 RTTestFailed(g_hTest, "%s(%s) failed: %Rrc\n", g_fRelativeDir ? "RTStrCopy" : "RTAbsPath", pszDir, rc);
1361 return RTTestSummaryAndDestroy(g_hTest);
1362 }
1363 RTPathEnsureTrailingSeparator(g_szDir, sizeof(g_szDir));
1364 g_cchDir = strlen(g_szDir);
1365
1366 /*
1367 * Create the test directory with an 'empty' subdirectory under it,
1368 * execute the tests, and remove directory when done.
1369 */
1370 RTTestBanner(g_hTest);
1371 if (!RTPathExists(g_szDir))
1372 {
1373 /* The base dir: */
1374 rc = RTDirCreate(g_szDir, 0755,
1375 RTDIRCREATE_FLAGS_NOT_CONTENT_INDEXED_DONT_SET | RTDIRCREATE_FLAGS_NOT_CONTENT_INDEXED_NOT_CRITICAL);
1376 if (RT_SUCCESS(rc))
1377 {
1378 RTTestIPrintf(RTTESTLVL_ALWAYS, "Test dir: %s\n", g_szDir);
1379
1380 if (g_cJobs == 1)
1381 rc = ioPerfDoTestSingle();
1382 else
1383 rc = ioPerfDoTestMulti();
1384
1385 g_szDir[g_cchDir] = '\0';
1386 rc = RTDirRemoveRecursive(g_szDir, RTDIRRMREC_F_CONTENT_AND_DIR | (g_fRelativeDir ? RTDIRRMREC_F_NO_ABS_PATH : 0));
1387 if (RT_FAILURE(rc))
1388 RTTestFailed(g_hTest, "RTDirRemoveRecursive(%s,) -> %Rrc\n", g_szDir, rc);
1389 }
1390 else
1391 RTTestFailed(g_hTest, "RTDirCreate(%s) -> %Rrc\n", g_szDir, rc);
1392 }
1393 else
1394 RTTestFailed(g_hTest, "Test directory already exists: %s\n", g_szDir);
1395
1396 return RTTestSummaryAndDestroy(g_hTest);
1397}
1398
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette