VirtualBox

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

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

ValidationKit/IoPerf: Updates to the core test loop

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 36.6 KB
Line 
1/* $Id: IoPerf.cpp 80065 2019-07-31 11:04:15Z vboxsync $ */
2/** @file
3 * IoPerf - Storage I/O Performance Benchmark.
4 */
5
6/*
7 * Copyright (C) 2019 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 *
17 * The contents of this file may alternatively be used under the terms
18 * of the Common Development and Distribution License Version 1.0
19 * (CDDL) only, as it comes in the "COPYING.CDDL" file of the
20 * VirtualBox OSE distribution, in which case the provisions of the
21 * CDDL are applicable instead of those of the GPL.
22 *
23 * You may elect to license modified versions of this file under the
24 * terms and conditions of either the GPL or the CDDL or both.
25 */
26
27
28/*********************************************************************************************************************************
29* Header Files *
30*********************************************************************************************************************************/
31#include <iprt/asm.h>
32#include <iprt/assert.h>
33#include <iprt/err.h>
34#include <iprt/dir.h>
35#include <iprt/file.h>
36#include <iprt/getopt.h>
37#include <iprt/initterm.h>
38#include <iprt/ioqueue.h>
39#include <iprt/list.h>
40#include <iprt/mem.h>
41#include <iprt/message.h>
42#include <iprt/param.h>
43#include <iprt/path.h>
44#include <iprt/process.h>
45#include <iprt/rand.h>
46#include <iprt/string.h>
47#include <iprt/stream.h>
48#include <iprt/system.h>
49#include <iprt/test.h>
50#include <iprt/time.h>
51#include <iprt/thread.h>
52#include <iprt/zero.h>
53
54
55/*********************************************************************************************************************************
56* Defined Constants And Macros *
57*********************************************************************************************************************************/
58
59/** Size multiplier for the random data buffer to seek around. */
60#define IOPERF_RAND_DATA_BUF_FACTOR 3
61
62
63/*********************************************************************************************************************************
64* Structures and Typedefs *
65*********************************************************************************************************************************/
66
67/** Forward declaration of the master. */
68typedef struct IOPERFMASTER *PIOPERFMASTER;
69
70/**
71 * I/O perf supported tests.
72 */
73typedef enum IOPERFTEST
74{
75 /** Invalid test handle. */
76 IOPERFTEST_INVALID = 0,
77 /** The test was disabled. */
78 IOPERFTEST_DISABLED,
79 IOPERFTEST_FIRST_WRITE,
80 IOPERFTEST_SEQ_READ,
81 IOPERFTEST_SEQ_WRITE,
82 IOPERFTEST_RND_READ,
83 IOPERFTEST_RND_WRITE,
84 IOPERFTEST_REV_READ,
85 IOPERFTEST_REV_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 * I/O perf request.
113 */
114typedef struct IOPERFREQ
115{
116 /** Start timestamp. */
117 uint64_t tsStart;
118 /** Request operation code. */
119 RTIOQUEUEOP enmOp;
120 /** Start offset. */
121 uint64_t offXfer;
122 /** Transfer size for the request. */
123 size_t cbXfer;
124 /** The buffer used for the transfer. */
125 void *pvXfer;
126 /** This is the statically assigned destination buffer for read requests for this request. */
127 void *pvXferRead;
128 /** Size of the read buffer. */
129 size_t cbXferRead;
130} IOPERFREQ;
131/** Pointer to an I/O perf request. */
132typedef IOPERFREQ *PIOPERFREQ;
133/** Pointer to a constant I/O perf request. */
134typedef const IOPERFREQ *PCIOPERFREQ;
135
136
137/**
138 * I/O perf job data.
139 */
140typedef struct IOPERFJOB
141{
142 /** Pointer to the master if multiple jobs are running. */
143 PIOPERFMASTER pMaster;
144 /** Job ID. */
145 uint32_t idJob;
146 /** The test this job is executing. */
147 volatile IOPERFTEST enmTest;
148 /** The thread executing the job. */
149 RTTHREAD hThread;
150 /** The I/O queue for the job. */
151 RTIOQUEUE hIoQueue;
152 /** The file path used. */
153 char *pszFilename;
154 /** The handle to use for the I/O queue. */
155 RTHANDLE Hnd;
156 /** Multi event semaphore to synchronise with other jobs. */
157 RTSEMEVENTMULTI hSemEvtMultiRendezvous;
158 /** The test set size. */
159 uint64_t cbTestSet;
160 /** Size of one I/O block. */
161 size_t cbIoBlock;
162 /** Maximum number of requests to queue. */
163 uint32_t cReqsMax;
164 /** Pointer to the array of request specific data. */
165 PIOPERFREQ paIoReqs;
166 /** Page aligned chunk of memory assigned as read buffers for the individual requests. */
167 void *pvIoReqReadBuf;
168 /** Size of the read memory buffer. */
169 size_t cbIoReqReadBuf;
170 /** Random number generator used. */
171 RTRAND hRand;
172 /** The random data buffer used for writes. */
173 uint8_t *pbRandWrite;
174 /** Size of the random write buffer in 512 byte blocks. */
175 uint32_t cRandWriteBlocks512B;
176 /** Test dependent data. */
177 union
178 {
179 /** Sequential read write. */
180 uint64_t offNextSeq;
181 } Tst;
182} IOPERFJOB;
183/** Pointer to an I/O Perf job. */
184typedef IOPERFJOB *PIOPERFJOB;
185
186
187/**
188 * I/O perf master instance coordinating the job execution.
189 */
190typedef struct IOPERFMASTER
191{
192 /** Event semaphore. */
193 /** Number of jobs. */
194 uint32_t cJobs;
195 /** Job instances, variable in size. */
196 IOPERFJOB aJobs[1];
197} IOPERFMASTER;
198
199
200enum
201{
202 kCmdOpt_First = 128,
203
204 kCmdOpt_FirstWrite = kCmdOpt_First,
205 kCmdOpt_NoFirstWrite,
206 kCmdOpt_SeqRead,
207 kCmdOpt_NoSeqRead,
208 kCmdOpt_SeqWrite,
209 kCmdOpt_NoSeqWrite,
210 kCmdOpt_RndRead,
211 kCmdOpt_NoRndRead,
212 kCmdOpt_RndWrite,
213 kCmdOpt_NoRndWrite,
214 kCmdOpt_RevRead,
215 kCmdOpt_NoRevRead,
216 kCmdOpt_RevWrite,
217 kCmdOpt_NoRevWrite,
218 kCmdOpt_SeqReadWrite,
219 kCmdOpt_NoSeqReadWrite,
220 kCmdOpt_RndReadWrite,
221 kCmdOpt_NoRndReadWrite,
222
223 kCmdOpt_End
224};
225
226
227/*********************************************************************************************************************************
228* Global Variables *
229*********************************************************************************************************************************/
230/** Command line parameters */
231static const RTGETOPTDEF g_aCmdOptions[] =
232{
233 { "--dir", 'd', RTGETOPT_REQ_STRING },
234 { "--relative-dir", 'r', RTGETOPT_REQ_NOTHING },
235
236 { "--jobs", 'j', RTGETOPT_REQ_UINT32 },
237 { "--io-engine", 'i', RTGETOPT_REQ_STRING },
238 { "--test-set-size", 's', RTGETOPT_REQ_UINT64 },
239 { "--block-size", 'b', RTGETOPT_REQ_UINT32 },
240 { "--maximum-requests", 'm', RTGETOPT_REQ_UINT32 },
241
242 { "--first-write", kCmdOpt_FirstWrite, RTGETOPT_REQ_NOTHING },
243 { "--no-first-write", kCmdOpt_NoFirstWrite, RTGETOPT_REQ_NOTHING },
244 { "--seq-read", kCmdOpt_SeqRead, RTGETOPT_REQ_NOTHING },
245 { "--no-seq-read", kCmdOpt_NoSeqRead, RTGETOPT_REQ_NOTHING },
246 { "--seq-write", kCmdOpt_SeqWrite, RTGETOPT_REQ_NOTHING },
247 { "--no-seq-write", kCmdOpt_NoSeqWrite, RTGETOPT_REQ_NOTHING },
248 { "--rnd-read", kCmdOpt_RndRead, RTGETOPT_REQ_NOTHING },
249 { "--no-rnd-read", kCmdOpt_NoRndRead, RTGETOPT_REQ_NOTHING },
250 { "--rnd-write", kCmdOpt_RndWrite, RTGETOPT_REQ_NOTHING },
251 { "--no-rnd-write", kCmdOpt_NoRndWrite, RTGETOPT_REQ_NOTHING },
252 { "--rev-read", kCmdOpt_RevRead, RTGETOPT_REQ_NOTHING },
253 { "--no-rev-read", kCmdOpt_NoRevRead, RTGETOPT_REQ_NOTHING },
254 { "--rev-write", kCmdOpt_RevWrite, RTGETOPT_REQ_NOTHING },
255 { "--no-rev-write", kCmdOpt_NoRevWrite, RTGETOPT_REQ_NOTHING },
256 { "--seq-read-write", kCmdOpt_SeqReadWrite, RTGETOPT_REQ_NOTHING },
257 { "--no-seq-read-write", kCmdOpt_NoSeqReadWrite, RTGETOPT_REQ_NOTHING },
258 { "--rnd-read-write", kCmdOpt_RndReadWrite, RTGETOPT_REQ_NOTHING },
259 { "--no-rnd-read-write", kCmdOpt_NoRndReadWrite, RTGETOPT_REQ_NOTHING },
260
261 { "--quiet", 'q', RTGETOPT_REQ_NOTHING },
262 { "--verbose", 'v', RTGETOPT_REQ_NOTHING },
263 { "--version", 'V', RTGETOPT_REQ_NOTHING },
264 { "--help", 'h', RTGETOPT_REQ_NOTHING } /* for Usage() */
265};
266
267/** The test handle. */
268static RTTEST g_hTest;
269/** Verbosity level. */
270static uint32_t g_uVerbosity = 0;
271/** Selected I/O engine for the tests, NULL means pick best default one. */
272static const char *g_pszIoEngine = NULL;
273/** Number of jobs to run concurrently. */
274static uint32_t g_cJobs = 1;
275/** Size of each test set (file) in bytes. */
276static uint64_t g_cbTestSet = _2G;
277/** Block size for each request. */
278static size_t g_cbIoBlock = _4K;
279/** Maximum number of concurrent requests for each job. */
280static uint32_t g_cReqsMax = 16;
281
282
283/** @name Configured tests, this must match the IOPERFTEST order.
284 * @{ */
285static IOPERFTEST g_aenmTests[] =
286{
287 IOPERFTEST_DISABLED, /** @< The invalid test value is disabled of course. */
288 IOPERFTEST_DISABLED,
289 IOPERFTEST_FIRST_WRITE,
290 IOPERFTEST_SEQ_READ,
291 IOPERFTEST_SEQ_WRITE,
292 IOPERFTEST_RND_READ,
293 IOPERFTEST_RND_WRITE,
294 IOPERFTEST_REV_READ,
295 IOPERFTEST_REV_WRITE,
296 IOPERFTEST_SEQ_READWRITE,
297 IOPERFTEST_RND_READWRITE,
298 IOPERFTEST_SHUTDOWN
299};
300/** The test index being selected next. */
301static uint32_t g_idxTest = 2;
302/** @} */
303
304/** Set if g_szDir and friends are path relative to CWD rather than absolute. */
305static bool g_fRelativeDir = false;
306/** The length of g_szDir. */
307static size_t g_cchDir;
308
309/** The test directory (absolute). This will always have a trailing slash. */
310static char g_szDir[RTPATH_BIG_MAX];
311
312
313/*********************************************************************************************************************************
314* Tests *
315*********************************************************************************************************************************/
316
317
318/**
319 * Selects the next test to run.
320 *
321 * @return Next test to run.
322 */
323static IOPERFTEST ioPerfJobTestSelectNext()
324{
325 AssertReturn(g_idxTest < RT_ELEMENTS(g_aenmTests), IOPERFTEST_SHUTDOWN);
326
327 while ( g_idxTest < RT_ELEMENTS(g_aenmTests)
328 && g_aenmTests[g_idxTest] == IOPERFTEST_DISABLED)
329 g_idxTest++;
330
331 AssertReturn(g_idxTest < RT_ELEMENTS(g_aenmTests), IOPERFTEST_SHUTDOWN);
332
333 return g_aenmTests[g_idxTest];
334}
335
336
337/**
338 * Returns the I/O queue operation for the next request.
339 *
340 * @returns I/O queue operation enum.
341 * @param pJob The job data for the current worker.
342 */
343static RTIOQUEUEOP ioPerfJobTestGetIoQOp(PIOPERFJOB pJob)
344{
345 switch (pJob->enmTest)
346 {
347 case IOPERFTEST_FIRST_WRITE:
348 case IOPERFTEST_SEQ_WRITE:
349 case IOPERFTEST_SEQ_READ:
350 case IOPERFTEST_REV_WRITE:
351 case IOPERFTEST_RND_WRITE:
352 return RTIOQUEUEOP_WRITE;
353
354 case IOPERFTEST_RND_READ:
355 case IOPERFTEST_REV_READ:
356 return RTIOQUEUEOP_READ;
357 case IOPERFTEST_SEQ_READWRITE:
358 case IOPERFTEST_RND_READWRITE:
359 AssertMsgFailed(("Not implemented!\n"));
360 break;
361 default:
362 AssertMsgFailed(("Invalid/unknown test selected: %d\n", pJob->enmTest));
363 break;
364 }
365
366 return RTIOQUEUEOP_INVALID;
367}
368
369
370/**
371 * Returns the offset to use for the next request.
372 *
373 * @returns Offset to use.
374 * @param pJob The job data for the current worker.
375 */
376static uint64_t ioPerfJobTestGetOffsetNext(PIOPERFJOB pJob)
377{
378 uint64_t offNext = 0;
379
380 switch (pJob->enmTest)
381 {
382 case IOPERFTEST_FIRST_WRITE:
383 case IOPERFTEST_SEQ_WRITE:
384 case IOPERFTEST_SEQ_READ:
385 offNext = pJob->Tst.offNextSeq;
386 pJob->Tst.offNextSeq += pJob->cbIoBlock;
387 break;
388
389 case IOPERFTEST_REV_WRITE:
390 case IOPERFTEST_REV_READ:
391 case IOPERFTEST_RND_WRITE:
392 case IOPERFTEST_RND_READ:
393 case IOPERFTEST_SEQ_READWRITE:
394 case IOPERFTEST_RND_READWRITE:
395 AssertMsgFailed(("Not implemented!\n"));
396 break;
397 default:
398 AssertMsgFailed(("Invalid/unknown test selected: %d\n", pJob->enmTest));
399 break;
400 }
401
402 return offNext;
403}
404
405
406/**
407 * Returns a pointer to the write buffer with random data for the given offset which
408 * is predictable for data verification.
409 *
410 * @returns Pointer to I/O block sized data buffer with random data.
411 * @param pJob The job data for the current worker.
412 * @param off The offset to get the buffer for.
413 */
414static void *ioPerfJobTestGetWriteBufForOffset(PIOPERFJOB pJob, uint64_t off)
415{
416 /*
417 * Dividing the file into 512 byte blocks so buffer pointers are at least
418 * 512 byte aligned to work with async I/O on some platforms (Linux and O_DIRECT for example).
419 */
420 uint64_t uBlock = off / 512;
421 uint32_t idxBuf = uBlock % pJob->cRandWriteBlocks512B;
422 return pJob->pbRandWrite + idxBuf * 512;
423}
424
425
426/**
427 * Initialize the given request for submission.
428 *
429 * @returns nothing.
430 * @param pJob The job data for the current worker.
431 * @param pIoReq The request to initialize.
432 */
433static void ioPerfJobTestReqInit(PIOPERFJOB pJob, PIOPERFREQ pIoReq)
434{
435 pIoReq->enmOp = ioPerfJobTestGetIoQOp(pJob);
436 pIoReq->offXfer = ioPerfJobTestGetOffsetNext(pJob);
437 pIoReq->cbXfer = pJob->cbIoBlock;
438 if (pIoReq->enmOp == RTIOQUEUEOP_READ)
439 pIoReq->pvXfer = pIoReq->pvXferRead;
440 else if (pIoReq->enmOp == RTIOQUEUEOP_WRITE)
441 pIoReq->pvXfer = ioPerfJobTestGetWriteBufForOffset(pJob, pIoReq->offXfer);
442 else /* Flush */
443 pIoReq->pvXfer = NULL;
444
445 pIoReq->tsStart = RTTimeNanoTS();
446}
447
448
449/**
450 * Initializes the test state for the current test.
451 *
452 * @returns IPRT status code.
453 * @param pJob The job data for the current worker.
454 */
455static int ioPerfJobTestInit(PIOPERFJOB pJob)
456{
457 switch (pJob->enmTest)
458 {
459 case IOPERFTEST_FIRST_WRITE:
460 case IOPERFTEST_SEQ_WRITE:
461 case IOPERFTEST_SEQ_READ:
462 pJob->Tst.offNextSeq = 0;
463 break;
464
465 case IOPERFTEST_REV_WRITE:
466 case IOPERFTEST_REV_READ:
467 case IOPERFTEST_RND_WRITE:
468 case IOPERFTEST_RND_READ:
469 case IOPERFTEST_SEQ_READWRITE:
470 case IOPERFTEST_RND_READWRITE:
471 AssertMsgFailed(("Not implemented!\n"));
472 break;
473 default:
474 AssertMsgFailed(("Invalid/unknown test selected: %d\n", pJob->enmTest));
475 break;
476 }
477
478 return VINF_SUCCESS;
479}
480
481
482/**
483 * Frees allocated resources specific for the current test.
484 *
485 * @returns nothing.
486 * @param pJob The job data for the current worker.
487 */
488static void ioPerfJobTestFinish(PIOPERFJOB pJob)
489{
490 switch (pJob->enmTest)
491 {
492 case IOPERFTEST_FIRST_WRITE:
493 case IOPERFTEST_SEQ_WRITE:
494 case IOPERFTEST_SEQ_READ:
495 break; /* Nothing to do. */
496
497 case IOPERFTEST_REV_WRITE:
498 case IOPERFTEST_REV_READ:
499 case IOPERFTEST_RND_WRITE:
500 case IOPERFTEST_RND_READ:
501 case IOPERFTEST_SEQ_READWRITE:
502 case IOPERFTEST_RND_READWRITE:
503 AssertMsgFailed(("Not implemented!\n"));
504 break;
505 default:
506 AssertMsgFailed(("Invalid/unknown test selected: %d\n", pJob->enmTest));
507 break;
508 }
509}
510
511
512/**
513 * Returns whether the current test is done with submitting new requests (reached test set size).
514 *
515 * @returns True when the test has submitted all required requests, false if there are still requests required
516 */
517static bool ioPerfJobTestIsDone(PIOPERFJOB pJob)
518{
519 switch (pJob->enmTest)
520 {
521 case IOPERFTEST_FIRST_WRITE:
522 case IOPERFTEST_SEQ_WRITE:
523 case IOPERFTEST_SEQ_READ:
524 return pJob->Tst.offNextSeq == pJob->cbTestSet;
525
526 case IOPERFTEST_REV_WRITE:
527 case IOPERFTEST_REV_READ:
528 case IOPERFTEST_RND_WRITE:
529 case IOPERFTEST_RND_READ:
530 case IOPERFTEST_SEQ_READWRITE:
531 case IOPERFTEST_RND_READWRITE:
532 AssertMsgFailed(("Not implemented!\n"));
533 break;
534 default:
535 AssertMsgFailed(("Invalid/unknown test selected: %d\n", pJob->enmTest));
536 break;
537 }
538
539 return true;
540}
541
542
543/**
544 * The test I/O loop pumping I/O.
545 *
546 * @returns IPRT status code.
547 * @param pJob The job data for the current worker.
548 */
549static int ioPerfJobTestIoLoop(PIOPERFJOB pJob)
550{
551 int rc = ioPerfJobTestInit(pJob);
552 if (RT_SUCCESS(rc))
553 {
554 /* Allocate the completion event array. */
555 uint32_t cReqsQueued = 0;
556 PRTIOQUEUECEVT paIoQCEvt = (PRTIOQUEUECEVT)RTMemAllocZ(pJob->cReqsMax * sizeof(RTIOQUEUECEVT));
557 if (RT_LIKELY(paIoQCEvt))
558 {
559 /* Queue requests up to the maximum. */
560 while ( (cReqsQueued < pJob->cReqsMax)
561 && !ioPerfJobTestIsDone(pJob)
562 && RT_SUCCESS(rc))
563 {
564 PIOPERFREQ pReq = &pJob->paIoReqs[cReqsQueued];
565 ioPerfJobTestReqInit(pJob, pReq);
566 rc = RTIoQueueRequestPrepare(pJob->hIoQueue, &pJob->Hnd, pReq->enmOp,
567 pReq->offXfer, pReq->pvXfer, pReq->cbXfer, 0 /*fReqFlags*/,
568 pReq);
569 cReqsQueued++;
570 }
571
572 /* Commit the prepared requests. */
573 if ( RT_SUCCESS(rc)
574 && cReqsQueued)
575 rc = RTIoQueueCommit(pJob->hIoQueue);
576
577 /* Enter wait loop and process completed requests. */
578 while ( RT_SUCCESS(rc)
579 && cReqsQueued)
580 {
581 uint32_t cCEvtCompleted = 0;
582
583 rc = RTIoQueueEvtWait(pJob->hIoQueue, paIoQCEvt, pJob->cReqsMax, 1 /*cMinWait*/,
584 &cCEvtCompleted, 0 /*fFlags*/);
585 if (RT_SUCCESS(rc))
586 {
587 uint32_t cReqsThisQueued = 0;
588
589 /* Process any completed event and continue to fill the queue as long as there is stuff to do. */
590 for (uint32_t i = 0; i < cCEvtCompleted; i++)
591 {
592 PIOPERFREQ pReq = (PIOPERFREQ)paIoQCEvt[i].pvUser;
593
594 if (RT_SUCCESS(paIoQCEvt[i].rcReq))
595 {
596 Assert(paIoQCEvt[i].cbXfered == pReq->cbXfer);
597
598 /** @todo Statistics collection. */
599 if (!ioPerfJobTestIsDone(pJob))
600 {
601 ioPerfJobTestReqInit(pJob, pReq);
602 rc = RTIoQueueRequestPrepare(pJob->hIoQueue, &pJob->Hnd, pReq->enmOp,
603 pReq->offXfer, pReq->pvXfer, pReq->cbXfer, 0 /*fReqFlags*/,
604 pReq);
605 cReqsThisQueued++;
606 }
607 else
608 cReqsQueued--;
609 }
610 }
611
612 if ( cReqsThisQueued
613 && RT_SUCCESS(rc))
614 rc = RTIoQueueCommit(pJob->hIoQueue);
615 }
616 }
617
618 RTMemFree(paIoQCEvt);
619 }
620
621 ioPerfJobTestFinish(pJob);
622 }
623
624 return rc;
625}
626
627
628/**
629 * Synchronizes with the other jobs and waits for the current test to execute.
630 *
631 * @returns IPRT status.
632 * @param pJob The job data for the current worker.
633 */
634static int ioPerfJobSync(PIOPERFJOB pJob)
635{
636 if (pJob->pMaster)
637 {
638 /* Enter the rendezvous semaphore. */
639 int rc = VINF_SUCCESS;
640
641 return rc;
642 }
643
644 /* Single threaded run, collect the results from our current test and select the next test. */
645 /** @todo Results and statistics collection. */
646 pJob->enmTest = ioPerfJobTestSelectNext();
647 return VINF_SUCCESS;
648}
649
650
651/**
652 * I/O perf job main work loop.
653 *
654 * @returns IPRT status code.
655 * @param pJob The job data for the current worker.
656 */
657static int ioPerfJobWorkLoop(PIOPERFJOB pJob)
658{
659 int rc = VINF_SUCCESS;
660 bool fShutdown = false;
661
662 do
663 {
664 /* Synchronize with the other jobs and the master. */
665 rc = ioPerfJobSync(pJob);
666 if (RT_FAILURE(rc))
667 break;
668
669 rc = ioPerfJobTestIoLoop(pJob);
670 } while ( RT_SUCCESS(rc)
671 && !fShutdown);
672
673 return rc;
674}
675
676
677/**
678 * Job thread entry point.
679 */
680static DECLCALLBACK(int) ioPerfJobThread(RTTHREAD hThrdSelf, void *pvUser)
681{
682 RT_NOREF(hThrdSelf);
683
684 PIOPERFJOB pJob = (PIOPERFJOB)pvUser;
685 return ioPerfJobWorkLoop(pJob);
686}
687
688
689/**
690 * Prepares the test set by laying out the files and filling them with data.
691 *
692 * @returns IPRT status code.
693 * @param pJob The job to initialize.
694 */
695static int ioPerfJobTestSetPrep(PIOPERFJOB pJob)
696{
697 int rc = RTRandAdvCreateParkMiller(&pJob->hRand);
698 if (RT_SUCCESS(rc))
699 {
700 rc = RTRandAdvSeed(pJob->hRand, RTTimeNanoTS());
701 if (RT_SUCCESS(rc))
702 {
703 /*
704 * Create a random data buffer for writes, we'll use multiple of the I/O block size to
705 * be able to seek in the buffer quite a bit to make the file content as random as possible
706 * to avoid mechanisms like compression or deduplication for now which can influence storage
707 * benchmarking unpredictably.
708 */
709 pJob->cRandWriteBlocks512B = ((IOPERF_RAND_DATA_BUF_FACTOR - 1) * pJob->cbIoBlock) / 512;
710 pJob->pbRandWrite = (uint8_t *)RTMemPageAllocZ(IOPERF_RAND_DATA_BUF_FACTOR * pJob->cbIoBlock);
711 if (RT_LIKELY(pJob->pbRandWrite))
712 {
713 RTRandAdvBytes(pJob->hRand, pJob->pbRandWrite, IOPERF_RAND_DATA_BUF_FACTOR * pJob->cbIoBlock);
714
715 /* Write the content here if the first write test is disabled. */
716 if (g_aenmTests[IOPERFTEST_FIRST_WRITE] == IOPERFTEST_DISABLED)
717 {
718 for (uint64_t off = 0; off < pJob->cbTestSet && RT_SUCCESS(rc); off += pJob->cbIoBlock)
719 {
720 void *pvWrite = ioPerfJobTestGetWriteBufForOffset(pJob, off);
721 rc = RTFileWriteAt(pJob->Hnd.u.hFile, off, pvWrite, pJob->cbIoBlock, NULL);
722 }
723 }
724 }
725 }
726 RTRandAdvDestroy(pJob->hRand);
727 }
728
729 return rc;
730}
731
732
733/**
734 * Initializes the given job instance.
735 *
736 * @returns IPRT status code.
737 * @param pJob The job to initialize.
738 * @param pMaster The coordination master if any.
739 * @param idJob ID of the job.
740 * @param pszIoEngine I/O queue engine for this job, NULL for best default.
741 * @param pszTestDir The test directory to create the file in - requires a slash a the end.
742 * @param enmPrepMethod Test set preparation method to use.
743 * @param cbTestSet Size of the test set ofr this job.
744 * @param cbIoBlock I/O block size for the given job.
745 * @param cReqsMax Maximum number of concurrent requests for this job.
746 */
747static int ioPerfJobInit(PIOPERFJOB pJob, PIOPERFMASTER pMaster, uint32_t idJob,
748 const char *pszIoEngine, const char *pszTestDir,
749 IOPERFTESTSETPREP enmPrepMethod,
750 uint64_t cbTestSet, size_t cbIoBlock, uint32_t cReqsMax)
751{
752 pJob->pMaster = pMaster;
753 pJob->idJob = idJob;
754 pJob->enmTest = IOPERFTEST_INVALID;
755 pJob->hThread = NIL_RTTHREAD;
756 pJob->Hnd.enmType = RTHANDLETYPE_FILE;
757 pJob->cbTestSet = cbTestSet;
758 pJob->cbIoBlock = cbIoBlock;
759 pJob->cReqsMax = cReqsMax;
760 pJob->cbIoReqReadBuf = cReqsMax * cbIoBlock;
761
762 int rc = VINF_SUCCESS;
763 pJob->paIoReqs = (PIOPERFREQ)RTMemAllocZ(cReqsMax * sizeof(IOPERFREQ));
764 if (RT_LIKELY(pJob->paIoReqs))
765 {
766 pJob->pvIoReqReadBuf = RTMemPageAlloc(pJob->cbIoReqReadBuf);
767 if (RT_LIKELY(pJob->pvIoReqReadBuf))
768 {
769 uint8_t *pbReadBuf = (uint8_t *)pJob->pvIoReqReadBuf;
770
771 for (uint32_t i = 0; i < cReqsMax; i++)
772 {
773 pJob->paIoReqs[i].pvXferRead = pbReadBuf;
774 pJob->paIoReqs[i].cbXferRead = cbIoBlock;
775 pbReadBuf += cbIoBlock;
776 }
777
778 /* Create the file. */
779 pJob->pszFilename = RTStrAPrintf2("%sioperf-%u.file", pszTestDir, idJob);
780 if (RT_LIKELY(pJob->pszFilename))
781 {
782 rc = RTFileOpen(&pJob->Hnd.u.hFile, pJob->pszFilename, RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE | RTFILE_O_READWRITE);
783 if (RT_SUCCESS(rc))
784 {
785 switch (enmPrepMethod)
786 {
787 case IOPERFTESTSETPREP_JUST_CREATE:
788 break;
789 case IOPERFTESTSETPREP_SET_SZ:
790 rc = RTFileSetSize(pJob->Hnd.u.hFile, pJob->cbTestSet);
791 break;
792 case IOPERFTESTSETPREP_SET_ALLOC_SZ:
793 rc = RTFileSetAllocationSize(pJob->Hnd.u.hFile, pJob->cbTestSet, RTFILE_ALLOC_SIZE_F_DEFAULT);
794 break;
795 default:
796 AssertMsgFailed(("Invalid file preparation method: %d\n", enmPrepMethod));
797 }
798
799 if (RT_SUCCESS(rc))
800 {
801 rc = ioPerfJobTestSetPrep(pJob);
802 if (RT_SUCCESS(rc))
803 {
804 /* Create I/O queue. */
805 PCRTIOQUEUEPROVVTABLE pIoQProv = NULL;
806 if (!pszIoEngine)
807 pIoQProv = RTIoQueueProviderGetBestForHndType(RTHANDLETYPE_FILE);
808 else
809 pIoQProv = RTIoQueueProviderGetById(pszIoEngine);
810
811 if (RT_LIKELY(pIoQProv))
812 {
813 rc = RTIoQueueCreate(&pJob->hIoQueue, pIoQProv, 0 /*fFlags*/, cReqsMax, cReqsMax);
814 if (RT_SUCCESS(rc))
815 {
816 rc = RTIoQueueHandleRegister(pJob->hIoQueue, &pJob->Hnd);
817 if (RT_SUCCESS(rc))
818 {
819 /* Spin up the worker thread. */
820 if (pMaster)
821 rc = RTThreadCreateF(&pJob->hThread, ioPerfJobThread, pJob, 0,
822 RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, "ioperf-%u", idJob);
823
824 if (RT_SUCCESS(rc))
825 return VINF_SUCCESS;
826 }
827 }
828 }
829 else
830 rc = VERR_NOT_SUPPORTED;
831 }
832
833 RTRandAdvDestroy(pJob->hRand);
834 }
835
836 RTFileClose(pJob->Hnd.u.hFile);
837 RTFileDelete(pJob->pszFilename);
838 }
839
840 RTStrFree(pJob->pszFilename);
841 }
842
843 RTMemPageFree(pJob->pvIoReqReadBuf, pJob->cbIoReqReadBuf);
844 }
845 else
846 rc = VERR_NO_STR_MEMORY;
847 }
848
849 return rc;
850}
851
852
853/**
854 * Teardown a job instance and free all associated resources.
855 *
856 * @returns IPRT status code.
857 * @param pJob The job to teardown.
858 */
859static int ioPerfJobTeardown(PIOPERFJOB pJob)
860{
861 if (pJob->pMaster)
862 {
863 int rc = RTThreadWait(pJob->hThread, RT_INDEFINITE_WAIT, NULL);
864 AssertRC(rc); RT_NOREF(rc);
865 }
866
867 RTIoQueueHandleDeregister(pJob->hIoQueue, &pJob->Hnd);
868 RTIoQueueDestroy(pJob->hIoQueue);
869 RTRandAdvDestroy(pJob->hRand);
870 RTMemPageFree(pJob->pbRandWrite, IOPERF_RAND_DATA_BUF_FACTOR * pJob->cbIoBlock);
871 RTFileClose(pJob->Hnd.u.hFile);
872 RTFileDelete(pJob->pszFilename);
873 RTStrFree(pJob->pszFilename);
874 RTMemPageFree(pJob->pvIoReqReadBuf, pJob->cbIoReqReadBuf);
875 RTMemFree(pJob->paIoReqs);
876 return VINF_SUCCESS;
877}
878
879
880/**
881 * Single job testing entry point.
882 *
883 * @returns IPRT status code.
884 */
885static int ioPerfDoTestSingle(void)
886{
887 IOPERFJOB Job;
888
889 int rc = ioPerfJobInit(&Job, NULL, 0, g_pszIoEngine,
890 g_szDir, IOPERFTESTSETPREP_SET_SZ,
891 g_cbTestSet, g_cbIoBlock, g_cReqsMax);
892 if (RT_SUCCESS(rc))
893 {
894 rc = ioPerfJobWorkLoop(&Job);
895 if (RT_SUCCESS(rc))
896 {
897 rc = ioPerfJobTeardown(&Job);
898 AssertRC(rc); RT_NOREF(rc);
899 }
900 }
901
902 return rc;
903}
904
905
906/**
907 * Multi job testing entry point.
908 *
909 * @returns IPRT status code.
910 */
911static int ioPerfDoTestMulti(void)
912{
913 return VERR_NOT_IMPLEMENTED;
914}
915
916
917/**
918 * Display the usage to @a pStrm.
919 */
920static void Usage(PRTSTREAM pStrm)
921{
922 char szExec[RTPATH_MAX];
923 RTStrmPrintf(pStrm, "usage: %s <-d <testdir>> [options]\n",
924 RTPathFilename(RTProcGetExecutablePath(szExec, sizeof(szExec))));
925 RTStrmPrintf(pStrm, "\n");
926 RTStrmPrintf(pStrm, "options: \n");
927
928 for (unsigned i = 0; i < RT_ELEMENTS(g_aCmdOptions); i++)
929 {
930 char szHelp[80];
931 const char *pszHelp;
932 switch (g_aCmdOptions[i].iShort)
933 {
934 case 'd': pszHelp = "The directory to use for testing. default: CWD/fstestdir"; break;
935 case 'r': pszHelp = "Don't abspath test dir (good for deep dirs). default: disabled"; break;
936 case 'v': pszHelp = "More verbose execution."; break;
937 case 'q': pszHelp = "Quiet execution."; break;
938 case 'h': pszHelp = "Displays this help and exit"; break;
939 case 'V': pszHelp = "Displays the program revision"; break;
940 default:
941 if (g_aCmdOptions[i].iShort >= kCmdOpt_First)
942 {
943 if (RTStrStartsWith(g_aCmdOptions[i].pszLong, "--no-"))
944 RTStrPrintf(szHelp, sizeof(szHelp), "Disables the '%s' test.", g_aCmdOptions[i].pszLong + 5);
945 else
946 RTStrPrintf(szHelp, sizeof(szHelp), "Enables the '%s' test.", g_aCmdOptions[i].pszLong + 2);
947 pszHelp = szHelp;
948 }
949 else
950 pszHelp = "Option undocumented";
951 break;
952 }
953 if ((unsigned)g_aCmdOptions[i].iShort < 127U)
954 {
955 char szOpt[64];
956 RTStrPrintf(szOpt, sizeof(szOpt), "%s, -%c", g_aCmdOptions[i].pszLong, g_aCmdOptions[i].iShort);
957 RTStrmPrintf(pStrm, " %-19s %s\n", szOpt, pszHelp);
958 }
959 else
960 RTStrmPrintf(pStrm, " %-19s %s\n", g_aCmdOptions[i].pszLong, pszHelp);
961 }
962}
963
964
965int main(int argc, char *argv[])
966{
967 /*
968 * Init IPRT and globals.
969 */
970 int rc = RTTestInitAndCreate("IoPerf", &g_hTest);
971 if (rc)
972 return rc;
973
974 /*
975 * Default values.
976 */
977 char szDefaultDir[32];
978 const char *pszDir = szDefaultDir;
979 RTStrPrintf(szDefaultDir, sizeof(szDefaultDir), "ioperfdir-%u" RTPATH_SLASH_STR, RTProcSelf());
980
981 RTGETOPTUNION ValueUnion;
982 RTGETOPTSTATE GetState;
983 RTGetOptInit(&GetState, argc, argv, g_aCmdOptions, RT_ELEMENTS(g_aCmdOptions), 1, 0 /* fFlags */);
984 while ((rc = RTGetOpt(&GetState, &ValueUnion)) != 0)
985 {
986 switch (rc)
987 {
988 case 'd':
989 pszDir = ValueUnion.psz;
990 break;
991
992 case 'r':
993 g_fRelativeDir = true;
994 break;
995
996 case 'i':
997 g_pszIoEngine = ValueUnion.psz;
998 break;
999
1000 case 's':
1001 g_cbTestSet = ValueUnion.u64;
1002 break;
1003
1004 case 'b':
1005 g_cbIoBlock = ValueUnion.u32;
1006 break;
1007
1008 case 'm':
1009 g_cReqsMax = ValueUnion.u32;
1010 break;
1011
1012 case 'q':
1013 g_uVerbosity = 0;
1014 break;
1015
1016 case 'v':
1017 g_uVerbosity++;
1018 break;
1019
1020 case 'h':
1021 Usage(g_pStdOut);
1022 return RTEXITCODE_SUCCESS;
1023
1024 case 'V':
1025 {
1026 char szRev[] = "$Revision: 80065 $";
1027 szRev[RT_ELEMENTS(szRev) - 2] = '\0';
1028 RTPrintf(RTStrStrip(strchr(szRev, ':') + 1));
1029 return RTEXITCODE_SUCCESS;
1030 }
1031
1032 default:
1033 return RTGetOptPrintError(rc, &ValueUnion);
1034 }
1035 }
1036
1037 /*
1038 * Populate g_szDir.
1039 */
1040 if (!g_fRelativeDir)
1041 rc = RTPathAbs(pszDir, g_szDir, sizeof(g_szDir));
1042 else
1043 rc = RTStrCopy(g_szDir, sizeof(g_szDir), pszDir);
1044 if (RT_FAILURE(rc))
1045 {
1046 RTTestFailed(g_hTest, "%s(%s) failed: %Rrc\n", g_fRelativeDir ? "RTStrCopy" : "RTAbsPath", pszDir, rc);
1047 return RTTestSummaryAndDestroy(g_hTest);
1048 }
1049 RTPathEnsureTrailingSeparator(g_szDir, sizeof(g_szDir));
1050 g_cchDir = strlen(g_szDir);
1051
1052 /*
1053 * Create the test directory with an 'empty' subdirectory under it,
1054 * execute the tests, and remove directory when done.
1055 */
1056 RTTestBanner(g_hTest);
1057 if (!RTPathExists(g_szDir))
1058 {
1059 /* The base dir: */
1060 rc = RTDirCreate(g_szDir, 0755,
1061 RTDIRCREATE_FLAGS_NOT_CONTENT_INDEXED_DONT_SET | RTDIRCREATE_FLAGS_NOT_CONTENT_INDEXED_NOT_CRITICAL);
1062 if (RT_SUCCESS(rc))
1063 {
1064 RTTestIPrintf(RTTESTLVL_ALWAYS, "Test dir: %s\n", g_szDir);
1065
1066 if (g_cJobs == 1)
1067 rc = ioPerfDoTestSingle();
1068 else
1069 rc = ioPerfDoTestMulti();
1070
1071 g_szDir[g_cchDir] = '\0';
1072 rc = RTDirRemoveRecursive(g_szDir, RTDIRRMREC_F_CONTENT_AND_DIR | (g_fRelativeDir ? RTDIRRMREC_F_NO_ABS_PATH : 0));
1073 if (RT_FAILURE(rc))
1074 RTTestFailed(g_hTest, "RTDirRemoveRecursive(%s,) -> %Rrc\n", g_szDir, rc);
1075 }
1076 else
1077 RTTestFailed(g_hTest, "RTDirCreate(%s) -> %Rrc\n", g_szDir, rc);
1078 }
1079 else
1080 RTTestFailed(g_hTest, "Test directory already exists: %s\n", g_szDir);
1081
1082 return RTTestSummaryAndDestroy(g_hTest);
1083}
1084
Note: See TracBrowser for help on using the repository browser.

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