Index: /trunk/src/VBox/ValidationKit/utils/fs/FsPerf.cpp
===================================================================
--- /trunk/src/VBox/ValidationKit/utils/fs/FsPerf.cpp	(revision 76998)
+++ /trunk/src/VBox/ValidationKit/utils/fs/FsPerf.cpp	(revision 76999)
@@ -267,4 +267,9 @@
     kCmdOpt_MMap,
     kCmdOpt_NoMMap,
+    kCmdOpt_IgnoreNoCache,
+    kCmdOpt_NoIgnoreNoCache,
+    kCmdOpt_IoFileSize,
+    kCmdOpt_SetBlockSize,
+    kCmdOpt_AddBlockSize,
 
     kCmdOpt_ShowDuration,
@@ -272,4 +277,9 @@
     kCmdOpt_ShowIterations,
     kCmdOpt_NoShowIterations,
+
+    kCmdOpt_ManyTreeFilesPerDir,
+    kCmdOpt_ManyTreeSubdirsPerDir,
+    kCmdOpt_ManyTreeDepth,
+
     kCmdOpt_End
 };
@@ -289,6 +299,9 @@
     { "--disable-all",      'z', RTGETOPT_REQ_NOTHING },
 
-    { "--many-files",       kCmdOpt_ManyFiles,      RTGETOPT_REQ_NOTHING },
-    { "--no-many-files",    kCmdOpt_NoManyFiles,    RTGETOPT_REQ_NOTHING },
+    { "--many-files",       kCmdOpt_ManyFiles,              RTGETOPT_REQ_UINT32 },
+    { "--no-many-files",    kCmdOpt_NoManyFiles,            RTGETOPT_REQ_NOTHING },
+    { "--files-per-dir",    kCmdOpt_ManyTreeFilesPerDir,    RTGETOPT_REQ_UINT32 },
+    { "--subdirs-per-dir",  kCmdOpt_ManyTreeSubdirsPerDir,  RTGETOPT_REQ_UINT32 },
+    { "--tree-depth",       kCmdOpt_ManyTreeDepth,          RTGETOPT_REQ_UINT32 },
 
     { "--open",             kCmdOpt_Open,           RTGETOPT_REQ_NOTHING },
@@ -328,4 +341,9 @@
     { "--mmap",             kCmdOpt_MMap,           RTGETOPT_REQ_NOTHING },
     { "--no-mmap",          kCmdOpt_NoMMap,         RTGETOPT_REQ_NOTHING },
+    { "--ignore-no-cache",  kCmdOpt_IgnoreNoCache,  RTGETOPT_REQ_NOTHING },
+    { "--no-ignore-no-cache",  kCmdOpt_NoIgnoreNoCache,  RTGETOPT_REQ_NOTHING },
+    { "--io-file-size",     kCmdOpt_IoFileSize,     RTGETOPT_REQ_UINT64 },
+    { "--set-block-size",   kCmdOpt_SetBlockSize,   RTGETOPT_REQ_UINT32 },
+    { "--add-block-size",   kCmdOpt_AddBlockSize,   RTGETOPT_REQ_UINT32 },
 
     { "--show-duration",        kCmdOpt_ShowDuration,       RTGETOPT_REQ_NOTHING },
@@ -387,5 +405,5 @@
 /** Number of files per directory in the 'manytree' construct. */
 static uint32_t     g_cManyTreeFilesPerDir      = 640;
-/* Number of subdirs per directory in the 'manytree' construct. */
+/** Number of subdirs per directory in the 'manytree' construct. */
 static uint32_t     g_cManyTreeSubdirsPerDir    = 16;
 /** The depth of the 'manytree' directory tree.  */
@@ -400,4 +418,6 @@
 /** The desired size of the test file we use for I/O. */
 static uint64_t     g_cbIoFile                  = _512M;
+/** Whether to be less strict with non-cache file handle. */
+static bool         g_fIgnoreNoCache            = false;
 
 /** The length of g_szDir. */
@@ -1860,27 +1880,30 @@
     uint32_t cbPage = PAGE_SIZE;
     memset(pbBuf, 0x66, cbBuf);
