VirtualBox

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

Last change on this file since 98006 was 96407, checked in by vboxsync, 3 years ago

scm copyright and license note update

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