VirtualBox

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

Last change on this file since 92241 was 86518, checked in by vboxsync, 4 years ago

ValidationKit/IoPerf: Fix wrong queue operation for the sequential read test

  • 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 86518 2020-10-11 16:59:25Z vboxsync $ */
2/** @file
3 * IoPerf - Storage I/O Performance Benchmark.
4 */
5
6/*
7 * Copyright (C) 2019-2020 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_REV_WRITE:
396 case IOPERFTEST_RND_WRITE:
397 return RTIOQUEUEOP_WRITE;
398
399 case IOPERFTEST_SEQ_READ:
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: 86518 $";
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