-    RTTESTI_CHECK_RC(RTFileSeek(hFileNoCache, 0, RTFILE_SEEK_BEGIN, NULL), VINF_SUCCESS);
-    for (size_t offBuf = 0; offBuf < cbBuf; )
-    {
-        uint32_t const cPagesLeft   = (uint32_t)((cbBuf - offBuf) / cbPage);
-        uint32_t const cPagesToRead = RTRandU32Ex(1, cPagesLeft);
-        size_t const   cbToRead     = cPagesToRead * (size_t)cbPage;
-        size_t cbActual = 0;
-        RTTESTI_CHECK_RC(RTFileRead(hFileNoCache, &pbBuf[offBuf], cbToRead, &cbActual), VINF_SUCCESS);
-        if (cbActual == cbToRead)
-            offBuf += cbActual;
-        else
-        {
-            RTTestIFailed("Attempting to read %#zx bytes at %#zx, only got %#x bytes back!\n", cbToRead, offBuf, cbActual);
-            if (cbActual)
+    if (!g_fIgnoreNoCache || hFileNoCache != NIL_RTFILE)
+    {
+        RTTESTI_CHECK_RC(RTFileSeek(hFileNoCache, 0, RTFILE_SEEK_BEGIN, NULL), VINF_SUCCESS);
+        for (size_t offBuf = 0; offBuf < cbBuf; )
+        {
+            uint32_t const cPagesLeft   = (uint32_t)((cbBuf - offBuf) / cbPage);
+            uint32_t const cPagesToRead = RTRandU32Ex(1, cPagesLeft);
+            size_t const   cbToRead     = cPagesToRead * (size_t)cbPage;
+            size_t cbActual = 0;
+            RTTESTI_CHECK_RC(RTFileRead(hFileNoCache, &pbBuf[offBuf], cbToRead, &cbActual), VINF_SUCCESS);
+            if (cbActual == cbToRead)
                 offBuf += cbActual;
             else
             {
-                memset(&pbBuf[offBuf], 0x11, cbPage);
-                offBuf += cbPage;
+                RTTestIFailed("Attempting to read %#zx bytes at %#zx, only got %#x bytes back!\n", cbToRead, offBuf, cbActual);
+                if (cbActual)
+                    offBuf += cbActual;
+                else
+                {
+                    memset(&pbBuf[offBuf], 0x11, cbPage);
+                    offBuf += cbPage;
+                }
             }
         }
-    }
-    fsPerfCheckReadBuf(__LINE__, 0, pbBuf, cbBuf);
+        fsPerfCheckReadBuf(__LINE__, 0, pbBuf, cbBuf);
+    }
 
     /*
@@ -2038,4 +2061,7 @@
     for (unsigned iFile = 0; iFile < RT_ELEMENTS(ahFiles); iFile++, bFiller++)
     {
+        if (g_fIgnoreNoCache && ahFiles[iFile] == NIL_RTFILE)
+            continue;
+
         fsPerfFillWriteBuf(0, pbBuf, cbBuf, bFiller);
         fsPerfCheckReadBuf(__LINE__, 0, pbBuf, cbBuf, bFiller);
@@ -2111,5 +2137,4 @@
 #endif
 
-    RT_NOREF(hFileNoCache, hFileWriteThru);
     RTMemPageFree(pbBuf, cbBuf);
 }
@@ -2292,31 +2317,34 @@
                      * Check that all the changes made it thru to the file:
                      */
-                    size_t   cbBuf = _2M;
-                    uint8_t *pbBuf = (uint8_t *)RTMemPageAlloc(_2M);
-                    if (!pbBuf)
+                    if (!g_fIgnoreNoCache || hFileNoCache != NIL_RTFILE)
                     {
-                        cbBuf = _4K;
-                        pbBuf = (uint8_t *)RTMemPageAlloc(_2M);
-                    }
-                    if (pbBuf)
-                    {
-                        RTTESTI_CHECK_RC(RTFileSeek(hFileNoCache, 0, RTFILE_SEEK_BEGIN, NULL), VINF_SUCCESS);
-                        size_t const cbToCheck = RT_MIN(cFlushes * cbFlush, cbFlushed);
-                        unsigned     cErrors   = 0;
-                        for (size_t offBuf = 0; cErrors < 32 && offBuf < cbToCheck; offBuf += cbBuf)
+                        size_t   cbBuf = _2M;
+                        uint8_t *pbBuf = (uint8_t *)RTMemPageAlloc(_2M);
+                        if (!pbBuf)
                         {
-                            size_t cbToRead = RT_MIN(cbBuf, cbToCheck - offBuf);
-                            RTTESTI_CHECK_RC(RTFileRead(hFileNoCache, pbBuf, cbToRead, NULL), VINF_SUCCESS);
-
-                            for (size_t offFlush = 0; offFlush < cbToRead; offFlush += PAGE_SIZE)
-                                if (*(size_t volatile *)&pbBuf[offFlush + 8] != cbFlush)
-                                {
-                                    RTTestIFailed("Flush issue at offset #%zx: %#zx, expected %#zx (cbFlush=%#zx)",
-                                                  offBuf, *(size_t volatile *)&pbBuf[offFlush + 8], cbFlush, cbFlush);
-                                    if (++cErrors > 32)
-                                        break;
-                                }
+                            cbBuf = _4K;
+                            pbBuf = (uint8_t *)RTMemPageAlloc(_2M);
                         }
-                        RTMemPageFree(pbBuf, cbBuf);
+                        if (pbBuf)
+                        {
+                            RTTESTI_CHECK_RC(RTFileSeek(hFileNoCache, 0, RTFILE_SEEK_BEGIN, NULL), VINF_SUCCESS);
+                            size_t const cbToCheck = RT_MIN(cFlushes * cbFlush, cbFlushed);
+                            unsigned     cErrors   = 0;
+                            for (size_t offBuf = 0; cErrors < 32 && offBuf < cbToCheck; offBuf += cbBuf)
+                            {
+                                size_t cbToRead = RT_MIN(cbBuf, cbToCheck - offBuf);
+                                RTTESTI_CHECK_RC(RTFileRead(hFileNoCache, pbBuf, cbToRead, NULL), VINF_SUCCESS);
+
+                                for (size_t offFlush = 0; offFlush < cbToRead; offFlush += PAGE_SIZE)
+                                    if (*(size_t volatile *)&pbBuf[offFlush + 8] != cbFlush)
+                                    {
+                                        RTTestIFailed("Flush issue at offset #%zx: %#zx, expected %#zx (cbFlush=%#zx)",
+                                                      offBuf, *(size_t volatile *)&pbBuf[offFlush + 8], cbFlush, cbFlush);
+                                        if (++cErrors > 32)
+                                            break;
+                                    }
+                            }
+                            RTMemPageFree(pbBuf, cbBuf);
+                        }
                     }
                 }
