VirtualBox

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

最後變更 在這個檔案從98763是 98763,由 vboxsync 提交於 2 年 前

ValidationKit/{IoPerf,tdStorageBenchmark}: Don't report statistics of the IoPerf tool if requested in the testcase, some functionality tests would produce too many values for the maximum limit configured on the testmanager

  • 屬性 svn:eol-style 設為 native
  • 屬性 svn:keywords 設為 Author Date Id Revision
檔案大小: 49.9 KB
 
1/* $Id: IoPerf.cpp 98763 2023-02-27 18:59:08Z vboxsync $ */
2/** @file
3 * IoPerf - Storage I/O Performance Benchmark.
4 */
5
6/*
7 * Copyright (C) 2019-2023 Oracle and/or its affiliates.
8 *
9 * This file is part of VirtualBox base platform packages, as
10 * available from https://www.alldomusa.eu.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 { "--report-io-stats", 't', RTGETOPT_REQ_BOOL },
293
294 { "--first-write", kCmdOpt_FirstWrite, RTGETOPT_REQ_NOTHING },
295 { "--no-first-write", kCmdOpt_NoFirstWrite, RTGETOPT_REQ_NOTHING },
296 { "--seq-read", kCmdOpt_SeqRead, RTGETOPT_REQ_NOTHING },
297 { "--no-seq-read", kCmdOpt_NoSeqRead, RTGETOPT_REQ_NOTHING },
298 { "--seq-write", kCmdOpt_SeqWrite, RTGETOPT_REQ_NOTHING },
299 { "--no-seq-write", kCmdOpt_NoSeqWrite, RTGETOPT_REQ_NOTHING },
300 { "--rnd-read", kCmdOpt_RndRead, RTGETOPT_REQ_NOTHING },
301 { "--no-rnd-read", kCmdOpt_NoRndRead, RTGETOPT_REQ_NOTHING },
302 { "--rnd-write", kCmdOpt_RndWrite, RTGETOPT_REQ_NOTHING },
303 { "--no-rnd-write", kCmdOpt_NoRndWrite, RTGETOPT_REQ_NOTHING },
304 { "--rev-read", kCmdOpt_RevRead, RTGETOPT_REQ_NOTHING },
305 { "--no-rev-read", kCmdOpt_NoRevRead, RTGETOPT_REQ_NOTHING },
306 { "--rev-write", kCmdOpt_RevWrite, RTGETOPT_REQ_NOTHING },
307 { "--no-rev-write", kCmdOpt_NoRevWrite, RTGETOPT_REQ_NOTHING },
308 { "--seq-read-write", kCmdOpt_SeqReadWrite, RTGETOPT_REQ_NOTHING },
309 { "--no-seq-read-write", kCmdOpt_NoSeqReadWrite, RTGETOPT_REQ_NOTHING },
310 { "--rnd-read-write", kCmdOpt_RndReadWrite, RTGETOPT_REQ_NOTHING },
311 { "--no-rnd-read-write", kCmdOpt_NoRndReadWrite, RTGETOPT_REQ_NOTHING },
312
313 { "--quiet", 'q', RTGETOPT_REQ_NOTHING },
314 { "--verbose", 'v', RTGETOPT_REQ_NOTHING },
315 { "--version", 'V', RTGETOPT_REQ_NOTHING },
316 { "--help", 'h', RTGETOPT_REQ_NOTHING } /* for Usage() */
317};
318
319/** The test handle. */
320static RTTEST g_hTest;
321/** Verbosity level. */
322static uint32_t g_uVerbosity = 0;
323/** Selected I/O engine for the tests, NULL means pick best default one. */
324static const char *g_pszIoEngine = NULL;
325/** Number of jobs to run concurrently. */
326static uint32_t g_cJobs = 1;
327/** Size of each test set (file) in bytes. */
328static uint64_t g_cbTestSet = _2G;
329/** Block size for each request. */
330static size_t g_cbIoBlock = _4K;
331/** Maximum number of concurrent requests for each job. */
332static uint32_t g_cReqsMax = 16;
333/** Flag whether to open the file without caching enabled. */
334static bool g_fNoCache = true;
335/** Write chance for mixed read/write tests. */
336static unsigned g_uWriteChance = 50;
337/** Flag whether to verify read data. */
338static bool g_fVerifyReads = true;
339/** Flag whether to report I/O statistics after each test. */
340static bool g_fReportIoStats = true;
341
342/** @name Configured tests, this must match the IOPERFTEST order.
343 * @{ */
344static IOPERFTEST g_aenmTests[] =
345{
346 IOPERFTEST_DISABLED, /** @< The invalid test value is disabled of course. */
347 IOPERFTEST_DISABLED,
348 IOPERFTEST_FIRST_WRITE,
349 IOPERFTEST_SEQ_READ,
350 IOPERFTEST_SEQ_WRITE,
351 IOPERFTEST_REV_READ,
352 IOPERFTEST_REV_WRITE,
353 IOPERFTEST_RND_READ,
354 IOPERFTEST_RND_WRITE,
355 IOPERFTEST_SEQ_READWRITE,
356 IOPERFTEST_RND_READWRITE,
357 IOPERFTEST_SHUTDOWN
358};
359/** The test index being selected next. */
360static uint32_t g_idxTest = 2;
361/** @} */
362
363/** Set if g_szDir and friends are path relative to CWD rather than absolute. */
364static bool g_fRelativeDir = false;
365/** The length of g_szDir. */
366static size_t g_cchDir;
367
368/** The test directory (absolute). This will always have a trailing slash. */
369static char g_szDir[RTPATH_BIG_MAX];
370
371
372/*********************************************************************************************************************************
373* Tests *
374*********************************************************************************************************************************/
375
376
377/**
378 * Selects the next test to run.
379 *
380 * @return Next test to run.
381 */
382static IOPERFTEST ioPerfJobTestSelectNext()
383{
384 AssertReturn(g_idxTest < RT_ELEMENTS(g_aenmTests), IOPERFTEST_SHUTDOWN);
385
386 while ( g_idxTest < RT_ELEMENTS(g_aenmTests)
387 && g_aenmTests[g_idxTest] == IOPERFTEST_DISABLED)
388 g_idxTest++;
389
390 AssertReturn(g_idxTest < RT_ELEMENTS(g_aenmTests), IOPERFTEST_SHUTDOWN);
391
392 return g_aenmTests[g_idxTest++];
393}
394
395
396/**
397 * Returns the I/O queue operation for the next request.
398 *
399 * @returns I/O queue operation enum.
400 * @param pJob The job data for the current worker.
401 */
402static RTIOQUEUEOP ioPerfJobTestGetIoQOp(PIOPERFJOB pJob)
403{
404 switch (pJob->enmTest)
405 {
406 case IOPERFTEST_FIRST_WRITE:
407 case IOPERFTEST_SEQ_WRITE:
408 case IOPERFTEST_REV_WRITE:
409 case IOPERFTEST_RND_WRITE:
410 return RTIOQUEUEOP_WRITE;
411
412 case IOPERFTEST_SEQ_READ:
413 case IOPERFTEST_RND_READ:
414 case IOPERFTEST_REV_READ:
415 return RTIOQUEUEOP_READ;
416
417 case IOPERFTEST_SEQ_READWRITE:
418 case IOPERFTEST_RND_READWRITE:
419 {
420 uint32_t uRnd = RTRandAdvU32Ex(pJob->hRand, 0, 100);
421 return (uRnd < pJob->uWriteChance) ? RTIOQUEUEOP_WRITE : RTIOQUEUEOP_READ;
422 }
423
424 default:
425 AssertMsgFailed(("Invalid/unknown test selected: %d\n", pJob->enmTest));
426 break;
427 }
428
429 return RTIOQUEUEOP_INVALID;
430}
431
432
433/**
434 * Returns the offset to use for the next request.
435 *
436 * @returns Offset to use.
437 * @param pJob The job data for the current worker.
438 */
439static uint64_t ioPerfJobTestGetOffsetNext(PIOPERFJOB pJob)
440{
441 uint64_t offNext = 0;
442
443 switch (pJob->enmTest)
444 {
445 case IOPERFTEST_FIRST_WRITE:
446 case IOPERFTEST_SEQ_WRITE:
447 case IOPERFTEST_SEQ_READ:
448 case IOPERFTEST_SEQ_READWRITE:
449 offNext = pJob->Tst.offNextSeq;
450 pJob->Tst.offNextSeq += pJob->cbIoBlock;
451 break;
452 case IOPERFTEST_REV_WRITE:
453 case IOPERFTEST_REV_READ:
454 offNext = pJob->Tst.offNextSeq;
455 if (pJob->Tst.offNextSeq == 0)
456 pJob->Tst.offNextSeq = pJob->cbTestSet;
457 else
458 pJob->Tst.offNextSeq -= pJob->cbIoBlock;
459 break;
460 case IOPERFTEST_RND_WRITE:
461 case IOPERFTEST_RND_READ:
462 case IOPERFTEST_RND_READWRITE:
463 {
464 int idx = -1;
465
466 idx = ASMBitFirstClear(pJob->Tst.Rnd.pbMapAccessed, pJob->Tst.Rnd.cBlocks);
467
468 /* In case this is the last request we don't need to search further. */
469 if (pJob->Tst.Rnd.cBlocksLeft > 1)
470 {
471 int idxIo;
472 idxIo = RTRandAdvU32Ex(pJob->hRand, idx, pJob->Tst.Rnd.cBlocks - 1);
473
474 /*
475 * If the bit is marked free use it, otherwise search for the next free bit
476 * and if that doesn't work use the first free bit.
477 */
478 if (ASMBitTest(pJob->Tst.Rnd.pbMapAccessed, idxIo))
479 {
480 idxIo = ASMBitNextClear(pJob->Tst.Rnd.pbMapAccessed, pJob->Tst.Rnd.cBlocks, idxIo);
481 if (idxIo != -1)
482 idx = idxIo;
483 }
484 else
485 idx = idxIo;
486 }
487
488 Assert(idx != -1);
489 offNext = (uint64_t)idx * pJob->cbIoBlock;
490 pJob->Tst.Rnd.cBlocksLeft--;
491 ASMBitSet(pJob->Tst.Rnd.pbMapAccessed, idx);
492 break;
493 }
494 default:
495 AssertMsgFailed(("Invalid/unknown test selected: %d\n", pJob->enmTest));
496 break;
497 }
498
499 return offNext;
500}
501
502
503/**
504 * Returns a pointer to the write buffer with random data for the given offset which
505 * is predictable for data verification.
506 *
507 * @returns Pointer to I/O block sized data buffer with random data.
508 * @param pJob The job data for the current worker.
509 * @param off The offset to get the buffer for.
510 */
511static void *ioPerfJobTestGetWriteBufForOffset(PIOPERFJOB pJob, uint64_t off)
512{
513 /*
514 * Dividing the file into 512 byte blocks so buffer pointers are at least
515 * 512 byte aligned to work with async I/O on some platforms (Linux and O_DIRECT for example).
516 */
517 uint64_t uBlock = off / 512;
518 uint32_t idxBuf = uBlock % pJob->cRandWriteBlocks512B;
519 return pJob->pbRandWrite + idxBuf * 512;
520}
521
522
523/**
524 * Initialize the given request for submission.
525 *
526 * @returns nothing.
527 * @param pJob The job data for the current worker.
528 * @param pIoReq The request to initialize.
529 */
530static void ioPerfJobTestReqInit(PIOPERFJOB pJob, PIOPERFREQ pIoReq)
531{
532 pIoReq->enmOp = ioPerfJobTestGetIoQOp(pJob);
533 pIoReq->offXfer = ioPerfJobTestGetOffsetNext(pJob);
534 pIoReq->cbXfer = pJob->cbIoBlock;
535 if (pIoReq->enmOp == RTIOQUEUEOP_READ)
536 pIoReq->pvXfer = pIoReq->pvXferRead;
537 else if (pIoReq->enmOp == RTIOQUEUEOP_WRITE)
538 pIoReq->pvXfer = ioPerfJobTestGetWriteBufForOffset(pJob, pIoReq->offXfer);
539 else /* Flush */
540 pIoReq->pvXfer = NULL;
541
542 Assert(pJob->idxReqStatNext < pJob->cReqStats);
543 if (RT_LIKELY(pJob->idxReqStatNext < pJob->cReqStats))
544 {
545 pIoReq->pStats = &pJob->paReqStats[pJob->idxReqStatNext++];
546 pIoReq->pStats->tsStart = RTTimeNanoTS();
547 }
548 else
549 pIoReq->pStats = NULL;
550}
551
552
553/**
554 * Returns a stringified version of the test given.
555 *
556 * @returns Pointer to string representation of the test.
557 * @param enmTest The test to stringify.
558 */
559static const char *ioPerfJobTestStringify(IOPERFTEST enmTest)
560{
561 switch (enmTest)
562 {
563 case IOPERFTEST_FIRST_WRITE:
564 return "FirstWrite";
565 case IOPERFTEST_SEQ_WRITE:
566 return "SequentialWrite";
567 case IOPERFTEST_SEQ_READ:
568 return "SequentialRead";
569 case IOPERFTEST_REV_WRITE:
570 return "ReverseWrite";
571 case IOPERFTEST_REV_READ:
572 return "ReverseRead";
573 case IOPERFTEST_RND_WRITE:
574 return "RandomWrite";
575 case IOPERFTEST_RND_READ:
576 return "RandomRead";
577 case IOPERFTEST_SEQ_READWRITE:
578 return "SequentialReadWrite";
579 case IOPERFTEST_RND_READWRITE:
580 return "RandomReadWrite";
581 default:
582 AssertMsgFailed(("Invalid/unknown test selected: %d\n", enmTest));
583 break;
584 }
585
586 return "INVALID_TEST";
587}
588
589
590/**
591 * Initializes the test state for the current test.
592 *
593 * @returns IPRT status code.
594 * @param pJob The job data for the current worker.
595 */
596static int ioPerfJobTestInit(PIOPERFJOB pJob)
597{
598 int rc = VINF_SUCCESS;
599
600 pJob->idxReqStatNext = 0;
601
602 switch (pJob->enmTest)
603 {
604 case IOPERFTEST_FIRST_WRITE:
605 case IOPERFTEST_SEQ_WRITE:
606 case IOPERFTEST_SEQ_READ:
607 case IOPERFTEST_SEQ_READWRITE:
608 pJob->Tst.offNextSeq = 0;
609 break;
610 case IOPERFTEST_REV_WRITE:
611 case IOPERFTEST_REV_READ:
612 pJob->Tst.offNextSeq = pJob->cbTestSet - pJob->cbIoBlock;
613 break;
614 case IOPERFTEST_RND_WRITE:
615 case IOPERFTEST_RND_READ:
616 case IOPERFTEST_RND_READWRITE:
617 {
618 pJob->Tst.Rnd.cBlocks = (uint32_t)( pJob->cbTestSet / pJob->cbIoBlock
619 + (pJob->cbTestSet % pJob->cbIoBlock ? 1 : 0));
620 pJob->Tst.Rnd.cBlocksLeft = pJob->Tst.Rnd.cBlocks;
621 pJob->Tst.Rnd.pbMapAccessed = (uint8_t *)RTMemAllocZ( pJob->Tst.Rnd.cBlocks / 8
622 + ((pJob->Tst.Rnd.cBlocks % 8)
623 ? 1
624 : 0));
625 if (!pJob->Tst.Rnd.pbMapAccessed)
626 rc = VERR_NO_MEMORY;
627 break;
628 }
629 default:
630 AssertMsgFailed(("Invalid/unknown test selected: %d\n", pJob->enmTest));
631 break;
632 }
633
634 pJob->tsStart = RTTimeNanoTS();
635 return rc;
636}
637
638
639/**
640 * Frees allocated resources specific for the current test.
641 *
642 * @returns nothing.
643 * @param pJob The job data for the current worker.
644 */
645static void ioPerfJobTestFinish(PIOPERFJOB pJob)
646{
647 pJob->tsFinish = RTTimeNanoTS();
648
649 switch (pJob->enmTest)
650 {
651 case IOPERFTEST_FIRST_WRITE:
652 case IOPERFTEST_SEQ_WRITE:
653 case IOPERFTEST_SEQ_READ:
654 case IOPERFTEST_REV_WRITE:
655 case IOPERFTEST_REV_READ:
656 case IOPERFTEST_SEQ_READWRITE:
657 break; /* Nothing to do. */
658
659 case IOPERFTEST_RND_WRITE:
660 case IOPERFTEST_RND_READ:
661 case IOPERFTEST_RND_READWRITE:
662 RTMemFree(pJob->Tst.Rnd.pbMapAccessed);
663 break;
664 default:
665 AssertMsgFailed(("Invalid/unknown test selected: %d\n", pJob->enmTest));
666 break;
667 }
668}
669
670
671/**
672 * Returns whether the current test is done with submitting new requests (reached test set size).
673 *
674 * @returns True when the test has submitted all required requests, false if there are still requests required
675 */
676static bool ioPerfJobTestIsDone(PIOPERFJOB pJob)
677{
678 switch (pJob->enmTest)
679 {
680 case IOPERFTEST_FIRST_WRITE:
681 case IOPERFTEST_SEQ_WRITE:
682 case IOPERFTEST_SEQ_READ:
683 case IOPERFTEST_REV_WRITE:
684 case IOPERFTEST_REV_READ:
685 case IOPERFTEST_SEQ_READWRITE:
686 return pJob->Tst.offNextSeq == pJob->cbTestSet;
687 case IOPERFTEST_RND_WRITE:
688 case IOPERFTEST_RND_READ:
689 case IOPERFTEST_RND_READWRITE:
690 return pJob->Tst.Rnd.cBlocksLeft == 0;
691 break;
692 default:
693 AssertMsgFailed(("Invalid/unknown test selected: %d\n", pJob->enmTest));
694 break;
695 }
696
697 return true;
698}
699
700
701/**
702 * The test I/O loop pumping I/O.
703 *
704 * @returns IPRT status code.
705 * @param pJob The job data for the current worker.
706 */
707static int ioPerfJobTestIoLoop(PIOPERFJOB pJob)
708{
709 int rc = ioPerfJobTestInit(pJob);
710 if (RT_SUCCESS(rc))
711 {
712 /* Allocate the completion event array. */
713 uint32_t cReqsQueued = 0;
714 PRTIOQUEUECEVT paIoQCEvt = (PRTIOQUEUECEVT)RTMemAllocZ(pJob->cReqsMax * sizeof(RTIOQUEUECEVT));
715 if (RT_LIKELY(paIoQCEvt))
716 {
717 /* Queue requests up to the maximum. */
718 while ( (cReqsQueued < pJob->cReqsMax)
719 && !ioPerfJobTestIsDone(pJob)
720 && RT_SUCCESS(rc))
721 {
722 PIOPERFREQ pReq = &pJob->paIoReqs[cReqsQueued];
723 ioPerfJobTestReqInit(pJob, pReq);
724 RTTESTI_CHECK_RC(RTIoQueueRequestPrepare(pJob->hIoQueue, &pJob->Hnd, pReq->enmOp,
725 pReq->offXfer, pReq->pvXfer, pReq->cbXfer, 0 /*fReqFlags*/,
726 pReq), VINF_SUCCESS);
727 cReqsQueued++;
728 }
729
730 /* Commit the prepared requests. */
731 if ( RT_SUCCESS(rc)
732 && cReqsQueued)
733 {
734 RTTESTI_CHECK_RC(RTIoQueueCommit(pJob->hIoQueue), VINF_SUCCESS);
735 }
736
737 /* Enter wait loop and process completed requests. */
738 while ( RT_SUCCESS(rc)
739 && cReqsQueued)
740 {
741 uint32_t cCEvtCompleted = 0;
742
743 RTTESTI_CHECK_RC(RTIoQueueEvtWait(pJob->hIoQueue, paIoQCEvt, pJob->cReqsMax, 1 /*cMinWait*/,
744 &cCEvtCompleted, 0 /*fFlags*/), VINF_SUCCESS);
745 if (RT_SUCCESS(rc))
746 {
747 uint32_t cReqsThisQueued = 0;
748
749 /* Process any completed event and continue to fill the queue as long as there is stuff to do. */
750 for (uint32_t i = 0; i < cCEvtCompleted; i++)
751 {
752 PIOPERFREQ pReq = (PIOPERFREQ)paIoQCEvt[i].pvUser;
753
754 if (RT_SUCCESS(paIoQCEvt[i].rcReq))
755 {
756 Assert(paIoQCEvt[i].cbXfered == pReq->cbXfer);
757
758 if (pReq->pStats)
759 pReq->pStats->tsComplete = RTTimeNanoTS();
760
761 if ( pJob->fVerifyReads
762 && pReq->enmOp == RTIOQUEUEOP_READ)
763 {
764 const void *pvBuf = ioPerfJobTestGetWriteBufForOffset(pJob, pReq->offXfer);
765 if (memcmp(pReq->pvXferRead, pvBuf, pReq->cbXfer))
766 {
767 if (g_uVerbosity > 1)
768 RTTestIFailed("IoPerf: Corrupted data detected by read at offset %#llu (sz: %zu)", pReq->offXfer, pReq->cbXfer);
769 else
770 RTTestIErrorInc();
771 }
772 }
773
774 if (!ioPerfJobTestIsDone(pJob))
775 {
776 ioPerfJobTestReqInit(pJob, pReq);
777 RTTESTI_CHECK_RC(RTIoQueueRequestPrepare(pJob->hIoQueue, &pJob->Hnd, pReq->enmOp,
778 pReq->offXfer, pReq->pvXfer, pReq->cbXfer, 0 /*fReqFlags*/,
779 pReq), VINF_SUCCESS);
780 cReqsThisQueued++;
781 }
782 else
783 cReqsQueued--;
784 }
785 else
786 RTTestIErrorInc();
787 }
788
789 if ( cReqsThisQueued
790 && RT_SUCCESS(rc))
791 {
792 RTTESTI_CHECK_RC(RTIoQueueCommit(pJob->hIoQueue), VINF_SUCCESS);
793 }
794 }
795 }
796
797 RTMemFree(paIoQCEvt);
798 }
799
800 ioPerfJobTestFinish(pJob);
801 }
802
803 return rc;
804}
805
806
807/**
808 * Calculates the statistic values for the given job after a
809 * test finished.
810 *
811 * @returns nothing.
812 * @param pJob The job data.
813 */
814static void ioPerfJobStats(PIOPERFJOB pJob)
815{
816 const char *pszTest = ioPerfJobTestStringify(pJob->enmTest);
817 uint64_t nsJobRuntime = pJob->tsFinish - pJob->tsStart;
818 RTTestIValueF(nsJobRuntime, RTTESTUNIT_NS, "%s/Job/%RU32/Runtime", pszTest, pJob->idJob);
819
820 uint64_t *paReqRuntimeNs = (uint64_t *)RTMemAllocZ(pJob->cReqStats * sizeof(uint64_t));
821 if (RT_LIKELY(paReqRuntimeNs))
822 {
823 /* Calculate runtimes for each request first. */
824 for (uint32_t i = 0; i < pJob->cReqStats; i++)
825 {
826 PIOPERFREQSTAT pStat = &pJob->paReqStats[i];
827 paReqRuntimeNs[i] = pStat->tsComplete - pStat->tsStart;
828 }
829
830 /* Get average bandwidth for the job. */
831 RTTestIValueF((uint64_t)((double)pJob->cbTestSet / ((double)nsJobRuntime / RT_NS_1SEC)),
832 RTTESTUNIT_BYTES_PER_SEC, "%s/Job/%RU32/AvgBandwidth", pszTest, pJob->idJob);
833
834 RTTestIValueF((uint64_t)(pJob->cReqStats / ((double)nsJobRuntime / RT_NS_1SEC)),
835 RTTESTUNIT_OCCURRENCES_PER_SEC, "%s/Job/%RU32/AvgIops", pszTest, pJob->idJob);
836
837 /* Calculate the average latency for the requests. */
838 uint64_t uLatency = 0;
839 for (uint32_t i = 0; i < pJob->cReqStats; i++)
840 uLatency += paReqRuntimeNs[i];
841 RTTestIValueF(uLatency / pJob->cReqStats, RTTESTUNIT_NS, "%s/Job/%RU32/AvgLatency", pszTest, pJob->idJob);
842
843 RTMemFree(paReqRuntimeNs);
844 }
845 else
846 RTTestIErrorInc();
847}
848
849
850/**
851 * Synchronizes with the other jobs and waits for the current test to execute.
852 *
853 * @returns IPRT status.
854 * @param pJob The job data for the current worker.
855 */
856static int ioPerfJobSync(PIOPERFJOB pJob)
857{
858 if (pJob->pMaster)
859 {
860 /* Enter the rendezvous semaphore. */
861 int rc = VINF_SUCCESS;
862
863 return rc;
864 }
865
866 /* Single threaded run, collect the results from our current test and select the next test. */
867 /** @todo Results and statistics collection. */
868 pJob->enmTest = ioPerfJobTestSelectNext();
869 return VINF_SUCCESS;
870}
871
872
873/**
874 * I/O perf job main work loop.
875 *
876 * @returns IPRT status code.
877 * @param pJob The job data for the current worker.
878 */
879static int ioPerfJobWorkLoop(PIOPERFJOB pJob)
880{
881 int rc = VINF_SUCCESS;
882
883 for (;;)
884 {
885 /* Synchronize with the other jobs and the master. */
886 rc = ioPerfJobSync(pJob);
887 if (RT_FAILURE(rc))
888 break;
889
890 if (pJob->enmTest == IOPERFTEST_SHUTDOWN)
891 break;
892
893 rc = ioPerfJobTestIoLoop(pJob);
894 if (RT_FAILURE(rc))
895 break;
896
897 /*
898 * Do the statistics here for a single job run,
899 * the master will do this for each job and combined statistics
900 * otherwise.
901 */
902 if ( !pJob->pMaster
903 && g_fReportIoStats)
904 ioPerfJobStats(pJob);
905 }
906
907 return rc;
908}
909
910
911/**
912 * Job thread entry point.
913 */
914static DECLCALLBACK(int) ioPerfJobThread(RTTHREAD hThrdSelf, void *pvUser)
915{
916 RT_NOREF(hThrdSelf);
917
918 PIOPERFJOB pJob = (PIOPERFJOB)pvUser;
919 return ioPerfJobWorkLoop(pJob);
920}
921
922
923/**
924 * Prepares the test set by laying out the files and filling them with data.
925 *
926 * @returns IPRT status code.
927 * @param pJob The job to initialize.
928 */
929static int ioPerfJobTestSetPrep(PIOPERFJOB pJob)
930{
931 int rc = RTRandAdvCreateParkMiller(&pJob->hRand);
932 if (RT_SUCCESS(rc))
933 {
934 rc = RTRandAdvSeed(pJob->hRand, RTTimeNanoTS());
935 if (RT_SUCCESS(rc))
936 {
937 /*
938 * Create a random data buffer for writes, we'll use multiple of the I/O block size to
939 * be able to seek in the buffer quite a bit to make the file content as random as possible
940 * to avoid mechanisms like compression or deduplication for now which can influence storage
941 * benchmarking unpredictably.
942 */
943 pJob->cRandWriteBlocks512B = (uint32_t)(((IOPERF_RAND_DATA_BUF_FACTOR - 1) * pJob->cbIoBlock) / 512);
944 pJob->pbRandWrite = (uint8_t *)RTMemPageAllocZ(IOPERF_RAND_DATA_BUF_FACTOR * pJob->cbIoBlock);
945 if (RT_LIKELY(pJob->pbRandWrite))
946 {
947 RTRandAdvBytes(pJob->hRand, pJob->pbRandWrite, IOPERF_RAND_DATA_BUF_FACTOR * pJob->cbIoBlock);
948
949 /* Write the content here if the first write test is disabled. */
950 if (g_aenmTests[IOPERFTEST_FIRST_WRITE] == IOPERFTEST_DISABLED)
951 {
952 for (uint64_t off = 0; off < pJob->cbTestSet && RT_SUCCESS(rc); off += pJob->cbIoBlock)
953 {
954 void *pvWrite = ioPerfJobTestGetWriteBufForOffset(pJob, off);
955 rc = RTFileWriteAt(pJob->Hnd.u.hFile, off, pvWrite, pJob->cbIoBlock, NULL);
956 }
957 }
958
959 if (RT_SUCCESS(rc))
960 return rc;
961
962 RTMemPageFree(pJob->pbRandWrite, IOPERF_RAND_DATA_BUF_FACTOR * pJob->cbIoBlock);
963 }
964 }
965 RTRandAdvDestroy(pJob->hRand);
966 }
967
968 return rc;
969}
970
971
972/**
973 * Initializes the given job instance.
974 *
975 * @returns IPRT status code.
976 * @param pJob The job to initialize.
977 * @param pMaster The coordination master if any.
978 * @param idJob ID of the job.
979 * @param pszIoEngine I/O queue engine for this job, NULL for best default.
980 * @param pszTestDir The test directory to create the file in - requires a slash a the end.
981 * @param enmPrepMethod Test set preparation method to use.
982 * @param cbTestSet Size of the test set ofr this job.
983 * @param cbIoBlock I/O block size for the given job.
984 * @param cReqsMax Maximum number of concurrent requests for this job.
985 * @param uWriteChance The write chance for mixed read/write tests.
986 * @param fVerifyReads Flag whether to verify read data.
987 */
988static int ioPerfJobInit(PIOPERFJOB pJob, PIOPERFMASTER pMaster, uint32_t idJob,
989 const char *pszIoEngine, const char *pszTestDir,
990 IOPERFTESTSETPREP enmPrepMethod,
991 uint64_t cbTestSet, size_t cbIoBlock, uint32_t cReqsMax,
992 unsigned uWriteChance, bool fVerifyReads)
993{
994 pJob->pMaster = pMaster;
995 pJob->idJob = idJob;
996 pJob->enmTest = IOPERFTEST_INVALID;
997 pJob->hThread = NIL_RTTHREAD;
998 pJob->Hnd.enmType = RTHANDLETYPE_FILE;
999 pJob->cbTestSet = cbTestSet;
1000 pJob->cbIoBlock = cbIoBlock;
1001 pJob->cReqsMax = cReqsMax;
1002 pJob->cbIoReqReadBuf = cReqsMax * cbIoBlock;
1003 pJob->uWriteChance = uWriteChance;
1004 pJob->fVerifyReads = fVerifyReads;
1005 pJob->cReqStats = (uint32_t)(pJob->cbTestSet / pJob->cbIoBlock + ((pJob->cbTestSet % pJob->cbIoBlock) ? 1 : 0));
1006 pJob->idxReqStatNext = 0;
1007
1008 int rc = VINF_SUCCESS;
1009 pJob->paIoReqs = (PIOPERFREQ)RTMemAllocZ(cReqsMax * sizeof(IOPERFREQ));
1010 if (RT_LIKELY(pJob->paIoReqs))
1011 {
1012 pJob->paReqStats = (PIOPERFREQSTAT)RTMemAllocZ(pJob->cReqStats * sizeof(IOPERFREQSTAT));
1013 if (RT_LIKELY(pJob->paReqStats))
1014 {
1015 pJob->pvIoReqReadBuf = RTMemPageAlloc(pJob->cbIoReqReadBuf);
1016 if (RT_LIKELY(pJob->pvIoReqReadBuf))
1017 {
1018 uint8_t *pbReadBuf = (uint8_t *)pJob->pvIoReqReadBuf;
1019
1020 for (uint32_t i = 0; i < cReqsMax; i++)
1021 {
1022 pJob->paIoReqs[i].pvXferRead = pbReadBuf;
1023 pJob->paIoReqs[i].cbXferRead = cbIoBlock;
1024 pbReadBuf += cbIoBlock;
1025 }
1026
1027 /* Create the file. */
1028 pJob->pszFilename = RTStrAPrintf2("%sioperf-%u.file", pszTestDir, idJob);
1029 if (RT_LIKELY(pJob->pszFilename))
1030 {
1031 uint32_t fOpen = RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE | RTFILE_O_READWRITE | RTFILE_O_ASYNC_IO;
1032 if (g_fNoCache)
1033 fOpen |= RTFILE_O_NO_CACHE;
1034 rc = RTFileOpen(&pJob->Hnd.u.hFile, pJob->pszFilename, fOpen);
1035 if (RT_SUCCESS(rc))
1036 {
1037 switch (enmPrepMethod)
1038 {
1039 case IOPERFTESTSETPREP_JUST_CREATE:
1040 break;
1041 case IOPERFTESTSETPREP_SET_SZ:
1042 rc = RTFileSetSize(pJob->Hnd.u.hFile, pJob->cbTestSet);
1043 break;
1044 case IOPERFTESTSETPREP_SET_ALLOC_SZ:
1045 rc = RTFileSetAllocationSize(pJob->Hnd.u.hFile, pJob->cbTestSet, RTFILE_ALLOC_SIZE_F_DEFAULT);
1046 break;
1047 default:
1048 AssertMsgFailed(("Invalid file preparation method: %d\n", enmPrepMethod));
1049 }
1050
1051 if (RT_SUCCESS(rc))
1052 {
1053 rc = ioPerfJobTestSetPrep(pJob);
1054 if (RT_SUCCESS(rc))
1055 {
1056 /* Create I/O queue. */
1057 PCRTIOQUEUEPROVVTABLE pIoQProv = NULL;
1058 if (!pszIoEngine)
1059 pIoQProv = RTIoQueueProviderGetBestForHndType(RTHANDLETYPE_FILE);
1060 else
1061 pIoQProv = RTIoQueueProviderGetById(pszIoEngine);
1062
1063 if (RT_LIKELY(pIoQProv))
1064 {
1065 rc = RTIoQueueCreate(&pJob->hIoQueue, pIoQProv, 0 /*fFlags*/, cReqsMax, cReqsMax);
1066 if (RT_SUCCESS(rc))
1067 {
1068 rc = RTIoQueueHandleRegister(pJob->hIoQueue, &pJob->Hnd);
1069 if (RT_SUCCESS(rc))
1070 {
1071 /* Spin up the worker thread. */
1072 if (pMaster)
1073 rc = RTThreadCreateF(&pJob->hThread, ioPerfJobThread, pJob, 0,
1074 RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, "ioperf-%u", idJob);
1075
1076 if (RT_SUCCESS(rc))
1077 return VINF_SUCCESS;
1078 }
1079 }
1080 }
1081 else
1082 rc = VERR_NOT_SUPPORTED;
1083 }
1084
1085 RTRandAdvDestroy(pJob->hRand);
1086 }
1087
1088 RTFileClose(pJob->Hnd.u.hFile);
1089 RTFileDelete(pJob->pszFilename);
1090 }
1091
1092 RTStrFree(pJob->pszFilename);
1093 }
1094 else
1095 rc = VERR_NO_STR_MEMORY;
1096
1097 RTMemPageFree(pJob->pvIoReqReadBuf, pJob->cbIoReqReadBuf);
1098 }
1099 else
1100 rc = VERR_NO_MEMORY;
1101
1102 RTMemFree(pJob->paReqStats);
1103 }
1104 else
1105 rc = VERR_NO_MEMORY;
1106 }
1107 else
1108 rc = VERR_NO_MEMORY;
1109
1110 return rc;
1111}
1112
1113
1114/**
1115 * Teardown a job instance and free all associated resources.
1116 *
1117 * @returns IPRT status code.
1118 * @param pJob The job to teardown.
1119 */
1120static int ioPerfJobTeardown(PIOPERFJOB pJob)
1121{
1122 if (pJob->pMaster)
1123 {
1124 int rc = RTThreadWait(pJob->hThread, RT_INDEFINITE_WAIT, NULL);
1125 AssertRC(rc); RT_NOREF(rc);
1126 }
1127
1128 RTIoQueueHandleDeregister(pJob->hIoQueue, &pJob->Hnd);
1129 RTIoQueueDestroy(pJob->hIoQueue);
1130 RTRandAdvDestroy(pJob->hRand);
1131 RTMemPageFree(pJob->pbRandWrite, IOPERF_RAND_DATA_BUF_FACTOR * pJob->cbIoBlock);
1132 RTFileClose(pJob->Hnd.u.hFile);
1133 RTFileDelete(pJob->pszFilename);
1134 RTStrFree(pJob->pszFilename);
1135 RTMemPageFree(pJob->pvIoReqReadBuf, pJob->cbIoReqReadBuf);
1136 RTMemFree(pJob->paIoReqs);
1137 RTMemFree(pJob->paReqStats);
1138 return VINF_SUCCESS;
1139}
1140
1141
1142/**
1143 * Single job testing entry point.
1144 *
1145 * @returns IPRT status code.
1146 */
1147static int ioPerfDoTestSingle(void)
1148{
1149 IOPERFJOB Job;
1150
1151 int rc = ioPerfJobInit(&Job, NULL, 0, g_pszIoEngine,
1152 g_szDir, IOPERFTESTSETPREP_SET_SZ,
1153 g_cbTestSet, g_cbIoBlock, g_cReqsMax,
1154 g_uWriteChance, g_fVerifyReads);
1155 if (RT_SUCCESS(rc))
1156 {
1157 rc = ioPerfJobWorkLoop(&Job);
1158 if (RT_SUCCESS(rc))
1159 {
1160 rc = ioPerfJobTeardown(&Job);
1161 AssertRC(rc); RT_NOREF(rc);
1162 }
1163 }
1164
1165 return rc;
1166}
1167
1168
1169/**
1170 * Multi job testing entry point.
1171 *
1172 * @returns IPRT status code.
1173 */
1174static int ioPerfDoTestMulti(void)
1175{
1176 return VERR_NOT_IMPLEMENTED;
1177}
1178
1179
1180/**
1181 * Display the usage to @a pStrm.
1182 */
1183static void Usage(PRTSTREAM pStrm)
1184{
1185 char szExec[RTPATH_MAX];
1186 RTStrmPrintf(pStrm, "usage: %s <-d <testdir>> [options]\n",
1187 RTPathFilename(RTProcGetExecutablePath(szExec, sizeof(szExec))));
1188 RTStrmPrintf(pStrm, "\n");
1189 RTStrmPrintf(pStrm, "options: \n");
1190
1191 for (unsigned i = 0; i < RT_ELEMENTS(g_aCmdOptions); i++)
1192 {
1193 char szHelp[80];
1194 const char *pszHelp;
1195 switch (g_aCmdOptions[i].iShort)
1196 {
1197 case 'd': pszHelp = "The directory to use for testing. default: CWD/fstestdir"; break;
1198 case 'r': pszHelp = "Don't abspath test dir (good for deep dirs). default: disabled"; break;
1199 case 'y': pszHelp = "Flag whether to verify read data. default: enabled"; break;
1200 case 'c': pszHelp = "Flag whether to use the filesystem cache. default: disabled"; break;
1201 case 'v': pszHelp = "More verbose execution."; break;
1202 case 'q': pszHelp = "Quiet execution."; break;
1203 case 'h': pszHelp = "Displays this help and exit"; break;
1204 case 'V': pszHelp = "Displays the program revision"; break;
1205 default:
1206 if (g_aCmdOptions[i].iShort >= kCmdOpt_First)
1207 {
1208 if (RTStrStartsWith(g_aCmdOptions[i].pszLong, "--no-"))
1209 RTStrPrintf(szHelp, sizeof(szHelp), "Disables the '%s' test.", g_aCmdOptions[i].pszLong + 5);
1210 else
1211 RTStrPrintf(szHelp, sizeof(szHelp), "Enables the '%s' test.", g_aCmdOptions[i].pszLong + 2);
1212 pszHelp = szHelp;
1213 }
1214 else
1215 pszHelp = "Option undocumented";
1216 break;
1217 }
1218 if ((unsigned)g_aCmdOptions[i].iShort < 127U)
1219 {
1220 char szOpt[64];
1221 RTStrPrintf(szOpt, sizeof(szOpt), "%s, -%c", g_aCmdOptions[i].pszLong, g_aCmdOptions[i].iShort);
1222 RTStrmPrintf(pStrm, " %-19s %s\n", szOpt, pszHelp);
1223 }
1224 else
1225 RTStrmPrintf(pStrm, " %-19s %s\n", g_aCmdOptions[i].pszLong, pszHelp);
1226 }
1227}
1228
1229
1230int main(int argc, char *argv[])
1231{
1232 /*
1233 * Init IPRT and globals.
1234 */
1235 int rc = RTTestInitAndCreate("IoPerf", &g_hTest);
1236 if (rc)
1237 return rc;
1238
1239 /*
1240 * Default values.
1241 */
1242 char szDefaultDir[32];
1243 const char *pszDir = szDefaultDir;
1244 RTStrPrintf(szDefaultDir, sizeof(szDefaultDir), "ioperfdir-%u" RTPATH_SLASH_STR, RTProcSelf());
1245
1246 RTGETOPTUNION ValueUnion;
1247 RTGETOPTSTATE GetState;
1248 RTGetOptInit(&GetState, argc, argv, g_aCmdOptions, RT_ELEMENTS(g_aCmdOptions), 1, 0 /* fFlags */);
1249 while ((rc = RTGetOpt(&GetState, &ValueUnion)) != 0)
1250 {
1251 switch (rc)
1252 {
1253 case 'd':
1254 pszDir = ValueUnion.psz;
1255 break;
1256
1257 case 'r':
1258 g_fRelativeDir = true;
1259 break;
1260
1261 case 'i':
1262 g_pszIoEngine = ValueUnion.psz;
1263 break;
1264
1265 case 's':
1266 g_cbTestSet = ValueUnion.u64;
1267 break;
1268
1269 case 'b':
1270 g_cbIoBlock = ValueUnion.u32;
1271 break;
1272
1273 case 'm':
1274 g_cReqsMax = ValueUnion.u32;
1275 break;
1276
1277 case 'y':
1278 g_fVerifyReads = ValueUnion.f;
1279 break;
1280
1281 case 'c':
1282 g_fNoCache = !ValueUnion.f;
1283 break;
1284
1285 case 't':
1286 g_fReportIoStats = ValueUnion.f;
1287 break;
1288
1289 case kCmdOpt_FirstWrite:
1290 g_aenmTests[IOPERFTEST_FIRST_WRITE] = IOPERFTEST_FIRST_WRITE;
1291 break;
1292 case kCmdOpt_NoFirstWrite:
1293 g_aenmTests[IOPERFTEST_FIRST_WRITE] = IOPERFTEST_DISABLED;
1294 break;
1295 case kCmdOpt_SeqRead:
1296 g_aenmTests[IOPERFTEST_SEQ_READ] = IOPERFTEST_SEQ_READ;
1297 break;
1298 case kCmdOpt_NoSeqRead:
1299 g_aenmTests[IOPERFTEST_SEQ_READ] = IOPERFTEST_DISABLED;
1300 break;
1301 case kCmdOpt_SeqWrite:
1302 g_aenmTests[IOPERFTEST_SEQ_WRITE] = IOPERFTEST_SEQ_WRITE;
1303 break;
1304 case kCmdOpt_NoSeqWrite:
1305 g_aenmTests[IOPERFTEST_SEQ_WRITE] = IOPERFTEST_DISABLED;
1306 break;
1307 case kCmdOpt_RndRead:
1308 g_aenmTests[IOPERFTEST_RND_READ] = IOPERFTEST_RND_READ;
1309 break;
1310 case kCmdOpt_NoRndRead:
1311 g_aenmTests[IOPERFTEST_RND_READ] = IOPERFTEST_DISABLED;
1312 break;
1313 case kCmdOpt_RndWrite:
1314 g_aenmTests[IOPERFTEST_RND_WRITE] = IOPERFTEST_RND_WRITE;
1315 break;
1316 case kCmdOpt_NoRndWrite:
1317 g_aenmTests[IOPERFTEST_RND_WRITE] = IOPERFTEST_DISABLED;
1318 break;
1319 case kCmdOpt_RevRead:
1320 g_aenmTests[IOPERFTEST_REV_READ] = IOPERFTEST_REV_READ;
1321 break;
1322 case kCmdOpt_NoRevRead:
1323 g_aenmTests[IOPERFTEST_REV_READ] = IOPERFTEST_DISABLED;
1324 break;
1325 case kCmdOpt_RevWrite:
1326 g_aenmTests[IOPERFTEST_REV_WRITE] = IOPERFTEST_REV_WRITE;
1327 break;
1328 case kCmdOpt_NoRevWrite:
1329 g_aenmTests[IOPERFTEST_REV_WRITE] = IOPERFTEST_DISABLED;
1330 break;
1331 case kCmdOpt_SeqReadWrite:
1332 g_aenmTests[IOPERFTEST_SEQ_READWRITE] = IOPERFTEST_SEQ_READWRITE;
1333 break;
1334 case kCmdOpt_NoSeqReadWrite:
1335 g_aenmTests[IOPERFTEST_SEQ_READWRITE] = IOPERFTEST_DISABLED;
1336 break;
1337 case kCmdOpt_RndReadWrite:
1338 g_aenmTests[IOPERFTEST_RND_READWRITE] = IOPERFTEST_RND_READWRITE;
1339 break;
1340 case kCmdOpt_NoRndReadWrite:
1341 g_aenmTests[IOPERFTEST_RND_READWRITE] = IOPERFTEST_DISABLED;
1342 break;
1343
1344 case 'q':
1345 g_uVerbosity = 0;
1346 break;
1347
1348 case 'v':
1349 g_uVerbosity++;
1350 break;
1351
1352 case 'h':
1353 Usage(g_pStdOut);
1354 return RTEXITCODE_SUCCESS;
1355
1356 case 'V':
1357 {
1358 char szRev[] = "$Revision: 98763 $";
1359 szRev[RT_ELEMENTS(szRev) - 2] = '\0';
1360 RTPrintf(RTStrStrip(strchr(szRev, ':') + 1));
1361 return RTEXITCODE_SUCCESS;
1362 }
1363
1364 default:
1365 return RTGetOptPrintError(rc, &ValueUnion);
1366 }
1367 }
1368
1369 /*
1370 * Populate g_szDir.
1371 */
1372 if (!g_fRelativeDir)
1373 rc = RTPathAbs(pszDir, g_szDir, sizeof(g_szDir));
1374 else
1375 rc = RTStrCopy(g_szDir, sizeof(g_szDir), pszDir);
1376 if (RT_FAILURE(rc))
1377 {
1378 RTTestFailed(g_hTest, "%s(%s) failed: %Rrc\n", g_fRelativeDir ? "RTStrCopy" : "RTAbsPath", pszDir, rc);
1379 return RTTestSummaryAndDestroy(g_hTest);
1380 }
1381 RTPathEnsureTrailingSeparator(g_szDir, sizeof(g_szDir));
1382 g_cchDir = strlen(g_szDir);
1383
1384 /*
1385 * Create the test directory with an 'empty' subdirectory under it,
1386 * execute the tests, and remove directory when done.
1387 */
1388 RTTestBanner(g_hTest);
1389 if (!RTPathExists(g_szDir))
1390 {
1391 /* The base dir: */
1392 rc = RTDirCreate(g_szDir, 0755,
1393 RTDIRCREATE_FLAGS_NOT_CONTENT_INDEXED_DONT_SET | RTDIRCREATE_FLAGS_NOT_CONTENT_INDEXED_NOT_CRITICAL);
1394 if (RT_SUCCESS(rc))
1395 {
1396 RTTestIPrintf(RTTESTLVL_ALWAYS, "Test dir: %s\n", g_szDir);
1397
1398 if (g_cJobs == 1)
1399 rc = ioPerfDoTestSingle();
1400 else
1401 rc = ioPerfDoTestMulti();
1402
1403 g_szDir[g_cchDir] = '\0';
1404 rc = RTDirRemoveRecursive(g_szDir, RTDIRRMREC_F_CONTENT_AND_DIR | (g_fRelativeDir ? RTDIRRMREC_F_NO_ABS_PATH : 0));
1405 if (RT_FAILURE(rc))
1406 RTTestFailed(g_hTest, "RTDirRemoveRecursive(%s,) -> %Rrc\n", g_szDir, rc);
1407 }
1408 else
1409 RTTestFailed(g_hTest, "RTDirCreate(%s) -> %Rrc\n", g_szDir, rc);
1410 }
1411 else
1412 RTTestFailed(g_hTest, "Test directory already exists: %s\n", g_szDir);
1413
1414 return RTTestSummaryAndDestroy(g_hTest);
1415}
1416
注意: 瀏覽 TracBrowser 來幫助您使用儲存庫瀏覽器

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