VirtualBox

source: vbox/trunk/src/VBox/ValidationKit/utils/fs/FsPerf.cpp@ 77926

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

FsPerf: Added splice() tests and benchmarks. bugref:9172

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 185.9 KB
Line 
1/* $Id: FsPerf.cpp 77926 2019-03-27 23:42:19Z vboxsync $ */
2/** @file
3 * FsPerf - File System (Shared Folders) 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#ifdef RT_OS_OS2
32# define INCL_BASE
33# include <os2.h>
34# undef RT_MAX
35#endif
36#include <iprt/alloca.h>
37#include <iprt/asm.h>
38#include <iprt/assert.h>
39#include <iprt/err.h>
40#include <iprt/dir.h>
41#include <iprt/file.h>
42#include <iprt/getopt.h>
43#include <iprt/initterm.h>
44#include <iprt/list.h>
45#include <iprt/mem.h>
46#include <iprt/message.h>
47#include <iprt/param.h>
48#include <iprt/path.h>
49#ifdef RT_OS_LINUX
50# include <iprt/pipe.h>
51#endif
52#include <iprt/process.h>
53#include <iprt/rand.h>
54#include <iprt/string.h>
55#include <iprt/stream.h>
56#include <iprt/system.h>
57#include <iprt/tcp.h>
58#include <iprt/test.h>
59#include <iprt/time.h>
60#include <iprt/thread.h>
61#include <iprt/zero.h>
62
63#ifdef RT_OS_WINDOWS
64# include <iprt/nt/nt-and-windows.h>
65#else
66# include <errno.h>
67# include <unistd.h>
68# include <sys/types.h>
69# include <sys/fcntl.h>
70# ifndef RT_OS_OS2
71# include <sys/mman.h>
72# include <sys/uio.h>
73# endif
74# include <sys/socket.h>
75# include <signal.h>
76# ifdef RT_OS_LINUX
77# include <sys/sendfile.h>
78# include <sys/syscall.h>
79# endif
80#endif
81
82
83/*********************************************************************************************************************************
84* Defined Constants And Macros *
85*********************************************************************************************************************************/
86/**
87 * Macro for profiling @a a_fnCall (typically forced inline) for about @a a_cNsTarget ns.
88 *
89 * Always does an even number of iterations.
90 */
91#define PROFILE_FN(a_fnCall, a_cNsTarget, a_szDesc) \
92 do { \
93 /* Estimate how many iterations we need to fill up the given timeslot: */ \
94 fsPerfYield(); \
95 uint64_t nsStart = RTTimeNanoTS(); \
96 uint64_t nsPrf; \
97 do \
98 nsPrf = RTTimeNanoTS(); \
99 while (nsPrf == nsStart); \
100 nsStart = nsPrf; \
101 \
102 uint64_t iIteration = 0; \
103 do \
104 { \
105 RTTESTI_CHECK_RC(a_fnCall, VINF_SUCCESS); \
106 iIteration++; \
107 nsPrf = RTTimeNanoTS() - nsStart; \
108 } while (nsPrf < RT_NS_10MS || (iIteration & 1)); \
109 nsPrf /= iIteration; \
110 if (nsPrf > g_nsPerNanoTSCall + 32) \
111 nsPrf -= g_nsPerNanoTSCall; \
112 \
113 uint64_t cIterations = (a_cNsTarget) / nsPrf; \
114 if (cIterations <= 1) \
115 cIterations = 2; \
116 else if (cIterations & 1) \
117 cIterations++; \
118 \
119 /* Do the actual profiling: */ \
120 fsPerfYield(); \
121 iIteration = 0; \
122 nsStart = RTTimeNanoTS(); \
123 for (; iIteration < cIterations; iIteration++) \
124 RTTESTI_CHECK_RC(a_fnCall, VINF_SUCCESS); \
125 nsPrf = RTTimeNanoTS() - nsStart; \
126 RTTestIValue(a_szDesc, nsPrf / cIterations, RTTESTUNIT_NS_PER_OCCURRENCE); \
127 if (g_fShowDuration) \
128 RTTestIValueF(nsPrf, RTTESTUNIT_NS, "%s duration", a_szDesc); \
129 if (g_fShowIterations) \
130 RTTestIValueF(iIteration, RTTESTUNIT_OCCURRENCES, "%s iterations", a_szDesc); \
131 } while (0)
132
133
134/**
135 * Macro for profiling an operation on each file in the manytree directory tree.
136 *
137 * Always does an even number of tree iterations.
138 */
139#define PROFILE_MANYTREE_FN(a_szPath, a_fnCall, a_cEstimationIterations, a_cNsTarget, a_szDesc) \
140 do { \
141 if (!g_fManyFiles) \
142 break; \
143 \
144 /* Estimate how many iterations we need to fill up the given timeslot: */ \
145 fsPerfYield(); \
146 uint64_t nsStart = RTTimeNanoTS(); \
147 uint64_t ns; \
148 do \
149 ns = RTTimeNanoTS(); \
150 while (ns == nsStart); \
151 nsStart = ns; \
152 \
153 PFSPERFNAMEENTRY pCur; \
154 uint64_t iIteration = 0; \
155 do \
156 { \
157 RTListForEach(&g_ManyTreeHead, pCur, FSPERFNAMEENTRY, Entry) \
158 { \
159 memcpy(a_szPath, pCur->szName, pCur->cchName); \
160 for (uint32_t i = 0; i < g_cManyTreeFilesPerDir; i++) \
161 { \
162 RTStrFormatU32(&a_szPath[pCur->cchName], sizeof(a_szPath) - pCur->cchName, i, 10, 5, 5, RTSTR_F_ZEROPAD); \
163 RTTESTI_CHECK_RC(a_fnCall, VINF_SUCCESS); \
164 } \
165 } \
166 iIteration++; \
167 ns = RTTimeNanoTS() - nsStart; \
168 } while (ns < RT_NS_10MS || (iIteration & 1)); \
169 ns /= iIteration; \
170 if (ns > g_nsPerNanoTSCall + 32) \
171 ns -= g_nsPerNanoTSCall; \
172 \
173 uint32_t cIterations = (a_cNsTarget) / ns; \
174 if (cIterations <= 1) \
175 cIterations = 2; \
176 else if (cIterations & 1) \
177 cIterations++; \
178 \
179 /* Do the actual profiling: */ \
180 fsPerfYield(); \
181 uint32_t cCalls = 0; \
182 nsStart = RTTimeNanoTS(); \
183 for (iIteration = 0; iIteration < cIterations; iIteration++) \
184 { \
185 RTListForEach(&g_ManyTreeHead, pCur, FSPERFNAMEENTRY, Entry) \
186 { \
187 memcpy(a_szPath, pCur->szName, pCur->cchName); \
188 for (uint32_t i = 0; i < g_cManyTreeFilesPerDir; i++) \
189 { \
190 RTStrFormatU32(&a_szPath[pCur->cchName], sizeof(a_szPath) - pCur->cchName, i, 10, 5, 5, RTSTR_F_ZEROPAD); \
191 RTTESTI_CHECK_RC(a_fnCall, VINF_SUCCESS); \
192 cCalls++; \
193 } \
194 } \
195 } \
196 ns = RTTimeNanoTS() - nsStart; \
197 RTTestIValueF(ns / cCalls, RTTESTUNIT_NS_PER_OCCURRENCE, a_szDesc); \
198 if (g_fShowDuration) \
199 RTTestIValueF(ns, RTTESTUNIT_NS, "%s duration", a_szDesc); \
200 if (g_fShowIterations) \
201 RTTestIValueF(iIteration, RTTESTUNIT_OCCURRENCES, "%s iterations", a_szDesc); \
202 } while (0)
203
204
205/**
206 * Execute a_fnCall for each file in the manytree.
207 */
208#define DO_MANYTREE_FN(a_szPath, a_fnCall) \
209 do { \
210 PFSPERFNAMEENTRY pCur; \
211 RTListForEach(&g_ManyTreeHead, pCur, FSPERFNAMEENTRY, Entry) \
212 { \
213 memcpy(a_szPath, pCur->szName, pCur->cchName); \
214 for (uint32_t i = 0; i < g_cManyTreeFilesPerDir; i++) \
215 { \
216 RTStrFormatU32(&a_szPath[pCur->cchName], sizeof(a_szPath) - pCur->cchName, i, 10, 5, 5, RTSTR_F_ZEROPAD); \
217 a_fnCall; \
218 } \
219 } \
220 } while (0)
221
222
223/** @def FSPERF_VERR_PATH_NOT_FOUND
224 * Hides the fact that we only get VERR_PATH_NOT_FOUND on non-unix systems. */
225#if defined(RT_OS_WINDOWS) //|| defined(RT_OS_OS2) - using posix APIs IIRC, so lost in translation.
226# define FSPERF_VERR_PATH_NOT_FOUND VERR_PATH_NOT_FOUND
227#else
228# define FSPERF_VERR_PATH_NOT_FOUND VERR_FILE_NOT_FOUND
229#endif
230
231
232/*********************************************************************************************************************************
233* Structures and Typedefs *
234*********************************************************************************************************************************/
235typedef struct FSPERFNAMEENTRY
236{
237 RTLISTNODE Entry;
238 uint16_t cchName;
239 char szName[RT_FLEXIBLE_ARRAY];
240} FSPERFNAMEENTRY;
241typedef FSPERFNAMEENTRY *PFSPERFNAMEENTRY;
242
243
244enum
245{
246 kCmdOpt_First = 128,
247
248 kCmdOpt_ManyFiles = kCmdOpt_First,
249 kCmdOpt_NoManyFiles,
250 kCmdOpt_Open,
251 kCmdOpt_NoOpen,
252 kCmdOpt_FStat,
253 kCmdOpt_NoFStat,
254 kCmdOpt_FChMod,
255 kCmdOpt_NoFChMod,
256 kCmdOpt_FUtimes,
257 kCmdOpt_NoFUtimes,
258 kCmdOpt_Stat,
259 kCmdOpt_NoStat,
260 kCmdOpt_ChMod,
261 kCmdOpt_NoChMod,
262 kCmdOpt_Utimes,
263 kCmdOpt_NoUtimes,
264 kCmdOpt_Rename,
265 kCmdOpt_NoRename,
266 kCmdOpt_DirOpen,
267 kCmdOpt_NoDirOpen,
268 kCmdOpt_DirEnum,
269 kCmdOpt_NoDirEnum,
270 kCmdOpt_MkRmDir,
271 kCmdOpt_NoMkRmDir,
272 kCmdOpt_StatVfs,
273 kCmdOpt_NoStatVfs,
274 kCmdOpt_Rm,
275 kCmdOpt_NoRm,
276 kCmdOpt_ChSize,
277 kCmdOpt_NoChSize,
278 kCmdOpt_ReadPerf,
279 kCmdOpt_NoReadPerf,
280 kCmdOpt_ReadTests,
281 kCmdOpt_NoReadTests,
282 kCmdOpt_SendFile,
283 kCmdOpt_NoSendFile,
284#ifdef RT_OS_LINUX
285 kCmdOpt_Splice,
286 kCmdOpt_NoSplice,
287#endif
288 kCmdOpt_WritePerf,
289 kCmdOpt_NoWritePerf,
290 kCmdOpt_WriteTests,
291 kCmdOpt_NoWriteTests,
292 kCmdOpt_Seek,
293 kCmdOpt_NoSeek,
294 kCmdOpt_FSync,
295 kCmdOpt_NoFSync,
296 kCmdOpt_MMap,
297 kCmdOpt_NoMMap,
298 kCmdOpt_IgnoreNoCache,
299 kCmdOpt_NoIgnoreNoCache,
300 kCmdOpt_IoFileSize,
301 kCmdOpt_SetBlockSize,
302 kCmdOpt_AddBlockSize,
303 kCmdOpt_Copy,
304 kCmdOpt_NoCopy,
305
306 kCmdOpt_ShowDuration,
307 kCmdOpt_NoShowDuration,
308 kCmdOpt_ShowIterations,
309 kCmdOpt_NoShowIterations,
310
311 kCmdOpt_ManyTreeFilesPerDir,
312 kCmdOpt_ManyTreeSubdirsPerDir,
313 kCmdOpt_ManyTreeDepth,
314
315 kCmdOpt_End
316};
317
318
319/*********************************************************************************************************************************
320* Global Variables *
321*********************************************************************************************************************************/
322/** Command line parameters */
323static const RTGETOPTDEF g_aCmdOptions[] =
324{
325 { "--dir", 'd', RTGETOPT_REQ_STRING },
326 { "--seconds", 's', RTGETOPT_REQ_UINT32 },
327 { "--milliseconds", 'm', RTGETOPT_REQ_UINT64 },
328
329 { "--enable-all", 'e', RTGETOPT_REQ_NOTHING },
330 { "--disable-all", 'z', RTGETOPT_REQ_NOTHING },
331
332 { "--many-files", kCmdOpt_ManyFiles, RTGETOPT_REQ_UINT32 },
333 { "--no-many-files", kCmdOpt_NoManyFiles, RTGETOPT_REQ_NOTHING },
334 { "--files-per-dir", kCmdOpt_ManyTreeFilesPerDir, RTGETOPT_REQ_UINT32 },
335 { "--subdirs-per-dir", kCmdOpt_ManyTreeSubdirsPerDir, RTGETOPT_REQ_UINT32 },
336 { "--tree-depth", kCmdOpt_ManyTreeDepth, RTGETOPT_REQ_UINT32 },
337
338 { "--open", kCmdOpt_Open, RTGETOPT_REQ_NOTHING },
339 { "--no-open", kCmdOpt_NoOpen, RTGETOPT_REQ_NOTHING },
340 { "--fstat", kCmdOpt_FStat, RTGETOPT_REQ_NOTHING },
341 { "--no-fstat", kCmdOpt_NoFStat, RTGETOPT_REQ_NOTHING },
342 { "--fchmod", kCmdOpt_FChMod, RTGETOPT_REQ_NOTHING },
343 { "--no-fchmod", kCmdOpt_NoFChMod, RTGETOPT_REQ_NOTHING },
344 { "--futimes", kCmdOpt_FUtimes, RTGETOPT_REQ_NOTHING },
345 { "--no-futimes", kCmdOpt_NoFUtimes, RTGETOPT_REQ_NOTHING },
346 { "--stat", kCmdOpt_Stat, RTGETOPT_REQ_NOTHING },
347 { "--no-stat", kCmdOpt_NoStat, RTGETOPT_REQ_NOTHING },
348 { "--chmod", kCmdOpt_ChMod, RTGETOPT_REQ_NOTHING },
349 { "--no-chmod", kCmdOpt_NoChMod, RTGETOPT_REQ_NOTHING },
350 { "--utimes", kCmdOpt_Utimes, RTGETOPT_REQ_NOTHING },
351 { "--no-utimes", kCmdOpt_NoUtimes, RTGETOPT_REQ_NOTHING },
352 { "--rename", kCmdOpt_Rename, RTGETOPT_REQ_NOTHING },
353 { "--no-rename", kCmdOpt_NoRename, RTGETOPT_REQ_NOTHING },
354 { "--dir-open", kCmdOpt_DirOpen, RTGETOPT_REQ_NOTHING },
355 { "--no-dir-open", kCmdOpt_NoDirOpen, RTGETOPT_REQ_NOTHING },
356 { "--dir-enum", kCmdOpt_DirEnum, RTGETOPT_REQ_NOTHING },
357 { "--no-dir-enum", kCmdOpt_NoDirEnum, RTGETOPT_REQ_NOTHING },
358 { "--mk-rm-dir", kCmdOpt_MkRmDir, RTGETOPT_REQ_NOTHING },
359 { "--no-mk-rm-dir", kCmdOpt_NoMkRmDir, RTGETOPT_REQ_NOTHING },
360 { "--stat-vfs", kCmdOpt_StatVfs, RTGETOPT_REQ_NOTHING },
361 { "--no-stat-vfs", kCmdOpt_NoStatVfs, RTGETOPT_REQ_NOTHING },
362 { "--rm", kCmdOpt_Rm, RTGETOPT_REQ_NOTHING },
363 { "--no-rm", kCmdOpt_NoRm, RTGETOPT_REQ_NOTHING },
364 { "--chsize", kCmdOpt_ChSize, RTGETOPT_REQ_NOTHING },
365 { "--no-chsize", kCmdOpt_NoChSize, RTGETOPT_REQ_NOTHING },
366 { "--read-tests", kCmdOpt_ReadTests, RTGETOPT_REQ_NOTHING },
367 { "--no-read-tests", kCmdOpt_NoReadTests, RTGETOPT_REQ_NOTHING },
368 { "--read-perf", kCmdOpt_ReadPerf, RTGETOPT_REQ_NOTHING },
369 { "--no-read-perf", kCmdOpt_NoReadPerf, RTGETOPT_REQ_NOTHING },
370 { "--sendfile", kCmdOpt_SendFile, RTGETOPT_REQ_NOTHING },
371 { "--no-sendfile", kCmdOpt_NoSendFile, RTGETOPT_REQ_NOTHING },
372#ifdef RT_OS_LINUX
373 { "--splice", kCmdOpt_Splice, RTGETOPT_REQ_NOTHING },
374 { "--no-splice", kCmdOpt_NoSplice, RTGETOPT_REQ_NOTHING },
375#endif
376 { "--write-tests", kCmdOpt_WriteTests, RTGETOPT_REQ_NOTHING },
377 { "--no-write-tests", kCmdOpt_NoWriteTests, RTGETOPT_REQ_NOTHING },
378 { "--write-perf", kCmdOpt_WritePerf, RTGETOPT_REQ_NOTHING },
379 { "--no-write-perf", kCmdOpt_NoWritePerf, RTGETOPT_REQ_NOTHING },
380 { "--seek", kCmdOpt_Seek, RTGETOPT_REQ_NOTHING },
381 { "--no-seek", kCmdOpt_NoSeek, RTGETOPT_REQ_NOTHING },
382 { "--fsync", kCmdOpt_FSync, RTGETOPT_REQ_NOTHING },
383 { "--no-fsync", kCmdOpt_NoFSync, RTGETOPT_REQ_NOTHING },
384 { "--mmap", kCmdOpt_MMap, RTGETOPT_REQ_NOTHING },
385 { "--no-mmap", kCmdOpt_NoMMap, RTGETOPT_REQ_NOTHING },
386 { "--ignore-no-cache", kCmdOpt_IgnoreNoCache, RTGETOPT_REQ_NOTHING },
387 { "--no-ignore-no-cache", kCmdOpt_NoIgnoreNoCache, RTGETOPT_REQ_NOTHING },
388 { "--io-file-size", kCmdOpt_IoFileSize, RTGETOPT_REQ_UINT64 },
389 { "--set-block-size", kCmdOpt_SetBlockSize, RTGETOPT_REQ_UINT32 },
390 { "--add-block-size", kCmdOpt_AddBlockSize, RTGETOPT_REQ_UINT32 },
391 { "--copy", kCmdOpt_Copy, RTGETOPT_REQ_NOTHING },
392 { "--no-copy", kCmdOpt_NoCopy, RTGETOPT_REQ_NOTHING },
393
394 { "--show-duration", kCmdOpt_ShowDuration, RTGETOPT_REQ_NOTHING },
395 { "--no-show-duration", kCmdOpt_NoShowDuration, RTGETOPT_REQ_NOTHING },
396 { "--show-iterations", kCmdOpt_ShowIterations, RTGETOPT_REQ_NOTHING },
397 { "--no-show-iterations", kCmdOpt_NoShowIterations, RTGETOPT_REQ_NOTHING },
398
399 { "--quiet", 'q', RTGETOPT_REQ_NOTHING },
400 { "--verbose", 'v', RTGETOPT_REQ_NOTHING },
401 { "--version", 'V', RTGETOPT_REQ_NOTHING },
402 { "--help", 'h', RTGETOPT_REQ_NOTHING } /* for Usage() */
403};
404
405/** The test handle. */
406static RTTEST g_hTest;
407/** The number of nanoseconds a RTTimeNanoTS call takes.
408 * This is used for adjusting loop count estimates. */
409static uint64_t g_nsPerNanoTSCall = 1;
410/** Whether or not to display the duration of each profile run.
411 * This is chiefly for verify the estimate phase. */
412static bool g_fShowDuration = false;
413/** Whether or not to display the iteration count for each profile run.
414 * This is chiefly for verify the estimate phase. */
415static bool g_fShowIterations = false;
416/** Verbosity level. */
417static uint32_t g_uVerbosity = 0;
418
419/** @name Selected subtest
420 * @{ */
421static bool g_fManyFiles = true;
422static bool g_fOpen = true;
423static bool g_fFStat = true;
424static bool g_fFChMod = true;
425static bool g_fFUtimes = true;
426static bool g_fStat = true;
427static bool g_fChMod = true;
428static bool g_fUtimes = true;
429static bool g_fRename = true;
430static bool g_fDirOpen = true;
431static bool g_fDirEnum = true;
432static bool g_fMkRmDir = true;
433static bool g_fStatVfs = true;
434static bool g_fRm = true;
435static bool g_fChSize = true;
436static bool g_fReadTests = true;
437static bool g_fReadPerf = true;
438static bool g_fSendFile = true;
439#ifdef RT_OS_LINUX
440static bool g_fSplice = true;
441#endif
442static bool g_fWriteTests= true;
443static bool g_fWritePerf = true;
444static bool g_fSeek = true;
445static bool g_fFSync = true;
446static bool g_fMMap = true;
447static bool g_fCopy = true;
448/** @} */
449
450/** The length of each test run. */
451static uint64_t g_nsTestRun = RT_NS_1SEC_64 * 10;
452
453/** For the 'manyfiles' subdir. */
454static uint32_t g_cManyFiles = 10000;
455
456/** Number of files in the 'manytree' directory tree. */
457static uint32_t g_cManyTreeFiles = 640 + 16*640 /*10880*/;
458/** Number of files per directory in the 'manytree' construct. */
459static uint32_t g_cManyTreeFilesPerDir = 640;
460/** Number of subdirs per directory in the 'manytree' construct. */
461static uint32_t g_cManyTreeSubdirsPerDir = 16;
462/** The depth of the 'manytree' directory tree. */
463static uint32_t g_cManyTreeDepth = 1;
464/** List of directories in the many tree, creation order. */
465static RTLISTANCHOR g_ManyTreeHead;
466
467/** Number of configured I/O block sizes. */
468static uint32_t g_cIoBlocks = 8;
469/** Configured I/O block sizes. */
470static uint32_t g_acbIoBlocks[16] = { 1, 512, 4096, 16384, 65536, _1M, _32M, _128M };
471/** The desired size of the test file we use for I/O. */
472static uint64_t g_cbIoFile = _512M;
473/** Whether to be less strict with non-cache file handle. */
474static bool g_fIgnoreNoCache = false;
475
476/** The length of g_szDir. */
477static size_t g_cchDir;
478/** The length of g_szEmptyDir. */
479static size_t g_cchEmptyDir;
480/** The length of g_szDeepDir. */
481static size_t g_cchDeepDir;
482
483/** The test directory (absolute). This will always have a trailing slash. */
484static char g_szDir[RTPATH_MAX];
485/** The test directory (absolute), 2nd copy for use with InDir2(). */
486static char g_szDir2[RTPATH_MAX];
487/** The empty test directory (absolute). This will always have a trailing slash. */
488static char g_szEmptyDir[RTPATH_MAX];
489/** The deep test directory (absolute). This will always have a trailing slash. */
490static char g_szDeepDir[RTPATH_MAX];
491
492
493/**
494 * Yield the CPU and stuff before starting a test run.
495 */
496DECLINLINE(void) fsPerfYield(void)
497{
498 RTThreadYield();
499 RTThreadYield();
500}
501
502
503/**
504 * Profiles the RTTimeNanoTS call, setting g_nsPerNanoTSCall.
505 */
506static void fsPerfNanoTS(void)
507{
508 fsPerfYield();
509
510 /* Make sure we start off on a changing timestamp on platforms will low time resoultion. */
511 uint64_t nsStart = RTTimeNanoTS();
512 uint64_t ns;
513 do
514 ns = RTTimeNanoTS();
515 while (ns == nsStart);
516 nsStart = ns;
517
518 /* Call it for 10 ms. */
519 uint32_t i = 0;
520 do
521 {
522 i++;
523 ns = RTTimeNanoTS();
524 }
525 while (ns - nsStart < RT_NS_10MS);
526
527 g_nsPerNanoTSCall = (ns - nsStart) / i;
528}
529
530
531/**
532 * Construct a path relative to the base test directory.
533 *
534 * @returns g_szDir.
535 * @param pszAppend What to append.
536 * @param cchAppend How much to append.
537 */
538DECLINLINE(char *) InDir(const char *pszAppend, size_t cchAppend)
539{
540 Assert(g_szDir[g_cchDir - 1] == RTPATH_SLASH);
541 memcpy(&g_szDir[g_cchDir], pszAppend, cchAppend);
542 g_szDir[g_cchDir + cchAppend] = '\0';
543 return &g_szDir[0];
544}
545
546
547/**
548 * Construct a path relative to the base test directory, 2nd copy.
549 *
550 * @returns g_szDir2.
551 * @param pszAppend What to append.
552 * @param cchAppend How much to append.
553 */
554DECLINLINE(char *) InDir2(const char *pszAppend, size_t cchAppend)
555{
556 Assert(g_szDir[g_cchDir - 1] == RTPATH_SLASH);
557 memcpy(g_szDir2, g_szDir, g_cchDir);
558 memcpy(&g_szDir2[g_cchDir], pszAppend, cchAppend);
559 g_szDir2[g_cchDir + cchAppend] = '\0';
560 return &g_szDir2[0];
561}
562
563
564/**
565 * Construct a path relative to the empty directory.
566 *
567 * @returns g_szEmptyDir.
568 * @param pszAppend What to append.
569 * @param cchAppend How much to append.
570 */
571DECLINLINE(char *) InEmptyDir(const char *pszAppend, size_t cchAppend)
572{
573 Assert(g_szEmptyDir[g_cchEmptyDir - 1] == RTPATH_SLASH);
574 memcpy(&g_szEmptyDir[g_cchEmptyDir], pszAppend, cchAppend);
575 g_szEmptyDir[g_cchEmptyDir + cchAppend] = '\0';
576 return &g_szEmptyDir[0];
577}
578
579
580/**
581 * Construct a path relative to the deep test directory.
582 *
583 * @returns g_szDeepDir.
584 * @param pszAppend What to append.
585 * @param cchAppend How much to append.
586 */
587DECLINLINE(char *) InDeepDir(const char *pszAppend, size_t cchAppend)
588{
589 Assert(g_szDeepDir[g_cchDeepDir - 1] == RTPATH_SLASH);
590 memcpy(&g_szDeepDir[g_cchDeepDir], pszAppend, cchAppend);
591 g_szDeepDir[g_cchDeepDir + cchAppend] = '\0';
592 return &g_szDeepDir[0];
593}
594
595
596/**
597 * Prepares the test area.
598 * @returns VBox status code.
599 */
600static int fsPrepTestArea(void)
601{
602 /* The empty subdir and associated globals: */
603 static char s_szEmpty[] = "empty";
604 memcpy(g_szEmptyDir, g_szDir, g_cchDir);
605 memcpy(&g_szEmptyDir[g_cchDir], s_szEmpty, sizeof(s_szEmpty));
606 g_cchEmptyDir = g_cchDir + sizeof(s_szEmpty) - 1;
607 RTTESTI_CHECK_RC_RET(RTDirCreate(g_szEmptyDir, 0755, 0), VINF_SUCCESS, rcCheck);
608 g_szEmptyDir[g_cchEmptyDir++] = RTPATH_SLASH;
609 g_szEmptyDir[g_cchEmptyDir] = '\0';
610 RTTestIPrintf(RTTESTLVL_ALWAYS, "Empty dir: %s\n", g_szEmptyDir);
611
612 /* Deep directory: */
613 memcpy(g_szDeepDir, g_szDir, g_cchDir);
614 g_cchDeepDir = g_cchDir;
615 do
616 {
617 static char const s_szSub[] = "d" RTPATH_SLASH_STR;
618 memcpy(&g_szDeepDir[g_cchDeepDir], s_szSub, sizeof(s_szSub));
619 g_cchDeepDir += sizeof(s_szSub) - 1;
620 RTTESTI_CHECK_RC_RET( RTDirCreate(g_szDeepDir, 0755, 0), VINF_SUCCESS, rcCheck);
621 } while (g_cchDeepDir < 176);
622 RTTestIPrintf(RTTESTLVL_ALWAYS, "Deep dir: %s\n", g_szDeepDir);
623
624 /* Create known file in both deep and shallow dirs: */
625 RTFILE hKnownFile;
626 RTTESTI_CHECK_RC_RET(RTFileOpen(&hKnownFile, InDir(RT_STR_TUPLE("known-file")),
627 RTFILE_O_CREATE | RTFILE_O_DENY_NONE | RTFILE_O_WRITE),
628 VINF_SUCCESS, rcCheck);
629 RTTESTI_CHECK_RC_RET(RTFileClose(hKnownFile), VINF_SUCCESS, rcCheck);
630
631 RTTESTI_CHECK_RC_RET(RTFileOpen(&hKnownFile, InDeepDir(RT_STR_TUPLE("known-file")),
632 RTFILE_O_CREATE | RTFILE_O_DENY_NONE | RTFILE_O_WRITE),
633 VINF_SUCCESS, rcCheck);
634 RTTESTI_CHECK_RC_RET(RTFileClose(hKnownFile), VINF_SUCCESS, rcCheck);
635
636 return VINF_SUCCESS;
637}
638
639
640/**
641 * Create a name list entry.
642 * @returns Pointer to the entry, NULL if out of memory.
643 * @param pchName The name.
644 * @param cchName The name length.
645 */
646PFSPERFNAMEENTRY fsPerfCreateNameEntry(const char *pchName, size_t cchName)
647{
648 PFSPERFNAMEENTRY pEntry = (PFSPERFNAMEENTRY)RTMemAllocVar(RT_UOFFSETOF_DYN(FSPERFNAMEENTRY, szName[cchName + 1]));
649 if (pEntry)
650 {
651 RTListInit(&pEntry->Entry);
652 pEntry->cchName = (uint16_t)cchName;
653 memcpy(pEntry->szName, pchName, cchName);
654 pEntry->szName[cchName] = '\0';
655 }
656 return pEntry;
657}
658
659
660static int fsPerfManyTreeRecursiveDirCreator(size_t cchDir, uint32_t iDepth)
661{
662 PFSPERFNAMEENTRY pEntry = fsPerfCreateNameEntry(g_szDir, cchDir);
663 RTTESTI_CHECK_RET(pEntry, VERR_NO_MEMORY);
664 RTListAppend(&g_ManyTreeHead, &pEntry->Entry);
665
666 RTTESTI_CHECK_RC_RET(RTDirCreate(g_szDir, 0755, RTDIRCREATE_FLAGS_NOT_CONTENT_INDEXED_DONT_SET | RTDIRCREATE_FLAGS_NOT_CONTENT_INDEXED_NOT_CRITICAL),
667 VINF_SUCCESS, rcCheck);
668
669 if (iDepth < g_cManyTreeDepth)
670 for (uint32_t i = 0; i < g_cManyTreeSubdirsPerDir; i++)
671 {
672 size_t cchSubDir = RTStrPrintf(&g_szDir[cchDir], sizeof(g_szDir) - cchDir, "d%02u" RTPATH_SLASH_STR, i);
673 RTTESTI_CHECK_RC_RET(fsPerfManyTreeRecursiveDirCreator(cchDir + cchSubDir, iDepth + 1), VINF_SUCCESS, rcCheck);
674 }
675
676 return VINF_SUCCESS;
677}
678
679
680void fsPerfManyFiles(void)
681{
682 RTTestISub("manyfiles");
683
684 /*
685 * Create a sub-directory with like 10000 files in it.
686 *
687 * This does push the directory organization of the underlying file system,
688 * which is something we might not want to profile with shared folders. It
689 * is however useful for directory enumeration.
690 */
691 RTTESTI_CHECK_RC_RETV(RTDirCreate(InDir(RT_STR_TUPLE("manyfiles")), 0755,
692 RTDIRCREATE_FLAGS_NOT_CONTENT_INDEXED_DONT_SET | RTDIRCREATE_FLAGS_NOT_CONTENT_INDEXED_NOT_CRITICAL),
693 VINF_SUCCESS);
694
695 size_t offFilename = strlen(g_szDir);
696 g_szDir[offFilename++] = RTPATH_SLASH;
697
698 fsPerfYield();
699 RTFILE hFile;
700 uint64_t const nsStart = RTTimeNanoTS();
701 for (uint32_t i = 0; i < g_cManyFiles; i++)
702 {
703 RTStrFormatU32(&g_szDir[offFilename], sizeof(g_szDir) - offFilename, i, 10, 5, 5, RTSTR_F_ZEROPAD);
704 RTTESTI_CHECK_RC_RETV(RTFileOpen(&hFile, g_szDir, RTFILE_O_CREATE | RTFILE_O_DENY_NONE | RTFILE_O_WRITE), VINF_SUCCESS);
705 RTTESTI_CHECK_RC(RTFileClose(hFile), VINF_SUCCESS);
706 }
707 uint64_t const cNsElapsed = RTTimeNanoTS() - nsStart;
708 RTTestIValueF(cNsElapsed, RTTESTUNIT_NS, "Creating %u empty files in single directory", g_cManyFiles);
709 RTTestIValueF(cNsElapsed / g_cManyFiles, RTTESTUNIT_NS_PER_OCCURRENCE, "Create empty file (single dir)");
710
711 /*
712 * Create a bunch of directories with exacly 32 files in each, hoping to
713 * avoid any directory organization artifacts.
714 */
715 /* Create the directories first, building a list of them for simplifying iteration: */
716 RTListInit(&g_ManyTreeHead);
717 InDir(RT_STR_TUPLE("manytree" RTPATH_SLASH_STR));
718 RTTESTI_CHECK_RC_RETV(fsPerfManyTreeRecursiveDirCreator(strlen(g_szDir), 0), VINF_SUCCESS);
719
720 /* Create the zero byte files: */
721 fsPerfYield();
722 uint64_t const nsStart2 = RTTimeNanoTS();
723 uint32_t cFiles = 0;
724 PFSPERFNAMEENTRY pCur;
725 RTListForEach(&g_ManyTreeHead, pCur, FSPERFNAMEENTRY, Entry)
726 {
727 char szPath[RTPATH_MAX];
728 memcpy(szPath, pCur->szName, pCur->cchName);
729 for (uint32_t i = 0; i < g_cManyTreeFilesPerDir; i++)
730 {
731 RTStrFormatU32(&szPath[pCur->cchName], sizeof(szPath) - pCur->cchName, i, 10, 5, 5, RTSTR_F_ZEROPAD);
732 RTTESTI_CHECK_RC_RETV(RTFileOpen(&hFile, szPath, RTFILE_O_CREATE | RTFILE_O_DENY_NONE | RTFILE_O_WRITE), VINF_SUCCESS);
733 RTTESTI_CHECK_RC(RTFileClose(hFile), VINF_SUCCESS);
734 cFiles++;
735 }
736 }
737 uint64_t const cNsElapsed2 = RTTimeNanoTS() - nsStart2;
738 RTTestIValueF(cNsElapsed2, RTTESTUNIT_NS, "Creating %u empty files in tree", cFiles);
739 RTTestIValueF(cNsElapsed2 / cFiles, RTTESTUNIT_NS_PER_OCCURRENCE, "Create empty file (tree)");
740 RTTESTI_CHECK(g_cManyTreeFiles == cFiles);
741}
742
743
744DECL_FORCE_INLINE(int) fsPerfOpenExistingOnceReadonly(const char *pszFile)
745{
746 RTFILE hFile;
747 RTTESTI_CHECK_RC_RET(RTFileOpen(&hFile, pszFile, RTFILE_O_OPEN | RTFILE_O_DENY_NONE | RTFILE_O_READ), VINF_SUCCESS, rcCheck);
748 RTTESTI_CHECK_RC(RTFileClose(hFile), VINF_SUCCESS);
749 return VINF_SUCCESS;
750}
751
752
753DECL_FORCE_INLINE(int) fsPerfOpenExistingOnceWriteonly(const char *pszFile)
754{
755 RTFILE hFile;
756 RTTESTI_CHECK_RC_RET(RTFileOpen(&hFile, pszFile, RTFILE_O_OPEN | RTFILE_O_DENY_NONE | RTFILE_O_WRITE), VINF_SUCCESS, rcCheck);
757 RTTESTI_CHECK_RC(RTFileClose(hFile), VINF_SUCCESS);
758 return VINF_SUCCESS;
759}
760
761
762/** @note tstRTFileOpenEx-1.cpp has a copy of this code. */
763static void tstOpenExTest(unsigned uLine, int cbExist, int cbNext, const char *pszFilename, uint64_t fAction,
764 int rcExpect, RTFILEACTION enmActionExpected)
765{
766 uint64_t const fCreateMode = (0644 << RTFILE_O_CREATE_MODE_SHIFT);
767 RTFILE hFile;
768 int rc;
769
770 /*
771 * File existence and size.
772 */
773 bool fOkay = false;
774 RTFSOBJINFO ObjInfo;
775 rc = RTPathQueryInfoEx(pszFilename, &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK);
776 if (RT_SUCCESS(rc))
777 fOkay = cbExist == (int64_t)ObjInfo.cbObject;
778 else
779 fOkay = rc == VERR_FILE_NOT_FOUND && cbExist < 0;
780 if (!fOkay)
781 {
782 if (cbExist >= 0)
783 {
784 rc = RTFileOpen(&hFile, pszFilename, RTFILE_O_WRITE | RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE | fCreateMode);
785 if (RT_SUCCESS(rc))
786 {
787 while (cbExist > 0)
788 {
789 int cbToWrite = (int)strlen(pszFilename);
790 if (cbToWrite > cbExist)
791 cbToWrite = cbExist;
792 rc = RTFileWrite(hFile, pszFilename, cbToWrite, NULL);
793 if (RT_FAILURE(rc))
794 {
795 RTTestIFailed("%u: RTFileWrite(%s,%#x) -> %Rrc\n", uLine, pszFilename, cbToWrite, rc);
796 break;
797 }
798 cbExist -= cbToWrite;
799 }
800
801 RTTESTI_CHECK_RC(RTFileClose(hFile), VINF_SUCCESS);
802 }
803 else
804 RTTestIFailed("%u: RTFileDelete(%s) -> %Rrc\n", uLine, pszFilename, rc);
805
806 }
807 else
808 {
809 rc = RTFileDelete(pszFilename);
810 if (rc != VINF_SUCCESS && rc != VERR_FILE_NOT_FOUND)
811 RTTestIFailed("%u: RTFileDelete(%s) -> %Rrc\n", uLine, pszFilename, rc);
812 }
813 }
814
815 /*
816 * The actual test.
817 */
818 RTFILEACTION enmActuallyTaken = RTFILEACTION_END;
819 hFile = NIL_RTFILE;
820 rc = RTFileOpenEx(pszFilename, fAction | RTFILE_O_READWRITE | RTFILE_O_DENY_NONE | fCreateMode, &hFile, &enmActuallyTaken);
821 if ( rc != rcExpect
822 || enmActuallyTaken != enmActionExpected
823 || (RT_SUCCESS(rc) ? hFile == NIL_RTFILE : hFile != NIL_RTFILE))
824 RTTestIFailed("%u: RTFileOpenEx(%s, %#llx) -> %Rrc + %d (hFile=%p), expected %Rrc + %d\n",
825 uLine, pszFilename, fAction, rc, enmActuallyTaken, hFile, rcExpect, enmActionExpected);
826 if (RT_SUCCESS(rc))
827 {
828 if ( enmActionExpected == RTFILEACTION_REPLACED
829 || enmActionExpected == RTFILEACTION_TRUNCATED)
830 {
831 uint8_t abBuf[16];
832 rc = RTFileRead(hFile, abBuf, 1, NULL);
833 if (rc != VERR_EOF)
834 RTTestIFailed("%u: RTFileRead(%s,,1,) -> %Rrc, expected VERR_EOF\n", uLine, pszFilename, rc);
835 }
836
837 while (cbNext > 0)
838 {
839 int cbToWrite = (int)strlen(pszFilename);
840 if (cbToWrite > cbNext)
841 cbToWrite = cbNext;
842 rc = RTFileWrite(hFile, pszFilename, cbToWrite, NULL);
843 if (RT_FAILURE(rc))
844 {
845 RTTestIFailed("%u: RTFileWrite(%s,%#x) -> %Rrc\n", uLine, pszFilename, cbToWrite, rc);
846 break;
847 }
848 cbNext -= cbToWrite;
849 }
850
851 rc = RTFileClose(hFile);
852 if (RT_FAILURE(rc))
853 RTTestIFailed("%u: RTFileClose(%p) -> %Rrc\n", uLine, hFile, rc);
854 }
855}
856
857
858void fsPerfOpen(void)
859{
860 RTTestISub("open");
861
862 /* Opening non-existing files. */
863 RTFILE hFile;
864 RTTESTI_CHECK_RC(RTFileOpen(&hFile, InEmptyDir(RT_STR_TUPLE("no-such-file")),
865 RTFILE_O_OPEN | RTFILE_O_DENY_NONE | RTFILE_O_READ), VERR_FILE_NOT_FOUND);
866 RTTESTI_CHECK_RC(RTFileOpen(&hFile, InEmptyDir(RT_STR_TUPLE("no-such-dir" RTPATH_SLASH_STR "no-such-file")),
867 RTFILE_O_OPEN | RTFILE_O_DENY_NONE | RTFILE_O_READ), FSPERF_VERR_PATH_NOT_FOUND);
868 RTTESTI_CHECK_RC(RTFileOpen(&hFile, InDir(RT_STR_TUPLE("known-file" RTPATH_SLASH_STR "no-such-file")),
869 RTFILE_O_OPEN | RTFILE_O_DENY_NONE | RTFILE_O_READ), VERR_PATH_NOT_FOUND);
870
871 /*
872 * The following is copied from tstRTFileOpenEx-1.cpp:
873 */
874 InDir(RT_STR_TUPLE("file1"));
875 tstOpenExTest(__LINE__, -1, -1, g_szDir, RTFILE_O_OPEN, VERR_FILE_NOT_FOUND, RTFILEACTION_INVALID);
876 tstOpenExTest(__LINE__, -1, -1, g_szDir, RTFILE_O_OPEN_CREATE, VINF_SUCCESS, RTFILEACTION_CREATED);
877 tstOpenExTest(__LINE__, 0, 0, g_szDir, RTFILE_O_OPEN_CREATE, VINF_SUCCESS, RTFILEACTION_OPENED);
878 tstOpenExTest(__LINE__, 0, 0, g_szDir, RTFILE_O_OPEN, VINF_SUCCESS, RTFILEACTION_OPENED);
879
880 tstOpenExTest(__LINE__, 0, 0, g_szDir, RTFILE_O_OPEN | RTFILE_O_TRUNCATE, VINF_SUCCESS, RTFILEACTION_TRUNCATED);
881 tstOpenExTest(__LINE__, 0, 10, g_szDir, RTFILE_O_OPEN_CREATE | RTFILE_O_TRUNCATE, VINF_SUCCESS, RTFILEACTION_TRUNCATED);
882 tstOpenExTest(__LINE__, 10, 10, g_szDir, RTFILE_O_OPEN_CREATE | RTFILE_O_TRUNCATE, VINF_SUCCESS, RTFILEACTION_TRUNCATED);
883 tstOpenExTest(__LINE__, 10, -1, g_szDir, RTFILE_O_OPEN | RTFILE_O_TRUNCATE, VINF_SUCCESS, RTFILEACTION_TRUNCATED);
884 tstOpenExTest(__LINE__, -1, -1, g_szDir, RTFILE_O_OPEN | RTFILE_O_TRUNCATE, VERR_FILE_NOT_FOUND, RTFILEACTION_INVALID);
885 tstOpenExTest(__LINE__, -1, 0, g_szDir, RTFILE_O_OPEN_CREATE | RTFILE_O_TRUNCATE, VINF_SUCCESS, RTFILEACTION_CREATED);
886
887 tstOpenExTest(__LINE__, 0, -1, g_szDir, RTFILE_O_CREATE_REPLACE, VINF_SUCCESS, RTFILEACTION_REPLACED);
888 tstOpenExTest(__LINE__, -1, 0, g_szDir, RTFILE_O_CREATE_REPLACE, VINF_SUCCESS, RTFILEACTION_CREATED);
889 tstOpenExTest(__LINE__, 0, -1, g_szDir, RTFILE_O_CREATE, VERR_ALREADY_EXISTS, RTFILEACTION_ALREADY_EXISTS);
890 tstOpenExTest(__LINE__, -1, -1, g_szDir, RTFILE_O_CREATE, VINF_SUCCESS, RTFILEACTION_CREATED);
891
892 tstOpenExTest(__LINE__, -1, 10, g_szDir, RTFILE_O_CREATE | RTFILE_O_TRUNCATE, VINF_SUCCESS, RTFILEACTION_CREATED);
893 tstOpenExTest(__LINE__, 10, 10, g_szDir, RTFILE_O_CREATE | RTFILE_O_TRUNCATE, VERR_ALREADY_EXISTS, RTFILEACTION_ALREADY_EXISTS);
894 tstOpenExTest(__LINE__, 10, -1, g_szDir, RTFILE_O_CREATE_REPLACE | RTFILE_O_TRUNCATE, VINF_SUCCESS, RTFILEACTION_REPLACED);
895 tstOpenExTest(__LINE__, -1, -1, g_szDir, RTFILE_O_CREATE_REPLACE | RTFILE_O_TRUNCATE, VINF_SUCCESS, RTFILEACTION_CREATED);
896
897 RTTESTI_CHECK_RC(RTFileDelete(g_szDir), VINF_SUCCESS);
898
899 /*
900 * Create file1 and then try exclusivly creating it again.
901 * Then profile opening it for reading.
902 */
903 RTFILE hFile1;
904 RTTESTI_CHECK_RC_RETV(RTFileOpen(&hFile1, InDir(RT_STR_TUPLE("file1")),
905 RTFILE_O_CREATE | RTFILE_O_DENY_NONE | RTFILE_O_WRITE), VINF_SUCCESS);
906 RTTESTI_CHECK_RC(RTFileOpen(&hFile, g_szDir, RTFILE_O_CREATE | RTFILE_O_DENY_NONE | RTFILE_O_WRITE), VERR_ALREADY_EXISTS);
907 RTTESTI_CHECK_RC(RTFileClose(hFile1), VINF_SUCCESS);
908
909 PROFILE_FN(fsPerfOpenExistingOnceReadonly(g_szDir), g_nsTestRun, "RTFileOpen/Close/Readonly");
910 PROFILE_FN(fsPerfOpenExistingOnceWriteonly(g_szDir), g_nsTestRun, "RTFileOpen/Close/Writeonly");
911
912 /*
913 * Profile opening in the deep directory too.
914 */
915 RTTESTI_CHECK_RC_RETV(RTFileOpen(&hFile1, InDeepDir(RT_STR_TUPLE("file1")),
916 RTFILE_O_CREATE | RTFILE_O_DENY_NONE | RTFILE_O_WRITE), VINF_SUCCESS);
917 RTTESTI_CHECK_RC(RTFileClose(hFile1), VINF_SUCCESS);
918 PROFILE_FN(fsPerfOpenExistingOnceReadonly(g_szDeepDir), g_nsTestRun, "RTFileOpen/Close/deep/readonly");
919 PROFILE_FN(fsPerfOpenExistingOnceWriteonly(g_szDeepDir), g_nsTestRun, "RTFileOpen/Close/deep/writeonly");
920
921 /* Manytree: */
922 char szPath[RTPATH_MAX];
923 PROFILE_MANYTREE_FN(szPath, fsPerfOpenExistingOnceReadonly(szPath), 1, g_nsTestRun, "RTFileOpen/Close/manytree/readonly");
924}
925
926
927void fsPerfFStat(void)
928{
929 RTTestISub("fstat");
930 RTFILE hFile1;
931 RTTESTI_CHECK_RC_RETV(RTFileOpen(&hFile1, InDir(RT_STR_TUPLE("file2")),
932 RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE | RTFILE_O_WRITE), VINF_SUCCESS);
933 RTFSOBJINFO ObjInfo = {0};
934 PROFILE_FN(RTFileQueryInfo(hFile1, &ObjInfo, RTFSOBJATTRADD_NOTHING), g_nsTestRun, "RTFileQueryInfo/NOTHING");
935 PROFILE_FN(RTFileQueryInfo(hFile1, &ObjInfo, RTFSOBJATTRADD_UNIX), g_nsTestRun, "RTFileQueryInfo/UNIX");
936
937 RTTESTI_CHECK_RC(RTFileClose(hFile1), VINF_SUCCESS);
938}
939
940
941void fsPerfFChMod(void)
942{
943 RTTestISub("fchmod");
944 RTFILE hFile1;
945 RTTESTI_CHECK_RC_RETV(RTFileOpen(&hFile1, InDir(RT_STR_TUPLE("file4")),
946 RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE | RTFILE_O_WRITE), VINF_SUCCESS);
947 RTFSOBJINFO ObjInfo = {0};
948 RTTESTI_CHECK_RC(RTFileQueryInfo(hFile1, &ObjInfo, RTFSOBJATTRADD_NOTHING), VINF_SUCCESS);
949 RTFMODE const fEvenMode = (ObjInfo.Attr.fMode & ~RTFS_UNIX_ALL_ACCESS_PERMS) | RTFS_DOS_READONLY | 0400;
950 RTFMODE const fOddMode = (ObjInfo.Attr.fMode & ~(RTFS_UNIX_ALL_ACCESS_PERMS | RTFS_DOS_READONLY)) | 0640;
951 PROFILE_FN(RTFileSetMode(hFile1, iIteration & 1 ? fOddMode : fEvenMode), g_nsTestRun, "RTFileSetMode");
952
953 RTFileSetMode(hFile1, ObjInfo.Attr.fMode);
954 RTTESTI_CHECK_RC(RTFileClose(hFile1), VINF_SUCCESS);
955}
956
957
958void fsPerfFUtimes(void)
959{
960 RTTestISub("futimes");
961 RTFILE hFile1;
962 RTTESTI_CHECK_RC_RETV(RTFileOpen(&hFile1, InDir(RT_STR_TUPLE("file5")),
963 RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE | RTFILE_O_WRITE), VINF_SUCCESS);
964 RTTIMESPEC Time1;
965 RTTimeNow(&Time1);
966 RTTIMESPEC Time2 = Time1;
967 RTTimeSpecSubSeconds(&Time2, 3636);
968
969 RTFSOBJINFO ObjInfo0 = {0};
970 RTTESTI_CHECK_RC(RTFileQueryInfo(hFile1, &ObjInfo0, RTFSOBJATTRADD_NOTHING), VINF_SUCCESS);
971
972 /* Modify modification time: */
973 RTTESTI_CHECK_RC(RTFileSetTimes(hFile1, NULL, &Time2, NULL, NULL), VINF_SUCCESS);
974 RTFSOBJINFO ObjInfo1 = {0};
975 RTTESTI_CHECK_RC(RTFileQueryInfo(hFile1, &ObjInfo1, RTFSOBJATTRADD_NOTHING), VINF_SUCCESS);
976 RTTESTI_CHECK((RTTimeSpecGetSeconds(&ObjInfo1.ModificationTime) >> 2) == (RTTimeSpecGetSeconds(&Time2) >> 2));
977 char sz1[RTTIME_STR_LEN], sz2[RTTIME_STR_LEN]; /* Div by 1000 here for posix impl. using timeval. */
978 RTTESTI_CHECK_MSG(RTTimeSpecGetNano(&ObjInfo1.AccessTime) / 1000 == RTTimeSpecGetNano(&ObjInfo0.AccessTime) / 1000,
979 ("%s, expected %s", RTTimeSpecToString(&ObjInfo1.AccessTime, sz1, sizeof(sz1)),
980 RTTimeSpecToString(&ObjInfo0.AccessTime, sz2, sizeof(sz2))));
981
982 /* Modify access time: */
983 RTTESTI_CHECK_RC(RTFileSetTimes(hFile1, &Time1, NULL, NULL, NULL), VINF_SUCCESS);
984 RTFSOBJINFO ObjInfo2 = {0};
985 RTTESTI_CHECK_RC(RTFileQueryInfo(hFile1, &ObjInfo2, RTFSOBJATTRADD_NOTHING), VINF_SUCCESS);
986 RTTESTI_CHECK((RTTimeSpecGetSeconds(&ObjInfo2.AccessTime) >> 2) == (RTTimeSpecGetSeconds(&Time1) >> 2));
987 RTTESTI_CHECK(RTTimeSpecGetNano(&ObjInfo2.ModificationTime) / 1000 == RTTimeSpecGetNano(&ObjInfo1.ModificationTime) / 1000);
988
989 /* Benchmark it: */
990 PROFILE_FN(RTFileSetTimes(hFile1, NULL, iIteration & 1 ? &Time1 : &Time2, NULL, NULL), g_nsTestRun, "RTFileSetTimes");
991
992 RTTESTI_CHECK_RC(RTFileClose(hFile1), VINF_SUCCESS);
993}
994
995
996void fsPerfStat(void)
997{
998 RTTestISub("stat");
999 RTFSOBJINFO ObjInfo;
1000
1001 /* Non-existing files. */
1002 RTTESTI_CHECK_RC(RTPathQueryInfoEx(InEmptyDir(RT_STR_TUPLE("no-such-file")),
1003 &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK), VERR_FILE_NOT_FOUND);
1004 RTTESTI_CHECK_RC(RTPathQueryInfoEx(InEmptyDir(RT_STR_TUPLE("no-such-dir" RTPATH_SLASH_STR "no-such-file")),
1005 &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK), FSPERF_VERR_PATH_NOT_FOUND);
1006 RTTESTI_CHECK_RC(RTPathQueryInfoEx(InDir(RT_STR_TUPLE("known-file" RTPATH_SLASH_STR "no-such-file")),
1007 &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK), VERR_PATH_NOT_FOUND);
1008
1009 /* Shallow: */
1010 RTFILE hFile1;
1011 RTTESTI_CHECK_RC_RETV(RTFileOpen(&hFile1, InDir(RT_STR_TUPLE("file3")),
1012 RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE | RTFILE_O_WRITE), VINF_SUCCESS);
1013 RTTESTI_CHECK_RC(RTFileClose(hFile1), VINF_SUCCESS);
1014
1015 PROFILE_FN(RTPathQueryInfoEx(g_szDir, &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK), g_nsTestRun,
1016 "RTPathQueryInfoEx/NOTHING");
1017 PROFILE_FN(RTPathQueryInfoEx(g_szDir, &ObjInfo, RTFSOBJATTRADD_UNIX, RTPATH_F_ON_LINK), g_nsTestRun,
1018 "RTPathQueryInfoEx/UNIX");
1019
1020
1021 /* Deep: */
1022 RTTESTI_CHECK_RC_RETV(RTFileOpen(&hFile1, InDeepDir(RT_STR_TUPLE("file3")),
1023 RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE | RTFILE_O_WRITE), VINF_SUCCESS);
1024 RTTESTI_CHECK_RC(RTFileClose(hFile1), VINF_SUCCESS);
1025
1026 PROFILE_FN(RTPathQueryInfoEx(g_szDeepDir, &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK), g_nsTestRun,
1027 "RTPathQueryInfoEx/deep/NOTHING");
1028 PROFILE_FN(RTPathQueryInfoEx(g_szDeepDir, &ObjInfo, RTFSOBJATTRADD_UNIX, RTPATH_F_ON_LINK), g_nsTestRun,
1029 "RTPathQueryInfoEx/deep/UNIX");
1030
1031 /* Manytree: */
1032 char szPath[RTPATH_MAX];
1033 PROFILE_MANYTREE_FN(szPath, RTPathQueryInfoEx(szPath, &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK),
1034 1, g_nsTestRun, "RTPathQueryInfoEx/manytree/NOTHING");
1035 PROFILE_MANYTREE_FN(szPath, RTPathQueryInfoEx(szPath, &ObjInfo, RTFSOBJATTRADD_UNIX, RTPATH_F_ON_LINK),
1036 1, g_nsTestRun, "RTPathQueryInfoEx/manytree/UNIX");
1037}
1038
1039
1040void fsPerfChmod(void)
1041{
1042 RTTestISub("chmod");
1043
1044 /* Non-existing files. */
1045 RTTESTI_CHECK_RC(RTPathSetMode(InEmptyDir(RT_STR_TUPLE("no-such-file")), 0665),
1046 VERR_FILE_NOT_FOUND);
1047 RTTESTI_CHECK_RC(RTPathSetMode(InEmptyDir(RT_STR_TUPLE("no-such-dir" RTPATH_SLASH_STR "no-such-file")), 0665),
1048 FSPERF_VERR_PATH_NOT_FOUND);
1049 RTTESTI_CHECK_RC(RTPathSetMode(InDir(RT_STR_TUPLE("known-file" RTPATH_SLASH_STR "no-such-file")), 0665), VERR_PATH_NOT_FOUND);
1050
1051 /* Shallow: */
1052 RTFILE hFile1;
1053 RTTESTI_CHECK_RC_RETV(RTFileOpen(&hFile1, InDir(RT_STR_TUPLE("file14")),
1054 RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE | RTFILE_O_WRITE), VINF_SUCCESS);
1055 RTTESTI_CHECK_RC(RTFileClose(hFile1), VINF_SUCCESS);
1056
1057 RTFSOBJINFO ObjInfo;
1058 RTTESTI_CHECK_RC(RTPathQueryInfoEx(g_szDir, &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK), VINF_SUCCESS);
1059 RTFMODE const fEvenMode = (ObjInfo.Attr.fMode & ~RTFS_UNIX_ALL_ACCESS_PERMS) | RTFS_DOS_READONLY | 0400;
1060 RTFMODE const fOddMode = (ObjInfo.Attr.fMode & ~(RTFS_UNIX_ALL_ACCESS_PERMS | RTFS_DOS_READONLY)) | 0640;
1061 PROFILE_FN(RTPathSetMode(g_szDir, iIteration & 1 ? fOddMode : fEvenMode), g_nsTestRun, "RTPathSetMode");
1062 RTPathSetMode(g_szDir, ObjInfo.Attr.fMode);
1063
1064 /* Deep: */
1065 RTTESTI_CHECK_RC_RETV(RTFileOpen(&hFile1, InDeepDir(RT_STR_TUPLE("file14")),
1066 RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE | RTFILE_O_WRITE), VINF_SUCCESS);
1067 RTTESTI_CHECK_RC(RTFileClose(hFile1), VINF_SUCCESS);
1068
1069 PROFILE_FN(RTPathSetMode(g_szDeepDir, iIteration & 1 ? fOddMode : fEvenMode), g_nsTestRun, "RTPathSetMode/deep");
1070 RTPathSetMode(g_szDeepDir, ObjInfo.Attr.fMode);
1071
1072 /* Manytree: */
1073 char szPath[RTPATH_MAX];
1074 PROFILE_MANYTREE_FN(szPath, RTPathSetMode(szPath, iIteration & 1 ? fOddMode : fEvenMode), 1, g_nsTestRun,
1075 "RTPathSetMode/manytree");
1076 DO_MANYTREE_FN(szPath, RTPathSetMode(szPath, ObjInfo.Attr.fMode));
1077}
1078
1079
1080void fsPerfUtimes(void)
1081{
1082 RTTestISub("utimes");
1083
1084 RTTIMESPEC Time1;
1085 RTTimeNow(&Time1);
1086 RTTIMESPEC Time2 = Time1;
1087 RTTimeSpecSubSeconds(&Time2, 3636);
1088
1089 /* Non-existing files. */
1090 RTTESTI_CHECK_RC(RTPathSetTimesEx(InEmptyDir(RT_STR_TUPLE("no-such-file")), NULL, &Time1, NULL, NULL, RTPATH_F_ON_LINK),
1091 VERR_FILE_NOT_FOUND);
1092 RTTESTI_CHECK_RC(RTPathSetTimesEx(InEmptyDir(RT_STR_TUPLE("no-such-dir" RTPATH_SLASH_STR "no-such-file")),
1093 NULL, &Time1, NULL, NULL, RTPATH_F_ON_LINK),
1094 FSPERF_VERR_PATH_NOT_FOUND);
1095 RTTESTI_CHECK_RC(RTPathSetTimesEx(InDir(RT_STR_TUPLE("known-file" RTPATH_SLASH_STR "no-such-file")),
1096 NULL, &Time1, NULL, NULL, RTPATH_F_ON_LINK),
1097 VERR_PATH_NOT_FOUND);
1098
1099 /* Shallow: */
1100 RTFILE hFile1;
1101 RTTESTI_CHECK_RC_RETV(RTFileOpen(&hFile1, InDir(RT_STR_TUPLE("file15")),
1102 RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE | RTFILE_O_WRITE), VINF_SUCCESS);
1103 RTTESTI_CHECK_RC(RTFileClose(hFile1), VINF_SUCCESS);
1104
1105 RTFSOBJINFO ObjInfo0 = {0};
1106 RTTESTI_CHECK_RC(RTPathQueryInfoEx(g_szDir, &ObjInfo0, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK), VINF_SUCCESS);
1107
1108 /* Modify modification time: */
1109 RTTESTI_CHECK_RC(RTPathSetTimesEx(g_szDir, NULL, &Time2, NULL, NULL, RTPATH_F_ON_LINK), VINF_SUCCESS);
1110 RTFSOBJINFO ObjInfo1;
1111 RTTESTI_CHECK_RC(RTPathQueryInfoEx(g_szDir, &ObjInfo1, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK), VINF_SUCCESS);
1112 RTTESTI_CHECK((RTTimeSpecGetSeconds(&ObjInfo1.ModificationTime) >> 2) == (RTTimeSpecGetSeconds(&Time2) >> 2));
1113 RTTESTI_CHECK(RTTimeSpecGetNano(&ObjInfo1.AccessTime) / 1000 == RTTimeSpecGetNano(&ObjInfo0.AccessTime) / 1000 /* posix timeval */);
1114
1115 /* Modify access time: */
1116 RTTESTI_CHECK_RC(RTPathSetTimesEx(g_szDir, &Time1, NULL, NULL, NULL, RTPATH_F_ON_LINK), VINF_SUCCESS);
1117 RTFSOBJINFO ObjInfo2 = {0};
1118 RTTESTI_CHECK_RC(RTPathQueryInfoEx(g_szDir, &ObjInfo2, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK), VINF_SUCCESS);
1119 RTTESTI_CHECK((RTTimeSpecGetSeconds(&ObjInfo2.AccessTime) >> 2) == (RTTimeSpecGetSeconds(&Time1) >> 2));
1120 RTTESTI_CHECK(RTTimeSpecGetNano(&ObjInfo2.ModificationTime) / 1000 == RTTimeSpecGetNano(&ObjInfo1.ModificationTime) / 1000 /* posix timeval */);
1121
1122 /* Profile shallow: */
1123 PROFILE_FN(RTPathSetTimesEx(g_szDir, iIteration & 1 ? &Time1 : &Time2, iIteration & 1 ? &Time2 : &Time1,
1124 NULL, NULL, RTPATH_F_ON_LINK),
1125 g_nsTestRun, "RTPathSetTimesEx");
1126
1127 /* Deep: */
1128 RTTESTI_CHECK_RC_RETV(RTFileOpen(&hFile1, InDeepDir(RT_STR_TUPLE("file15")),
1129 RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE | RTFILE_O_WRITE), VINF_SUCCESS);
1130 RTTESTI_CHECK_RC(RTFileClose(hFile1), VINF_SUCCESS);
1131
1132 PROFILE_FN(RTPathSetTimesEx(g_szDeepDir, iIteration & 1 ? &Time1 : &Time2, iIteration & 1 ? &Time2 : &Time1,
1133 NULL, NULL, RTPATH_F_ON_LINK),
1134 g_nsTestRun, "RTPathSetTimesEx/deep");
1135
1136 /* Manytree: */
1137 char szPath[RTPATH_MAX];
1138 PROFILE_MANYTREE_FN(szPath, RTPathSetTimesEx(szPath, iIteration & 1 ? &Time1 : &Time2, iIteration & 1 ? &Time2 : &Time1,
1139 NULL, NULL, RTPATH_F_ON_LINK),
1140 1, g_nsTestRun, "RTPathSetTimesEx/manytree");
1141}
1142
1143
1144DECL_FORCE_INLINE(int) fsPerfRenameMany(const char *pszFile, uint32_t iIteration)
1145{
1146 char szRenamed[RTPATH_MAX];
1147 strcat(strcpy(szRenamed, pszFile), "-renamed");
1148 if (!(iIteration & 1))
1149 return RTPathRename(pszFile, szRenamed, 0);
1150 return RTPathRename(szRenamed, pszFile, 0);
1151}
1152
1153
1154void fsPerfRename(void)
1155{
1156 RTTestISub("rename");
1157 char szPath[RTPATH_MAX];
1158
1159/** @todo rename directories too! */
1160/** @todo check overwriting files and directoris (empty ones should work on
1161 * unix). */
1162
1163 /* Non-existing files. */
1164 strcpy(szPath, InEmptyDir(RT_STR_TUPLE("other-no-such-file")));
1165 RTTESTI_CHECK_RC(RTPathRename(InEmptyDir(RT_STR_TUPLE("no-such-file")), szPath, 0), VERR_FILE_NOT_FOUND);
1166 strcpy(szPath, InEmptyDir(RT_STR_TUPLE("no-such-dir" RTPATH_SLASH_STR "other-no-such-file")));
1167 RTTESTI_CHECK_RC(RTPathRename(InEmptyDir(RT_STR_TUPLE("no-such-dir" RTPATH_SLASH_STR "no-such-file")), szPath, 0),
1168 FSPERF_VERR_PATH_NOT_FOUND);
1169 strcpy(szPath, InEmptyDir(RT_STR_TUPLE("other-no-such-file")));
1170 RTTESTI_CHECK_RC(RTPathRename(InDir(RT_STR_TUPLE("known-file" RTPATH_SLASH_STR "no-such-file")), szPath, 0), VERR_PATH_NOT_FOUND);
1171
1172 RTFILE hFile1;
1173 RTTESTI_CHECK_RC_RETV(RTFileOpen(&hFile1, InDir(RT_STR_TUPLE("file16")),
1174 RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE | RTFILE_O_WRITE), VINF_SUCCESS);
1175 RTTESTI_CHECK_RC(RTFileClose(hFile1), VINF_SUCCESS);
1176 strcat(strcpy(szPath, g_szDir), "-no-such-dir" RTPATH_SLASH_STR "file16");
1177 RTTESTI_CHECK_RC(RTPathRename(szPath, g_szDir, 0), FSPERF_VERR_PATH_NOT_FOUND);
1178 RTTESTI_CHECK_RC(RTPathRename(g_szDir, szPath, 0), FSPERF_VERR_PATH_NOT_FOUND);
1179
1180 /* Shallow: */
1181 strcat(strcpy(szPath, g_szDir), "-other");
1182 PROFILE_FN(RTPathRename(iIteration & 1 ? szPath : g_szDir, iIteration & 1 ? g_szDir : szPath, 0), g_nsTestRun, "RTPathRename");
1183
1184 /* Deep: */
1185 RTTESTI_CHECK_RC_RETV(RTFileOpen(&hFile1, InDeepDir(RT_STR_TUPLE("file15")),
1186 RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE | RTFILE_O_WRITE), VINF_SUCCESS);
1187 RTTESTI_CHECK_RC(RTFileClose(hFile1), VINF_SUCCESS);
1188
1189 strcat(strcpy(szPath, g_szDeepDir), "-other");
1190 PROFILE_FN(RTPathRename(iIteration & 1 ? szPath : g_szDeepDir, iIteration & 1 ? g_szDeepDir : szPath, 0),
1191 g_nsTestRun, "RTPathRename/deep");
1192
1193 /* Manytree: */
1194 PROFILE_MANYTREE_FN(szPath, fsPerfRenameMany(szPath, iIteration), 2, g_nsTestRun, "RTPathRename/manytree");
1195}
1196
1197
1198DECL_FORCE_INLINE(int) fsPerfOpenClose(const char *pszDir)
1199{
1200 RTDIR hDir;
1201 RTTESTI_CHECK_RC_RET(RTDirOpen(&hDir, pszDir), VINF_SUCCESS, rcCheck);
1202 RTTESTI_CHECK_RC(RTDirClose(hDir), VINF_SUCCESS);
1203 return VINF_SUCCESS;
1204}
1205
1206
1207void vsPerfDirOpen(void)
1208{
1209 RTTestISub("dir open");
1210 RTDIR hDir;
1211
1212 /*
1213 * Non-existing files.
1214 */
1215 RTTESTI_CHECK_RC(RTDirOpen(&hDir, InEmptyDir(RT_STR_TUPLE("no-such-file"))), VERR_FILE_NOT_FOUND);
1216 RTTESTI_CHECK_RC(RTDirOpen(&hDir, InEmptyDir(RT_STR_TUPLE("no-such-dir" RTPATH_SLASH_STR "no-such-file"))), FSPERF_VERR_PATH_NOT_FOUND);
1217 RTTESTI_CHECK_RC(RTDirOpen(&hDir, InDir(RT_STR_TUPLE("known-file" RTPATH_SLASH_STR "no-such-file"))), VERR_PATH_NOT_FOUND);
1218
1219 /*
1220 * Check that open + close works.
1221 */
1222 g_szEmptyDir[g_cchEmptyDir] = '\0';
1223 RTTESTI_CHECK_RC_RETV(RTDirOpen(&hDir, g_szEmptyDir), VINF_SUCCESS);
1224 RTTESTI_CHECK_RC(RTDirClose(hDir), VINF_SUCCESS);
1225
1226
1227 /*
1228 * Profile empty dir and dir with many files.
1229 */
1230 g_szEmptyDir[g_cchEmptyDir] = '\0';
1231 PROFILE_FN(fsPerfOpenClose(g_szEmptyDir), g_nsTestRun, "RTDirOpen/Close empty");
1232 if (g_fManyFiles)
1233 {
1234 InDir(RT_STR_TUPLE("manyfiles"));
1235 PROFILE_FN(fsPerfOpenClose(g_szDir), g_nsTestRun, "RTDirOpen/Close manyfiles");
1236 }
1237}
1238
1239
1240DECL_FORCE_INLINE(int) fsPerfEnumEmpty(void)
1241{
1242 RTDIR hDir;
1243 g_szEmptyDir[g_cchEmptyDir] = '\0';
1244 RTTESTI_CHECK_RC_RET(RTDirOpen(&hDir, g_szEmptyDir), VINF_SUCCESS, rcCheck);
1245
1246 RTDIRENTRY Entry;
1247 RTTESTI_CHECK_RC(RTDirRead(hDir, &Entry, NULL), VINF_SUCCESS);
1248 RTTESTI_CHECK_RC(RTDirRead(hDir, &Entry, NULL), VINF_SUCCESS);
1249 RTTESTI_CHECK_RC(RTDirRead(hDir, &Entry, NULL), VERR_NO_MORE_FILES);
1250
1251 RTTESTI_CHECK_RC(RTDirClose(hDir), VINF_SUCCESS);
1252 return VINF_SUCCESS;
1253}
1254
1255
1256DECL_FORCE_INLINE(int) fsPerfEnumManyFiles(void)
1257{
1258 RTDIR hDir;
1259 RTTESTI_CHECK_RC_RET(RTDirOpen(&hDir, InDir(RT_STR_TUPLE("manyfiles"))), VINF_SUCCESS, rcCheck);
1260 uint32_t cLeft = g_cManyFiles + 2;
1261 for (;;)
1262 {
1263 RTDIRENTRY Entry;
1264 if (cLeft > 0)
1265 RTTESTI_CHECK_RC_BREAK(RTDirRead(hDir, &Entry, NULL), VINF_SUCCESS);
1266 else
1267 {
1268 RTTESTI_CHECK_RC(RTDirRead(hDir, &Entry, NULL), VERR_NO_MORE_FILES);
1269 break;
1270 }
1271 cLeft--;
1272 }
1273 RTTESTI_CHECK_RC(RTDirClose(hDir), VINF_SUCCESS);
1274 return VINF_SUCCESS;
1275}
1276
1277
1278void vsPerfDirEnum(void)
1279{
1280 RTTestISub("dir enum");
1281 RTDIR hDir;
1282
1283 /*
1284 * The empty directory.
1285 */
1286 g_szEmptyDir[g_cchEmptyDir] = '\0';
1287 RTTESTI_CHECK_RC_RETV(RTDirOpen(&hDir, g_szEmptyDir), VINF_SUCCESS);
1288
1289 uint32_t fDots = 0;
1290 RTDIRENTRY Entry;
1291 RTTESTI_CHECK_RC(RTDirRead(hDir, &Entry, NULL), VINF_SUCCESS);
1292 RTTESTI_CHECK(RTDirEntryIsStdDotLink(&Entry));
1293 fDots |= RT_BIT_32(Entry.cbName - 1);
1294
1295 RTTESTI_CHECK_RC(RTDirRead(hDir, &Entry, NULL), VINF_SUCCESS);
1296 RTTESTI_CHECK(RTDirEntryIsStdDotLink(&Entry));
1297 fDots |= RT_BIT_32(Entry.cbName - 1);
1298 RTTESTI_CHECK(fDots == 3);
1299
1300 RTTESTI_CHECK_RC(RTDirRead(hDir, &Entry, NULL), VERR_NO_MORE_FILES);
1301
1302 RTTESTI_CHECK_RC(RTDirClose(hDir), VINF_SUCCESS);
1303
1304 /*
1305 * The directory with many files in it.
1306 */
1307 if (g_fManyFiles)
1308 {
1309 fDots = 0;
1310 uint32_t const cBitmap = RT_ALIGN_32(g_cManyFiles, 64);
1311 void *pvBitmap = alloca(cBitmap / 8);
1312 RT_BZERO(pvBitmap, cBitmap / 8);
1313 for (uint32_t i = g_cManyFiles; i < cBitmap; i++)
1314 ASMBitSet(pvBitmap, i);
1315
1316 uint32_t cFiles = 0;
1317 RTTESTI_CHECK_RC_RETV(RTDirOpen(&hDir, InDir(RT_STR_TUPLE("manyfiles"))), VINF_SUCCESS);
1318 for (;;)
1319 {
1320 int rc = RTDirRead(hDir, &Entry, NULL);
1321 if (rc == VINF_SUCCESS)
1322 {
1323 if (Entry.szName[0] == '.')
1324 {
1325 if (Entry.szName[1] == '.')
1326 {
1327 RTTESTI_CHECK(!(fDots & 2));
1328 fDots |= 2;
1329 }
1330 else
1331 {
1332 RTTESTI_CHECK(Entry.szName[1] == '\0');
1333 RTTESTI_CHECK(!(fDots & 1));
1334 fDots |= 1;
1335 }
1336 }
1337 else
1338 {
1339 uint32_t iFile = UINT32_MAX;
1340 RTTESTI_CHECK_RC(RTStrToUInt32Full(Entry.szName, 10, &iFile), VINF_SUCCESS);
1341 if ( iFile < g_cManyFiles
1342 && !ASMBitTest(pvBitmap, iFile))
1343 {
1344 ASMBitSet(pvBitmap, iFile);
1345 cFiles++;
1346 }
1347 else
1348 RTTestFailed(g_hTest, "line %u: iFile=%u g_cManyFiles=%u\n", __LINE__, iFile, g_cManyFiles);
1349 }
1350 }
1351 else if (rc == VERR_NO_MORE_FILES)
1352 break;
1353 else
1354 {
1355 RTTestFailed(g_hTest, "RTDirRead failed enumerating manyfiles: %Rrc\n", rc);
1356 RTDirClose(hDir);
1357 return;
1358 }
1359 }
1360 RTTESTI_CHECK_RC(RTDirClose(hDir), VINF_SUCCESS);
1361 RTTESTI_CHECK(fDots == 3);
1362 RTTESTI_CHECK(cFiles == g_cManyFiles);
1363 RTTESTI_CHECK(ASMMemIsAllU8(pvBitmap, cBitmap / 8, 0xff));
1364 }
1365
1366 /*
1367 * Profile.
1368 */
1369 PROFILE_FN(fsPerfEnumEmpty(),g_nsTestRun, "RTDirOpen/Read/Close empty");
1370 if (g_fManyFiles)
1371 PROFILE_FN(fsPerfEnumManyFiles(), g_nsTestRun, "RTDirOpen/Read/Close manyfiles");
1372}
1373
1374
1375void fsPerfMkRmDir(void)
1376{
1377 RTTestISub("mkdir/rmdir");
1378
1379 /* Non-existing directories: */
1380 RTTESTI_CHECK_RC(RTDirRemove(InEmptyDir(RT_STR_TUPLE("no-such-dir"))), VERR_FILE_NOT_FOUND);
1381 RTTESTI_CHECK_RC(RTDirRemove(InEmptyDir(RT_STR_TUPLE("no-such-dir" RTPATH_SLASH_STR "no-such-file"))), FSPERF_VERR_PATH_NOT_FOUND);
1382 RTTESTI_CHECK_RC(RTDirRemove(InDir(RT_STR_TUPLE("known-file" RTPATH_SLASH_STR "no-such-file"))), VERR_PATH_NOT_FOUND);
1383 RTTESTI_CHECK_RC(RTDirCreate(InEmptyDir(RT_STR_TUPLE("no-such-dir" RTPATH_SLASH_STR "no-such-file")), 0755, 0), FSPERF_VERR_PATH_NOT_FOUND);
1384 RTTESTI_CHECK_RC(RTDirCreate(InDir(RT_STR_TUPLE("known-file" RTPATH_SLASH_STR "no-such-file")), 0755, 0), VERR_PATH_NOT_FOUND);
1385
1386 /** @todo check what happens if non-final path component isn't a directory. unix
1387 * should return ENOTDIR and IPRT translates that to VERR_PATH_NOT_FOUND.
1388 * Curious what happens on windows. */
1389
1390 /* Already existing directories and files: */
1391 RTTESTI_CHECK_RC(RTDirCreate(InEmptyDir(RT_STR_TUPLE(".")), 0755, 0), VERR_ALREADY_EXISTS);
1392 RTTESTI_CHECK_RC(RTDirCreate(InEmptyDir(RT_STR_TUPLE("..")), 0755, 0), VERR_ALREADY_EXISTS);
1393
1394 /* Remove directory with subdirectories: */
1395#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
1396 RTTESTI_CHECK_RC(RTDirRemove(InDir(RT_STR_TUPLE("."))), VERR_DIR_NOT_EMPTY);
1397#else
1398 RTTESTI_CHECK_RC(RTDirRemove(InDir(RT_STR_TUPLE("."))), VERR_INVALID_PARAMETER); /* EINVAL for '.' */
1399#endif
1400#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
1401 int rc = RTDirRemove(InDir(RT_STR_TUPLE("..")));
1402# ifdef RT_OS_WINDOWS
1403 if (rc != VERR_DIR_NOT_EMPTY /*ntfs root*/ && rc != VERR_SHARING_VIOLATION /*ntfs weird*/)
1404 RTTestIFailed("RTDirRemove(%s) -> %Rrc, expected VERR_DIR_NOT_EMPTY or VERR_SHARING_VIOLATION", g_szDir, rc);
1405# else
1406 if (rc != VERR_DIR_NOT_EMPTY && rc != VERR_RESOURCE_BUSY /*IPRT/kLIBC fun*/)
1407 RTTestIFailed("RTDirRemove(%s) -> %Rrc, expected VERR_DIR_NOT_EMPTY or VERR_RESOURCE_BUSY", g_szDir, rc);
1408
1409 APIRET orc;
1410 RTTESTI_CHECK_MSG((orc = DosDelete((PCSZ)InEmptyDir(RT_STR_TUPLE(".")))) == ERROR_ACCESS_DENIED,
1411 ("DosDelete(%s) -> %u, expected %u\n", g_szEmptyDir, orc, ERROR_ACCESS_DENIED));
1412 RTTESTI_CHECK_MSG((orc = DosDelete((PCSZ)InEmptyDir(RT_STR_TUPLE("..")))) == ERROR_ACCESS_DENIED,
1413 ("DosDelete(%s) -> %u, expected %u\n", g_szEmptyDir, orc, ERROR_ACCESS_DENIED));
1414 RTTESTI_CHECK_MSG((orc = DosDelete((PCSZ)InEmptyDir(RT_STR_TUPLE("")))) == ERROR_PATH_NOT_FOUND, /* a little weird (fsrouter) */
1415 ("DosDelete(%s) -> %u, expected %u\n", g_szEmptyDir, orc, ERROR_PATH_NOT_FOUND));
1416
1417# endif
1418#else
1419 RTTESTI_CHECK_RC(RTDirRemove(InDir(RT_STR_TUPLE(".."))), VERR_DIR_NOT_EMPTY);
1420#endif
1421 RTTESTI_CHECK_RC(RTDirRemove(InDir(RT_STR_TUPLE(""))), VERR_DIR_NOT_EMPTY);
1422
1423 /* Create a directory and remove it: */
1424 RTTESTI_CHECK_RC(RTDirCreate(InDir(RT_STR_TUPLE("subdir-1")), 0755, 0), VINF_SUCCESS);
1425 RTTESTI_CHECK_RC(RTDirRemove(g_szDir), VINF_SUCCESS);
1426
1427 /* Create a file and try remove it or create a directory with the same name: */
1428 RTFILE hFile1;
1429 RTTESTI_CHECK_RC_RETV(RTFileOpen(&hFile1, InDir(RT_STR_TUPLE("file18")),
1430 RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE | RTFILE_O_WRITE), VINF_SUCCESS);
1431 RTTESTI_CHECK_RC(RTFileClose(hFile1), VINF_SUCCESS);
1432 RTTESTI_CHECK_RC(RTDirRemove(g_szDir), VERR_NOT_A_DIRECTORY);
1433 RTTESTI_CHECK_RC(RTDirCreate(g_szDir, 0755, 0), VERR_ALREADY_EXISTS);
1434 RTTESTI_CHECK_RC(RTDirCreate(InDir(RT_STR_TUPLE("file18" RTPATH_SLASH_STR "subdir")), 0755, 0), VERR_PATH_NOT_FOUND);
1435
1436 /*
1437 * Profile alternately creating and removing a bunch of directories.
1438 */
1439 RTTESTI_CHECK_RC_RETV(RTDirCreate(InDir(RT_STR_TUPLE("subdir-2")), 0755, 0), VINF_SUCCESS);
1440 size_t cchDir = strlen(g_szDir);
1441 g_szDir[cchDir++] = RTPATH_SLASH;
1442 g_szDir[cchDir++] = 's';
1443
1444 uint32_t cCreated = 0;
1445 uint64_t nsCreate = 0;
1446 uint64_t nsRemove = 0;
1447 for (;;)
1448 {
1449 /* Create a bunch: */
1450 uint64_t nsStart = RTTimeNanoTS();
1451 for (uint32_t i = 0; i < 998; i++)
1452 {
1453 RTStrFormatU32(&g_szDir[cchDir], sizeof(g_szDir) - cchDir, i, 10, 3, 3, RTSTR_F_ZEROPAD);
1454 RTTESTI_CHECK_RC_RETV(RTDirCreate(g_szDir, 0755, 0), VINF_SUCCESS);
1455 }
1456 nsCreate += RTTimeNanoTS() - nsStart;
1457 cCreated += 998;
1458
1459 /* Remove the bunch: */
1460 nsStart = RTTimeNanoTS();
1461 for (uint32_t i = 0; i < 998; i++)
1462 {
1463 RTStrFormatU32(&g_szDir[cchDir], sizeof(g_szDir) - cchDir, i, 10, 3, 3, RTSTR_F_ZEROPAD);
1464 RTTESTI_CHECK_RC_RETV(RTDirRemove(g_szDir), VINF_SUCCESS);
1465 }
1466 nsRemove = RTTimeNanoTS() - nsStart;
1467
1468 /* Check if we got time for another round: */
1469 if ( ( nsRemove >= g_nsTestRun
1470 && nsCreate >= g_nsTestRun)
1471 || nsCreate + nsRemove >= g_nsTestRun * 3)
1472 break;
1473 }
1474 RTTestIValue("RTDirCreate", nsCreate / cCreated, RTTESTUNIT_NS_PER_OCCURRENCE);
1475 RTTestIValue("RTDirRemove", nsRemove / cCreated, RTTESTUNIT_NS_PER_OCCURRENCE);
1476}
1477
1478
1479void fsPerfStatVfs(void)
1480{
1481 RTTestISub("statvfs");
1482
1483 g_szEmptyDir[g_cchEmptyDir] = '\0';
1484 RTFOFF cbTotal;
1485 RTFOFF cbFree;
1486 uint32_t cbBlock;
1487 uint32_t cbSector;
1488 RTTESTI_CHECK_RC(RTFsQuerySizes(g_szEmptyDir, &cbTotal, &cbFree, &cbBlock, &cbSector), VINF_SUCCESS);
1489
1490 uint32_t uSerial;
1491 RTTESTI_CHECK_RC(RTFsQuerySerial(g_szEmptyDir, &uSerial), VINF_SUCCESS);
1492
1493 RTFSPROPERTIES Props;
1494 RTTESTI_CHECK_RC(RTFsQueryProperties(g_szEmptyDir, &Props), VINF_SUCCESS);
1495
1496 RTFSTYPE enmType;
1497 RTTESTI_CHECK_RC(RTFsQueryType(g_szEmptyDir, &enmType), VINF_SUCCESS);
1498
1499 g_szDeepDir[g_cchDeepDir] = '\0';
1500 PROFILE_FN(RTFsQuerySizes(g_szEmptyDir, &cbTotal, &cbFree, &cbBlock, &cbSector), g_nsTestRun, "RTFsQuerySize/empty");
1501 PROFILE_FN(RTFsQuerySizes(g_szDeepDir, &cbTotal, &cbFree, &cbBlock, &cbSector), g_nsTestRun, "RTFsQuerySize/deep");
1502}
1503
1504
1505void fsPerfRm(void)
1506{
1507 RTTestISub("rm");
1508
1509 /* Non-existing files. */
1510 RTTESTI_CHECK_RC(RTFileDelete(InEmptyDir(RT_STR_TUPLE("no-such-file"))), VERR_FILE_NOT_FOUND);
1511 RTTESTI_CHECK_RC(RTFileDelete(InEmptyDir(RT_STR_TUPLE("no-such-dir" RTPATH_SLASH_STR "no-such-file"))), FSPERF_VERR_PATH_NOT_FOUND);
1512 RTTESTI_CHECK_RC(RTFileDelete(InDir(RT_STR_TUPLE("known-file" RTPATH_SLASH_STR "no-such-file"))), VERR_PATH_NOT_FOUND);
1513
1514 /* Directories: */
1515#if defined(RT_OS_WINDOWS)
1516 RTTESTI_CHECK_RC(RTFileDelete(InEmptyDir(RT_STR_TUPLE("."))), VERR_ACCESS_DENIED);
1517 RTTESTI_CHECK_RC(RTFileDelete(InEmptyDir(RT_STR_TUPLE(".."))), VERR_ACCESS_DENIED);
1518 RTTESTI_CHECK_RC(RTFileDelete(InEmptyDir(RT_STR_TUPLE(""))), VERR_ACCESS_DENIED);
1519#elif defined(RT_OS_DARWIN) /* unlink() on xnu 16.7.0 is behaviour totally werid: */
1520 RTTESTI_CHECK_RC(RTFileDelete(InEmptyDir(RT_STR_TUPLE("."))), VERR_INVALID_PARAMETER);
1521 RTTESTI_CHECK_RC(RTFileDelete(InEmptyDir(RT_STR_TUPLE(".."))), VINF_SUCCESS /*WTF?!?*/);
1522 RTTESTI_CHECK_RC(RTFileDelete(InEmptyDir(RT_STR_TUPLE(""))), VERR_ACCESS_DENIED);
1523#elif defined(RT_OS_OS2) /* OS/2 has a busted unlink, it think it should remove directories too. */
1524 RTTESTI_CHECK_RC(RTFileDelete(InDir(RT_STR_TUPLE("."))), VERR_DIR_NOT_EMPTY);
1525 int rc = RTFileDelete(InDir(RT_STR_TUPLE("..")));
1526 if (rc != VERR_DIR_NOT_EMPTY && rc != VERR_FILE_NOT_FOUND && rc != VERR_RESOURCE_BUSY)
1527 RTTestIFailed("RTFileDelete(%s) -> %Rrc, expected VERR_DIR_NOT_EMPTY or VERR_FILE_NOT_FOUND or VERR_RESOURCE_BUSY", g_szDir, rc);
1528 RTTESTI_CHECK_RC(RTFileDelete(InDir(RT_STR_TUPLE(""))), VERR_DIR_NOT_EMPTY);
1529 APIRET orc;
1530 RTTESTI_CHECK_MSG((orc = DosDelete((PCSZ)InEmptyDir(RT_STR_TUPLE(".")))) == ERROR_ACCESS_DENIED,
1531 ("DosDelete(%s) -> %u, expected %u\n", g_szEmptyDir, orc, ERROR_ACCESS_DENIED));
1532 RTTESTI_CHECK_MSG((orc = DosDelete((PCSZ)InEmptyDir(RT_STR_TUPLE("..")))) == ERROR_ACCESS_DENIED,
1533 ("DosDelete(%s) -> %u, expected %u\n", g_szEmptyDir, orc, ERROR_ACCESS_DENIED));
1534 RTTESTI_CHECK_MSG((orc = DosDelete((PCSZ)InEmptyDir(RT_STR_TUPLE("")))) == ERROR_PATH_NOT_FOUND,
1535 ("DosDelete(%s) -> %u, expected %u\n", g_szEmptyDir, orc, ERROR_PATH_NOT_FOUND)); /* hpfs+jfs; weird. */
1536
1537#else
1538 RTTESTI_CHECK_RC(RTFileDelete(InEmptyDir(RT_STR_TUPLE("."))), VERR_IS_A_DIRECTORY);
1539 RTTESTI_CHECK_RC(RTFileDelete(InEmptyDir(RT_STR_TUPLE(".."))), VERR_IS_A_DIRECTORY);
1540 RTTESTI_CHECK_RC(RTFileDelete(InEmptyDir(RT_STR_TUPLE(""))), VERR_IS_A_DIRECTORY);
1541#endif
1542
1543 /* Shallow: */
1544 RTFILE hFile1;
1545 RTTESTI_CHECK_RC_RETV(RTFileOpen(&hFile1, InDir(RT_STR_TUPLE("file19")),
1546 RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE | RTFILE_O_WRITE), VINF_SUCCESS);
1547 RTTESTI_CHECK_RC(RTFileClose(hFile1), VINF_SUCCESS);
1548 RTTESTI_CHECK_RC(RTFileDelete(g_szDir), VINF_SUCCESS);
1549 RTTESTI_CHECK_RC(RTFileDelete(g_szDir), VERR_FILE_NOT_FOUND);
1550
1551 if (g_fManyFiles)
1552 {
1553 /*
1554 * Profile the deletion of the manyfiles content.
1555 */
1556 {
1557 InDir(RT_STR_TUPLE("manyfiles" RTPATH_SLASH_STR));
1558 size_t const offFilename = strlen(g_szDir);
1559 fsPerfYield();
1560 uint64_t const nsStart = RTTimeNanoTS();
1561 for (uint32_t i = 0; i < g_cManyFiles; i++)
1562 {
1563 RTStrFormatU32(&g_szDir[offFilename], sizeof(g_szDir) - offFilename, i, 10, 5, 5, RTSTR_F_ZEROPAD);
1564 RTTESTI_CHECK_RC_RETV(RTFileDelete(g_szDir), VINF_SUCCESS);
1565 }
1566 uint64_t const cNsElapsed = RTTimeNanoTS() - nsStart;
1567 RTTestIValueF(cNsElapsed, RTTESTUNIT_NS, "Deleted %u empty files from a single directory", g_cManyFiles);
1568 RTTestIValueF(cNsElapsed / g_cManyFiles, RTTESTUNIT_NS_PER_OCCURRENCE, "Delete file (single dir)");
1569 }
1570
1571 /*
1572 * Ditto for the manytree.
1573 */
1574 {
1575 char szPath[RTPATH_MAX];
1576 uint64_t const nsStart = RTTimeNanoTS();
1577 DO_MANYTREE_FN(szPath, RTTESTI_CHECK_RC_RETV(RTFileDelete(szPath), VINF_SUCCESS));
1578 uint64_t const cNsElapsed = RTTimeNanoTS() - nsStart;
1579 RTTestIValueF(cNsElapsed, RTTESTUNIT_NS, "Deleted %u empty files in tree", g_cManyTreeFiles);
1580 RTTestIValueF(cNsElapsed / g_cManyTreeFiles, RTTESTUNIT_NS_PER_OCCURRENCE, "Delete file (tree)");
1581 }
1582 }
1583}
1584
1585
1586void fsPerfChSize(void)
1587{
1588 RTTestISub("chsize");
1589
1590 /*
1591 * We need some free space to perform this test.
1592 */
1593 g_szDir[g_cchDir] = '\0';
1594 RTFOFF cbFree = 0;
1595 RTTESTI_CHECK_RC_RETV(RTFsQuerySizes(g_szDir, NULL, &cbFree, NULL, NULL), VINF_SUCCESS);
1596 if (cbFree < _1M)
1597 {
1598 RTTestSkipped(g_hTest, "Insufficent free space: %'RU64 bytes, requires >= 1MB", cbFree);
1599 return;
1600 }
1601
1602 /*
1603 * Create a file and play around with it's size.
1604 * We let the current file position follow the end position as we make changes.
1605 */
1606 RTFILE hFile1;
1607 RTTESTI_CHECK_RC_RETV(RTFileOpen(&hFile1, InDir(RT_STR_TUPLE("file20")),
1608 RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE | RTFILE_O_READWRITE), VINF_SUCCESS);
1609 uint64_t cbFile = UINT64_MAX;
1610 RTTESTI_CHECK_RC(RTFileGetSize(hFile1, &cbFile), VINF_SUCCESS);
1611 RTTESTI_CHECK(cbFile == 0);
1612
1613 uint8_t abBuf[4096];
1614 static uint64_t const s_acbChanges[] =
1615 {
1616 1023, 1024, 1024, 1025, 8192, 11111, _1M, _8M, _8M,
1617 _4M, _2M + 1, _1M - 1, 65537, 65536, 32768, 8000, 7999, 7998, 1024, 1, 0
1618 };
1619 uint64_t cbOld = 0;
1620 for (unsigned i = 0; i < RT_ELEMENTS(s_acbChanges); i++)
1621 {
1622 uint64_t cbNew = s_acbChanges[i];
1623 if (cbNew + _64K >= (uint64_t)cbFree)
1624 continue;
1625
1626 RTTESTI_CHECK_RC(RTFileSetSize(hFile1, cbNew), VINF_SUCCESS);
1627 RTTESTI_CHECK_RC(RTFileGetSize(hFile1, &cbFile), VINF_SUCCESS);
1628 RTTESTI_CHECK_MSG(cbFile == cbNew, ("cbFile=%#RX64 cbNew=%#RX64\n", cbFile, cbNew));
1629
1630 if (cbNew > cbOld)
1631 {
1632 /* Check that the extension is all zeroed: */
1633 uint64_t cbLeft = cbNew - cbOld;
1634 while (cbLeft > 0)
1635 {
1636 memset(abBuf, 0xff, sizeof(abBuf));
1637 size_t cbToRead = sizeof(abBuf);
1638 if (cbToRead > cbLeft)
1639 cbToRead = (size_t)cbLeft;
1640 RTTESTI_CHECK_RC(RTFileRead(hFile1, abBuf, cbToRead, NULL), VINF_SUCCESS);
1641 RTTESTI_CHECK(ASMMemIsZero(abBuf, cbToRead));
1642 cbLeft -= cbToRead;
1643 }
1644 }
1645 else
1646 {
1647 /* Check that reading fails with EOF because current position is now beyond the end: */
1648 RTTESTI_CHECK_RC(RTFileRead(hFile1, abBuf, 1, NULL), VERR_EOF);
1649
1650 /* Keep current position at the end of the file: */
1651 RTTESTI_CHECK_RC(RTFileSeek(hFile1, cbNew, RTFILE_SEEK_BEGIN, NULL), VINF_SUCCESS);
1652 }
1653 cbOld = cbNew;
1654 }
1655
1656 /*
1657 * Profile just the file setting operation itself, keeping the changes within
1658 * an allocation unit to avoid needing to adjust the actual (host) FS allocation.
1659 * ASSUMES allocation unit >= 512 and power of two.
1660 */
1661 RTTESTI_CHECK_RC(RTFileSetSize(hFile1, _64K), VINF_SUCCESS);
1662 PROFILE_FN(RTFileSetSize(hFile1, _64K - (iIteration & 255) - 128), g_nsTestRun, "RTFileSetSize/noalloc");
1663
1664 RTTESTI_CHECK_RC(RTFileSetSize(hFile1, 0), VINF_SUCCESS);
1665 RTTESTI_CHECK_RC(RTFileClose(hFile1), VINF_SUCCESS);
1666 RTTESTI_CHECK_RC(RTFileDelete(g_szDir), VINF_SUCCESS);
1667}
1668
1669
1670int fsPerfIoPrepFile(RTFILE hFile1, uint64_t cbFile, uint8_t **ppbFree)
1671{
1672 /*
1673 * Seek to the end - 4K and write the last 4K.
1674 * This should have the effect of filling the whole file with zeros.
1675 */
1676 RTTESTI_CHECK_RC_RET(RTFileSeek(hFile1, cbFile - _4K, RTFILE_SEEK_BEGIN, NULL), VINF_SUCCESS, rcCheck);
1677 RTTESTI_CHECK_RC_RET(RTFileWrite(hFile1, g_abRTZero4K, _4K, NULL), VINF_SUCCESS, rcCheck);
1678
1679 /*
1680 * Check that the space we searched across actually is zero filled.
1681 */
1682 RTTESTI_CHECK_RC_RET(RTFileSeek(hFile1, 0, RTFILE_SEEK_BEGIN, NULL), VINF_SUCCESS, rcCheck);
1683 size_t cbBuf = _1M;
1684 uint8_t *pbBuf = *ppbFree = (uint8_t *)RTMemAlloc(cbBuf);
1685 RTTESTI_CHECK_RET(pbBuf != NULL, VERR_NO_MEMORY);
1686 uint64_t cbLeft = cbFile;
1687 while (cbLeft > 0)
1688 {
1689 size_t cbToRead = cbBuf;
1690 if (cbToRead > cbLeft)
1691 cbToRead = (size_t)cbLeft;
1692 pbBuf[cbToRead] = 0xff;
1693
1694 RTTESTI_CHECK_RC_RET(RTFileRead(hFile1, pbBuf, cbToRead, NULL), VINF_SUCCESS, rcCheck);
1695 RTTESTI_CHECK_RET(ASMMemIsZero(pbBuf, cbToRead), VERR_MISMATCH);
1696
1697 cbLeft -= cbToRead;
1698 }
1699
1700 /*
1701 * Fill the file with 0xf6 and insert offset markers with 1KB intervals.
1702 */
1703 RTTESTI_CHECK_RC_RET(RTFileSeek(hFile1, 0, RTFILE_SEEK_BEGIN, NULL), VINF_SUCCESS, rcCheck);
1704 memset(pbBuf, 0xf6, cbBuf);
1705 cbLeft = cbFile;
1706 uint64_t off = 0;
1707 while (cbLeft > 0)
1708 {
1709 Assert(!(off & (_1K - 1)));
1710 Assert(!(cbBuf & (_1K - 1)));
1711 for (size_t offBuf = 0; offBuf < cbBuf; offBuf += _1K, off += _1K)
1712 *(uint64_t *)&pbBuf[offBuf] = off;
1713
1714 size_t cbToWrite = cbBuf;
1715 if (cbToWrite > cbLeft)
1716 cbToWrite = (size_t)cbLeft;
1717
1718 RTTESTI_CHECK_RC_RET(RTFileWrite(hFile1, pbBuf, cbToWrite, NULL), VINF_SUCCESS, rcCheck);
1719
1720 cbLeft -= cbToWrite;
1721 }
1722
1723 return VINF_SUCCESS;
1724}
1725
1726
1727/**
1728 * Checks the content read from the file fsPerfIoPrepFile() prepared.
1729 */
1730bool fsPerfCheckReadBuf(unsigned uLineNo, uint64_t off, uint8_t const *pbBuf, size_t cbBuf, uint8_t bFiller = 0xf6)
1731{
1732 uint32_t cMismatches = 0;
1733 size_t offBuf = 0;
1734 uint32_t offBlock = (uint32_t)(off & (_1K - 1));
1735 while (offBuf < cbBuf)
1736 {
1737 /*
1738 * Check the offset marker:
1739 */
1740 if (offBlock < sizeof(uint64_t))
1741 {
1742 RTUINT64U uMarker;
1743 uMarker.u = off + offBuf - offBlock;
1744 unsigned offMarker = offBlock & (sizeof(uint64_t) - 1);
1745 while (offMarker < sizeof(uint64_t) && offBuf < cbBuf)
1746 {
1747 if (uMarker.au8[offMarker] != pbBuf[offBuf])
1748 {
1749 RTTestIFailed("%u: Mismatch at buffer/file offset %#zx/%#RX64: %#x, expected %#x",
1750 uLineNo, offBuf, off + offBuf, pbBuf[offBuf], uMarker.au8[offMarker]);
1751 if (cMismatches++ > 32)
1752 return false;
1753 }
1754 offMarker++;
1755 offBuf++;
1756 }
1757 offBlock = sizeof(uint64_t);
1758 }
1759
1760 /*
1761 * Check the filling:
1762 */
1763 size_t cbFilling = RT_MIN(_1K - offBlock, cbBuf - offBuf);
1764 if ( cbFilling == 0
1765 || ASMMemIsAllU8(&pbBuf[offBuf], cbFilling, bFiller))
1766 offBuf += cbFilling;
1767 else
1768 {
1769 /* Some mismatch, locate it/them: */
1770 while (cbFilling > 0 && offBuf < cbBuf)
1771 {
1772 if (pbBuf[offBuf] != bFiller)
1773 {
1774 RTTestIFailed("%u: Mismatch at buffer/file offset %#zx/%#RX64: %#x, expected %#04x",
1775 uLineNo, offBuf, off + offBuf, pbBuf[offBuf], bFiller);
1776 if (cMismatches++ > 32)
1777 return false;
1778 }
1779 offBuf++;
1780 cbFilling--;
1781 }
1782 }
1783 offBlock = 0;
1784 }
1785 return cMismatches == 0;
1786}
1787
1788
1789/**
1790 * Sets up write buffer with offset markers and fillers.
1791 */
1792void fsPerfFillWriteBuf(uint64_t off, uint8_t *pbBuf, size_t cbBuf, uint8_t bFiller = 0xf6)
1793{
1794 uint32_t offBlock = (uint32_t)(off & (_1K - 1));
1795 while (cbBuf > 0)
1796 {
1797 /* The marker. */
1798 if (offBlock < sizeof(uint64_t))
1799 {
1800 RTUINT64U uMarker;
1801 uMarker.u = off + offBlock;
1802 if (cbBuf > sizeof(uMarker) - offBlock)
1803 {
1804 memcpy(pbBuf, &uMarker.au8[offBlock], sizeof(uMarker) - offBlock);
1805 pbBuf += sizeof(uMarker) - offBlock;
1806 cbBuf -= sizeof(uMarker) - offBlock;
1807 off += sizeof(uMarker) - offBlock;
1808 }
1809 else
1810 {
1811 memcpy(pbBuf, &uMarker.au8[offBlock], cbBuf);
1812 return;
1813 }
1814 offBlock = sizeof(uint64_t);
1815 }
1816
1817 /* Do the filling. */
1818 size_t cbFilling = RT_MIN(_1K - offBlock, cbBuf);
1819 memset(pbBuf, bFiller, cbFilling);
1820 pbBuf += cbFilling;
1821 cbBuf -= cbFilling;
1822 off += cbFilling;
1823
1824 offBlock = 0;
1825 }
1826}
1827
1828
1829
1830void fsPerfIoSeek(RTFILE hFile1, uint64_t cbFile)
1831{
1832 /*
1833 * Do a bunch of search tests, most which are random.
1834 */
1835 struct
1836 {
1837 int rc;
1838 uint32_t uMethod;
1839 int64_t offSeek;
1840 uint64_t offActual;
1841
1842 } aSeeks[9 + 64] =
1843 {
1844 { VINF_SUCCESS, RTFILE_SEEK_BEGIN, 0, 0 },
1845 { VINF_SUCCESS, RTFILE_SEEK_CURRENT, 0, 0 },
1846 { VINF_SUCCESS, RTFILE_SEEK_END, 0, cbFile },
1847 { VINF_SUCCESS, RTFILE_SEEK_CURRENT, -4096, cbFile - 4096 },
1848 { VINF_SUCCESS, RTFILE_SEEK_CURRENT, 4096 - (int64_t)cbFile, 0 },
1849 { VINF_SUCCESS, RTFILE_SEEK_END, -(int64_t)cbFile/2, cbFile / 2 + (cbFile & 1) },
1850 { VINF_SUCCESS, RTFILE_SEEK_CURRENT, -(int64_t)cbFile/2, 0 },
1851#if defined(RT_OS_WINDOWS)
1852 { VERR_NEGATIVE_SEEK, RTFILE_SEEK_CURRENT, -1, 0 },
1853#else
1854 { VERR_INVALID_PARAMETER, RTFILE_SEEK_CURRENT, -1, 0 },
1855#endif
1856 { VINF_SUCCESS, RTFILE_SEEK_CURRENT, 0, 0 },
1857 };
1858
1859 uint64_t offActual = 0;
1860 for (unsigned i = 9; i < RT_ELEMENTS(aSeeks); i++)
1861 {
1862 switch (RTRandU32Ex(RTFILE_SEEK_BEGIN, RTFILE_SEEK_END))
1863 {
1864 default: AssertFailedBreak();
1865 case RTFILE_SEEK_BEGIN:
1866 aSeeks[i].uMethod = RTFILE_SEEK_BEGIN;
1867 aSeeks[i].rc = VINF_SUCCESS;
1868 aSeeks[i].offSeek = RTRandU64Ex(0, cbFile + cbFile / 8);
1869 aSeeks[i].offActual = offActual = aSeeks[i].offSeek;
1870 break;
1871
1872 case RTFILE_SEEK_CURRENT:
1873 aSeeks[i].uMethod = RTFILE_SEEK_CURRENT;
1874 aSeeks[i].rc = VINF_SUCCESS;
1875 aSeeks[i].offSeek = (int64_t)RTRandU64Ex(0, cbFile + cbFile / 8) - (int64_t)offActual;
1876 aSeeks[i].offActual = offActual += aSeeks[i].offSeek;
1877 break;
1878
1879 case RTFILE_SEEK_END:
1880 aSeeks[i].uMethod = RTFILE_SEEK_END;
1881 aSeeks[i].rc = VINF_SUCCESS;
1882 aSeeks[i].offSeek = -(int64_t)RTRandU64Ex(0, cbFile);
1883 aSeeks[i].offActual = offActual = cbFile + aSeeks[i].offSeek;
1884 break;
1885 }
1886 }
1887
1888 for (unsigned iDoReadCheck = 0; iDoReadCheck < 2; iDoReadCheck++)
1889 {
1890 for (uint32_t i = 0; i < RT_ELEMENTS(aSeeks); i++)
1891 {
1892 offActual = UINT64_MAX;
1893 int rc = RTFileSeek(hFile1, aSeeks[i].offSeek, aSeeks[i].uMethod, &offActual);
1894 if (rc != aSeeks[i].rc)
1895 RTTestIFailed("Seek #%u: Expected %Rrc, got %Rrc", i, aSeeks[i].rc, rc);
1896 if (RT_SUCCESS(rc) && offActual != aSeeks[i].offActual)
1897 RTTestIFailed("Seek #%u: offActual %#RX64, expected %#RX64", i, offActual, aSeeks[i].offActual);
1898 if (RT_SUCCESS(rc))
1899 {
1900 uint64_t offTell = RTFileTell(hFile1);
1901 if (offTell != offActual)
1902 RTTestIFailed("Seek #%u: offActual %#RX64, RTFileTell %#RX64", i, offActual, offTell);
1903 }
1904
1905 if (RT_SUCCESS(rc) && offActual + _2K <= cbFile && iDoReadCheck)
1906 {
1907 uint8_t abBuf[_2K];
1908 RTTESTI_CHECK_RC(rc = RTFileRead(hFile1, abBuf, sizeof(abBuf), NULL), VINF_SUCCESS);
1909 if (RT_SUCCESS(rc))
1910 {
1911 size_t offMarker = (size_t)(RT_ALIGN_64(offActual, _1K) - offActual);
1912 uint64_t uMarker = *(uint64_t *)&abBuf[offMarker]; /** @todo potentially unaligned access */
1913 if (uMarker != offActual + offMarker)
1914 RTTestIFailed("Seek #%u: Invalid marker value (@ %#RX64): %#RX64, expected %#RX64",
1915 i, offActual, uMarker, offActual + offMarker);
1916
1917 RTTESTI_CHECK_RC(RTFileSeek(hFile1, -(int64_t)sizeof(abBuf), RTFILE_SEEK_CURRENT, NULL), VINF_SUCCESS);
1918 }
1919 }
1920 }
1921 }
1922
1923
1924 /*
1925 * Profile seeking relative to the beginning of the file and relative
1926 * to the end. The latter might be more expensive in a SF context.
1927 */
1928 PROFILE_FN(RTFileSeek(hFile1, iIteration < cbFile ? iIteration : iIteration % cbFile, RTFILE_SEEK_BEGIN, NULL),
1929 g_nsTestRun, "RTFileSeek/BEGIN");
1930 PROFILE_FN(RTFileSeek(hFile1, iIteration < cbFile ? -(int64_t)iIteration : -(int64_t)(iIteration % cbFile), RTFILE_SEEK_END, NULL),
1931 g_nsTestRun, "RTFileSeek/END");
1932
1933}
1934
1935#if defined(RT_OS_LINUX)
1936
1937/**
1938 * Send file thread arguments.
1939 */
1940typedef struct FSPERFSENDFILEARGS
1941{
1942 uint64_t offFile;
1943 size_t cbSend;
1944 uint64_t cbSent;
1945 size_t cbBuf;
1946 uint8_t *pbBuf;
1947 uint8_t bFiller;
1948 bool fCheckBuf;
1949 RTSOCKET hSocket;
1950 uint64_t volatile tsThreadDone;
1951} FSPERFSENDFILEARGS;
1952
1953/** Thread receiving the bytes from a sendfile() call. */
1954static DECLCALLBACK(int) fsPerfSendFileThread(RTTHREAD hSelf, void *pvUser)
1955{
1956 FSPERFSENDFILEARGS *pArgs = (FSPERFSENDFILEARGS *)pvUser;
1957 int rc = VINF_SUCCESS;
1958
1959 if (pArgs->fCheckBuf)
1960 RTTestSetDefault(g_hTest, NULL);
1961
1962 uint64_t cbReceived = 0;
1963 while (cbReceived < pArgs->cbSent)
1964 {
1965 size_t const cbToRead = RT_MIN(pArgs->cbBuf, pArgs->cbSent - cbReceived);
1966 size_t cbActual = 0;
1967 RTTEST_CHECK_RC_BREAK(g_hTest, rc = RTTcpRead(pArgs->hSocket, pArgs->pbBuf, cbToRead, &cbActual), VINF_SUCCESS);
1968 RTTEST_CHECK_BREAK(g_hTest, cbActual != 0);
1969 RTTEST_CHECK(g_hTest, cbActual <= cbToRead);
1970 if (pArgs->fCheckBuf)
1971 fsPerfCheckReadBuf(__LINE__, pArgs->offFile + cbReceived, pArgs->pbBuf, cbActual, pArgs->bFiller);
1972 cbReceived += cbActual;
1973 }
1974
1975 pArgs->tsThreadDone = RTTimeNanoTS();
1976
1977 if (cbReceived == pArgs->cbSent && RT_SUCCESS(rc))
1978 {
1979 size_t cbActual = 0;
1980 rc = RTSocketReadNB(pArgs->hSocket, pArgs->pbBuf, 1, &cbActual);
1981 if (rc != VINF_SUCCESS && rc != VINF_TRY_AGAIN)
1982 RTTestFailed(g_hTest, "RTSocketReadNB(sendfile client socket) -> %Rrc; expected VINF_SUCCESS or VINF_TRY_AGAIN\n", rc);
1983 else if (cbActual != 0)
1984 RTTestFailed(g_hTest, "sendfile client socket still contains data when done!\n");
1985 }
1986
1987 RTTEST_CHECK_RC(g_hTest, RTSocketClose(pArgs->hSocket), VINF_SUCCESS);
1988 pArgs->hSocket = NIL_RTSOCKET;
1989
1990 RT_NOREF(hSelf);
1991 return rc;
1992}
1993
1994
1995static uint64_t fsPerfSendFileOne(FSPERFSENDFILEARGS *pArgs, RTFILE hFile1, uint64_t offFile,
1996 size_t cbSend, uint64_t cbSent, uint8_t bFiller, bool fCheckBuf, unsigned iLine)
1997{
1998 /* Copy parameters to the argument structure: */
1999 pArgs->offFile = offFile;
2000 pArgs->cbSend = cbSend;
2001 pArgs->cbSent = cbSent;
2002 pArgs->bFiller = bFiller;
2003 pArgs->fCheckBuf = fCheckBuf;
2004
2005 /* Create a socket pair. */
2006 pArgs->hSocket = NIL_RTSOCKET;
2007 RTSOCKET hServer = NIL_RTSOCKET;
2008 RTTESTI_CHECK_RC_RET(RTTcpCreatePair(&hServer, &pArgs->hSocket, 0), VINF_SUCCESS, 0);
2009
2010 /* Create the receiving thread: */
2011 int rc;
2012 RTTHREAD hThread = NIL_RTTHREAD;
2013 RTTESTI_CHECK_RC(rc = RTThreadCreate(&hThread, fsPerfSendFileThread, pArgs, 0,
2014 RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, "sendfile"), VINF_SUCCESS);
2015 if (RT_SUCCESS(rc))
2016 {
2017 uint64_t const tsStart = RTTimeNanoTS();
2018
2019# if defined(RT_OS_LINUX) || defined(RT_OS_SOLARIS)
2020 /* SystemV sendfile: */
2021 loff_t offFileSf = pArgs->offFile;
2022 ssize_t cbActual = sendfile((int)RTSocketToNative(hServer), (int)RTFileToNative(hFile1), &offFileSf, pArgs->cbSend);
2023 int const iErr = errno;
2024 if (cbActual < 0)
2025 RTTestIFailed("%u: sendfile(socket, file, &%#X64, %#zx) failed (%zd): %d (%Rrc), offFileSf=%#RX64\n",
2026 iLine, pArgs->offFile, pArgs->cbSend, cbActual, iErr, RTErrConvertFromErrno(iErr), (uint64_t)offFileSf);
2027 else if ((uint64_t)cbActual != pArgs->cbSent)
2028 RTTestIFailed("%u: sendfile(socket, file, &%#RX64, %#zx): %#zx, expected %#RX64 (offFileSf=%#RX64)\n",
2029 iLine, pArgs->offFile, pArgs->cbSend, cbActual, pArgs->cbSent, (uint64_t)offFileSf);
2030 else if ((uint64_t)offFileSf != pArgs->offFile + pArgs->cbSent)
2031 RTTestIFailed("%u: sendfile(socket, file, &%#RX64, %#zx): %#zx; offFileSf=%#RX64, expected %#RX64\n",
2032 iLine, pArgs->offFile, pArgs->cbSend, cbActual, (uint64_t)offFileSf, pArgs->offFile + pArgs->cbSent);
2033#else
2034 /* BSD sendfile: */
2035# ifdef SF_SYNC
2036 int fSfFlags = SF_SYNC;
2037# else
2038 int fSfFlags = 0;
2039# endif
2040 off_t cbActual = pArgs->cbSend;
2041 rc = sendfile((int)RTFileToNative(hFile1), (int)RTSocketToNative(hServer),
2042 pArgs->offFile, cbActual, NULL, &cbActual, fSfFlags);
2043 int const iErr = errno;
2044 if (rc != 0)
2045 RTTestIFailed("%u: sendfile(file, socket, %#RX64, %#zx, NULL,, %#x) failed (%d): %d (%Rrc), cbActual=%#RX64\n",
2046 iLine, pArgs->offFile, (size_t)pArgs->cbSend, rc, iErr, RTErrConvertFromErrno(iErr), (uint64_t)cbActual);
2047 if ((uint64_t)cbActual != pArgs->cbSent)
2048 RTTestIFailed("%u: sendfile(file, socket, %#RX64, %#zx, NULL,, %#x): cbActual=%#RX64, expected %#RX64 (rc=%d, errno=%d)\n",
2049 iLine, pArgs->offFile, (size_t)pArgs->cbSend, (uint64_t)cbActual, pArgs->cbSent, rc, iErr);
2050# endif
2051 RTTESTI_CHECK_RC(RTSocketClose(hServer), VINF_SUCCESS);
2052 RTTESTI_CHECK_RC(RTThreadWait(hThread, 30 * RT_NS_1SEC, NULL), VINF_SUCCESS);
2053
2054 if (pArgs->tsThreadDone >= tsStart)
2055 return RT_MAX(pArgs->tsThreadDone - tsStart, 1);
2056 }
2057 return 0;
2058}
2059
2060
2061static void fsPerfSendFile(RTFILE hFile1, uint64_t cbFile)
2062{
2063 RTTestISub("sendfile");
2064# ifdef RT_OS_LINUX
2065 uint64_t const cbFileMax = RT_MIN(cbFile, UINT32_MAX - PAGE_OFFSET_MASK);
2066# else
2067 uint64_t const cbFileMax = RT_MIN(cbFile, SSIZE_MAX - PAGE_OFFSET_MASK);
2068# endif
2069 signal(SIGPIPE, SIG_IGN);
2070
2071 /*
2072 * Allocate a buffer.
2073 */
2074 FSPERFSENDFILEARGS Args;
2075 Args.cbBuf = RT_MIN(cbFileMax, _16M);
2076 Args.pbBuf = (uint8_t *)RTMemAlloc(Args.cbBuf);
2077 while (!Args.pbBuf)
2078 {
2079 Args.cbBuf /= 8;
2080 RTTESTI_CHECK_RETV(Args.cbBuf >= _64K);
2081 Args.pbBuf = (uint8_t *)RTMemAlloc(Args.cbBuf);
2082 }
2083
2084 /*
2085 * First iteration with default buffer content.
2086 */
2087 fsPerfSendFileOne(&Args, hFile1, 0, cbFileMax, cbFileMax, 0xf6, true /*fCheckBuf*/, __LINE__);
2088 if (cbFileMax == cbFile)
2089 fsPerfSendFileOne(&Args, hFile1, 63, cbFileMax, cbFileMax - 63, 0xf6, true /*fCheckBuf*/, __LINE__);
2090 else
2091 fsPerfSendFileOne(&Args, hFile1, 63, cbFileMax - 63, cbFileMax - 63, 0xf6, true /*fCheckBuf*/, __LINE__);
2092
2093 /*
2094 * Write a block using the regular API and then send it, checking that
2095 * the any caching that sendfile does is correctly updated.
2096 */
2097 uint8_t bFiller = 0xf6;
2098 size_t cbToSend = RT_MIN(cbFileMax, Args.cbBuf);
2099 do
2100 {
2101 fsPerfSendFileOne(&Args, hFile1, 0, cbToSend, cbToSend, bFiller, true /*fCheckBuf*/, __LINE__); /* prime cache */
2102
2103 bFiller += 1;
2104 fsPerfFillWriteBuf(0, Args.pbBuf, cbToSend, bFiller);
2105 RTTESTI_CHECK_RC(RTFileWriteAt(hFile1, 0, Args.pbBuf, cbToSend, NULL), VINF_SUCCESS);
2106
2107 fsPerfSendFileOne(&Args, hFile1, 0, cbToSend, cbToSend, bFiller, true /*fCheckBuf*/, __LINE__);
2108
2109 cbToSend /= 2;
2110 } while (cbToSend >= PAGE_SIZE && ((unsigned)bFiller - 0xf7U) < 64);
2111
2112 /*
2113 * Restore buffer content
2114 */
2115 bFiller = 0xf6;
2116 fsPerfFillWriteBuf(0, Args.pbBuf, Args.cbBuf, bFiller);
2117 RTTESTI_CHECK_RC(RTFileWriteAt(hFile1, 0, Args.pbBuf, Args.cbBuf, NULL), VINF_SUCCESS);
2118
2119 /*
2120 * Do 128 random sends.
2121 */
2122 uint64_t const cbSmall = RT_MIN(_256K, cbFileMax / 16);
2123 for (uint32_t iTest = 0; iTest < 128; iTest++)
2124 {
2125 cbToSend = (size_t)RTRandU64Ex(1, iTest < 64 ? cbSmall : cbFileMax);
2126 uint64_t const offToSendFrom = RTRandU64Ex(0, cbFile - 1);
2127 uint64_t const cbSent = offToSendFrom + cbToSend <= cbFile ? cbToSend : cbFile - offToSendFrom;
2128
2129 fsPerfSendFileOne(&Args, hFile1, offToSendFrom, cbToSend, cbSent, bFiller, true /*fCheckBuf*/, __LINE__);
2130 }
2131
2132 /*
2133 * Benchmark it.
2134 */
2135 uint32_t cIterations = 0;
2136 uint64_t nsElapsed = 0;
2137 for (;;)
2138 {
2139 uint64_t cNsThis = fsPerfSendFileOne(&Args, hFile1, 0, cbFileMax, cbFileMax, 0xf6, false /*fCheckBuf*/, __LINE__);
2140 nsElapsed += cNsThis;
2141 cIterations++;
2142 if (!cNsThis || nsElapsed >= g_nsTestRun)
2143 break;
2144 }
2145 uint64_t cbTotal = cbFileMax * cIterations;
2146 RTTestIValue("latency", nsElapsed / cIterations, RTTESTUNIT_NS_PER_CALL);
2147 RTTestIValue("throughput", (uint64_t)(cbTotal / ((double)nsElapsed / RT_NS_1SEC)), RTTESTUNIT_BYTES_PER_SEC);
2148 RTTestIValue("calls", cIterations, RTTESTUNIT_CALLS);
2149 RTTestIValue("bytes", cbTotal, RTTESTUNIT_BYTES);
2150 if (g_fShowDuration)
2151 RTTestIValue("duration", nsElapsed, RTTESTUNIT_NS);
2152
2153 /*
2154 * Cleanup.
2155 */
2156 RTMemFree(Args.pbBuf);
2157}
2158
2159#endif /* RT_OS_LINUX */
2160#ifdef RT_OS_LINUX
2161
2162/**
2163 * Send file thread arguments.
2164 */
2165typedef struct FSPERFSPLICEARGS
2166{
2167 uint64_t offFile;
2168 size_t cbSend;
2169 uint64_t cbSent;
2170 size_t cbBuf;
2171 uint8_t *pbBuf;
2172 uint8_t bFiller;
2173 bool fCheckBuf;
2174 uint32_t cCalls;
2175 RTPIPE hPipe;
2176 uint64_t volatile tsThreadDone;
2177} FSPERFSPLICEARGS;
2178
2179
2180/** Thread receiving the bytes from a splice() call. */
2181static DECLCALLBACK(int) fsPerfSpliceRecvThread(RTTHREAD hSelf, void *pvUser)
2182{
2183 FSPERFSPLICEARGS *pArgs = (FSPERFSPLICEARGS *)pvUser;
2184 int rc = VINF_SUCCESS;
2185
2186 if (pArgs->fCheckBuf)
2187 RTTestSetDefault(g_hTest, NULL);
2188
2189 uint64_t cbReceived = 0;
2190 while (cbReceived < pArgs->cbSent)
2191 {
2192 size_t const cbToRead = RT_MIN(pArgs->cbBuf, pArgs->cbSent - cbReceived);
2193 size_t cbActual = 0;
2194 RTTEST_CHECK_RC_BREAK(g_hTest, rc = RTPipeReadBlocking(pArgs->hPipe, pArgs->pbBuf, cbToRead, &cbActual), VINF_SUCCESS);
2195 RTTEST_CHECK_BREAK(g_hTest, cbActual != 0);
2196 RTTEST_CHECK(g_hTest, cbActual <= cbToRead);
2197 if (pArgs->fCheckBuf)
2198 fsPerfCheckReadBuf(__LINE__, pArgs->offFile + cbReceived, pArgs->pbBuf, cbActual, pArgs->bFiller);
2199 cbReceived += cbActual;
2200 }
2201
2202 pArgs->tsThreadDone = RTTimeNanoTS();
2203
2204 if (cbReceived == pArgs->cbSent && RT_SUCCESS(rc))
2205 {
2206 size_t cbActual = 0;
2207 rc = RTPipeRead(pArgs->hPipe, pArgs->pbBuf, 1, &cbActual);
2208 if (rc != VINF_SUCCESS && rc != VINF_TRY_AGAIN && rc != VERR_BROKEN_PIPE)
2209 RTTestFailed(g_hTest, "RTPipeReadBlocking() -> %Rrc; expected VINF_SUCCESS or VINF_TRY_AGAIN\n", rc);
2210 else if (cbActual != 0)
2211 RTTestFailed(g_hTest, "splice read pipe still contains data when done!\n");
2212 }
2213
2214 RTTEST_CHECK_RC(g_hTest, RTPipeClose(pArgs->hPipe), VINF_SUCCESS);
2215 pArgs->hPipe = NIL_RTPIPE;
2216
2217 RT_NOREF(hSelf);
2218 return rc;
2219}
2220
2221
2222/** Sends hFile1 to a pipe via the Linux-specific splice() syscall. */
2223static uint64_t fsPerfSpliceSendFile(FSPERFSPLICEARGS *pArgs, RTFILE hFile1, uint64_t offFile,
2224 size_t cbSend, uint64_t cbSent, uint8_t bFiller, bool fCheckBuf, unsigned iLine)
2225{
2226 /* Copy parameters to the argument structure: */
2227 pArgs->offFile = offFile;
2228 pArgs->cbSend = cbSend;
2229 pArgs->cbSent = cbSent;
2230 pArgs->bFiller = bFiller;
2231 pArgs->fCheckBuf = fCheckBuf;
2232
2233 /* Create a socket pair. */
2234 pArgs->hPipe = NIL_RTPIPE;
2235 RTPIPE hPipeW = NIL_RTPIPE;
2236 RTTESTI_CHECK_RC_RET(RTPipeCreate(&pArgs->hPipe, &hPipeW, 0 /*fFlags*/), VINF_SUCCESS, 0);
2237
2238 /* Create the receiving thread: */
2239 int rc;
2240 RTTHREAD hThread = NIL_RTTHREAD;
2241 RTTESTI_CHECK_RC(rc = RTThreadCreate(&hThread, fsPerfSpliceRecvThread, pArgs, 0,
2242 RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, "splicerecv"), VINF_SUCCESS);
2243 if (RT_SUCCESS(rc))
2244 {
2245 uint64_t const tsStart = RTTimeNanoTS();
2246 size_t cbLeft = cbSend;
2247 size_t cbTotal = 0;
2248 do
2249 {
2250 loff_t offFileIn = offFile;
2251 ssize_t cbActual = splice((int)RTFileToNative(hFile1), &offFileIn, (int)RTPipeToNative(hPipeW), NULL,
2252 cbLeft, 0 /*fFlags*/);
2253 int const iErr = errno;
2254 if (RT_UNLIKELY(cbActual < 0))
2255 {
2256 RTTestIFailed("%u: splice(file, &%#RX64, pipe, NULL, %#zx, 0) failed (%zd): %d (%Rrc), offFileIn=%#RX64\n",
2257 iLine, offFile, cbLeft, cbActual, iErr, RTErrConvertFromErrno(iErr), (uint64_t)offFileIn);
2258 break;
2259 }
2260 RTTESTI_CHECK_BREAK((uint64_t)cbActual <= cbLeft);
2261 if ((uint64_t)offFileIn != offFile + (uint64_t)cbActual)
2262 {
2263 RTTestIFailed("%u: splice(file, &%#RX64, pipe, NULL, %#zx, 0): %#zx; offFileIn=%#RX64, expected %#RX64\n",
2264 iLine, offFile, cbLeft, cbActual, (uint64_t)offFileIn, offFile + (uint64_t)cbActual);
2265 break;
2266 }
2267 if (cbActual > 0)
2268 {
2269 pArgs->cCalls++;
2270 offFile += (size_t)cbActual;
2271 cbTotal += (size_t)cbActual;
2272 cbLeft -= (size_t)cbActual;
2273 }
2274 else
2275 break;
2276 } while (cbLeft > 0);
2277
2278 if (cbTotal != pArgs->cbSent)
2279 RTTestIFailed("%u: spliced a total of %#zx bytes, expected %#zx!\n", iLine, cbTotal, pArgs->cbSent);
2280
2281 RTTESTI_CHECK_RC(RTPipeClose(hPipeW), VINF_SUCCESS);
2282 RTTESTI_CHECK_RC(RTThreadWait(hThread, 30 * RT_NS_1SEC, NULL), VINF_SUCCESS);
2283
2284 if (pArgs->tsThreadDone >= tsStart)
2285 return RT_MAX(pArgs->tsThreadDone - tsStart, 1);
2286 }
2287 return 0;
2288}
2289
2290
2291static void fsPerfSpliceToPipe(RTFILE hFile1, uint64_t cbFile)
2292{
2293 RTTestISub("splice/to-pipe");
2294
2295 /*
2296 * splice was introduced in 2.6.17 according to the man-page.
2297 */
2298 char szRelease[64];
2299 RTSystemQueryOSInfo(RTSYSOSINFO_RELEASE, szRelease, sizeof(szRelease));
2300 if (RTStrVersionCompare(szRelease, "2.6.17") < 0)
2301 {
2302 RTTestPassed(g_hTest, "too old kernel (%s)", szRelease);
2303 return;
2304 }
2305
2306 uint64_t const cbFileMax = RT_MIN(cbFile, UINT32_MAX - PAGE_OFFSET_MASK);
2307 signal(SIGPIPE, SIG_IGN);
2308
2309 /*
2310 * Allocate a buffer.
2311 */
2312 FSPERFSPLICEARGS Args;
2313 Args.cbBuf = RT_MIN(cbFileMax, _16M);
2314 Args.pbBuf = (uint8_t *)RTMemAlloc(Args.cbBuf);
2315 while (!Args.pbBuf)
2316 {
2317 Args.cbBuf /= 8;
2318 RTTESTI_CHECK_RETV(Args.cbBuf >= _64K);
2319 Args.pbBuf = (uint8_t *)RTMemAlloc(Args.cbBuf);
2320 }
2321
2322 /*
2323 * First iteration with default buffer content.
2324 */
2325 fsPerfSpliceSendFile(&Args, hFile1, 0, cbFileMax, cbFileMax, 0xf6, true /*fCheckBuf*/, __LINE__);
2326 if (cbFileMax == cbFile)
2327 fsPerfSpliceSendFile(&Args, hFile1, 63, cbFileMax, cbFileMax - 63, 0xf6, true /*fCheckBuf*/, __LINE__);
2328 else
2329 fsPerfSpliceSendFile(&Args, hFile1, 63, cbFileMax - 63, cbFileMax - 63, 0xf6, true /*fCheckBuf*/, __LINE__);
2330
2331 /*
2332 * Write a block using the regular API and then send it, checking that
2333 * the any caching that sendfile does is correctly updated.
2334 */
2335 uint8_t bFiller = 0xf6;
2336 size_t cbToSend = RT_MIN(cbFileMax, Args.cbBuf);
2337 do
2338 {
2339 fsPerfSpliceSendFile(&Args, hFile1, 0, cbToSend, cbToSend, bFiller, true /*fCheckBuf*/, __LINE__); /* prime cache */
2340
2341 bFiller += 1;
2342 fsPerfFillWriteBuf(0, Args.pbBuf, cbToSend, bFiller);
2343 RTTESTI_CHECK_RC(RTFileWriteAt(hFile1, 0, Args.pbBuf, cbToSend, NULL), VINF_SUCCESS);
2344
2345 fsPerfSpliceSendFile(&Args, hFile1, 0, cbToSend, cbToSend, bFiller, true /*fCheckBuf*/, __LINE__);
2346
2347 cbToSend /= 2;
2348 } while (cbToSend >= PAGE_SIZE && ((unsigned)bFiller - 0xf7U) < 64);
2349
2350 /*
2351 * Restore buffer content
2352 */
2353 bFiller = 0xf6;
2354 fsPerfFillWriteBuf(0, Args.pbBuf, Args.cbBuf, bFiller);
2355 RTTESTI_CHECK_RC(RTFileWriteAt(hFile1, 0, Args.pbBuf, Args.cbBuf, NULL), VINF_SUCCESS);
2356
2357 /*
2358 * Do 128 random sends.
2359 */
2360 uint64_t const cbSmall = RT_MIN(_256K, cbFileMax / 16);
2361 for (uint32_t iTest = 0; iTest < 128; iTest++)
2362 {
2363 cbToSend = (size_t)RTRandU64Ex(1, iTest < 64 ? cbSmall : cbFileMax);
2364 uint64_t const offToSendFrom = RTRandU64Ex(0, cbFile - 1);
2365 uint64_t const cbSent = offToSendFrom + cbToSend <= cbFile ? cbToSend : cbFile - offToSendFrom;
2366
2367 fsPerfSpliceSendFile(&Args, hFile1, offToSendFrom, cbToSend, cbSent, bFiller, true /*fCheckBuf*/, __LINE__);
2368 }
2369
2370 /*
2371 * Benchmark it.
2372 */
2373 Args.cCalls = 0;
2374 uint32_t cIterations = 0;
2375 uint64_t nsElapsed = 0;
2376 for (;;)
2377 {
2378 uint64_t cNsThis = fsPerfSpliceSendFile(&Args, hFile1, 0, cbFileMax, cbFileMax, 0xf6, false /*fCheckBuf*/, __LINE__);
2379 nsElapsed += cNsThis;
2380 cIterations++;
2381 if (!cNsThis || nsElapsed >= g_nsTestRun)
2382 break;
2383 }
2384 uint64_t cbTotal = cbFileMax * cIterations;
2385 RTTestIValue("latency", nsElapsed / Args.cCalls, RTTESTUNIT_NS_PER_CALL);
2386 RTTestIValue("throughput", (uint64_t)(cbTotal / ((double)nsElapsed / RT_NS_1SEC)), RTTESTUNIT_BYTES_PER_SEC);
2387 RTTestIValue("calls", Args.cCalls, RTTESTUNIT_CALLS);
2388 RTTestIValue("bytes/call", cbTotal / Args.cCalls, RTTESTUNIT_BYTES);
2389 RTTestIValue("iterations", cIterations, RTTESTUNIT_NONE);
2390 RTTestIValue("bytes", cbTotal, RTTESTUNIT_BYTES);
2391 if (g_fShowDuration)
2392 RTTestIValue("duration", nsElapsed, RTTESTUNIT_NS);
2393
2394 /*
2395 * Cleanup.
2396 */
2397 RTMemFree(Args.pbBuf);
2398}
2399
2400
2401/** Thread sending the bytes to a splice() call. */
2402static DECLCALLBACK(int) fsPerfSpliceSendThread(RTTHREAD hSelf, void *pvUser)
2403{
2404 FSPERFSPLICEARGS *pArgs = (FSPERFSPLICEARGS *)pvUser;
2405 int rc = VINF_SUCCESS;
2406
2407 uint64_t offFile = pArgs->offFile;
2408 uint64_t cbTotalSent = 0;
2409 while (cbTotalSent < pArgs->cbSent)
2410 {
2411 size_t const cbToSend = RT_MIN(pArgs->cbBuf, pArgs->cbSent - cbTotalSent);
2412 fsPerfFillWriteBuf(offFile, pArgs->pbBuf, cbToSend, pArgs->bFiller);
2413 RTTEST_CHECK_RC_BREAK(g_hTest, rc = RTPipeWriteBlocking(pArgs->hPipe, pArgs->pbBuf, cbToSend, NULL), VINF_SUCCESS);
2414 offFile += cbToSend;
2415 cbTotalSent += cbToSend;
2416 }
2417
2418 pArgs->tsThreadDone = RTTimeNanoTS();
2419
2420 RTTEST_CHECK_RC(g_hTest, RTPipeClose(pArgs->hPipe), VINF_SUCCESS);
2421 pArgs->hPipe = NIL_RTPIPE;
2422
2423 RT_NOREF(hSelf);
2424 return rc;
2425}
2426
2427
2428/** Fill hFile1 via a pipe and the Linux-specific splice() syscall. */
2429static uint64_t fsPerfSpliceWriteFile(FSPERFSPLICEARGS *pArgs, RTFILE hFile1, uint64_t offFile,
2430 size_t cbSend, uint64_t cbSent, uint8_t bFiller, bool fCheckFile, unsigned iLine)
2431{
2432 /* Copy parameters to the argument structure: */
2433 pArgs->offFile = offFile;
2434 pArgs->cbSend = cbSend;
2435 pArgs->cbSent = cbSent;
2436 pArgs->bFiller = bFiller;
2437 pArgs->fCheckBuf = false;
2438
2439 /* Create a socket pair. */
2440 pArgs->hPipe = NIL_RTPIPE;
2441 RTPIPE hPipeR = NIL_RTPIPE;
2442 RTTESTI_CHECK_RC_RET(RTPipeCreate(&hPipeR, &pArgs->hPipe, 0 /*fFlags*/), VINF_SUCCESS, 0);
2443
2444 /* Create the receiving thread: */
2445 int rc;
2446 RTTHREAD hThread = NIL_RTTHREAD;
2447 RTTESTI_CHECK_RC(rc = RTThreadCreate(&hThread, fsPerfSpliceSendThread, pArgs, 0,
2448 RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, "splicerecv"), VINF_SUCCESS);
2449 if (RT_SUCCESS(rc))
2450 {
2451 /*
2452 * Do the splicing.
2453 */
2454 uint64_t const tsStart = RTTimeNanoTS();
2455 size_t cbLeft = cbSend;
2456 size_t cbTotal = 0;
2457 do
2458 {
2459 loff_t offFileOut = offFile;
2460 ssize_t cbActual = splice((int)RTPipeToNative(hPipeR), NULL, (int)RTFileToNative(hFile1), &offFileOut,
2461 cbLeft, 0 /*fFlags*/);
2462 int const iErr = errno;
2463 if (RT_UNLIKELY(cbActual < 0))
2464 {
2465 RTTestIFailed("%u: splice(pipe, NULL, file, &%#RX64, %#zx, 0) failed (%zd): %d (%Rrc), offFileOut=%#RX64\n",
2466 iLine, offFile, cbLeft, cbActual, iErr, RTErrConvertFromErrno(iErr), (uint64_t)offFileOut);
2467 break;
2468 }
2469 RTTESTI_CHECK_BREAK((uint64_t)cbActual <= cbLeft);
2470 if ((uint64_t)offFileOut != offFile + (uint64_t)cbActual)
2471 {
2472 RTTestIFailed("%u: splice(pipe, NULL, file, &%#RX64, %#zx, 0): %#zx; offFileOut=%#RX64, expected %#RX64\n",
2473 iLine, offFile, cbLeft, cbActual, (uint64_t)offFileOut, offFile + (uint64_t)cbActual);
2474 break;
2475 }
2476 if (cbActual > 0)
2477 {
2478 pArgs->cCalls++;
2479 offFile += (size_t)cbActual;
2480 cbTotal += (size_t)cbActual;
2481 cbLeft -= (size_t)cbActual;
2482 }
2483 else
2484 break;
2485 } while (cbLeft > 0);
2486 uint64_t const nsElapsed = RTTimeNanoTS() - tsStart;
2487
2488 if (cbTotal != pArgs->cbSent)
2489 RTTestIFailed("%u: spliced a total of %#zx bytes, expected %#zx!\n", iLine, cbTotal, pArgs->cbSent);
2490
2491 RTTESTI_CHECK_RC(RTPipeClose(hPipeR), VINF_SUCCESS);
2492 RTTESTI_CHECK_RC(RTThreadWait(hThread, 30 * RT_NS_1SEC, NULL), VINF_SUCCESS);
2493
2494 /* Check the file content. */
2495 if (fCheckFile && cbTotal == pArgs->cbSent)
2496 {
2497 offFile = pArgs->offFile;
2498 cbLeft = cbSent;
2499 while (cbLeft > 0)
2500 {
2501 size_t cbToRead = RT_MIN(cbLeft, pArgs->cbBuf);
2502 RTTESTI_CHECK_RC_BREAK(RTFileReadAt(hFile1, offFile, pArgs->pbBuf, cbToRead, NULL), VINF_SUCCESS);
2503 if (!fsPerfCheckReadBuf(iLine, offFile, pArgs->pbBuf, cbToRead, pArgs->bFiller))
2504 break;
2505 offFile += cbToRead;
2506 cbLeft -= cbToRead;
2507 }
2508 }
2509 return nsElapsed;
2510 }
2511 return 0;
2512}
2513
2514
2515static void fsPerfSpliceToFile(RTFILE hFile1, uint64_t cbFile)
2516{
2517 RTTestISub("splice/to-file");
2518
2519 /*
2520 * splice was introduced in 2.6.17 according to the man-page.
2521 */
2522 char szRelease[64];
2523 RTSystemQueryOSInfo(RTSYSOSINFO_RELEASE, szRelease, sizeof(szRelease));
2524 if (RTStrVersionCompare(szRelease, "2.6.17") < 0)
2525 {
2526 RTTestPassed(g_hTest, "too old kernel (%s)", szRelease);
2527 return;
2528 }
2529
2530 uint64_t const cbFileMax = RT_MIN(cbFile, UINT32_MAX - PAGE_OFFSET_MASK);
2531 signal(SIGPIPE, SIG_IGN);
2532
2533 /*
2534 * Allocate a buffer.
2535 */
2536 FSPERFSPLICEARGS Args;
2537 Args.cbBuf = RT_MIN(cbFileMax, _16M);
2538 Args.pbBuf = (uint8_t *)RTMemAlloc(Args.cbBuf);
2539 while (!Args.pbBuf)
2540 {
2541 Args.cbBuf /= 8;
2542 RTTESTI_CHECK_RETV(Args.cbBuf >= _64K);
2543 Args.pbBuf = (uint8_t *)RTMemAlloc(Args.cbBuf);
2544 }
2545
2546 /*
2547 * Do the whole file.
2548 */
2549 uint8_t bFiller = 0x76;
2550 fsPerfSpliceWriteFile(&Args, hFile1, 0, cbFileMax, cbFileMax, bFiller, true /*fCheckFile*/, __LINE__);
2551
2552 /*
2553 * Do 64 random chunks (this is slower).
2554 */
2555 uint64_t const cbSmall = RT_MIN(_256K, cbFileMax / 16);
2556 for (uint32_t iTest = 0; iTest < 64; iTest++)
2557 {
2558 size_t const cbToWrite = (size_t)RTRandU64Ex(1, iTest < 24 ? cbSmall : cbFileMax);
2559 uint64_t const offToWriteAt = RTRandU64Ex(0, cbFile - cbToWrite);
2560 uint64_t const cbTryRead = cbToWrite + (iTest & 1 ? RTRandU32Ex(0, _64K) : 0);
2561
2562 bFiller++;
2563 fsPerfSpliceWriteFile(&Args, hFile1, offToWriteAt, cbTryRead, cbToWrite, bFiller, true /*fCheckFile*/, __LINE__);
2564 }
2565
2566 /*
2567 * Benchmark it.
2568 */
2569 Args.cCalls = 0;
2570 uint32_t cIterations = 0;
2571 uint64_t nsElapsed = 0;
2572 for (;;)
2573 {
2574 uint64_t cNsThis = fsPerfSpliceWriteFile(&Args, hFile1, 0, cbFileMax, cbFileMax, 0xf6, false /*fCheckBuf*/, __LINE__);
2575 nsElapsed += cNsThis;
2576 cIterations++;
2577 if (!cNsThis || nsElapsed >= g_nsTestRun)
2578 break;
2579 }
2580 uint64_t cbTotal = cbFileMax * cIterations;
2581 RTTestIValue("latency", nsElapsed / Args.cCalls, RTTESTUNIT_NS_PER_CALL);
2582 RTTestIValue("throughput", (uint64_t)(cbTotal / ((double)nsElapsed / RT_NS_1SEC)), RTTESTUNIT_BYTES_PER_SEC);
2583 RTTestIValue("calls", Args.cCalls, RTTESTUNIT_CALLS);
2584 RTTestIValue("bytes/call", cbTotal / Args.cCalls, RTTESTUNIT_BYTES);
2585 RTTestIValue("iterations", cIterations, RTTESTUNIT_NONE);
2586 RTTestIValue("bytes", cbTotal, RTTESTUNIT_BYTES);
2587 if (g_fShowDuration)
2588 RTTestIValue("duration", nsElapsed, RTTESTUNIT_NS);
2589
2590 /*
2591 * Cleanup.
2592 */
2593 RTMemFree(Args.pbBuf);
2594}
2595
2596#endif /* RT_OS_LINUX */
2597
2598/** For fsPerfIoRead and fsPerfIoWrite. */
2599#define PROFILE_IO_FN(a_szOperation, a_fnCall) \
2600 do \
2601 { \
2602 RTTESTI_CHECK_RC_RETV(RTFileSeek(hFile1, 0, RTFILE_SEEK_BEGIN, NULL), VINF_SUCCESS); \
2603 uint64_t offActual = 0; \
2604 uint32_t cSeeks = 0; \
2605 \
2606 /* Estimate how many iterations we need to fill up the given timeslot: */ \
2607 fsPerfYield(); \
2608 uint64_t nsStart = RTTimeNanoTS(); \
2609 uint64_t ns; \
2610 do \
2611 ns = RTTimeNanoTS(); \
2612 while (ns == nsStart); \
2613 nsStart = ns; \
2614 \
2615 uint64_t iIteration = 0; \
2616 do \
2617 { \
2618 RTTESTI_CHECK_RC(a_fnCall, VINF_SUCCESS); \
2619 iIteration++; \
2620 ns = RTTimeNanoTS() - nsStart; \
2621 } while (ns < RT_NS_10MS); \
2622 ns /= iIteration; \
2623 if (ns > g_nsPerNanoTSCall + 32) \
2624 ns -= g_nsPerNanoTSCall; \
2625 uint64_t cIterations = g_nsTestRun / ns; \
2626 if (cIterations < 2) \
2627 cIterations = 2; \
2628 else if (cIterations & 1) \
2629 cIterations++; \
2630 \
2631 /* Do the actual profiling: */ \
2632 cSeeks = 0; \
2633 iIteration = 0; \
2634 fsPerfYield(); \
2635 nsStart = RTTimeNanoTS(); \
2636 for (uint32_t iAdjust = 0; iAdjust < 4; iAdjust++) \
2637 { \
2638 for (; iIteration < cIterations; iIteration++)\
2639 RTTESTI_CHECK_RC(a_fnCall, VINF_SUCCESS); \
2640 ns = RTTimeNanoTS() - nsStart;\
2641 if (ns >= g_nsTestRun - (g_nsTestRun / 10)) \
2642 break; \
2643 cIterations += cIterations / 4; \
2644 if (cIterations & 1) \
2645 cIterations++; \
2646 nsStart += g_nsPerNanoTSCall; \
2647 } \
2648 RTTestIValueF(ns / iIteration, \
2649 RTTESTUNIT_NS_PER_OCCURRENCE, a_szOperation "/seq/%RU32 latency", cbBlock); \
2650 RTTestIValueF((uint64_t)((uint64_t)iIteration * cbBlock / ((double)ns / RT_NS_1SEC)), \
2651 RTTESTUNIT_BYTES_PER_SEC, a_szOperation "/seq/%RU32 throughput", cbBlock); \
2652 RTTestIValueF(iIteration, \
2653 RTTESTUNIT_CALLS, a_szOperation "/seq/%RU32 calls", cbBlock); \
2654 RTTestIValueF((uint64_t)iIteration * cbBlock, \
2655 RTTESTUNIT_BYTES, a_szOperation "/seq/%RU32 bytes", cbBlock); \
2656 RTTestIValueF(cSeeks, \
2657 RTTESTUNIT_OCCURRENCES, a_szOperation "/seq/%RU32 seeks", cbBlock); \
2658 if (g_fShowDuration) \
2659 RTTestIValueF(ns, RTTESTUNIT_NS, a_szOperation "/seq/%RU32 duration", cbBlock); \
2660 } while (0)
2661
2662
2663/**
2664 * One RTFileRead profiling iteration.
2665 */
2666DECL_FORCE_INLINE(int) fsPerfIoReadWorker(RTFILE hFile1, uint64_t cbFile, uint32_t cbBlock, uint8_t *pbBlock,
2667 uint64_t *poffActual, uint32_t *pcSeeks)
2668{
2669 /* Do we need to seek back to the start? */
2670 if (*poffActual + cbBlock <= cbFile)
2671 { /* likely */ }
2672 else
2673 {
2674 RTTESTI_CHECK_RC_RET(RTFileSeek(hFile1, 0, RTFILE_SEEK_BEGIN, NULL), VINF_SUCCESS, rcCheck);
2675 *pcSeeks += 1;
2676 *poffActual = 0;
2677 }
2678
2679 size_t cbActuallyRead = 0;
2680 RTTESTI_CHECK_RC_RET(RTFileRead(hFile1, pbBlock, cbBlock, &cbActuallyRead), VINF_SUCCESS, rcCheck);
2681 if (cbActuallyRead == cbBlock)
2682 {
2683 *poffActual += cbActuallyRead;
2684 return VINF_SUCCESS;
2685 }
2686 RTTestIFailed("RTFileRead at %#RX64 returned just %#x bytes, expected %#x", *poffActual, cbActuallyRead, cbBlock);
2687 *poffActual += cbActuallyRead;
2688 return VERR_READ_ERROR;
2689}
2690
2691
2692void fsPerfIoReadBlockSize(RTFILE hFile1, uint64_t cbFile, uint32_t cbBlock)
2693{
2694 RTTestISubF("IO - Sequential read %RU32", cbBlock);
2695
2696 uint8_t *pbBuf = (uint8_t *)RTMemPageAlloc(cbBlock);
2697 if (pbBuf)
2698 {
2699 memset(pbBuf, 0xf7, cbBlock);
2700 PROFILE_IO_FN("RTFileRead", fsPerfIoReadWorker(hFile1, cbFile, cbBlock, pbBuf, &offActual, &cSeeks));
2701 RTMemPageFree(pbBuf, cbBlock);
2702 }
2703 else
2704 RTTestSkipped(g_hTest, "insufficient (virtual) memory available");
2705}
2706
2707
2708/** preadv is too new to be useful, so we use the readv api via this wrapper. */
2709DECLINLINE(int) myFileSgReadAt(RTFILE hFile, RTFOFF off, PRTSGBUF pSgBuf, size_t cbToRead, size_t *pcbRead)
2710{
2711 int rc = RTFileSeek(hFile, off, RTFILE_SEEK_BEGIN, NULL);
2712 if (RT_SUCCESS(rc))
2713 rc = RTFileSgRead(hFile, pSgBuf, cbToRead, pcbRead);
2714 return rc;
2715}
2716
2717
2718void fsPerfRead(RTFILE hFile1, RTFILE hFileNoCache, uint64_t cbFile)
2719{
2720 RTTestISubF("IO - RTFileRead");
2721
2722 /*
2723 * Allocate a big buffer we can play around with. Min size is 1MB.
2724 */
2725 size_t cbBuf = cbFile < _64M ? (size_t)cbFile : _64M;
2726 uint8_t *pbBuf = (uint8_t *)RTMemPageAlloc(cbBuf);
2727 while (!pbBuf)
2728 {
2729 cbBuf /= 2;
2730 RTTESTI_CHECK_RETV(cbBuf >= _1M);
2731 pbBuf = (uint8_t *)RTMemPageAlloc(_32M);
2732 }
2733
2734#if 1
2735 /*
2736 * Start at the beginning and read the full buffer in random small chunks, thereby
2737 * checking that unaligned buffer addresses, size and file offsets work fine.
2738 */
2739 struct
2740 {
2741 uint64_t offFile;
2742 uint32_t cbMax;
2743 } aRuns[] = { { 0, 127 }, { cbFile - cbBuf, UINT32_MAX }, { 0, UINT32_MAX -1 }};
2744 for (uint32_t i = 0; i < RT_ELEMENTS(aRuns); i++)
2745 {
2746 memset(pbBuf, 0x55, cbBuf);
2747 RTTESTI_CHECK_RC(RTFileSeek(hFile1, aRuns[i].offFile, RTFILE_SEEK_BEGIN, NULL), VINF_SUCCESS);
2748 for (size_t offBuf = 0; offBuf < cbBuf; )
2749 {
2750 uint32_t const cbLeft = (uint32_t)(cbBuf - offBuf);
2751 uint32_t const cbToRead = aRuns[i].cbMax < UINT32_MAX / 2 ? RTRandU32Ex(1, RT_MIN(aRuns[i].cbMax, cbLeft))
2752 : aRuns[i].cbMax == UINT32_MAX ? RTRandU32Ex(RT_MAX(cbLeft / 4, 1), cbLeft)
2753 : RTRandU32Ex(cbLeft >= _8K ? _8K : 1, RT_MIN(_1M, cbLeft));
2754 size_t cbActual = 0;
2755 RTTESTI_CHECK_RC(RTFileRead(hFile1, &pbBuf[offBuf], cbToRead, &cbActual), VINF_SUCCESS);
2756 if (cbActual == cbToRead)
2757 {
2758 offBuf += cbActual;
2759 RTTESTI_CHECK_MSG(RTFileTell(hFile1) == aRuns[i].offFile + offBuf,
2760 ("%#RX64, expected %#RX64\n", RTFileTell(hFile1), aRuns[i].offFile + offBuf));
2761 }
2762 else
2763 {
2764 RTTestIFailed("Attempting to read %#x bytes at %#zx, only got %#x bytes back! (cbLeft=%#x cbBuf=%#zx)\n",
2765 cbToRead, offBuf, cbActual, cbLeft, cbBuf);
2766 if (cbActual)
2767 offBuf += cbActual;
2768 else
2769 pbBuf[offBuf++] = 0x11;
2770 }
2771 }
2772 fsPerfCheckReadBuf(__LINE__, aRuns[i].offFile, pbBuf, cbBuf);
2773 }
2774
2775 /*
2776 * Test reading beyond the end of the file.
2777 */
2778 size_t const acbMax[] = { cbBuf, _64K, _16K, _4K, 256 };
2779 uint32_t const aoffFromEos[] =
2780 { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 32, 63, 64, 127, 128, 255, 254, 256, 1023, 1024, 2048,
2781 4092, 4093, 4094, 4095, 4096, 4097, 4098, 4099, 4100, 8192, 16384, 32767, 32768, 32769, 65535, 65536, _1M - 1
2782 };
2783 for (unsigned iMax = 0; iMax < RT_ELEMENTS(acbMax); iMax++)
2784 {
2785 size_t const cbMaxRead = acbMax[iMax];
2786 for (uint32_t iOffFromEos = 0; iOffFromEos < RT_ELEMENTS(aoffFromEos); iOffFromEos++)
2787 {
2788 uint32_t off = aoffFromEos[iOffFromEos];
2789 if (off >= cbMaxRead)
2790 continue;
2791 RTTESTI_CHECK_RC(RTFileSeek(hFile1, cbFile - off, RTFILE_SEEK_BEGIN, NULL), VINF_SUCCESS);
2792 size_t cbActual = ~(size_t)0;
2793 RTTESTI_CHECK_RC(RTFileRead(hFile1, pbBuf, cbMaxRead, &cbActual), VINF_SUCCESS);
2794 RTTESTI_CHECK(cbActual == off);
2795
2796 RTTESTI_CHECK_RC(RTFileSeek(hFile1, cbFile - off, RTFILE_SEEK_BEGIN, NULL), VINF_SUCCESS);
2797 cbActual = ~(size_t)0;
2798 RTTESTI_CHECK_RC(RTFileRead(hFile1, pbBuf, off, &cbActual), VINF_SUCCESS);
2799 RTTESTI_CHECK_MSG(cbActual == off, ("%#zx vs %#zx\n", cbActual, off));
2800
2801 cbActual = ~(size_t)0;
2802 RTTESTI_CHECK_RC(RTFileRead(hFile1, pbBuf, 1, &cbActual), VINF_SUCCESS);
2803 RTTESTI_CHECK_MSG(cbActual == 0, ("cbActual=%zu\n", cbActual));
2804
2805 RTTESTI_CHECK_RC(RTFileRead(hFile1, pbBuf, cbMaxRead, NULL), VERR_EOF);
2806
2807 /* Repeat using native APIs in case IPRT or other layers hid status codes: */
2808#if defined(RT_OS_OS2) || defined(RT_OS_WINDOWS)
2809 RTTESTI_CHECK_RC(RTFileSeek(hFile1, cbFile - off, RTFILE_SEEK_BEGIN, NULL), VINF_SUCCESS);
2810# ifdef RT_OS_OS2
2811 ULONG cbActual2 = ~(ULONG)0;
2812 APIRET orc = DosRead((HFILE)RTFileToNative(hFile1), pbBuf, cbMaxRead, &cbActual2);
2813 RTTESTI_CHECK_MSG(orc == NO_ERROR, ("orc=%u, expected 0\n", orc));
2814 RTTESTI_CHECK_MSG(cbActual2 == off, ("%#x vs %#x\n", cbActual2, off));
2815# else
2816 IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER;
2817 NTSTATUS rcNt = NtReadFile((HANDLE)RTFileToNative(hFile1), NULL /*hEvent*/, NULL /*ApcRoutine*/, NULL /*ApcContext*/,
2818 &Ios, pbBuf, (ULONG)cbMaxRead, NULL /*poffFile*/, NULL /*Key*/);
2819 if (off == 0)
2820 RTTESTI_CHECK_MSG(rcNt == STATUS_END_OF_FILE, ("rcNt=%#x, expected %#x\n", rcNt, STATUS_END_OF_FILE));
2821 else
2822 RTTESTI_CHECK_MSG(rcNt == STATUS_SUCCESS, ("rcNt=%#x, expected 0 (off=%#x cbMaxRead=%#zx)\n", rcNt, off, cbMaxRead));
2823 RTTESTI_CHECK_MSG(Ios.Information == off, ("%#zx vs %#x\n", Ios.Information, off));
2824# endif
2825
2826# ifdef RT_OS_OS2
2827 cbActual2 = ~(ULONG)0;
2828 orc = DosRead((HFILE)RTFileToNative(hFile1), pbBuf, 1, &cbActual2);
2829 RTTESTI_CHECK_MSG(orc == NO_ERROR, ("orc=%u, expected 0\n", orc));
2830 RTTESTI_CHECK_MSG(cbActual2 == 0, ("cbActual2=%u\n", cbActual2));
2831# else
2832 RTNT_IO_STATUS_BLOCK_REINIT(&Ios);
2833 rcNt = NtReadFile((HANDLE)RTFileToNative(hFile1), NULL /*hEvent*/, NULL /*ApcRoutine*/, NULL /*ApcContext*/,
2834 &Ios, pbBuf, 1, NULL /*poffFile*/, NULL /*Key*/);
2835 RTTESTI_CHECK_MSG(rcNt == STATUS_END_OF_FILE, ("rcNt=%#x, expected %#x\n", rcNt, STATUS_END_OF_FILE));
2836# endif
2837
2838#endif
2839 }
2840 }
2841
2842 /*
2843 * Test reading beyond end of the file.
2844 */
2845 for (unsigned iMax = 0; iMax < RT_ELEMENTS(acbMax); iMax++)
2846 {
2847 size_t const cbMaxRead = acbMax[iMax];
2848 for (uint32_t off = 0; off < 256; off++)
2849 {
2850 RTTESTI_CHECK_RC(RTFileSeek(hFile1, cbFile + off, RTFILE_SEEK_BEGIN, NULL), VINF_SUCCESS);
2851 size_t cbActual = ~(size_t)0;
2852 RTTESTI_CHECK_RC(RTFileRead(hFile1, pbBuf, cbMaxRead, &cbActual), VINF_SUCCESS);
2853 RTTESTI_CHECK(cbActual == 0);
2854
2855 RTTESTI_CHECK_RC(RTFileRead(hFile1, pbBuf, cbMaxRead, NULL), VERR_EOF);
2856
2857 /* Repeat using native APIs in case IPRT or other layers hid status codes: */
2858#if defined(RT_OS_OS2) || defined(RT_OS_WINDOWS)
2859 RTTESTI_CHECK_RC(RTFileSeek(hFile1, cbFile + off, RTFILE_SEEK_BEGIN, NULL), VINF_SUCCESS);
2860# ifdef RT_OS_OS2
2861 ULONG cbActual2 = ~(ULONG)0;
2862 APIRET orc = DosRead((HFILE)RTFileToNative(hFile1), pbBuf, cbMaxRead, &cbActual2);
2863 RTTESTI_CHECK_MSG(orc == NO_ERROR, ("orc=%u, expected 0\n", orc));
2864 RTTESTI_CHECK_MSG(cbActual2 == 0, ("%#x vs %#x\n", cbActual2, off));
2865# else
2866 IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER;
2867 NTSTATUS rcNt = NtReadFile((HANDLE)RTFileToNative(hFile1), NULL /*hEvent*/, NULL /*ApcRoutine*/, NULL /*ApcContext*/,
2868 &Ios, pbBuf, (ULONG)cbMaxRead, NULL /*poffFile*/, NULL /*Key*/);
2869 RTTESTI_CHECK_MSG(rcNt == STATUS_END_OF_FILE, ("rcNt=%#x, expected %#x\n", rcNt, STATUS_END_OF_FILE));
2870# endif
2871#endif
2872 }
2873 }
2874
2875 /*
2876 * Do uncached access, must be page aligned.
2877 */
2878 uint32_t cbPage = PAGE_SIZE;
2879 memset(pbBuf, 0x66, cbBuf);
2880 if (!g_fIgnoreNoCache || hFileNoCache != NIL_RTFILE)
2881 {
2882 RTTESTI_CHECK_RC(RTFileSeek(hFileNoCache, 0, RTFILE_SEEK_BEGIN, NULL), VINF_SUCCESS);
2883 for (size_t offBuf = 0; offBuf < cbBuf; )
2884 {
2885 uint32_t const cPagesLeft = (uint32_t)((cbBuf - offBuf) / cbPage);
2886 uint32_t const cPagesToRead = RTRandU32Ex(1, cPagesLeft);
2887 size_t const cbToRead = cPagesToRead * (size_t)cbPage;
2888 size_t cbActual = 0;
2889 RTTESTI_CHECK_RC(RTFileRead(hFileNoCache, &pbBuf[offBuf], cbToRead, &cbActual), VINF_SUCCESS);
2890 if (cbActual == cbToRead)
2891 offBuf += cbActual;
2892 else
2893 {
2894 RTTestIFailed("Attempting to read %#zx bytes at %#zx, only got %#x bytes back!\n", cbToRead, offBuf, cbActual);
2895 if (cbActual)
2896 offBuf += cbActual;
2897 else
2898 {
2899 memset(&pbBuf[offBuf], 0x11, cbPage);
2900 offBuf += cbPage;
2901 }
2902 }
2903 }
2904 fsPerfCheckReadBuf(__LINE__, 0, pbBuf, cbBuf);
2905 }
2906
2907 /*
2908 * Check reading zero bytes at the end of the file.
2909 * Requires native call because RTFileWrite doesn't call kernel on zero byte reads.
2910 */
2911 RTTESTI_CHECK_RC(RTFileSeek(hFile1, 0, RTFILE_SEEK_END, NULL), VINF_SUCCESS);
2912# ifdef RT_OS_WINDOWS
2913 IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER;
2914 NTSTATUS rcNt = NtReadFile((HANDLE)RTFileToNative(hFile1), NULL, NULL, NULL, &Ios, pbBuf, 0, NULL, NULL);
2915 RTTESTI_CHECK_MSG(rcNt == STATUS_SUCCESS, ("rcNt=%#x", rcNt));
2916 RTTESTI_CHECK(Ios.Status == STATUS_SUCCESS);
2917 RTTESTI_CHECK(Ios.Information == 0);
2918
2919 RTNT_IO_STATUS_BLOCK_REINIT(&Ios);
2920 rcNt = NtReadFile((HANDLE)RTFileToNative(hFile1), NULL, NULL, NULL, &Ios, pbBuf, 1, NULL, NULL);
2921 RTTESTI_CHECK_MSG(rcNt == STATUS_END_OF_FILE, ("rcNt=%#x", rcNt));
2922 RTTESTI_CHECK(Ios.Status == STATUS_END_OF_FILE);
2923 RTTESTI_CHECK(Ios.Information == 0);
2924# else
2925 ssize_t cbRead = read((int)RTFileToNative(hFile1), pbBuf, 0);
2926 RTTESTI_CHECK(cbRead == 0);
2927# endif
2928
2929#else
2930 RT_NOREF(hFileNoCache);
2931#endif
2932
2933 /*
2934 * Scatter read function operation.
2935 */
2936#ifdef RT_OS_WINDOWS
2937 /** @todo RTFileSgReadAt is just a RTFileReadAt loop for windows NT. Need
2938 * to use ReadFileScatter (nocache + page aligned). */
2939#elif !defined(RT_OS_OS2) /** @todo implement RTFileSg using list i/o */
2940
2941# ifdef UIO_MAXIOV
2942 RTSGSEG aSegs[UIO_MAXIOV];
2943# else
2944 RTSGSEG aSegs[512];
2945# endif
2946 RTSGBUF SgBuf;
2947 uint32_t cIncr = 1;
2948 for (uint32_t cSegs = 1; cSegs <= RT_ELEMENTS(aSegs); cSegs += cIncr)
2949 {
2950 size_t const cbSeg = cbBuf / cSegs;
2951 size_t const cbToRead = cbSeg * cSegs;
2952 for (uint32_t iSeg = 0; iSeg < cSegs; iSeg++)
2953 {
2954 aSegs[iSeg].cbSeg = cbSeg;
2955 aSegs[iSeg].pvSeg = &pbBuf[cbToRead - (iSeg + 1) * cbSeg];
2956 }
2957 RTSgBufInit(&SgBuf, &aSegs[0], cSegs);
2958 int rc = myFileSgReadAt(hFile1, 0, &SgBuf, cbToRead, NULL);
2959 if (RT_SUCCESS(rc))
2960 for (uint32_t iSeg = 0; iSeg < cSegs; iSeg++)
2961 {
2962 if (!fsPerfCheckReadBuf(__LINE__, iSeg * cbSeg, &pbBuf[cbToRead - (iSeg + 1) * cbSeg], cbSeg))
2963 {
2964 cSegs = RT_ELEMENTS(aSegs);
2965 break;
2966 }
2967 }
2968 else
2969 {
2970 RTTestIFailed("myFileSgReadAt failed: %Rrc - cSegs=%u cbSegs=%#zx cbToRead=%#zx", rc, cSegs, cbSeg, cbToRead);
2971 break;
2972 }
2973 if (cSegs == 16)
2974 cIncr = 7;
2975 else if (cSegs == 16 * 7 + 16 /*= 128*/)
2976 cIncr = 64;
2977 }
2978
2979 for (uint32_t iTest = 0; iTest < 128; iTest++)
2980 {
2981 uint32_t cSegs = RTRandU32Ex(1, RT_ELEMENTS(aSegs));
2982 uint32_t iZeroSeg = cSegs > 10 ? RTRandU32Ex(0, cSegs - 1) : UINT32_MAX / 2;
2983 uint32_t cZeroSegs = cSegs > 10 ? RTRandU32Ex(1, RT_MIN(cSegs - iZeroSeg, 25)) : 0;
2984 size_t cbToRead = 0;
2985 size_t cbLeft = cbBuf;
2986 uint8_t *pbCur = &pbBuf[cbBuf];
2987 for (uint32_t iSeg = 0; iSeg < cSegs; iSeg++)
2988 {
2989 uint32_t iAlign = RTRandU32Ex(0, 3);
2990 if (iAlign & 2) /* end is page aligned */
2991 {
2992 cbLeft -= (uintptr_t)pbCur & PAGE_OFFSET_MASK;
2993 pbCur -= (uintptr_t)pbCur & PAGE_OFFSET_MASK;
2994 }
2995
2996 size_t cbSegOthers = (cSegs - iSeg) * _8K;
2997 size_t cbSegMax = cbLeft > cbSegOthers ? cbLeft - cbSegOthers
2998 : cbLeft > cSegs ? cbLeft - cSegs
2999 : cbLeft;
3000 size_t cbSeg = cbLeft != 0 ? RTRandU32Ex(0, cbSegMax) : 0;
3001 if (iAlign & 1) /* start is page aligned */
3002 cbSeg += ((uintptr_t)pbCur - cbSeg) & PAGE_OFFSET_MASK;
3003
3004 if (iSeg - iZeroSeg < cZeroSegs)
3005 cbSeg = 0;
3006
3007 cbToRead += cbSeg;
3008 cbLeft -= cbSeg;
3009 pbCur -= cbSeg;
3010 aSegs[iSeg].cbSeg = cbSeg;
3011 aSegs[iSeg].pvSeg = pbCur;
3012 }
3013
3014 uint64_t offFile = cbToRead < cbFile ? RTRandU64Ex(0, cbFile - cbToRead) : 0;
3015 RTSgBufInit(&SgBuf, &aSegs[0], cSegs);
3016 int rc = myFileSgReadAt(hFile1, offFile, &SgBuf, cbToRead, NULL);
3017 if (RT_SUCCESS(rc))
3018 for (uint32_t iSeg = 0; iSeg < cSegs; iSeg++)
3019 {
3020 if (!fsPerfCheckReadBuf(__LINE__, offFile, (uint8_t *)aSegs[iSeg].pvSeg, aSegs[iSeg].cbSeg))
3021 {
3022 RTTestIFailureDetails("iSeg=%#x cSegs=%#x cbSeg=%#zx cbToRead=%#zx\n", iSeg, cSegs, aSegs[iSeg].cbSeg, cbToRead);
3023 iTest = _16K;
3024 break;
3025 }
3026 offFile += aSegs[iSeg].cbSeg;
3027 }
3028 else
3029 {
3030 RTTestIFailed("myFileSgReadAt failed: %Rrc - cSegs=%#x cbToRead=%#zx", rc, cSegs, cbToRead);
3031 for (uint32_t iSeg = 0; iSeg < cSegs; iSeg++)
3032 RTTestIFailureDetails("aSeg[%u] = %p LB %#zx (last %p)\n", iSeg, aSegs[iSeg].pvSeg, aSegs[iSeg].cbSeg,
3033 (uint8_t *)aSegs[iSeg].pvSeg + aSegs[iSeg].cbSeg - 1);
3034 break;
3035 }
3036 }
3037
3038 /* reading beyond the end of the file */
3039 for (uint32_t cSegs = 1; cSegs < 6; cSegs++)
3040 for (uint32_t iTest = 0; iTest < 128; iTest++)
3041 {
3042 uint32_t const cbToRead = RTRandU32Ex(0, cbBuf);
3043 uint32_t const cbBeyond = cbToRead ? RTRandU32Ex(0, cbToRead) : 0;
3044 uint32_t const cbSeg = cbToRead / cSegs;
3045 uint32_t cbLeft = cbToRead;
3046 uint8_t *pbCur = &pbBuf[cbToRead];
3047 for (uint32_t iSeg = 0; iSeg < cSegs; iSeg++)
3048 {
3049 aSegs[iSeg].cbSeg = iSeg + 1 < cSegs ? cbSeg : cbLeft;
3050 aSegs[iSeg].pvSeg = pbCur -= aSegs[iSeg].cbSeg;
3051 cbLeft -= aSegs[iSeg].cbSeg;
3052 }
3053 Assert(pbCur == pbBuf);
3054
3055 uint64_t offFile = cbFile + cbBeyond - cbToRead;
3056 RTSgBufInit(&SgBuf, &aSegs[0], cSegs);
3057 int rcExpect = cbBeyond == 0 || cbToRead == 0 ? VINF_SUCCESS : VERR_EOF;
3058 int rc = myFileSgReadAt(hFile1, offFile, &SgBuf, cbToRead, NULL);
3059 if (rc != rcExpect)
3060 {
3061 RTTestIFailed("myFileSgReadAt failed: %Rrc - cSegs=%#x cbToRead=%#zx cbBeyond=%#zx\n", rc, cSegs, cbToRead, cbBeyond);
3062 for (uint32_t iSeg = 0; iSeg < cSegs; iSeg++)
3063 RTTestIFailureDetails("aSeg[%u] = %p LB %#zx (last %p)\n", iSeg, aSegs[iSeg].pvSeg, aSegs[iSeg].cbSeg,
3064 (uint8_t *)aSegs[iSeg].pvSeg + aSegs[iSeg].cbSeg - 1);
3065 }
3066
3067 RTSgBufInit(&SgBuf, &aSegs[0], cSegs);
3068 size_t cbActual = 0;
3069 rc = myFileSgReadAt(hFile1, offFile, &SgBuf, cbToRead, &cbActual);
3070 if (rc != VINF_SUCCESS || cbActual != cbToRead - cbBeyond)
3071 RTTestIFailed("myFileSgReadAt failed: %Rrc cbActual=%#zu - cSegs=%#x cbToRead=%#zx cbBeyond=%#zx expected %#zx\n",
3072 rc, cbActual, cSegs, cbToRead, cbBeyond, cbToRead - cbBeyond);
3073 if (RT_SUCCESS(rc) && cbActual > 0)
3074 for (uint32_t iSeg = 0; iSeg < cSegs; iSeg++)
3075 {
3076 if (!fsPerfCheckReadBuf(__LINE__, offFile, (uint8_t *)aSegs[iSeg].pvSeg, RT_MIN(cbActual, aSegs[iSeg].cbSeg)))
3077 {
3078 RTTestIFailureDetails("iSeg=%#x cSegs=%#x cbSeg=%#zx cbActual%#zx cbToRead=%#zx cbBeyond=%#zx\n",
3079 iSeg, cSegs, aSegs[iSeg].cbSeg, cbActual, cbToRead, cbBeyond);
3080 iTest = _16K;
3081 break;
3082 }
3083 if (cbActual <= aSegs[iSeg].cbSeg)
3084 break;
3085 cbActual -= aSegs[iSeg].cbSeg;
3086 offFile += aSegs[iSeg].cbSeg;
3087 }
3088 }
3089
3090#endif
3091
3092 /*
3093 * Other OS specific stuff.
3094 */
3095#ifdef RT_OS_WINDOWS
3096 /* Check that reading at an offset modifies the position: */
3097 RTTESTI_CHECK_RC(RTFileSeek(hFile1, 0, RTFILE_SEEK_END, NULL), VINF_SUCCESS);
3098 RTTESTI_CHECK(RTFileTell(hFile1) == cbFile);
3099
3100 RTNT_IO_STATUS_BLOCK_REINIT(&Ios);
3101 LARGE_INTEGER offNt;
3102 offNt.QuadPart = cbFile / 2;
3103 rcNt = NtReadFile((HANDLE)RTFileToNative(hFile1), NULL, NULL, NULL, &Ios, pbBuf, _4K, &offNt, NULL);
3104 RTTESTI_CHECK_MSG(rcNt == STATUS_SUCCESS, ("rcNt=%#x", rcNt));
3105 RTTESTI_CHECK(Ios.Status == STATUS_SUCCESS);
3106 RTTESTI_CHECK(Ios.Information == _4K);
3107 RTTESTI_CHECK(RTFileTell(hFile1) == cbFile / 2 + _4K);
3108 fsPerfCheckReadBuf(__LINE__, cbFile / 2, pbBuf, _4K);
3109#endif
3110
3111
3112 RTMemPageFree(pbBuf, cbBuf);
3113}
3114
3115
3116/**
3117 * One RTFileWrite profiling iteration.
3118 */
3119DECL_FORCE_INLINE(int) fsPerfIoWriteWorker(RTFILE hFile1, uint64_t cbFile, uint32_t cbBlock, uint8_t *pbBlock,
3120 uint64_t *poffActual, uint32_t *pcSeeks)
3121{
3122 /* Do we need to seek back to the start? */
3123 if (*poffActual + cbBlock <= cbFile)
3124 { /* likely */ }
3125 else
3126 {
3127 RTTESTI_CHECK_RC_RET(RTFileSeek(hFile1, 0, RTFILE_SEEK_BEGIN, NULL), VINF_SUCCESS, rcCheck);
3128 *pcSeeks += 1;
3129 *poffActual = 0;
3130 }
3131
3132 size_t cbActuallyWritten = 0;
3133 RTTESTI_CHECK_RC_RET(RTFileWrite(hFile1, pbBlock, cbBlock, &cbActuallyWritten), VINF_SUCCESS, rcCheck);
3134 if (cbActuallyWritten == cbBlock)
3135 {
3136 *poffActual += cbActuallyWritten;
3137 return VINF_SUCCESS;
3138 }
3139 RTTestIFailed("RTFileWrite at %#RX64 returned just %#x bytes, expected %#x", *poffActual, cbActuallyWritten, cbBlock);
3140 *poffActual += cbActuallyWritten;
3141 return VERR_WRITE_ERROR;
3142}
3143
3144
3145void fsPerfIoWriteBlockSize(RTFILE hFile1, uint64_t cbFile, uint32_t cbBlock)
3146{
3147 RTTestISubF("IO - Sequential write %RU32", cbBlock);
3148
3149 uint8_t *pbBuf = (uint8_t *)RTMemPageAlloc(cbBlock);
3150 if (pbBuf)
3151 {
3152 memset(pbBuf, 0xf7, cbBlock);
3153 PROFILE_IO_FN("RTFileWrite", fsPerfIoWriteWorker(hFile1, cbFile, cbBlock, pbBuf, &offActual, &cSeeks));
3154 RTMemPageFree(pbBuf, cbBlock);
3155 }
3156 else
3157 RTTestSkipped(g_hTest, "insufficient (virtual) memory available");
3158}
3159
3160
3161/** pwritev is too new to be useful, so we use the writev api via this wrapper. */
3162DECLINLINE(int) myFileSgWriteAt(RTFILE hFile, RTFOFF off, PRTSGBUF pSgBuf, size_t cbToWrite, size_t *pcbWritten)
3163{
3164 int rc = RTFileSeek(hFile, off, RTFILE_SEEK_BEGIN, NULL);
3165 if (RT_SUCCESS(rc))
3166 rc = RTFileSgWrite(hFile, pSgBuf, cbToWrite, pcbWritten);
3167 return rc;
3168}
3169
3170
3171void fsPerfWrite(RTFILE hFile1, RTFILE hFileNoCache, RTFILE hFileWriteThru, uint64_t cbFile)
3172{
3173 RTTestISubF("IO - RTFileWrite");
3174
3175 /*
3176 * Allocate a big buffer we can play around with. Min size is 1MB.
3177 */
3178 size_t cbBuf = cbFile < _64M ? (size_t)cbFile : _64M;
3179 uint8_t *pbBuf = (uint8_t *)RTMemPageAlloc(cbBuf);
3180 while (!pbBuf)
3181 {
3182 cbBuf /= 2;
3183 RTTESTI_CHECK_RETV(cbBuf >= _1M);
3184 pbBuf = (uint8_t *)RTMemPageAlloc(_32M);
3185 }
3186
3187 uint8_t bFiller = 0x88;
3188
3189#if 1
3190 /*
3191 * Start at the beginning and write out the full buffer in random small chunks, thereby
3192 * checking that unaligned buffer addresses, size and file offsets work fine.
3193 */
3194 struct
3195 {
3196 uint64_t offFile;
3197 uint32_t cbMax;
3198 } aRuns[] = { { 0, 127 }, { cbFile - cbBuf, UINT32_MAX }, { 0, UINT32_MAX -1 }};
3199 for (uint32_t i = 0; i < RT_ELEMENTS(aRuns); i++, bFiller)
3200 {
3201 fsPerfFillWriteBuf(aRuns[i].offFile, pbBuf, cbBuf, bFiller);
3202 fsPerfCheckReadBuf(__LINE__, aRuns[i].offFile, pbBuf, cbBuf, bFiller);
3203
3204 RTTESTI_CHECK_RC(RTFileSeek(hFile1, aRuns[i].offFile, RTFILE_SEEK_BEGIN, NULL), VINF_SUCCESS);
3205 for (size_t offBuf = 0; offBuf < cbBuf; )
3206 {
3207 uint32_t const cbLeft = (uint32_t)(cbBuf - offBuf);
3208 uint32_t const cbToWrite = aRuns[i].cbMax < UINT32_MAX / 2 ? RTRandU32Ex(1, RT_MIN(aRuns[i].cbMax, cbLeft))
3209 : aRuns[i].cbMax == UINT32_MAX ? RTRandU32Ex(RT_MAX(cbLeft / 4, 1), cbLeft)
3210 : RTRandU32Ex(cbLeft >= _8K ? _8K : 1, RT_MIN(_1M, cbLeft));
3211 size_t cbActual = 0;
3212 RTTESTI_CHECK_RC(RTFileWrite(hFile1, &pbBuf[offBuf], cbToWrite, &cbActual), VINF_SUCCESS);
3213 if (cbActual == cbToWrite)
3214 {
3215 offBuf += cbActual;
3216 RTTESTI_CHECK_MSG(RTFileTell(hFile1) == aRuns[i].offFile + offBuf,
3217 ("%#RX64, expected %#RX64\n", RTFileTell(hFile1), aRuns[i].offFile + offBuf));
3218 }
3219 else
3220 {
3221 RTTestIFailed("Attempting to write %#x bytes at %#zx (%#x left), only got %#x written!\n",
3222 cbToWrite, offBuf, cbLeft, cbActual);
3223 if (cbActual)
3224 offBuf += cbActual;
3225 else
3226 pbBuf[offBuf++] = 0x11;
3227 }
3228 }
3229
3230 RTTESTI_CHECK_RC(RTFileReadAt(hFile1, aRuns[i].offFile, pbBuf, cbBuf, NULL), VINF_SUCCESS);
3231 fsPerfCheckReadBuf(__LINE__, aRuns[i].offFile, pbBuf, cbBuf, bFiller);
3232 }
3233
3234
3235 /*
3236 * Do uncached and write-thru accesses, must be page aligned.
3237 */
3238 RTFILE ahFiles[2] = { hFileWriteThru, hFileNoCache };
3239 for (unsigned iFile = 0; iFile < RT_ELEMENTS(ahFiles); iFile++, bFiller++)
3240 {
3241 if (g_fIgnoreNoCache && ahFiles[iFile] == NIL_RTFILE)
3242 continue;
3243
3244 fsPerfFillWriteBuf(0, pbBuf, cbBuf, bFiller);
3245 fsPerfCheckReadBuf(__LINE__, 0, pbBuf, cbBuf, bFiller);
3246 RTTESTI_CHECK_RC(RTFileSeek(ahFiles[iFile], 0, RTFILE_SEEK_BEGIN, NULL), VINF_SUCCESS);
3247
3248 uint32_t cbPage = PAGE_SIZE;
3249 for (size_t offBuf = 0; offBuf < cbBuf; )
3250 {
3251 uint32_t const cPagesLeft = (uint32_t)((cbBuf - offBuf) / cbPage);
3252 uint32_t const cPagesToWrite = RTRandU32Ex(1, cPagesLeft);
3253 size_t const cbToWrite = cPagesToWrite * (size_t)cbPage;
3254 size_t cbActual = 0;
3255 RTTESTI_CHECK_RC(RTFileWrite(ahFiles[iFile], &pbBuf[offBuf], cbToWrite, &cbActual), VINF_SUCCESS);
3256 if (cbActual == cbToWrite)
3257 {
3258 RTTESTI_CHECK_RC(RTFileReadAt(hFile1, offBuf, pbBuf, cbToWrite, NULL), VINF_SUCCESS);
3259 fsPerfCheckReadBuf(__LINE__, offBuf, pbBuf, cbToWrite, bFiller);
3260 offBuf += cbActual;
3261 }
3262 else
3263 {
3264 RTTestIFailed("Attempting to read %#zx bytes at %#zx, only got %#x written!\n", cbToWrite, offBuf, cbActual);
3265 if (cbActual)
3266 offBuf += cbActual;
3267 else
3268 {
3269 memset(&pbBuf[offBuf], 0x11, cbPage);
3270 offBuf += cbPage;
3271 }
3272 }
3273 }
3274
3275 RTTESTI_CHECK_RC(RTFileReadAt(ahFiles[iFile], 0, pbBuf, cbBuf, NULL), VINF_SUCCESS);
3276 fsPerfCheckReadBuf(__LINE__, 0, pbBuf, cbBuf, bFiller);
3277 }
3278
3279 /*
3280 * Check the behavior of writing zero bytes to the file _4K from the end
3281 * using native API. In the olden days zero sized write have been known
3282 * to be used to truncate a file.
3283 */
3284 RTTESTI_CHECK_RC(RTFileSeek(hFile1, -_4K, RTFILE_SEEK_END, NULL), VINF_SUCCESS);
3285# ifdef RT_OS_WINDOWS
3286 IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER;
3287 NTSTATUS rcNt = NtWriteFile((HANDLE)RTFileToNative(hFile1), NULL, NULL, NULL, &Ios, pbBuf, 0, NULL, NULL);
3288 RTTESTI_CHECK_MSG(rcNt == STATUS_SUCCESS, ("rcNt=%#x", rcNt));
3289 RTTESTI_CHECK(Ios.Status == STATUS_SUCCESS);
3290 RTTESTI_CHECK(Ios.Information == 0);
3291# else
3292 ssize_t cbWritten = write((int)RTFileToNative(hFile1), pbBuf, 0);
3293 RTTESTI_CHECK(cbWritten == 0);
3294# endif
3295 RTTESTI_CHECK_RC(RTFileRead(hFile1, pbBuf, _4K, NULL), VINF_SUCCESS);
3296 fsPerfCheckReadBuf(__LINE__, cbFile - _4K, pbBuf, _4K, pbBuf[0x8]);
3297
3298#else
3299 RT_NOREF(hFileNoCache, hFileWriteThru);
3300#endif
3301
3302 /*
3303 * Gather write function operation.
3304 */
3305#ifdef RT_OS_WINDOWS
3306 /** @todo RTFileSgWriteAt is just a RTFileWriteAt loop for windows NT. Need
3307 * to use WriteFileGather (nocache + page aligned). */
3308#elif !defined(RT_OS_OS2) /** @todo implement RTFileSg using list i/o */
3309
3310# ifdef UIO_MAXIOV
3311 RTSGSEG aSegs[UIO_MAXIOV];
3312# else
3313 RTSGSEG aSegs[512];
3314# endif
3315 RTSGBUF SgBuf;
3316 uint32_t cIncr = 1;
3317 for (uint32_t cSegs = 1; cSegs <= RT_ELEMENTS(aSegs); cSegs += cIncr, bFiller++)
3318 {
3319 size_t const cbSeg = cbBuf / cSegs;
3320 size_t const cbToWrite = cbSeg * cSegs;
3321 for (uint32_t iSeg = 0; iSeg < cSegs; iSeg++)
3322 {
3323 aSegs[iSeg].cbSeg = cbSeg;
3324 aSegs[iSeg].pvSeg = &pbBuf[cbToWrite - (iSeg + 1) * cbSeg];
3325 fsPerfFillWriteBuf(iSeg * cbSeg, (uint8_t *)aSegs[iSeg].pvSeg, cbSeg, bFiller);
3326 }
3327 RTSgBufInit(&SgBuf, &aSegs[0], cSegs);
3328 int rc = myFileSgWriteAt(hFile1, 0, &SgBuf, cbToWrite, NULL);
3329 if (RT_SUCCESS(rc))
3330 {
3331 RTTESTI_CHECK_RC(RTFileReadAt(hFile1, 0, pbBuf, cbToWrite, NULL), VINF_SUCCESS);
3332 fsPerfCheckReadBuf(__LINE__, 0, pbBuf, cbToWrite, bFiller);
3333 }
3334 else
3335 {
3336 RTTestIFailed("myFileSgWriteAt failed: %Rrc - cSegs=%u cbSegs=%#zx cbToWrite=%#zx", rc, cSegs, cbSeg, cbToWrite);
3337 break;
3338 }
3339 if (cSegs == 16)
3340 cIncr = 7;
3341 else if (cSegs == 16 * 7 + 16 /*= 128*/)
3342 cIncr = 64;
3343 }
3344
3345 /* random stuff, including zero segments. */
3346 for (uint32_t iTest = 0; iTest < 128; iTest++, bFiller++)
3347 {
3348 uint32_t cSegs = RTRandU32Ex(1, RT_ELEMENTS(aSegs));
3349 uint32_t iZeroSeg = cSegs > 10 ? RTRandU32Ex(0, cSegs - 1) : UINT32_MAX / 2;
3350 uint32_t cZeroSegs = cSegs > 10 ? RTRandU32Ex(1, RT_MIN(cSegs - iZeroSeg, 25)) : 0;
3351 size_t cbToWrite = 0;
3352 size_t cbLeft = cbBuf;
3353 uint8_t *pbCur = &pbBuf[cbBuf];
3354 for (uint32_t iSeg = 0; iSeg < cSegs; iSeg++)
3355 {
3356 uint32_t iAlign = RTRandU32Ex(0, 3);
3357 if (iAlign & 2) /* end is page aligned */
3358 {
3359 cbLeft -= (uintptr_t)pbCur & PAGE_OFFSET_MASK;
3360 pbCur -= (uintptr_t)pbCur & PAGE_OFFSET_MASK;
3361 }
3362
3363 size_t cbSegOthers = (cSegs - iSeg) * _8K;
3364 size_t cbSegMax = cbLeft > cbSegOthers ? cbLeft - cbSegOthers
3365 : cbLeft > cSegs ? cbLeft - cSegs
3366 : cbLeft;
3367 size_t cbSeg = cbLeft != 0 ? RTRandU32Ex(0, cbSegMax) : 0;
3368 if (iAlign & 1) /* start is page aligned */
3369 cbSeg += ((uintptr_t)pbCur - cbSeg) & PAGE_OFFSET_MASK;
3370
3371 if (iSeg - iZeroSeg < cZeroSegs)
3372 cbSeg = 0;
3373
3374 cbToWrite += cbSeg;
3375 cbLeft -= cbSeg;
3376 pbCur -= cbSeg;
3377 aSegs[iSeg].cbSeg = cbSeg;
3378 aSegs[iSeg].pvSeg = pbCur;
3379 }
3380
3381 uint64_t const offFile = cbToWrite < cbFile ? RTRandU64Ex(0, cbFile - cbToWrite) : 0;
3382 uint64_t offFill = offFile;
3383 for (uint32_t iSeg = 0; iSeg < cSegs; iSeg++)
3384 if (aSegs[iSeg].cbSeg)
3385 {
3386 fsPerfFillWriteBuf(offFill, (uint8_t *)aSegs[iSeg].pvSeg, aSegs[iSeg].cbSeg, bFiller);
3387 offFill += aSegs[iSeg].cbSeg;
3388 }
3389
3390 RTSgBufInit(&SgBuf, &aSegs[0], cSegs);
3391 int rc = myFileSgWriteAt(hFile1, offFile, &SgBuf, cbToWrite, NULL);
3392 if (RT_SUCCESS(rc))
3393 {
3394 RTTESTI_CHECK_RC(RTFileReadAt(hFile1, offFile, pbBuf, cbToWrite, NULL), VINF_SUCCESS);
3395 fsPerfCheckReadBuf(__LINE__, offFile, pbBuf, cbToWrite, bFiller);
3396 }
3397 else
3398 {
3399 RTTestIFailed("myFileSgWriteAt failed: %Rrc - cSegs=%#x cbToWrite=%#zx", rc, cSegs, cbToWrite);
3400 break;
3401 }
3402 }
3403
3404#endif
3405
3406 /*
3407 * Other OS specific stuff.
3408 */
3409#ifdef RT_OS_WINDOWS
3410 /* Check that reading at an offset modifies the position: */
3411 RTTESTI_CHECK_RC(RTFileReadAt(hFile1, cbFile / 2, pbBuf, _4K, NULL), VINF_SUCCESS);
3412 RTTESTI_CHECK_RC(RTFileSeek(hFile1, 0, RTFILE_SEEK_END, NULL), VINF_SUCCESS);
3413 RTTESTI_CHECK(RTFileTell(hFile1) == cbFile);
3414
3415 RTNT_IO_STATUS_BLOCK_REINIT(&Ios);
3416 LARGE_INTEGER offNt;
3417 offNt.QuadPart = cbFile / 2;
3418 rcNt = NtWriteFile((HANDLE)RTFileToNative(hFile1), NULL, NULL, NULL, &Ios, pbBuf, _4K, &offNt, NULL);
3419 RTTESTI_CHECK_MSG(rcNt == STATUS_SUCCESS, ("rcNt=%#x", rcNt));
3420 RTTESTI_CHECK(Ios.Status == STATUS_SUCCESS);
3421 RTTESTI_CHECK(Ios.Information == _4K);
3422 RTTESTI_CHECK(RTFileTell(hFile1) == cbFile / 2 + _4K);
3423#endif
3424
3425 RTMemPageFree(pbBuf, cbBuf);
3426}
3427
3428
3429/**
3430 * Worker for testing RTFileFlush.
3431 */
3432DECL_FORCE_INLINE(int) fsPerfFSyncWorker(RTFILE hFile1, uint64_t cbFile, uint8_t *pbBuf, size_t cbBuf, uint64_t *poffFile)
3433{
3434 if (*poffFile + cbBuf <= cbFile)
3435 { /* likely */ }
3436 else
3437 {
3438 RTTESTI_CHECK_RC(RTFileSeek(hFile1, 0, RTFILE_SEEK_BEGIN, NULL), VINF_SUCCESS);
3439 *poffFile = 0;
3440 }
3441
3442 RTTESTI_CHECK_RC_RET(RTFileWrite(hFile1, pbBuf, cbBuf, NULL), VINF_SUCCESS, rcCheck);
3443 RTTESTI_CHECK_RC_RET(RTFileFlush(hFile1), VINF_SUCCESS, rcCheck);
3444
3445 *poffFile += cbBuf;
3446 return VINF_SUCCESS;
3447}
3448
3449
3450void fsPerfFSync(RTFILE hFile1, uint64_t cbFile)
3451{
3452 RTTestISub("fsync");
3453
3454 RTTESTI_CHECK_RC(RTFileFlush(hFile1), VINF_SUCCESS);
3455
3456 PROFILE_FN(RTFileFlush(hFile1), g_nsTestRun, "RTFileFlush");
3457
3458 size_t cbBuf = PAGE_SIZE;
3459 uint8_t *pbBuf = (uint8_t *)RTMemPageAlloc(cbBuf);
3460 RTTESTI_CHECK_RETV(pbBuf != NULL);
3461 memset(pbBuf, 0xf4, cbBuf);
3462
3463 RTTESTI_CHECK_RC(RTFileSeek(hFile1, 0, RTFILE_SEEK_BEGIN, NULL), VINF_SUCCESS);
3464 uint64_t offFile = 0;
3465 PROFILE_FN(fsPerfFSyncWorker(hFile1, cbFile, pbBuf, cbBuf, &offFile), g_nsTestRun, "RTFileWrite[Page]/RTFileFlush");
3466
3467 RTMemPageFree(pbBuf, cbBuf);
3468}
3469
3470
3471#ifndef RT_OS_OS2
3472/**
3473 * Worker for profiling msync.
3474 */
3475DECL_FORCE_INLINE(int) fsPerfMSyncWorker(uint8_t *pbMapping, size_t offMapping, size_t cbFlush, size_t *pcbFlushed)
3476{
3477 uint8_t *pbCur = &pbMapping[offMapping];
3478 for (size_t offFlush = 0; offFlush < cbFlush; offFlush += PAGE_SIZE)
3479 *(size_t volatile *)&pbCur[offFlush + 8] = cbFlush;
3480# ifdef RT_OS_WINDOWS
3481 RTTESTI_CHECK(FlushViewOfFile(pbCur, cbFlush));
3482# else
3483 RTTESTI_CHECK(msync(pbCur, cbFlush, MS_SYNC) == 0);
3484# endif
3485 if (*pcbFlushed < offMapping + cbFlush)
3486 *pcbFlushed = offMapping + cbFlush;
3487 return VINF_SUCCESS;
3488}
3489#endif /* !RT_OS_OS2 */
3490
3491
3492void fsPerfMMap(RTFILE hFile1, RTFILE hFileNoCache, uint64_t cbFile)
3493{
3494 RTTestISub("mmap");
3495#if !defined(RT_OS_OS2)
3496 static const char * const s_apszStates[] = { "readonly", "writecopy", "readwrite" };
3497 enum { kMMap_ReadOnly = 0, kMMap_WriteCopy, kMMap_ReadWrite, kMMap_End };
3498 for (int enmState = kMMap_ReadOnly; enmState < kMMap_End; enmState++)
3499 {
3500 /*
3501 * Do the mapping.
3502 */
3503 size_t cbMapping = (size_t)cbFile;
3504 if (cbMapping != cbFile)
3505 cbMapping = _256M;
3506 uint8_t *pbMapping;
3507
3508# ifdef RT_OS_WINDOWS
3509 HANDLE hSection;
3510 pbMapping = NULL;
3511 for (;; cbMapping /= 2)
3512 {
3513 hSection = CreateFileMapping((HANDLE)RTFileToNative(hFile1), NULL,
3514 enmState == kMMap_ReadOnly ? PAGE_READONLY
3515 : enmState == kMMap_WriteCopy ? PAGE_WRITECOPY : PAGE_READWRITE,
3516 (uint32_t)((uint64_t)cbMapping >> 32), (uint32_t)cbMapping, NULL);
3517 DWORD dwErr1 = GetLastError();
3518 DWORD dwErr2 = 0;
3519 if (hSection != NULL)
3520 {
3521 pbMapping = (uint8_t *)MapViewOfFile(hSection,
3522 enmState == kMMap_ReadOnly ? FILE_MAP_READ
3523 : enmState == kMMap_WriteCopy ? FILE_MAP_COPY
3524 : FILE_MAP_WRITE,
3525 0, 0, cbMapping);
3526 if (pbMapping)
3527 break;
3528 dwErr2 = GetLastError();
3529 CloseHandle(hSection);
3530 }
3531 if (cbMapping <= _2M)
3532 {
3533 RTTestIFailed("%u/%s: CreateFileMapping or MapViewOfFile failed: %u, %u",
3534 enmState, s_apszStates[enmState], dwErr1, dwErr2);
3535 break;
3536 }
3537 }
3538# else
3539 for (;; cbMapping /= 2)
3540 {
3541 pbMapping = (uint8_t *)mmap(NULL, cbMapping,
3542 enmState == kMMap_ReadOnly ? PROT_READ : PROT_READ | PROT_WRITE,
3543 enmState == kMMap_WriteCopy ? MAP_PRIVATE : MAP_SHARED,
3544 (int)RTFileToNative(hFile1), 0);
3545 if ((void *)pbMapping != MAP_FAILED)
3546 break;
3547 if (cbMapping <= _2M)
3548 {
3549 RTTestIFailed("%u/%s: mmap failed: %s (%u)", enmState, s_apszStates[enmState], strerror(errno), errno);
3550 break;
3551 }
3552 }
3553# endif
3554 if (cbMapping <= _2M)
3555 continue;
3556
3557 /*
3558 * Time page-ins just for fun.
3559 */
3560 size_t const cPages = cbMapping >> PAGE_SHIFT;
3561 size_t uDummy = 0;
3562 uint64_t ns = RTTimeNanoTS();
3563 for (size_t iPage = 0; iPage < cPages; iPage++)
3564 uDummy += ASMAtomicReadU8(&pbMapping[iPage << PAGE_SHIFT]);
3565 ns = RTTimeNanoTS() - ns;
3566 RTTestIValueF(ns / cPages, RTTESTUNIT_NS_PER_OCCURRENCE, "page-in %s", s_apszStates[enmState]);
3567
3568 /* Check the content. */
3569 fsPerfCheckReadBuf(__LINE__, 0, pbMapping, cbMapping);
3570
3571 if (enmState != kMMap_ReadOnly)
3572 {
3573 /* Write stuff to the first two megabytes. In the COW case, we'll detect
3574 corruption of shared data during content checking of the RW iterations. */
3575 fsPerfFillWriteBuf(0, pbMapping, _2M, 0xf7);
3576 if (enmState == kMMap_ReadWrite)
3577 {
3578 /* For RW we can try read back from the file handle and check if we get
3579 a match there first. */
3580 uint8_t abBuf[_4K];
3581 for (uint32_t off = 0; off < _2M; off += sizeof(abBuf))
3582 {
3583 RTTESTI_CHECK_RC(RTFileReadAt(hFile1, off, abBuf, sizeof(abBuf), NULL), VINF_SUCCESS);
3584 fsPerfCheckReadBuf(__LINE__, off, abBuf, sizeof(abBuf), 0xf7);
3585 }
3586# ifdef RT_OS_WINDOWS
3587 RTTESTI_CHECK(FlushViewOfFile(pbMapping, _2M));
3588# else
3589 RTTESTI_CHECK(msync(pbMapping, _2M, MS_SYNC) == 0);
3590# endif
3591
3592 /*
3593 * Time modifying and flushing a few different number of pages.
3594 */
3595 static size_t const s_acbFlush[] = { PAGE_SIZE, PAGE_SIZE * 2, PAGE_SIZE * 3, PAGE_SIZE * 8, PAGE_SIZE * 16, _2M };
3596 for (unsigned iFlushSize = 0 ; iFlushSize < RT_ELEMENTS(s_acbFlush); iFlushSize++)
3597 {
3598 size_t const cbFlush = s_acbFlush[iFlushSize];
3599 if (cbFlush > cbMapping)
3600 continue;
3601
3602 char szDesc[80];
3603 RTStrPrintf(szDesc, sizeof(szDesc), "touch/flush/%zu", cbFlush);
3604 size_t const cFlushes = cbMapping / cbFlush;
3605 size_t const cbMappingUsed = cFlushes * cbFlush;
3606 size_t cbFlushed = 0;
3607 PROFILE_FN(fsPerfMSyncWorker(pbMapping, (iIteration * cbFlush) % cbMappingUsed, cbFlush, &cbFlushed),
3608 g_nsTestRun, szDesc);
3609
3610 /*
3611 * Check that all the changes made it thru to the file:
3612 */
3613 if (!g_fIgnoreNoCache || hFileNoCache != NIL_RTFILE)
3614 {
3615 size_t cbBuf = _2M;
3616 uint8_t *pbBuf = (uint8_t *)RTMemPageAlloc(cbBuf);
3617 if (!pbBuf)
3618 {
3619 cbBuf = _4K;
3620 pbBuf = (uint8_t *)RTMemPageAlloc(cbBuf);
3621 }
3622 RTTESTI_CHECK(pbBuf != NULL);
3623 if (pbBuf)
3624 {
3625 RTTESTI_CHECK_RC(RTFileSeek(hFileNoCache, 0, RTFILE_SEEK_BEGIN, NULL), VINF_SUCCESS);
3626 size_t const cbToCheck = RT_MIN(cFlushes * cbFlush, cbFlushed);
3627 unsigned cErrors = 0;
3628 for (size_t offBuf = 0; cErrors < 32 && offBuf < cbToCheck; offBuf += cbBuf)
3629 {
3630 size_t cbToRead = RT_MIN(cbBuf, cbToCheck - offBuf);
3631 RTTESTI_CHECK_RC(RTFileRead(hFileNoCache, pbBuf, cbToRead, NULL), VINF_SUCCESS);
3632
3633 for (size_t offFlush = 0; offFlush < cbToRead; offFlush += PAGE_SIZE)
3634 if (*(size_t volatile *)&pbBuf[offFlush + 8] != cbFlush)
3635 {
3636 RTTestIFailed("Flush issue at offset #%zx: %#zx, expected %#zx (cbFlush=%#zx, %#RX64)",
3637 offBuf + offFlush + 8, *(size_t volatile *)&pbBuf[offFlush + 8],
3638 cbFlush, cbFlush, *(uint64_t volatile *)&pbBuf[offFlush]);
3639 if (++cErrors > 32)
3640 break;
3641 }
3642 }
3643 RTMemPageFree(pbBuf, cbBuf);
3644 }
3645 }
3646 }
3647
3648# if 0 /* not needed, very very slow */
3649 /*
3650 * Restore the file to 0xf6 state for the next test.
3651 */
3652 RTTestIPrintf(RTTESTLVL_ALWAYS, "Restoring content...\n");
3653 fsPerfFillWriteBuf(0, pbMapping, cbMapping, 0xf6);
3654# ifdef RT_OS_WINDOWS
3655 RTTESTI_CHECK(FlushViewOfFile(pbMapping, cbMapping));
3656# else
3657 RTTESTI_CHECK(msync(pbMapping, cbMapping, MS_SYNC) == 0);
3658# endif
3659 RTTestIPrintf(RTTESTLVL_ALWAYS, "... done\n");
3660# endif
3661 }
3662 }
3663
3664 /*
3665 * Observe how regular writes affects a read-only or readwrite mapping.
3666 * These should ideally be immediately visible in the mapping, at least
3667 * when not performed thru an no-cache handle.
3668 */
3669 if (enmState == kMMap_ReadOnly || enmState == kMMap_ReadWrite)
3670 {
3671 size_t cbBuf = RT_MIN(_2M, cbMapping / 2);
3672 uint8_t *pbBuf = (uint8_t *)RTMemPageAlloc(cbBuf);
3673 if (!pbBuf)
3674 {
3675 cbBuf = _4K;
3676 pbBuf = (uint8_t *)RTMemPageAlloc(cbBuf);
3677 }
3678 RTTESTI_CHECK(pbBuf != NULL);
3679 if (pbBuf)
3680 {
3681 /* Do a number of random writes to the file (using hFile1).
3682 Immediately undoing them. */
3683 for (uint32_t i = 0; i < 128; i++)
3684 {
3685 /* Generate a randomly sized write at a random location, making
3686 sure it differs from whatever is there already before writing. */
3687 uint32_t const cbToWrite = RTRandU32Ex(1, (uint32_t)cbBuf);
3688 uint64_t const offToWrite = RTRandU64Ex(0, cbMapping - cbToWrite);
3689
3690 fsPerfFillWriteBuf(offToWrite, pbBuf, cbToWrite, 0xf8);
3691 pbBuf[0] = ~pbBuf[0];
3692 if (cbToWrite > 1)
3693 pbBuf[cbToWrite - 1] = ~pbBuf[cbToWrite - 1];
3694 RTTESTI_CHECK_RC(RTFileWriteAt(hFile1, offToWrite, pbBuf, cbToWrite, NULL), VINF_SUCCESS);
3695
3696 /* Check the mapping. */
3697 if (memcmp(&pbMapping[(size_t)offToWrite], pbBuf, cbToWrite) != 0)
3698 {
3699 RTTestIFailed("Write #%u @ %#RX64 LB %#x was not reflected in the mapping!\n", i, offToWrite, cbToWrite);
3700 }
3701
3702 /* Restore */
3703 fsPerfFillWriteBuf(offToWrite, pbBuf, cbToWrite, 0xf6);
3704 RTTESTI_CHECK_RC(RTFileWriteAt(hFile1, offToWrite, pbBuf, cbToWrite, NULL), VINF_SUCCESS);
3705 }
3706
3707 RTMemPageFree(pbBuf, cbBuf);
3708 }
3709 }
3710
3711 /*
3712 * Unmap it.
3713 */
3714# ifdef RT_OS_WINDOWS
3715 RTTESTI_CHECK(UnmapViewOfFile(pbMapping));
3716 RTTESTI_CHECK(CloseHandle(hSection));
3717# else
3718 RTTESTI_CHECK(munmap(pbMapping, cbMapping) == 0);
3719# endif
3720 }
3721
3722 /*
3723 * Memory mappings without open handles (pretty common).
3724 */
3725 for (uint32_t i = 0; i < 32; i++)
3726 {
3727 /* Create a new file, 256 KB in size, and fill it with random bytes.
3728 Try uncached access if we can to force the page-in to do actual reads. */
3729 char szFile2[RTPATH_MAX + 32];
3730 memcpy(szFile2, g_szDir, g_cchDir);
3731 RTStrPrintf(&szFile2[g_cchDir], sizeof(szFile2) - g_cchDir, "mmap-%u.noh", i);
3732 RTFILE hFile2 = NIL_RTFILE;
3733 int rc = (i & 3) == 3 ? VERR_TRY_AGAIN
3734 : RTFileOpen(&hFile2, szFile2, RTFILE_O_READWRITE | RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE | RTFILE_O_NO_CACHE);
3735 if (RT_FAILURE(rc))
3736 {
3737 RTTESTI_CHECK_RC_BREAK(RTFileOpen(&hFile2, szFile2, RTFILE_O_READWRITE | RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE),
3738 VINF_SUCCESS);
3739 }
3740
3741 static char s_abContent[256*1024];
3742 RTRandBytes(s_abContent, sizeof(s_abContent));
3743 RTTESTI_CHECK_RC(RTFileWrite(hFile2, s_abContent, sizeof(s_abContent), NULL), VINF_SUCCESS);
3744 RTTESTI_CHECK_RC(RTFileClose(hFile2), VINF_SUCCESS);
3745
3746 /* Reopen the file with normal caching. Every second time, we also
3747 does a read-only open of it to confuse matters. */
3748 RTFILE hFile3 = NIL_RTFILE;
3749 if ((i & 3) == 3)
3750 RTTESTI_CHECK_RC(RTFileOpen(&hFile3, szFile2, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE), VINF_SUCCESS);
3751 hFile2 = NIL_RTFILE;
3752 RTTESTI_CHECK_RC_BREAK(RTFileOpen(&hFile2, szFile2, RTFILE_O_READWRITE | RTFILE_O_OPEN | RTFILE_O_DENY_NONE),
3753 VINF_SUCCESS);
3754 if ((i & 3) == 1)
3755 RTTESTI_CHECK_RC(RTFileOpen(&hFile3, szFile2, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE), VINF_SUCCESS);
3756
3757 /* Memory map it read-write (no COW). */
3758#ifdef RT_OS_WINDOWS
3759 HANDLE hSection = CreateFileMapping((HANDLE)RTFileToNative(hFile2), NULL, PAGE_READWRITE, 0, sizeof(s_abContent), NULL);
3760 RTTESTI_CHECK_MSG(hSection != NULL, ("last error %u\n", GetLastError));
3761 uint8_t *pbMapping = (uint8_t *)MapViewOfFile(hSection, FILE_MAP_WRITE, 0, 0, sizeof(s_abContent));
3762 RTTESTI_CHECK_MSG(pbMapping != NULL, ("last error %u\n", GetLastError));
3763 RTTESTI_CHECK_MSG(CloseHandle(hSection), ("last error %u\n", GetLastError));
3764# else
3765 uint8_t *pbMapping = (uint8_t *)mmap(NULL, sizeof(s_abContent), PROT_READ | PROT_WRITE, MAP_SHARED,
3766 (int)RTFileToNative(hFile2), 0);
3767 if ((void *)pbMapping == MAP_FAILED)
3768 pbMapping = NULL;
3769 RTTESTI_CHECK_MSG(pbMapping != NULL, ("errno=%s (%d)\n", strerror(errno), errno));
3770# endif
3771
3772 /* Close the file handles. */
3773 if ((i & 7) == 7)
3774 {
3775 RTTESTI_CHECK_RC(RTFileClose(hFile3), VINF_SUCCESS);
3776 hFile3 = NIL_RTFILE;
3777 }
3778 RTTESTI_CHECK_RC(RTFileClose(hFile2), VINF_SUCCESS);
3779 if ((i & 7) == 5)
3780 {
3781 RTTESTI_CHECK_RC(RTFileClose(hFile3), VINF_SUCCESS);
3782 hFile3 = NIL_RTFILE;
3783 }
3784 if (pbMapping)
3785 {
3786 RTThreadSleep(2); /* fudge for cleanup/whatever */
3787
3788 /* Page in the mapping by comparing with the content we wrote above. */
3789 RTTESTI_CHECK(memcmp(pbMapping, s_abContent, sizeof(s_abContent)) == 0);
3790
3791 /* Now dirty everything by inverting everything. */
3792 size_t *puCur = (size_t *)pbMapping;
3793 size_t cLeft = sizeof(s_abContent) / sizeof(*puCur);
3794 while (cLeft-- > 0)
3795 {
3796 *puCur = ~*puCur;
3797 puCur++;
3798 }
3799
3800 /* Sync it all. */
3801# ifdef RT_OS_WINDOWS
3802 RTTESTI_CHECK(FlushViewOfFile(pbMapping, sizeof(s_abContent)));
3803# else
3804 RTTESTI_CHECK(msync(pbMapping, sizeof(s_abContent), MS_SYNC) == 0);
3805# endif
3806
3807 /* Unmap it. */
3808# ifdef RT_OS_WINDOWS
3809 RTTESTI_CHECK(UnmapViewOfFile(pbMapping));
3810# else
3811 RTTESTI_CHECK(munmap(pbMapping, sizeof(s_abContent)) == 0);
3812# endif
3813 }
3814
3815 if (hFile3 != NIL_RTFILE)
3816 RTTESTI_CHECK_RC(RTFileClose(hFile3), VINF_SUCCESS);
3817 RTTESTI_CHECK_RC(RTFileDelete(szFile2), VINF_SUCCESS);
3818 }
3819
3820
3821#else
3822 RTTestSkipped(g_hTest, "not supported/implemented");
3823 RT_NOREF(hFile1, hFileNoCache, cbFile);
3824#endif
3825}
3826
3827
3828/**
3829 * This does the read, write and seek tests.
3830 */
3831void fsPerfIo(void)
3832{
3833 RTTestISub("I/O");
3834
3835 /*
3836 * Determin the size of the test file.
3837 */
3838 g_szDir[g_cchDir] = '\0';
3839 RTFOFF cbFree = 0;
3840 RTTESTI_CHECK_RC_RETV(RTFsQuerySizes(g_szDir, NULL, &cbFree, NULL, NULL), VINF_SUCCESS);
3841 uint64_t cbFile = g_cbIoFile;
3842 if (cbFile + _16M < (uint64_t)cbFree)
3843 cbFile = RT_ALIGN_64(cbFile, _64K);
3844 else if (cbFree < _32M)
3845 {
3846 RTTestSkipped(g_hTest, "Insufficent free space: %'RU64 bytes, requires >= 32MB", cbFree);
3847 return;
3848 }
3849 else
3850 {
3851 cbFile = cbFree - (cbFree > _128M ? _64M : _16M);
3852 cbFile = RT_ALIGN_64(cbFile, _64K);
3853 RTTestIPrintf(RTTESTLVL_ALWAYS, "Adjusted file size to %'RU64 bytes, due to %'RU64 bytes free.\n", cbFile, cbFree);
3854 }
3855 if (cbFile < _64K)
3856 {
3857 RTTestSkipped(g_hTest, "Specified test file size too small: %'RU64 bytes, requires >= 64KB", cbFile);
3858 return;
3859 }
3860
3861 /*
3862 * Create a cbFile sized test file.
3863 */
3864 RTFILE hFile1;
3865 RTTESTI_CHECK_RC_RETV(RTFileOpen(&hFile1, InDir(RT_STR_TUPLE("file21")),
3866 RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE | RTFILE_O_READWRITE), VINF_SUCCESS);
3867 RTFILE hFileNoCache;
3868 if (!g_fIgnoreNoCache)
3869 RTTESTI_CHECK_RC_RETV(RTFileOpen(&hFileNoCache, g_szDir,
3870 RTFILE_O_OPEN | RTFILE_O_DENY_NONE | RTFILE_O_READWRITE | RTFILE_O_NO_CACHE),
3871 VINF_SUCCESS);
3872 else
3873 {
3874 int rc = RTFileOpen(&hFileNoCache, g_szDir, RTFILE_O_OPEN | RTFILE_O_DENY_NONE | RTFILE_O_READWRITE | RTFILE_O_NO_CACHE);
3875 if (RT_FAILURE(rc))
3876 {
3877 RTTestIPrintf(RTTESTLVL_ALWAYS, "Unable to open I/O file with non-cache flag (%Rrc), skipping related tests.\n", rc);
3878 hFileNoCache = NIL_RTFILE;
3879 }
3880 }
3881 RTFILE hFileWriteThru;
3882 RTTESTI_CHECK_RC_RETV(RTFileOpen(&hFileWriteThru, g_szDir,
3883 RTFILE_O_OPEN | RTFILE_O_DENY_NONE | RTFILE_O_READWRITE | RTFILE_O_WRITE_THROUGH),
3884 VINF_SUCCESS);
3885
3886 uint8_t *pbFree = NULL;
3887 int rc = fsPerfIoPrepFile(hFile1, cbFile, &pbFree);
3888 RTMemFree(pbFree);
3889 if (RT_SUCCESS(rc))
3890 {
3891 /*
3892 * Do the testing & profiling.
3893 */
3894 if (g_fSeek)
3895 fsPerfIoSeek(hFile1, cbFile);
3896
3897 if (g_fReadTests)
3898 fsPerfRead(hFile1, hFileNoCache, cbFile);
3899 if (g_fReadPerf)
3900 for (unsigned i = 0; i < g_cIoBlocks; i++)
3901 fsPerfIoReadBlockSize(hFile1, cbFile, g_acbIoBlocks[i]);
3902#if defined(RT_OS_LINUX)
3903 if (g_fSendFile)
3904 fsPerfSendFile(hFile1, cbFile);
3905#endif
3906#ifdef RT_OS_LINUX
3907 if (g_fSplice)
3908 fsPerfSpliceToPipe(hFile1, cbFile);
3909#endif
3910 if (g_fMMap)
3911 fsPerfMMap(hFile1, hFileNoCache, cbFile);
3912
3913 /* This is destructive to the file content. */
3914 if (g_fWriteTests)
3915 fsPerfWrite(hFile1, hFileNoCache, hFileWriteThru, cbFile);
3916 if (g_fWritePerf)
3917 for (unsigned i = 0; i < g_cIoBlocks; i++)
3918 fsPerfIoWriteBlockSize(hFile1, cbFile, g_acbIoBlocks[i]);
3919#ifdef RT_OS_LINUX
3920 if (g_fSplice)
3921 fsPerfSpliceToFile(hFile1, cbFile);
3922#endif
3923 if (g_fFSync)
3924 fsPerfFSync(hFile1, cbFile);
3925 }
3926
3927 RTTESTI_CHECK_RC(RTFileSetSize(hFile1, 0), VINF_SUCCESS);
3928 RTTESTI_CHECK_RC(RTFileClose(hFile1), VINF_SUCCESS);
3929 if (hFileNoCache != NIL_RTFILE || !g_fIgnoreNoCache)
3930 RTTESTI_CHECK_RC(RTFileClose(hFileNoCache), VINF_SUCCESS);
3931 RTTESTI_CHECK_RC(RTFileClose(hFileWriteThru), VINF_SUCCESS);
3932 RTTESTI_CHECK_RC(RTFileDelete(g_szDir), VINF_SUCCESS);
3933}
3934
3935
3936DECL_FORCE_INLINE(int) fsPerfCopyWorker1(const char *pszSrc, const char *pszDst)
3937{
3938 RTFileDelete(pszDst);
3939 return RTFileCopy(pszSrc, pszDst);
3940}
3941
3942
3943#ifdef RT_OS_LINUX
3944DECL_FORCE_INLINE(int) fsPerfCopyWorkerSendFile(RTFILE hFile1, RTFILE hFile2, size_t cbFile)
3945{
3946 RTTESTI_CHECK_RC_RET(RTFileSeek(hFile2, 0, RTFILE_SEEK_BEGIN, NULL), VINF_SUCCESS, rcCheck);
3947
3948 loff_t off = 0;
3949 ssize_t cbSent = sendfile((int)RTFileToNative(hFile2), (int)RTFileToNative(hFile1), &off, cbFile);
3950 if (cbSent > 0 && (size_t)cbSent == cbFile)
3951 return 0;
3952
3953 int rc = VERR_GENERAL_FAILURE;
3954 if (cbSent < 0)
3955 {
3956 rc = RTErrConvertFromErrno(errno);
3957 RTTestIFailed("sendfile(file,file,NULL,%#zx) failed (%zd): %d (%Rrc)", cbFile, cbSent, errno, rc);
3958 }
3959 else
3960 RTTestIFailed("sendfile(file,file,NULL,%#zx) returned %#zx, expected %#zx (diff %zd)",
3961 cbFile, cbSent, cbFile, cbSent - cbFile);
3962 return rc;
3963}
3964#endif /* RT_OS_LINUX */
3965
3966
3967static void fsPerfCopy(void)
3968{
3969 RTTestISub("copy");
3970
3971 /*
3972 * Non-existing files.
3973 */
3974 RTTESTI_CHECK_RC(RTFileCopy(InEmptyDir(RT_STR_TUPLE("no-such-file")),
3975 InDir2(RT_STR_TUPLE("whatever"))), VERR_FILE_NOT_FOUND);
3976 RTTESTI_CHECK_RC(RTFileCopy(InEmptyDir(RT_STR_TUPLE("no-such-dir" RTPATH_SLASH_STR "no-such-file")),
3977 InDir2(RT_STR_TUPLE("no-such-file"))), FSPERF_VERR_PATH_NOT_FOUND);
3978 RTTESTI_CHECK_RC(RTFileCopy(InDir(RT_STR_TUPLE("known-file" RTPATH_SLASH_STR "no-such-file")),
3979 InDir2(RT_STR_TUPLE("whatever"))), VERR_PATH_NOT_FOUND);
3980
3981 RTTESTI_CHECK_RC(RTFileCopy(InDir(RT_STR_TUPLE("known-file")),
3982 InEmptyDir(RT_STR_TUPLE("no-such-dir" RTPATH_SLASH_STR "no-such-file"))), FSPERF_VERR_PATH_NOT_FOUND);
3983 RTTESTI_CHECK_RC(RTFileCopy(InDir(RT_STR_TUPLE("known-file")),
3984 InDir2(RT_STR_TUPLE("known-file" RTPATH_SLASH_STR "no-such-file"))), VERR_PATH_NOT_FOUND);
3985
3986 /*
3987 * Determin the size of the test file.
3988 * We want to be able to make 1 copy of it.
3989 */
3990 g_szDir[g_cchDir] = '\0';
3991 RTFOFF cbFree = 0;
3992 RTTESTI_CHECK_RC_RETV(RTFsQuerySizes(g_szDir, NULL, &cbFree, NULL, NULL), VINF_SUCCESS);
3993 uint64_t cbFile = g_cbIoFile;
3994 if (cbFile + _16M < (uint64_t)cbFree)
3995 cbFile = RT_ALIGN_64(cbFile, _64K);
3996 else if (cbFree < _32M)
3997 {
3998 RTTestSkipped(g_hTest, "Insufficent free space: %'RU64 bytes, requires >= 32MB", cbFree);
3999 return;
4000 }
4001 else
4002 {
4003 cbFile = cbFree - (cbFree > _128M ? _64M : _16M);
4004 cbFile = RT_ALIGN_64(cbFile, _64K);
4005 RTTestIPrintf(RTTESTLVL_ALWAYS, "Adjusted file size to %'RU64 bytes, due to %'RU64 bytes free.\n", cbFile, cbFree);
4006 }
4007 if (cbFile < _512K * 2)
4008 {
4009 RTTestSkipped(g_hTest, "Specified test file size too small: %'RU64 bytes, requires >= 1MB", cbFile);
4010 return;
4011 }
4012 cbFile /= 2;
4013
4014 /*
4015 * Create a cbFile sized test file.
4016 */
4017 RTFILE hFile1;
4018 RTTESTI_CHECK_RC_RETV(RTFileOpen(&hFile1, InDir(RT_STR_TUPLE("file22")),
4019 RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE | RTFILE_O_READWRITE), VINF_SUCCESS);
4020 uint8_t *pbFree = NULL;
4021 int rc = fsPerfIoPrepFile(hFile1, cbFile, &pbFree);
4022 RTMemFree(pbFree);
4023 RTTESTI_CHECK_RC(RTFileClose(hFile1), VINF_SUCCESS);
4024 if (RT_SUCCESS(rc))
4025 {
4026 /*
4027 * Make copies.
4028 */
4029 /* plain */
4030 RTFileDelete(InDir2(RT_STR_TUPLE("file23")));
4031 RTTESTI_CHECK_RC(RTFileCopy(g_szDir, g_szDir2), VINF_SUCCESS);
4032 RTTESTI_CHECK_RC(RTFileCopy(g_szDir, g_szDir2), VERR_ALREADY_EXISTS);
4033 RTTESTI_CHECK_RC(RTFileCompare(g_szDir, g_szDir2), VINF_SUCCESS);
4034
4035 /* by handle */
4036 hFile1 = NIL_RTFILE;
4037 RTTESTI_CHECK_RC(RTFileOpen(&hFile1, g_szDir, RTFILE_O_OPEN | RTFILE_O_DENY_NONE | RTFILE_O_READ), VINF_SUCCESS);
4038 RTFILE hFile2 = NIL_RTFILE;
4039 RTTESTI_CHECK_RC(RTFileOpen(&hFile2, g_szDir2, RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE | RTFILE_O_WRITE), VINF_SUCCESS);
4040 RTTESTI_CHECK_RC(RTFileCopyByHandles(hFile1, hFile2), VINF_SUCCESS);
4041 RTTESTI_CHECK_RC(RTFileClose(hFile2), VINF_SUCCESS);
4042 RTTESTI_CHECK_RC(RTFileClose(hFile1), VINF_SUCCESS);
4043 RTTESTI_CHECK_RC(RTFileCompare(g_szDir, g_szDir2), VINF_SUCCESS);
4044
4045 /* copy part */
4046 hFile1 = NIL_RTFILE;
4047 RTTESTI_CHECK_RC(RTFileOpen(&hFile1, g_szDir, RTFILE_O_OPEN | RTFILE_O_DENY_NONE | RTFILE_O_READ), VINF_SUCCESS);
4048 hFile2 = NIL_RTFILE;
4049 RTTESTI_CHECK_RC(RTFileOpen(&hFile2, g_szDir2, RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE | RTFILE_O_WRITE), VINF_SUCCESS);
4050 RTTESTI_CHECK_RC(RTFileCopyPart(hFile1, 0, hFile2, 0, cbFile / 2, 0, NULL), VINF_SUCCESS);
4051 RTTESTI_CHECK_RC(RTFileCopyPart(hFile1, cbFile / 2, hFile2, cbFile / 2, cbFile - cbFile / 2, 0, NULL), VINF_SUCCESS);
4052 RTTESTI_CHECK_RC(RTFileClose(hFile2), VINF_SUCCESS);
4053 RTTESTI_CHECK_RC(RTFileClose(hFile1), VINF_SUCCESS);
4054 RTTESTI_CHECK_RC(RTFileCompare(g_szDir, g_szDir2), VINF_SUCCESS);
4055
4056#ifdef RT_OS_LINUX
4057 /*
4058 * On linux we can also use sendfile between two files, except for 2.5.x to 2.6.33.
4059 */
4060 uint64_t const cbFileMax = RT_MIN(cbFile, UINT32_C(0x7ffff000));
4061 char szRelease[64];
4062 RTSystemQueryOSInfo(RTSYSOSINFO_RELEASE, szRelease, sizeof(szRelease));
4063 bool const fSendFileBetweenFiles = RTStrVersionCompare(szRelease, "2.5.0") < 0
4064 || RTStrVersionCompare(szRelease, "2.6.33") >= 0;
4065 if (fSendFileBetweenFiles)
4066 {
4067 /* Copy the whole file: */
4068 hFile1 = NIL_RTFILE;
4069 RTTESTI_CHECK_RC(RTFileOpen(&hFile1, g_szDir, RTFILE_O_OPEN | RTFILE_O_DENY_NONE | RTFILE_O_READ), VINF_SUCCESS);
4070 RTFileDelete(g_szDir2);
4071 hFile2 = NIL_RTFILE;
4072 RTTESTI_CHECK_RC(RTFileOpen(&hFile2, g_szDir2, RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE | RTFILE_O_WRITE), VINF_SUCCESS);
4073 ssize_t cbSent = sendfile((int)RTFileToNative(hFile2), (int)RTFileToNative(hFile1), NULL, cbFile);
4074 if (cbSent < 0)
4075 RTTestIFailed("sendfile(file,file,NULL,%#zx) failed (%zd): %d (%Rrc)",
4076 cbFile, cbSent, errno, RTErrConvertFromErrno(errno));
4077 else if ((size_t)cbSent != cbFileMax)
4078 RTTestIFailed("sendfile(file,file,NULL,%#zx) returned %#zx, expected %#zx (diff %zd)",
4079 cbFile, cbSent, cbFileMax, cbSent - cbFileMax);
4080 RTTESTI_CHECK_RC(RTFileClose(hFile2), VINF_SUCCESS);
4081 RTTESTI_CHECK_RC(RTFileClose(hFile1), VINF_SUCCESS);
4082 RTTESTI_CHECK_RC(RTFileCompare(g_szDir, g_szDir2), VINF_SUCCESS);
4083
4084 /* Try copy a little bit too much: */
4085 if (cbFile == cbFileMax)
4086 {
4087 hFile1 = NIL_RTFILE;
4088 RTTESTI_CHECK_RC(RTFileOpen(&hFile1, g_szDir, RTFILE_O_OPEN | RTFILE_O_DENY_NONE | RTFILE_O_READ), VINF_SUCCESS);
4089 RTFileDelete(g_szDir2);
4090 hFile2 = NIL_RTFILE;
4091 RTTESTI_CHECK_RC(RTFileOpen(&hFile2, g_szDir2, RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE | RTFILE_O_WRITE), VINF_SUCCESS);
4092 size_t cbToCopy = cbFile + RTRandU32Ex(1, _64M);
4093 cbSent = sendfile((int)RTFileToNative(hFile2), (int)RTFileToNative(hFile1), NULL, cbToCopy);
4094 if (cbSent < 0)
4095 RTTestIFailed("sendfile(file,file,NULL,%#zx) failed (%zd): %d (%Rrc)",
4096 cbToCopy, cbSent, errno, RTErrConvertFromErrno(errno));
4097 else if ((size_t)cbSent != cbFile)
4098 RTTestIFailed("sendfile(file,file,NULL,%#zx) returned %#zx, expected %#zx (diff %zd)",
4099 cbToCopy, cbSent, cbFile, cbSent - cbFile);
4100 RTTESTI_CHECK_RC(RTFileClose(hFile2), VINF_SUCCESS);
4101 RTTESTI_CHECK_RC(RTFileCompare(g_szDir, g_szDir2), VINF_SUCCESS);
4102 }
4103
4104 /* Do partial copy: */
4105 hFile2 = NIL_RTFILE;
4106 RTTESTI_CHECK_RC(RTFileOpen(&hFile2, g_szDir2, RTFILE_O_OPEN | RTFILE_O_DENY_NONE | RTFILE_O_WRITE), VINF_SUCCESS);
4107 for (uint32_t i = 0; i < 64; i++)
4108 {
4109 size_t cbToCopy = RTRandU32Ex(0, cbFileMax - 1);
4110 uint32_t const offFile = RTRandU32Ex(1, (uint64_t)RT_MIN(cbFileMax - cbToCopy, UINT32_MAX));
4111 RTTESTI_CHECK_RC_BREAK(RTFileSeek(hFile2, offFile, RTFILE_SEEK_BEGIN, NULL), VINF_SUCCESS);
4112 loff_t offFile2 = offFile;
4113 cbSent = sendfile((int)RTFileToNative(hFile2), (int)RTFileToNative(hFile1), &offFile2, cbToCopy);
4114 if (cbSent < 0)
4115 RTTestIFailed("sendfile(file,file,%#x,%#zx) failed (%zd): %d (%Rrc)",
4116 offFile, cbToCopy, cbSent, errno, RTErrConvertFromErrno(errno));
4117 else if ((size_t)cbSent != cbToCopy)
4118 RTTestIFailed("sendfile(file,file,%#x,%#zx) returned %#zx, expected %#zx (diff %zd)",
4119 offFile, cbToCopy, cbSent, cbToCopy, cbSent - cbToCopy);
4120 else if (offFile2 != (loff_t)(offFile + cbToCopy))
4121 RTTestIFailed("sendfile(file,file,%#x,%#zx) returned %#zx + off=%#RX64, expected off %#x",
4122 offFile, cbToCopy, cbSent, offFile2, offFile + cbToCopy);
4123 }
4124 RTTESTI_CHECK_RC(RTFileClose(hFile2), VINF_SUCCESS);
4125 RTTESTI_CHECK_RC(RTFileClose(hFile1), VINF_SUCCESS);
4126 RTTESTI_CHECK_RC(RTFileCompare(g_szDir, g_szDir2), VINF_SUCCESS);
4127 }
4128#endif
4129
4130 /*
4131 * Do some benchmarking.
4132 */
4133#define PROFILE_COPY_FN(a_szOperation, a_fnCall) \
4134 do \
4135 { \
4136 /* Estimate how many iterations we need to fill up the given timeslot: */ \
4137 fsPerfYield(); \
4138 uint64_t nsStart = RTTimeNanoTS(); \
4139 uint64_t ns; \
4140 do \
4141 ns = RTTimeNanoTS(); \
4142 while (ns == nsStart); \
4143 nsStart = ns; \
4144 \
4145 uint64_t iIteration = 0; \
4146 do \
4147 { \
4148 RTTESTI_CHECK_RC(a_fnCall, VINF_SUCCESS); \
4149 iIteration++; \
4150 ns = RTTimeNanoTS() - nsStart; \
4151 } while (ns < RT_NS_10MS); \
4152 ns /= iIteration; \
4153 if (ns > g_nsPerNanoTSCall + 32) \
4154 ns -= g_nsPerNanoTSCall; \
4155 uint64_t cIterations = g_nsTestRun / ns; \
4156 if (cIterations < 2) \
4157 cIterations = 2; \
4158 else if (cIterations & 1) \
4159 cIterations++; \
4160 \
4161 /* Do the actual profiling: */ \
4162 iIteration = 0; \
4163 fsPerfYield(); \
4164 nsStart = RTTimeNanoTS(); \
4165 for (uint32_t iAdjust = 0; iAdjust < 4; iAdjust++) \
4166 { \
4167 for (; iIteration < cIterations; iIteration++)\
4168 RTTESTI_CHECK_RC(a_fnCall, VINF_SUCCESS); \
4169 ns = RTTimeNanoTS() - nsStart;\
4170 if (ns >= g_nsTestRun - (g_nsTestRun / 10)) \
4171 break; \
4172 cIterations += cIterations / 4; \
4173 if (cIterations & 1) \
4174 cIterations++; \
4175 nsStart += g_nsPerNanoTSCall; \
4176 } \
4177 RTTestIValueF(ns / iIteration, \
4178 RTTESTUNIT_NS_PER_OCCURRENCE, a_szOperation " latency"); \
4179 RTTestIValueF((uint64_t)((uint64_t)iIteration * cbFile / ((double)ns / RT_NS_1SEC)), \
4180 RTTESTUNIT_BYTES_PER_SEC, a_szOperation " throughput"); \
4181 RTTestIValueF((uint64_t)iIteration * cbFile, \
4182 RTTESTUNIT_BYTES, a_szOperation " bytes"); \
4183 RTTestIValueF(iIteration, \
4184 RTTESTUNIT_OCCURRENCES, a_szOperation " iterations"); \
4185 if (g_fShowDuration) \
4186 RTTestIValueF(ns, RTTESTUNIT_NS, a_szOperation " duration"); \
4187 } while (0)
4188
4189 PROFILE_COPY_FN("RTFileCopy/Replace", fsPerfCopyWorker1(g_szDir, g_szDir2));
4190
4191 hFile1 = NIL_RTFILE;
4192 RTTESTI_CHECK_RC(RTFileOpen(&hFile1, g_szDir, RTFILE_O_OPEN | RTFILE_O_DENY_NONE | RTFILE_O_READ), VINF_SUCCESS);
4193 RTFileDelete(g_szDir2);
4194 hFile2 = NIL_RTFILE;
4195 RTTESTI_CHECK_RC(RTFileOpen(&hFile2, g_szDir2, RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE | RTFILE_O_WRITE), VINF_SUCCESS);
4196 PROFILE_COPY_FN("RTFileCopyByHandles/Overwrite", RTFileCopyByHandles(hFile1, hFile2));
4197 RTTESTI_CHECK_RC(RTFileClose(hFile2), VINF_SUCCESS);
4198 RTTESTI_CHECK_RC(RTFileClose(hFile1), VINF_SUCCESS);
4199
4200 /* We could benchmark RTFileCopyPart with various block sizes and whatnot...
4201 But it's currently well covered by the two previous operations. */
4202
4203#ifdef RT_OS_LINUX
4204 if (fSendFileBetweenFiles)
4205 {
4206 hFile1 = NIL_RTFILE;
4207 RTTESTI_CHECK_RC(RTFileOpen(&hFile1, g_szDir, RTFILE_O_OPEN | RTFILE_O_DENY_NONE | RTFILE_O_READ), VINF_SUCCESS);
4208 RTFileDelete(g_szDir2);
4209 hFile2 = NIL_RTFILE;
4210 RTTESTI_CHECK_RC(RTFileOpen(&hFile2, g_szDir2, RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE | RTFILE_O_WRITE), VINF_SUCCESS);
4211 PROFILE_COPY_FN("sendfile/overwrite", fsPerfCopyWorkerSendFile(hFile1, hFile2, cbFileMax));
4212 RTTESTI_CHECK_RC(RTFileClose(hFile2), VINF_SUCCESS);
4213 RTTESTI_CHECK_RC(RTFileClose(hFile1), VINF_SUCCESS);
4214 }
4215#endif
4216 }
4217
4218 /*
4219 * Clean up.
4220 */
4221 RTFileDelete(InDir2(RT_STR_TUPLE("file22c1")));
4222 RTFileDelete(InDir2(RT_STR_TUPLE("file22c2")));
4223 RTFileDelete(InDir2(RT_STR_TUPLE("file22c3")));
4224 RTTESTI_CHECK_RC(RTFileDelete(g_szDir), VINF_SUCCESS);
4225}
4226
4227
4228/**
4229 * Display the usage to @a pStrm.
4230 */
4231static void Usage(PRTSTREAM pStrm)
4232{
4233 char szExec[RTPATH_MAX];
4234 RTStrmPrintf(pStrm, "usage: %s <-d <testdir>> [options]\n",
4235 RTPathFilename(RTProcGetExecutablePath(szExec, sizeof(szExec))));
4236 RTStrmPrintf(pStrm, "\n");
4237 RTStrmPrintf(pStrm, "options: \n");
4238
4239 for (unsigned i = 0; i < RT_ELEMENTS(g_aCmdOptions); i++)
4240 {
4241 char szHelp[80];
4242 const char *pszHelp;
4243 switch (g_aCmdOptions[i].iShort)
4244 {
4245 case 'd': pszHelp = "The directory to use for testing. default: CWD/fstestdir"; break;
4246 case 'e': pszHelp = "Enables all tests. default: -e"; break;
4247 case 'z': pszHelp = "Disables all tests. default: -e"; break;
4248 case 's': pszHelp = "Set benchmark duration in seconds. default: 10 sec"; break;
4249 case 'm': pszHelp = "Set benchmark duration in milliseconds. default: 10000 ms"; break;
4250 case 'v': pszHelp = "More verbose execution."; break;
4251 case 'q': pszHelp = "Quiet execution."; break;
4252 case 'h': pszHelp = "Displays this help and exit"; break;
4253 case 'V': pszHelp = "Displays the program revision"; break;
4254 case kCmdOpt_ShowDuration: pszHelp = "Show duration of profile runs. default: --no-show-duration"; break;
4255 case kCmdOpt_NoShowDuration: pszHelp = "Hide duration of profile runs. default: --no-show-duration"; break;
4256 case kCmdOpt_ShowIterations: pszHelp = "Show iteration count for profile runs. default: --no-show-iterations"; break;
4257 case kCmdOpt_NoShowIterations: pszHelp = "Hide iteration count for profile runs. default: --no-show-iterations"; break;
4258 case kCmdOpt_ManyFiles: pszHelp = "Count of files in big test dir. default: --many-files 10000"; break;
4259 case kCmdOpt_NoManyFiles: pszHelp = "Skip big test dir with many files. default: --many-files 10000"; break;
4260 case kCmdOpt_ManyTreeFilesPerDir: pszHelp = "Count of files per directory in test tree. default: 640"; break;
4261 case kCmdOpt_ManyTreeSubdirsPerDir: pszHelp = "Count of subdirs per directory in test tree. default: 16"; break;
4262 case kCmdOpt_ManyTreeDepth: pszHelp = "Depth of test tree (not counting root). default: 1"; break;
4263 case kCmdOpt_IgnoreNoCache: pszHelp = "Ignore error wrt no-cache handle. default: --no-ignore-no-cache"; break;
4264 case kCmdOpt_NoIgnoreNoCache: pszHelp = "Do not ignore error wrt no-cache handle. default: --no-ignore-no-cache"; break;
4265 case kCmdOpt_IoFileSize: pszHelp = "Size of file used for I/O tests. default: 512 MB"; break;
4266 case kCmdOpt_SetBlockSize: pszHelp = "Sets single I/O block size (in bytes)."; break;
4267 case kCmdOpt_AddBlockSize: pszHelp = "Adds an I/O block size (in bytes)."; break;
4268 default:
4269 if (g_aCmdOptions[i].iShort >= kCmdOpt_First)
4270 {
4271 if (RTStrStartsWith(g_aCmdOptions[i].pszLong, "--no-"))
4272 RTStrPrintf(szHelp, sizeof(szHelp), "Disables the '%s' test.", g_aCmdOptions[i].pszLong + 5);
4273 else
4274 RTStrPrintf(szHelp, sizeof(szHelp), "Enables the '%s' test.", g_aCmdOptions[i].pszLong + 2);
4275 pszHelp = szHelp;
4276 }
4277 else
4278 pszHelp = "Option undocumented";
4279 break;
4280 }
4281 if ((unsigned)g_aCmdOptions[i].iShort < 127U)
4282 {
4283 char szOpt[64];
4284 RTStrPrintf(szOpt, sizeof(szOpt), "%s, -%c", g_aCmdOptions[i].pszLong, g_aCmdOptions[i].iShort);
4285 RTStrmPrintf(pStrm, " %-19s %s\n", szOpt, pszHelp);
4286 }
4287 else
4288 RTStrmPrintf(pStrm, " %-19s %s\n", g_aCmdOptions[i].pszLong, pszHelp);
4289 }
4290}
4291
4292
4293static uint32_t fsPerfCalcManyTreeFiles(void)
4294{
4295 uint32_t cDirs = 1;
4296 for (uint32_t i = 0, cDirsAtLevel = 1; i < g_cManyTreeDepth; i++)
4297 {
4298 cDirs += cDirsAtLevel * g_cManyTreeSubdirsPerDir;
4299 cDirsAtLevel *= g_cManyTreeSubdirsPerDir;
4300 }
4301 return g_cManyTreeFilesPerDir * cDirs;
4302}
4303
4304
4305int main(int argc, char *argv[])
4306{
4307 /*
4308 * Init IPRT and globals.
4309 */
4310 int rc = RTTestInitAndCreate("FsPerf", &g_hTest);
4311 if (rc)
4312 return rc;
4313 RTListInit(&g_ManyTreeHead);
4314
4315 /*
4316 * Default values.
4317 */
4318 rc = RTPathGetCurrent(g_szDir, sizeof(g_szDir) / 2);
4319 if (RT_SUCCESS(rc))
4320 rc = RTPathAppend(g_szDir, sizeof(g_szDir) / 2, "fstestdir-");
4321 if (RT_SUCCESS(rc))
4322 {
4323 g_cchDir = strlen(g_szDir);
4324 g_cchDir += RTStrPrintf(&g_szDir[g_cchDir], sizeof(g_szDir) - g_cchDir, "%u" RTPATH_SLASH_STR, RTProcSelf());
4325 }
4326 else
4327 {
4328 RTTestFailed(g_hTest, "RTPathGetCurrent (or RTPathAppend) failed: %Rrc\n", rc);
4329 return RTTestSummaryAndDestroy(g_hTest);
4330 }
4331
4332 RTGETOPTUNION ValueUnion;
4333 RTGETOPTSTATE GetState;
4334 RTGetOptInit(&GetState, argc, argv, g_aCmdOptions, RT_ELEMENTS(g_aCmdOptions), 1, 0 /* fFlags */);
4335 while ((rc = RTGetOpt(&GetState, &ValueUnion)) != 0)
4336 {
4337 switch (rc)
4338 {
4339 case 'd':
4340 rc = RTPathAbs(ValueUnion.psz, g_szDir, sizeof(g_szDir) / 2);
4341 if (RT_SUCCESS(rc))
4342 {
4343 RTPathEnsureTrailingSeparator(g_szDir, sizeof(g_szDir));
4344 g_cchDir = strlen(g_szDir);
4345 break;
4346 }
4347 RTTestFailed(g_hTest, "RTPathAbs(%s) failed: %Rrc\n", ValueUnion.psz, rc);
4348 return RTTestSummaryAndDestroy(g_hTest);
4349
4350 case 's':
4351 if (ValueUnion.u32 == 0)
4352 g_nsTestRun = RT_NS_1SEC_64 * 10;
4353 else
4354 g_nsTestRun = ValueUnion.u32 * RT_NS_1SEC_64;
4355 break;
4356
4357 case 'm':
4358 if (ValueUnion.u64 == 0)
4359 g_nsTestRun = RT_NS_1SEC_64 * 10;
4360 else
4361 g_nsTestRun = ValueUnion.u64 * RT_NS_1MS;
4362 break;
4363
4364 case 'e':
4365 g_fManyFiles = true;
4366 g_fOpen = true;
4367 g_fFStat = true;
4368 g_fFChMod = true;
4369 g_fFUtimes = true;
4370 g_fStat = true;
4371 g_fChMod = true;
4372 g_fUtimes = true;
4373 g_fRename = true;
4374 g_fDirOpen = true;
4375 g_fDirEnum = true;
4376 g_fMkRmDir = true;
4377 g_fStatVfs = true;
4378 g_fRm = true;
4379 g_fChSize = true;
4380 g_fReadTests = true;
4381 g_fReadPerf = true;
4382 g_fSendFile = true;
4383#ifdef RT_OS_LINUX
4384 g_fSplice = true;
4385#endif
4386 g_fWriteTests= true;
4387 g_fWritePerf = true;
4388 g_fSeek = true;
4389 g_fFSync = true;
4390 g_fMMap = true;
4391 g_fCopy = true;
4392 break;
4393
4394 case 'z':
4395 g_fManyFiles = false;
4396 g_fOpen = false;
4397 g_fFStat = false;
4398 g_fFChMod = false;
4399 g_fFUtimes = false;
4400 g_fStat = false;
4401 g_fChMod = false;
4402 g_fUtimes = false;
4403 g_fRename = false;
4404 g_fDirOpen = false;
4405 g_fDirEnum = false;
4406 g_fMkRmDir = false;
4407 g_fStatVfs = false;
4408 g_fRm = false;
4409 g_fChSize = false;
4410 g_fReadTests = false;
4411 g_fReadPerf = false;
4412 g_fSendFile = false;
4413#ifdef RT_OS_LINUX
4414 g_fSplice = false;
4415#endif
4416 g_fWriteTests= false;
4417 g_fWritePerf = false;
4418 g_fSeek = false;
4419 g_fFSync = false;
4420 g_fMMap = false;
4421 g_fCopy = false;
4422 break;
4423
4424#define CASE_OPT(a_Stem) \
4425 case RT_CONCAT(kCmdOpt_,a_Stem): RT_CONCAT(g_f,a_Stem) = true; break; \
4426 case RT_CONCAT(kCmdOpt_No,a_Stem): RT_CONCAT(g_f,a_Stem) = false; break
4427 CASE_OPT(Open);
4428 CASE_OPT(FStat);
4429 CASE_OPT(FChMod);
4430 CASE_OPT(FUtimes);
4431 CASE_OPT(Stat);
4432 CASE_OPT(ChMod);
4433 CASE_OPT(Utimes);
4434 CASE_OPT(Rename);
4435 CASE_OPT(DirOpen);
4436 CASE_OPT(DirEnum);
4437 CASE_OPT(MkRmDir);
4438 CASE_OPT(StatVfs);
4439 CASE_OPT(Rm);
4440 CASE_OPT(ChSize);
4441 CASE_OPT(ReadTests);
4442 CASE_OPT(ReadPerf);
4443 CASE_OPT(SendFile);
4444#ifdef RT_OS_LINUX
4445 CASE_OPT(Splice);
4446#endif
4447 CASE_OPT(WriteTests);
4448 CASE_OPT(WritePerf);
4449 CASE_OPT(Seek);
4450 CASE_OPT(FSync);
4451 CASE_OPT(MMap);
4452 CASE_OPT(IgnoreNoCache);
4453 CASE_OPT(Copy);
4454
4455 CASE_OPT(ShowDuration);
4456 CASE_OPT(ShowIterations);
4457#undef CASE_OPT
4458
4459 case kCmdOpt_ManyFiles:
4460 g_fManyFiles = ValueUnion.u32 > 0;
4461 g_cManyFiles = ValueUnion.u32;
4462 break;
4463
4464 case kCmdOpt_NoManyFiles:
4465 g_fManyFiles = false;
4466 break;
4467
4468 case kCmdOpt_ManyTreeFilesPerDir:
4469 if (ValueUnion.u32 > 0 && ValueUnion.u32 <= _64M)
4470 {
4471 g_cManyTreeFilesPerDir = ValueUnion.u32;
4472 g_cManyTreeFiles = fsPerfCalcManyTreeFiles();
4473 break;
4474 }
4475 RTTestFailed(g_hTest, "Out of range --files-per-dir value: %u (%#x)\n", ValueUnion.u32, ValueUnion.u32);
4476 return RTTestSummaryAndDestroy(g_hTest);
4477
4478 case kCmdOpt_ManyTreeSubdirsPerDir:
4479 if (ValueUnion.u32 > 0 && ValueUnion.u32 <= 1024)
4480 {
4481 g_cManyTreeSubdirsPerDir = ValueUnion.u32;
4482 g_cManyTreeFiles = fsPerfCalcManyTreeFiles();
4483 break;
4484 }
4485 RTTestFailed(g_hTest, "Out of range --subdirs-per-dir value: %u (%#x)\n", ValueUnion.u32, ValueUnion.u32);
4486 return RTTestSummaryAndDestroy(g_hTest);
4487
4488 case kCmdOpt_ManyTreeDepth:
4489 if (ValueUnion.u32 <= 8)
4490 {
4491 g_cManyTreeDepth = ValueUnion.u32;
4492 g_cManyTreeFiles = fsPerfCalcManyTreeFiles();
4493 break;
4494 }
4495 RTTestFailed(g_hTest, "Out of range --tree-depth value: %u (%#x)\n", ValueUnion.u32, ValueUnion.u32);
4496 return RTTestSummaryAndDestroy(g_hTest);
4497
4498 case kCmdOpt_IoFileSize:
4499 if (ValueUnion.u64 == 0)
4500 g_cbIoFile = _512M;
4501 else
4502 g_cbIoFile = ValueUnion.u64;
4503 break;
4504
4505 case kCmdOpt_SetBlockSize:
4506 if (ValueUnion.u32 > 0)
4507 {
4508 g_cIoBlocks = 1;
4509 g_acbIoBlocks[0] = ValueUnion.u32;
4510 }
4511 else
4512 {
4513 RTTestFailed(g_hTest, "Invalid I/O block size: %u (%#x)\n", ValueUnion.u32, ValueUnion.u32);
4514 return RTTestSummaryAndDestroy(g_hTest);
4515 }
4516 break;
4517
4518 case kCmdOpt_AddBlockSize:
4519 if (g_cIoBlocks >= RT_ELEMENTS(g_acbIoBlocks))
4520 RTTestFailed(g_hTest, "Too many I/O block sizes: max %u\n", RT_ELEMENTS(g_acbIoBlocks));
4521 else if (ValueUnion.u32 == 0)
4522 RTTestFailed(g_hTest, "Invalid I/O block size: %u (%#x)\n", ValueUnion.u32, ValueUnion.u32);
4523 else
4524 {
4525 g_acbIoBlocks[g_cIoBlocks++] = ValueUnion.u32;
4526 break;
4527 }
4528 return RTTestSummaryAndDestroy(g_hTest);
4529
4530 case 'q':
4531 g_uVerbosity = 0;
4532 break;
4533
4534 case 'v':
4535 g_uVerbosity++;
4536 break;
4537
4538 case 'h':
4539 Usage(g_pStdOut);
4540 return RTEXITCODE_SUCCESS;
4541
4542 case 'V':
4543 {
4544 char szRev[] = "$Revision: 77926 $";
4545 szRev[RT_ELEMENTS(szRev) - 2] = '\0';
4546 RTPrintf(RTStrStrip(strchr(szRev, ':') + 1));
4547 return RTEXITCODE_SUCCESS;
4548 }
4549
4550 default:
4551 return RTGetOptPrintError(rc, &ValueUnion);
4552 }
4553 }
4554
4555 /*
4556 * Create the test directory with an 'empty' subdirectory under it,
4557 * execute the tests, and remove directory when done.
4558 */
4559 RTTestBanner(g_hTest);
4560 if (!RTPathExists(g_szDir))
4561 {
4562 /* The base dir: */
4563 rc = RTDirCreate(g_szDir, 0755,
4564 RTDIRCREATE_FLAGS_NOT_CONTENT_INDEXED_DONT_SET | RTDIRCREATE_FLAGS_NOT_CONTENT_INDEXED_NOT_CRITICAL);
4565 if (RT_SUCCESS(rc))
4566 {
4567 RTTestIPrintf(RTTESTLVL_ALWAYS, "Test dir: %s\n", g_szDir);
4568 rc = fsPrepTestArea();
4569 if (RT_SUCCESS(rc))
4570 {
4571 /* Profile RTTimeNanoTS(). */
4572 fsPerfNanoTS();
4573
4574 /* Do tests: */
4575 if (g_fManyFiles)
4576 fsPerfManyFiles();
4577 if (g_fOpen)
4578 fsPerfOpen();
4579 if (g_fFStat)
4580 fsPerfFStat();
4581 if (g_fFChMod)
4582 fsPerfFChMod();
4583 if (g_fFUtimes)
4584 fsPerfFUtimes();
4585 if (g_fStat)
4586 fsPerfStat();
4587 if (g_fChMod)
4588 fsPerfChmod();
4589 if (g_fUtimes)
4590 fsPerfUtimes();
4591 if (g_fRename)
4592 fsPerfRename();
4593 if (g_fDirOpen)
4594 vsPerfDirOpen();
4595 if (g_fDirEnum)
4596 vsPerfDirEnum();
4597 if (g_fMkRmDir)
4598 fsPerfMkRmDir();
4599 if (g_fStatVfs)
4600 fsPerfStatVfs();
4601 if (g_fRm || g_fManyFiles)
4602 fsPerfRm(); /* deletes manyfiles and manytree */
4603 if (g_fChSize)
4604 fsPerfChSize();
4605 if ( g_fReadPerf || g_fReadTests || g_fSendFile || g_fWritePerf || g_fWriteTests
4606#ifdef RT_OS_LINUX
4607 || g_fSplice
4608#endif
4609 || g_fSeek || g_fFSync || g_fMMap)
4610 fsPerfIo();
4611 if (g_fCopy)
4612 fsPerfCopy();
4613 }
4614
4615 /* Cleanup: */
4616 g_szDir[g_cchDir] = '\0';
4617 rc = RTDirRemoveRecursive(g_szDir, RTDIRRMREC_F_CONTENT_AND_DIR);
4618 if (RT_FAILURE(rc))
4619 RTTestFailed(g_hTest, "RTDirRemoveRecursive(%s,) -> %Rrc\n", g_szDir, rc);
4620 }
4621 else
4622 RTTestFailed(g_hTest, "RTDirCreate(%s) -> %Rrc\n", g_szDir, rc);
4623 }
4624 else
4625 RTTestFailed(g_hTest, "Test directory already exists: %s\n", g_szDir);
4626
4627 return RTTestSummaryAndDestroy(g_hTest);
4628}
4629
Note: See TracBrowser for help on using the repository browser.

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