@@ -2383,7 +2411,17 @@
                                      RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE | RTFILE_O_READWRITE), VINF_SUCCESS);
     RTFILE hFileNoCache;
-    RTTESTI_CHECK_RC_RETV(RTFileOpen(&hFileNoCache, g_szDir,
-                                     RTFILE_O_OPEN | RTFILE_O_DENY_NONE | RTFILE_O_READWRITE | RTFILE_O_NO_CACHE),
-                          VINF_SUCCESS);
+    if (!g_fIgnoreNoCache)
+        RTTESTI_CHECK_RC_RETV(RTFileOpen(&hFileNoCache, g_szDir,
+                                         RTFILE_O_OPEN | RTFILE_O_DENY_NONE | RTFILE_O_READWRITE | RTFILE_O_NO_CACHE),
+                              VINF_SUCCESS);
+    else
+    {
+        int rc = RTFileOpen(&hFileNoCache, g_szDir, RTFILE_O_OPEN | RTFILE_O_DENY_NONE | RTFILE_O_READWRITE | RTFILE_O_NO_CACHE);
+        if (RT_FAILURE(rc))
+        {
+            RTTestIPrintf(RTTESTLVL_ALWAYS, "Unable to open I/O file with non-cache flag (%Rrc), skipping related tests.\n", rc);
+            hFileNoCache = NIL_RTFILE;
+        }
+    }
     RTFILE hFileWriteThru;
     RTTESTI_CHECK_RC_RETV(RTFileOpen(&hFileWriteThru, g_szDir,
@@ -2422,5 +2460,6 @@
     RTTESTI_CHECK_RC(RTFileSetSize(hFile1, 0), VINF_SUCCESS);
     RTTESTI_CHECK_RC(RTFileClose(hFile1), VINF_SUCCESS);
-    RTTESTI_CHECK_RC(RTFileClose(hFileNoCache), VINF_SUCCESS);
+    if (hFileNoCache != NIL_RTFILE || !g_fIgnoreNoCache)
+        RTTESTI_CHECK_RC(RTFileClose(hFileNoCache), VINF_SUCCESS);
     RTTESTI_CHECK_RC(RTFileClose(hFileWriteThru), VINF_SUCCESS);
     RTTESTI_CHECK_RC(RTFileDelete(g_szDir), VINF_SUCCESS);
@@ -2428,4 +2467,7 @@
 
 
+/**
+ * Display the usage to @a pStrm.
+ */
 static void Usage(PRTSTREAM pStrm)
 {
@@ -2442,17 +2484,27 @@
         switch (g_aCmdOptions[i].iShort)
         {
-            case 'd':   pszHelp = "The directory to use for testing.  Default: CWD/fstestdir"; break;
-            case 'e':   pszHelp = "Enables all tests.   Default: -e"; break;
-            case 'z':   pszHelp = "Disables all tests.  Default: -e"; break;
-            case 's':   pszHelp = "Set benchmark duration in seconds.  Default: 10 sec"; break;
-            case 'm':   pszHelp = "Set benchmark duration in milliseconds.  Default: 10000 ms"; break;
-            case 'v':   pszHelp = "More verbose execution."; break;
-            case 'q':   pszHelp = "Quiet execution."; break;
-            case 'h':   pszHelp = "Displays this help and exit"; break;
-            case 'V':   pszHelp = "Displays the program revision"; break;
-            case kCmdOpt_ShowDuration:      pszHelp =  "Show duration of profile runs. default: --no-show-duration"; break;
-            case kCmdOpt_NoShowDuration:    pszHelp =  "Hide duration of profile runs. default: --no-show-duration"; break;
-            case kCmdOpt_ShowIterations:    pszHelp =  "Show iteration count for profile runs. default: --no-show-iterations"; break;
-            case kCmdOpt_NoShowIterations:  pszHelp =  "Hide iteration count for profile runs. default: --no-show-iterations"; break;
+            case 'd':                           pszHelp = "The directory to use for testing.            default: CWD/fstestdir"; break;
+            case 'e':                           pszHelp = "Enables all tests.                           default: -e"; break;
+            case 'z':                           pszHelp = "Disables all tests.                          default: -e"; break;
+            case 's':                           pszHelp = "Set benchmark duration in seconds.           default: 10 sec"; break;
+            case 'm':                           pszHelp = "Set benchmark duration in milliseconds.      default: 10000 ms"; break;
+            case 'v':                           pszHelp = "More verbose execution."; break;
+            case 'q':                           pszHelp = "Quiet execution."; break;
+            case 'h':                           pszHelp = "Displays this help and exit"; break;
+            case 'V':                           pszHelp = "Displays the program revision"; break;
+            case kCmdOpt_ShowDuration:          pszHelp = "Show duration of profile runs.               default: --no-show-duration"; break;
+            case kCmdOpt_NoShowDuration:        pszHelp = "Hide duration of profile runs.               default: --no-show-duration"; break;
+            case kCmdOpt_ShowIterations:        pszHelp = "Show iteration count for profile runs.       default: --no-show-iterations"; break;
+            case kCmdOpt_NoShowIterations:      pszHelp = "Hide iteration count for profile runs.       default: --no-show-iterations"; break;
+            case kCmdOpt_ManyFiles:             pszHelp = "Count of files in big test dir.              default: --many-files 10000"; break;
+            case kCmdOpt_NoManyFiles:           pszHelp = "Skip big test dir with many files.           default: --many-files 10000"; break;
+            case kCmdOpt_ManyTreeFilesPerDir:   pszHelp = "Count of files per directory in test tree.   default: 640"; break;
+            case kCmdOpt_ManyTreeSubdirsPerDir: pszHelp = "Count of subdirs per directory in test tree. default: 16"; break;
+            case kCmdOpt_ManyTreeDepth:         pszHelp = "Depth of test tree (not counting root).      default: 1"; break;
+            case kCmdOpt_IgnoreNoCache:         pszHelp = "Ignore error wrt no-cache handle.            default: --no-ignore-no-cache"; break;
+            case kCmdOpt_NoIgnoreNoCache:       pszHelp = "Do not ignore error wrt no-cache handle.     default: --no-ignore-no-cache"; break;
+            case kCmdOpt_IoFileSize:            pszHelp = "Size of file used for I/O tests.             default: 512 MB"; break;
+            case kCmdOpt_SetBlockSize:          pszHelp = "Sets single I/O block size (in bytes)."; break;
+            case kCmdOpt_AddBlockSize:          pszHelp = "Adds an I/O block size (in bytes)."; break;
             default:
                 if (g_aCmdOptions[i].iShort >= kCmdOpt_First)
@@ -2472,9 +2524,21 @@
             char szOpt[64];
             RTStrPrintf(szOpt, sizeof(szOpt), "%s, -%c", g_aCmdOptions[i].pszLong, g_aCmdOptions[i].iShort);
-            RTStrmPrintf(pStrm, "  %-20s%s\n", szOpt, pszHelp);
+            RTStrmPrintf(pStrm, "  %-19s %s\n", szOpt, pszHelp);
         }
         else
-            RTStrmPrintf(pStrm, "  %-20s%s\n", g_aCmdOptions[i].pszLong, pszHelp);
-    }
+            RTStrmPrintf(pStrm, "  %-19s %s\n", g_aCmdOptions[i].pszLong, pszHelp);
+    }
+}
+
+
+static uint32_t fsPerfCalcManyTreeFiles(void)
+{
+    uint32_t cDirs = 1;
+    for (uint32_t i = 0, cDirsAtLevel = 1; i < g_cManyTreeDepth; i++)
+    {
+        cDirs += cDirsAtLevel * g_cManyTreeSubdirsPerDir;
+        cDirsAtLevel *= g_cManyTreeSubdirsPerDir;
+    }
+    return g_cManyTreeFilesPerDir * cDirs;
 }
 
@@ -2586,5 +2650,4 @@
             case RT_CONCAT(kCmdOpt_,a_Stem):   RT_CONCAT(g_f,a_Stem) = true; break; \
             case RT_CONCAT(kCmdOpt_No,a_Stem): RT_CONCAT(g_f,a_Stem) = false; break
-            CASE_OPT(ManyFiles);
             CASE_OPT(Open);
             CASE_OPT(FStat);
@@ -2605,8 +2668,80 @@
             CASE_OPT(FSync);
             CASE_OPT(MMap);
+            CASE_OPT(IgnoreNoCache);
 
             CASE_OPT(ShowDuration);
             CASE_OPT(ShowIterations);
 #undef CASE_OPT
+
+            case kCmdOpt_ManyFiles:
+                g_fManyFiles = ValueUnion.u32 > 0;
+                g_cManyFiles = ValueUnion.u32;
+                break;
+
+            case kCmdOpt_NoManyFiles:
+                g_fManyFiles = false;
+                break;
+
+            case kCmdOpt_ManyTreeFilesPerDir:
+                if (ValueUnion.u32 > 0 && ValueUnion.u32 <= _64M)
+                {
+                    g_cManyTreeFilesPerDir = ValueUnion.u32;
+                    g_cManyTreeFiles = fsPerfCalcManyTreeFiles();
+                    break;
+                }
+                RTTestFailed(g_hTest, "Out of range --files-per-dir value: %u (%#x)\n", ValueUnion.u32, ValueUnion.u32);
+                return RTTestSummaryAndDestroy(g_hTest);
+
+            case kCmdOpt_ManyTreeSubdirsPerDir:
+                if (ValueUnion.u32 > 0 && ValueUnion.u32 <= 1024)
+                {
+                    g_cManyTreeSubdirsPerDir = ValueUnion.u32;
+                    g_cManyTreeFiles = fsPerfCalcManyTreeFiles();
+                    break;
+                }
+                RTTestFailed(g_hTest, "Out of range --subdirs-per-dir value: %u (%#x)\n", ValueUnion.u32, ValueUnion.u32);
+                return RTTestSummaryAndDestroy(g_hTest);
+
+            case kCmdOpt_ManyTreeDepth:
+                if (ValueUnion.u32 <= 8)
+                {
+                    g_cManyTreeDepth = ValueUnion.u32;
+                    g_cManyTreeFiles = fsPerfCalcManyTreeFiles();
+                    break;
+                }
+                RTTestFailed(g_hTest, "Out of range --tree-depth value: %u (%#x)\n", ValueUnion.u32, ValueUnion.u32);
+                return RTTestSummaryAndDestroy(g_hTest);
+
+            case kCmdOpt_IoFileSize:
+                if (ValueUnion.u64 == 0)
+                    g_cbIoFile = _512M;
+                else
+                    g_cbIoFile = ValueUnion.u64;
+                break;
+
+            case kCmdOpt_SetBlockSize:
+                if (ValueUnion.u32 > 0)
+                {
+                    g_cIoBlocks = 1;
+                    g_acbIoBlocks[0] = ValueUnion.u32;
+                }
+                else
+                {
+                    RTTestFailed(g_hTest, "Invalid I/O block size: %u (%#x)\n", ValueUnion.u32, ValueUnion.u32);
+                    return RTTestSummaryAndDestroy(g_hTest);
+                }
+                break;
+
+            case kCmdOpt_AddBlockSize:
+                if (g_cIoBlocks >= RT_ELEMENTS(g_acbIoBlocks))
+                    RTTestFailed(g_hTest, "Too many I/O block sizes: max %u\n", RT_ELEMENTS(g_acbIoBlocks));
+                else if (ValueUnion.u32 == 0)
+                    RTTestFailed(g_hTest, "Invalid I/O block size: %u (%#x)\n", ValueUnion.u32, ValueUnion.u32);
+                else
+                {
+                    g_acbIoBlocks[g_cIoBlocks++] = ValueUnion.u32;
+                    break;
+                }
+                return RTTestSummaryAndDestroy(g_hTest);
 
             case 'q':
