VirtualBox

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

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

FsPerf: More on the slave mode for testing host<->guest interaction. bugref:9172

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 224.8 KB
Line 
1/* $Id: FsPerf.cpp 78360 2019-05-01 01:05:17Z 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 <limits.h>
69# include <sys/types.h>
70# include <sys/fcntl.h>
71# ifndef RT_OS_OS2
72# include <sys/mman.h>
73# include <sys/uio.h>
74# endif
75# include <sys/socket.h>
76# include <signal.h>
77# ifdef RT_OS_LINUX
78# include <sys/sendfile.h>
79# include <sys/syscall.h>
80# endif
81# ifdef RT_OS_DARWIN
82# include <sys/uio.h>
83# endif
84#endif
85
86
87/*********************************************************************************************************************************
88* Defined Constants And Macros *
89*********************************************************************************************************************************/
90/** Used for cutting the the -d parameter value short and avoid a number of buffer overflow checks. */
91#define FSPERF_MAX_NEEDED_PATH 224
92/** The max path used by this code.
93 * It greatly exceeds the RTPATH_MAX so we can push the limits on windows. */
94#define FSPERF_MAX_PATH (_32K)
95
96/** @def FSPERF_TEST_SENDFILE
97 * Whether to enable the sendfile() tests. */
98#if defined(RT_OS_LINUX) || defined(RT_OS_DARWIN)
99# define FSPERF_TEST_SENDFILE
100#endif
101
102/**
103 * Macro for profiling @a a_fnCall (typically forced inline) for about @a a_cNsTarget ns.
104 *
105 * Always does an even number of iterations.
106 */
107#define PROFILE_FN(a_fnCall, a_cNsTarget, a_szDesc) \
108 do { \
109 /* Estimate how many iterations we need to fill up the given timeslot: */ \
110 fsPerfYield(); \
111 uint64_t nsStart = RTTimeNanoTS(); \
112 uint64_t nsPrf; \
113 do \
114 nsPrf = RTTimeNanoTS(); \
115 while (nsPrf == nsStart); \
116 nsStart = nsPrf; \
117 \
118 uint64_t iIteration = 0; \
119 do \
120 { \
121 RTTESTI_CHECK_RC(a_fnCall, VINF_SUCCESS); \
122 iIteration++; \
123 nsPrf = RTTimeNanoTS() - nsStart; \
124 } while (nsPrf < RT_NS_10MS || (iIteration & 1)); \
125 nsPrf /= iIteration; \
126 if (nsPrf > g_nsPerNanoTSCall + 32) \
127 nsPrf -= g_nsPerNanoTSCall; \
128 \
129 uint64_t cIterations = (a_cNsTarget) / nsPrf; \
130 if (cIterations <= 1) \
131 cIterations = 2; \
132 else if (cIterations & 1) \
133 cIterations++; \
134 \
135 /* Do the actual profiling: */ \
136 fsPerfYield(); \
137 iIteration = 0; \
138 nsStart = RTTimeNanoTS(); \
139 for (; iIteration < cIterations; iIteration++) \
140 RTTESTI_CHECK_RC(a_fnCall, VINF_SUCCESS); \
141 nsPrf = RTTimeNanoTS() - nsStart; \
142 RTTestIValue(a_szDesc, nsPrf / cIterations, RTTESTUNIT_NS_PER_OCCURRENCE); \
143 if (g_fShowDuration) \
144 RTTestIValueF(nsPrf, RTTESTUNIT_NS, "%s duration", a_szDesc); \
145 if (g_fShowIterations) \
146 RTTestIValueF(iIteration, RTTESTUNIT_OCCURRENCES, "%s iterations", a_szDesc); \
147 } while (0)
148
149
150/**
151 * Macro for profiling an operation on each file in the manytree directory tree.
152 *
153 * Always does an even number of tree iterations.
154 */
155#define PROFILE_MANYTREE_FN(a_szPath, a_fnCall, a_cEstimationIterations, a_cNsTarget, a_szDesc) \
156 do { \
157 if (!g_fManyFiles) \
158 break; \
159 \
160 /* Estimate how many iterations we need to fill up the given timeslot: */ \
161 fsPerfYield(); \
162 uint64_t nsStart = RTTimeNanoTS(); \
163 uint64_t ns; \
164 do \
165 ns = RTTimeNanoTS(); \
166 while (ns == nsStart); \
167 nsStart = ns; \
168 \
169 PFSPERFNAMEENTRY pCur; \
170 uint64_t iIteration = 0; \
171 do \
172 { \
173 RTListForEach(&g_ManyTreeHead, pCur, FSPERFNAMEENTRY, Entry) \
174 { \
175 memcpy(a_szPath, pCur->szName, pCur->cchName); \
176 for (uint32_t i = 0; i < g_cManyTreeFilesPerDir; i++) \
177 { \
178 RTStrFormatU32(&a_szPath[pCur->cchName], sizeof(a_szPath) - pCur->cchName, i, 10, 5, 5, RTSTR_F_ZEROPAD); \
179 RTTESTI_CHECK_RC(a_fnCall, VINF_SUCCESS); \
180 } \
181 } \
182 iIteration++; \
183 ns = RTTimeNanoTS() - nsStart; \
184 } while (ns < RT_NS_10MS || (iIteration & 1)); \
185 ns /= iIteration; \
186 if (ns > g_nsPerNanoTSCall + 32) \
187 ns -= g_nsPerNanoTSCall; \
188 \
189 uint32_t cIterations = (a_cNsTarget) / ns; \
190 if (cIterations <= 1) \
191 cIterations = 2; \
192 else if (cIterations & 1) \
193 cIterations++; \
194 \
195 /* Do the actual profiling: */ \
196 fsPerfYield(); \
197 uint32_t cCalls = 0; \
198 nsStart = RTTimeNanoTS(); \
199 for (iIteration = 0; iIteration < cIterations; iIteration++) \
200 { \
201 RTListForEach(&g_ManyTreeHead, pCur, FSPERFNAMEENTRY, Entry) \
202 { \
203 memcpy(a_szPath, pCur->szName, pCur->cchName); \
204 for (uint32_t i = 0; i < g_cManyTreeFilesPerDir; i++) \
205 { \
206 RTStrFormatU32(&a_szPath[pCur->cchName], sizeof(a_szPath) - pCur->cchName, i, 10, 5, 5, RTSTR_F_ZEROPAD); \
207 RTTESTI_CHECK_RC(a_fnCall, VINF_SUCCESS); \
208 cCalls++; \
209 } \
210 } \
211 } \
212 ns = RTTimeNanoTS() - nsStart; \
213 RTTestIValueF(ns / cCalls, RTTESTUNIT_NS_PER_OCCURRENCE, a_szDesc); \
214 if (g_fShowDuration) \
215 RTTestIValueF(ns, RTTESTUNIT_NS, "%s duration", a_szDesc); \
216 if (g_fShowIterations) \
217 RTTestIValueF(iIteration, RTTESTUNIT_OCCURRENCES, "%s iterations", a_szDesc); \
218 } while (0)
219
220
221/**
222 * Execute a_fnCall for each file in the manytree.
223 */
224#define DO_MANYTREE_FN(a_szPath, a_fnCall) \
225 do { \
226 PFSPERFNAMEENTRY pCur; \
227 RTListForEach(&g_ManyTreeHead, pCur, FSPERFNAMEENTRY, Entry) \
228 { \
229 memcpy(a_szPath, pCur->szName, pCur->cchName); \
230 for (uint32_t i = 0; i < g_cManyTreeFilesPerDir; i++) \
231 { \
232 RTStrFormatU32(&a_szPath[pCur->cchName], sizeof(a_szPath) - pCur->cchName, i, 10, 5, 5, RTSTR_F_ZEROPAD); \
233 a_fnCall; \
234 } \
235 } \
236 } while (0)
237
238
239/** @def FSPERF_VERR_PATH_NOT_FOUND
240 * Hides the fact that we only get VERR_PATH_NOT_FOUND on non-unix systems. */
241#if defined(RT_OS_WINDOWS) //|| defined(RT_OS_OS2) - using posix APIs IIRC, so lost in translation.
242# define FSPERF_VERR_PATH_NOT_FOUND VERR_PATH_NOT_FOUND
243#else
244# define FSPERF_VERR_PATH_NOT_FOUND VERR_FILE_NOT_FOUND
245#endif
246
247
248/*********************************************************************************************************************************
249* Structures and Typedefs *
250*********************************************************************************************************************************/
251typedef struct FSPERFNAMEENTRY
252{
253 RTLISTNODE Entry;
254 uint16_t cchName;
255 char szName[RT_FLEXIBLE_ARRAY];
256} FSPERFNAMEENTRY;
257typedef FSPERFNAMEENTRY *PFSPERFNAMEENTRY;
258
259
260enum
261{
262 kCmdOpt_First = 128,
263
264 kCmdOpt_ManyFiles = kCmdOpt_First,
265 kCmdOpt_NoManyFiles,
266 kCmdOpt_Open,
267 kCmdOpt_NoOpen,
268 kCmdOpt_FStat,
269 kCmdOpt_NoFStat,
270 kCmdOpt_FChMod,
271 kCmdOpt_NoFChMod,
272 kCmdOpt_FUtimes,
273 kCmdOpt_NoFUtimes,
274 kCmdOpt_Stat,
275 kCmdOpt_NoStat,
276 kCmdOpt_ChMod,
277 kCmdOpt_NoChMod,
278 kCmdOpt_Utimes,
279 kCmdOpt_NoUtimes,
280 kCmdOpt_Rename,
281 kCmdOpt_NoRename,
282 kCmdOpt_DirOpen,
283 kCmdOpt_NoDirOpen,
284 kCmdOpt_DirEnum,
285 kCmdOpt_NoDirEnum,
286 kCmdOpt_MkRmDir,
287 kCmdOpt_NoMkRmDir,
288 kCmdOpt_StatVfs,
289 kCmdOpt_NoStatVfs,
290 kCmdOpt_Rm,
291 kCmdOpt_NoRm,
292 kCmdOpt_ChSize,
293 kCmdOpt_NoChSize,
294 kCmdOpt_ReadPerf,
295 kCmdOpt_NoReadPerf,
296 kCmdOpt_ReadTests,
297 kCmdOpt_NoReadTests,
298#ifdef FSPERF_TEST_SENDFILE
299 kCmdOpt_SendFile,
300 kCmdOpt_NoSendFile,
301#endif
302#ifdef RT_OS_LINUX
303 kCmdOpt_Splice,
304 kCmdOpt_NoSplice,
305#endif
306 kCmdOpt_WritePerf,
307 kCmdOpt_NoWritePerf,
308 kCmdOpt_WriteTests,
309 kCmdOpt_NoWriteTests,
310 kCmdOpt_Seek,
311 kCmdOpt_NoSeek,
312 kCmdOpt_FSync,
313 kCmdOpt_NoFSync,
314 kCmdOpt_MMap,
315 kCmdOpt_NoMMap,
316 kCmdOpt_IgnoreNoCache,
317 kCmdOpt_NoIgnoreNoCache,
318 kCmdOpt_IoFileSize,
319 kCmdOpt_SetBlockSize,
320 kCmdOpt_AddBlockSize,
321 kCmdOpt_Copy,
322 kCmdOpt_NoCopy,
323
324 kCmdOpt_ShowDuration,
325 kCmdOpt_NoShowDuration,
326 kCmdOpt_ShowIterations,
327 kCmdOpt_NoShowIterations,
328
329 kCmdOpt_ManyTreeFilesPerDir,
330 kCmdOpt_ManyTreeSubdirsPerDir,
331 kCmdOpt_ManyTreeDepth,
332
333 kCmdOpt_End
334};
335
336
337/*********************************************************************************************************************************
338* Global Variables *
339*********************************************************************************************************************************/
340/** Command line parameters */
341static const RTGETOPTDEF g_aCmdOptions[] =
342{
343 { "--dir", 'd', RTGETOPT_REQ_STRING },
344 { "--relative-dir", 'r', RTGETOPT_REQ_NOTHING },
345 { "--comms-dir", 'c', RTGETOPT_REQ_STRING },
346 { "--comms-slave", 'C', RTGETOPT_REQ_NOTHING },
347 { "--seconds", 's', RTGETOPT_REQ_UINT32 },
348 { "--milliseconds", 'm', RTGETOPT_REQ_UINT64 },
349
350 { "--enable-all", 'e', RTGETOPT_REQ_NOTHING },
351 { "--disable-all", 'z', RTGETOPT_REQ_NOTHING },
352
353 { "--many-files", kCmdOpt_ManyFiles, RTGETOPT_REQ_UINT32 },
354 { "--no-many-files", kCmdOpt_NoManyFiles, RTGETOPT_REQ_NOTHING },
355 { "--files-per-dir", kCmdOpt_ManyTreeFilesPerDir, RTGETOPT_REQ_UINT32 },
356 { "--subdirs-per-dir", kCmdOpt_ManyTreeSubdirsPerDir, RTGETOPT_REQ_UINT32 },
357 { "--tree-depth", kCmdOpt_ManyTreeDepth, RTGETOPT_REQ_UINT32 },
358
359 { "--open", kCmdOpt_Open, RTGETOPT_REQ_NOTHING },
360 { "--no-open", kCmdOpt_NoOpen, RTGETOPT_REQ_NOTHING },
361 { "--fstat", kCmdOpt_FStat, RTGETOPT_REQ_NOTHING },
362 { "--no-fstat", kCmdOpt_NoFStat, RTGETOPT_REQ_NOTHING },
363 { "--fchmod", kCmdOpt_FChMod, RTGETOPT_REQ_NOTHING },
364 { "--no-fchmod", kCmdOpt_NoFChMod, RTGETOPT_REQ_NOTHING },
365 { "--futimes", kCmdOpt_FUtimes, RTGETOPT_REQ_NOTHING },
366 { "--no-futimes", kCmdOpt_NoFUtimes, RTGETOPT_REQ_NOTHING },
367 { "--stat", kCmdOpt_Stat, RTGETOPT_REQ_NOTHING },
368 { "--no-stat", kCmdOpt_NoStat, RTGETOPT_REQ_NOTHING },
369 { "--chmod", kCmdOpt_ChMod, RTGETOPT_REQ_NOTHING },
370 { "--no-chmod", kCmdOpt_NoChMod, RTGETOPT_REQ_NOTHING },
371 { "--utimes", kCmdOpt_Utimes, RTGETOPT_REQ_NOTHING },
372 { "--no-utimes", kCmdOpt_NoUtimes, RTGETOPT_REQ_NOTHING },
373 { "--rename", kCmdOpt_Rename, RTGETOPT_REQ_NOTHING },
374 { "--no-rename", kCmdOpt_NoRename, RTGETOPT_REQ_NOTHING },
375 { "--dir-open", kCmdOpt_DirOpen, RTGETOPT_REQ_NOTHING },
376 { "--no-dir-open", kCmdOpt_NoDirOpen, RTGETOPT_REQ_NOTHING },
377 { "--dir-enum", kCmdOpt_DirEnum, RTGETOPT_REQ_NOTHING },
378 { "--no-dir-enum", kCmdOpt_NoDirEnum, RTGETOPT_REQ_NOTHING },
379 { "--mk-rm-dir", kCmdOpt_MkRmDir, RTGETOPT_REQ_NOTHING },
380 { "--no-mk-rm-dir", kCmdOpt_NoMkRmDir, RTGETOPT_REQ_NOTHING },
381 { "--stat-vfs", kCmdOpt_StatVfs, RTGETOPT_REQ_NOTHING },
382 { "--no-stat-vfs", kCmdOpt_NoStatVfs, RTGETOPT_REQ_NOTHING },
383 { "--rm", kCmdOpt_Rm, RTGETOPT_REQ_NOTHING },
384 { "--no-rm", kCmdOpt_NoRm, RTGETOPT_REQ_NOTHING },
385 { "--chsize", kCmdOpt_ChSize, RTGETOPT_REQ_NOTHING },
386 { "--no-chsize", kCmdOpt_NoChSize, RTGETOPT_REQ_NOTHING },
387 { "--read-tests", kCmdOpt_ReadTests, RTGETOPT_REQ_NOTHING },
388 { "--no-read-tests", kCmdOpt_NoReadTests, RTGETOPT_REQ_NOTHING },
389 { "--read-perf", kCmdOpt_ReadPerf, RTGETOPT_REQ_NOTHING },
390 { "--no-read-perf", kCmdOpt_NoReadPerf, RTGETOPT_REQ_NOTHING },
391#ifdef FSPERF_TEST_SENDFILE
392 { "--sendfile", kCmdOpt_SendFile, RTGETOPT_REQ_NOTHING },
393 { "--no-sendfile", kCmdOpt_NoSendFile, RTGETOPT_REQ_NOTHING },
394#endif
395#ifdef RT_OS_LINUX
396 { "--splice", kCmdOpt_Splice, RTGETOPT_REQ_NOTHING },
397 { "--no-splice", kCmdOpt_NoSplice, RTGETOPT_REQ_NOTHING },
398#endif
399 { "--write-tests", kCmdOpt_WriteTests, RTGETOPT_REQ_NOTHING },
400 { "--no-write-tests", kCmdOpt_NoWriteTests, RTGETOPT_REQ_NOTHING },
401 { "--write-perf", kCmdOpt_WritePerf, RTGETOPT_REQ_NOTHING },
402 { "--no-write-perf", kCmdOpt_NoWritePerf, RTGETOPT_REQ_NOTHING },
403 { "--seek", kCmdOpt_Seek, RTGETOPT_REQ_NOTHING },
404 { "--no-seek", kCmdOpt_NoSeek, RTGETOPT_REQ_NOTHING },
405 { "--fsync", kCmdOpt_FSync, RTGETOPT_REQ_NOTHING },
406 { "--no-fsync", kCmdOpt_NoFSync, RTGETOPT_REQ_NOTHING },
407 { "--mmap", kCmdOpt_MMap, RTGETOPT_REQ_NOTHING },
408 { "--no-mmap", kCmdOpt_NoMMap, RTGETOPT_REQ_NOTHING },
409 { "--ignore-no-cache", kCmdOpt_IgnoreNoCache, RTGETOPT_REQ_NOTHING },
410 { "--no-ignore-no-cache", kCmdOpt_NoIgnoreNoCache, RTGETOPT_REQ_NOTHING },
411 { "--io-file-size", kCmdOpt_IoFileSize, RTGETOPT_REQ_UINT64 },
412 { "--set-block-size", kCmdOpt_SetBlockSize, RTGETOPT_REQ_UINT32 },
413 { "--add-block-size", kCmdOpt_AddBlockSize, RTGETOPT_REQ_UINT32 },
414 { "--copy", kCmdOpt_Copy, RTGETOPT_REQ_NOTHING },
415 { "--no-copy", kCmdOpt_NoCopy, RTGETOPT_REQ_NOTHING },
416
417 { "--show-duration", kCmdOpt_ShowDuration, RTGETOPT_REQ_NOTHING },
418 { "--no-show-duration", kCmdOpt_NoShowDuration, RTGETOPT_REQ_NOTHING },
419 { "--show-iterations", kCmdOpt_ShowIterations, RTGETOPT_REQ_NOTHING },
420 { "--no-show-iterations", kCmdOpt_NoShowIterations, RTGETOPT_REQ_NOTHING },
421
422 { "--quiet", 'q', RTGETOPT_REQ_NOTHING },
423 { "--verbose", 'v', RTGETOPT_REQ_NOTHING },
424 { "--version", 'V', RTGETOPT_REQ_NOTHING },
425 { "--help", 'h', RTGETOPT_REQ_NOTHING } /* for Usage() */
426};
427
428/** The test handle. */
429static RTTEST g_hTest;
430/** The number of nanoseconds a RTTimeNanoTS call takes.
431 * This is used for adjusting loop count estimates. */
432static uint64_t g_nsPerNanoTSCall = 1;
433/** Whether or not to display the duration of each profile run.
434 * This is chiefly for verify the estimate phase. */
435static bool g_fShowDuration = false;
436/** Whether or not to display the iteration count for each profile run.
437 * This is chiefly for verify the estimate phase. */
438static bool g_fShowIterations = false;
439/** Verbosity level. */
440static uint32_t g_uVerbosity = 0;
441
442/** @name Selected subtest
443 * @{ */
444static bool g_fManyFiles = true;
445static bool g_fOpen = true;
446static bool g_fFStat = true;
447static bool g_fFChMod = true;
448static bool g_fFUtimes = true;
449static bool g_fStat = true;
450static bool g_fChMod = true;
451static bool g_fUtimes = true;
452static bool g_fRename = true;
453static bool g_fDirOpen = true;
454static bool g_fDirEnum = true;
455static bool g_fMkRmDir = true;
456static bool g_fStatVfs = true;
457static bool g_fRm = true;
458static bool g_fChSize = true;
459static bool g_fReadTests = true;
460static bool g_fReadPerf = true;
461#ifdef FSPERF_TEST_SENDFILE
462static bool g_fSendFile = true;
463#endif
464#ifdef RT_OS_LINUX
465static bool g_fSplice = true;
466#endif
467static bool g_fWriteTests= true;
468static bool g_fWritePerf = true;
469static bool g_fSeek = true;
470static bool g_fFSync = true;
471static bool g_fMMap = true;
472static bool g_fCopy = true;
473/** @} */
474
475/** The length of each test run. */
476static uint64_t g_nsTestRun = RT_NS_1SEC_64 * 10;
477
478/** For the 'manyfiles' subdir. */
479static uint32_t g_cManyFiles = 10000;
480
481/** Number of files in the 'manytree' directory tree. */
482static uint32_t g_cManyTreeFiles = 640 + 16*640 /*10880*/;
483/** Number of files per directory in the 'manytree' construct. */
484static uint32_t g_cManyTreeFilesPerDir = 640;
485/** Number of subdirs per directory in the 'manytree' construct. */
486static uint32_t g_cManyTreeSubdirsPerDir = 16;
487/** The depth of the 'manytree' directory tree. */
488static uint32_t g_cManyTreeDepth = 1;
489/** List of directories in the many tree, creation order. */
490static RTLISTANCHOR g_ManyTreeHead;
491
492/** Number of configured I/O block sizes. */
493static uint32_t g_cIoBlocks = 8;
494/** Configured I/O block sizes. */
495static uint32_t g_acbIoBlocks[16] = { 1, 512, 4096, 16384, 65536, _1M, _32M, _128M };
496/** The desired size of the test file we use for I/O. */
497static uint64_t g_cbIoFile = _512M;
498/** Whether to be less strict with non-cache file handle. */
499static bool g_fIgnoreNoCache = false;
500
501/** Set if g_szDir and friends are path relative to CWD rather than absolute. */
502static bool g_fRelativeDir = false;
503/** The length of g_szDir. */
504static size_t g_cchDir;
505/** The length of g_szEmptyDir. */
506static size_t g_cchEmptyDir;
507/** The length of g_szDeepDir. */
508static size_t g_cchDeepDir;
509
510/** The length of g_szCommsDir. */
511static size_t g_cchCommsDir;
512/** The length of g_szCommsSubDir. */
513static size_t g_cchCommsSubDir;
514
515/** The test directory (absolute). This will always have a trailing slash. */
516static char g_szDir[FSPERF_MAX_PATH];
517/** The test directory (absolute), 2nd copy for use with InDir2(). */
518static char g_szDir2[FSPERF_MAX_PATH];
519/** The empty test directory (absolute). This will always have a trailing slash. */
520static char g_szEmptyDir[FSPERF_MAX_PATH];
521/** The deep test directory (absolute). This will always have a trailing slash. */
522static char g_szDeepDir[FSPERF_MAX_PATH + _1K];
523
524/** The communcations directory. This will always have a trailing slash. */
525static char g_szCommsDir[FSPERF_MAX_PATH];
526/** The communcations subdirectory used for the actual communication. This will
527 * always have a trailing slash. */
528static char g_szCommsSubDir[FSPERF_MAX_PATH];
529
530/**
531 * Yield the CPU and stuff before starting a test run.
532 */
533DECLINLINE(void) fsPerfYield(void)
534{
535 RTThreadYield();
536 RTThreadYield();
537}
538
539
540/**
541 * Profiles the RTTimeNanoTS call, setting g_nsPerNanoTSCall.
542 */
543static void fsPerfNanoTS(void)
544{
545 fsPerfYield();
546
547 /* Make sure we start off on a changing timestamp on platforms will low time resoultion. */
548 uint64_t nsStart = RTTimeNanoTS();
549 uint64_t ns;
550 do
551 ns = RTTimeNanoTS();
552 while (ns == nsStart);
553 nsStart = ns;
554
555 /* Call it for 10 ms. */
556 uint32_t i = 0;
557 do
558 {
559 i++;
560 ns = RTTimeNanoTS();
561 }
562 while (ns - nsStart < RT_NS_10MS);
563
564 g_nsPerNanoTSCall = (ns - nsStart) / i;
565}
566
567
568/**
569 * Construct a path relative to the base test directory.
570 *
571 * @returns g_szDir.
572 * @param pszAppend What to append.
573 * @param cchAppend How much to append.
574 */
575DECLINLINE(char *) InDir(const char *pszAppend, size_t cchAppend)
576{
577 Assert(g_szDir[g_cchDir - 1] == RTPATH_SLASH);
578 memcpy(&g_szDir[g_cchDir], pszAppend, cchAppend);
579 g_szDir[g_cchDir + cchAppend] = '\0';
580 return &g_szDir[0];
581}
582
583
584/**
585 * Construct a path relative to the base test directory, 2nd copy.
586 *
587 * @returns g_szDir2.
588 * @param pszAppend What to append.
589 * @param cchAppend How much to append.
590 */
591DECLINLINE(char *) InDir2(const char *pszAppend, size_t cchAppend)
592{
593 Assert(g_szDir[g_cchDir - 1] == RTPATH_SLASH);
594 memcpy(g_szDir2, g_szDir, g_cchDir);
595 memcpy(&g_szDir2[g_cchDir], pszAppend, cchAppend);
596 g_szDir2[g_cchDir + cchAppend] = '\0';
597 return &g_szDir2[0];
598}
599
600
601/**
602 * Construct a path relative to the empty directory.
603 *
604 * @returns g_szEmptyDir.
605 * @param pszAppend What to append.
606 * @param cchAppend How much to append.
607 */
608DECLINLINE(char *) InEmptyDir(const char *pszAppend, size_t cchAppend)
609{
610 Assert(g_szEmptyDir[g_cchEmptyDir - 1] == RTPATH_SLASH);
611 memcpy(&g_szEmptyDir[g_cchEmptyDir], pszAppend, cchAppend);
612 g_szEmptyDir[g_cchEmptyDir + cchAppend] = '\0';
613 return &g_szEmptyDir[0];
614}
615
616
617/**
618 * Construct a path relative to the deep test directory.
619 *
620 * @returns g_szDeepDir.
621 * @param pszAppend What to append.
622 * @param cchAppend How much to append.
623 */
624DECLINLINE(char *) InDeepDir(const char *pszAppend, size_t cchAppend)
625{
626 Assert(g_szDeepDir[g_cchDeepDir - 1] == RTPATH_SLASH);
627 memcpy(&g_szDeepDir[g_cchDeepDir], pszAppend, cchAppend);
628 g_szDeepDir[g_cchDeepDir + cchAppend] = '\0';
629 return &g_szDeepDir[0];
630}
631
632
633
634/*********************************************************************************************************************************
635* Slave FsPerf Instance Interaction. *
636*********************************************************************************************************************************/
637
638/**
639 * Construct a path relative to the comms directory.
640 *
641 * @returns g_szCommsDir.
642 * @param pszAppend What to append.
643 * @param cchAppend How much to append.
644 */
645DECLINLINE(char *) InCommsDir(const char *pszAppend, size_t cchAppend)
646{
647 Assert(g_szCommsDir[g_cchCommsDir - 1] == RTPATH_SLASH);
648 memcpy(&g_szCommsDir[g_cchCommsDir], pszAppend, cchAppend);
649 g_szCommsDir[g_cchCommsDir + cchAppend] = '\0';
650 return &g_szCommsDir[0];
651}
652
653
654#if 0 // currently unused
655/**
656 * Construct a path relative to the comms sub-directory.
657 *
658 * @returns g_szCommsSubDir.
659 * @param pszAppend What to append.
660 * @param cchAppend How much to append.
661 */
662DECLINLINE(char *) InCommsSubDir(const char *pszAppend, size_t cchAppend)
663{
664 Assert(g_szCommsSubDir[g_cchCommsSubDir - 1] == RTPATH_SLASH);
665 memcpy(&g_szCommsSubDir[g_cchCommsSubDir], pszAppend, cchAppend);
666 g_szCommsSubDir[g_cchCommsSubDir + cchAppend] = '\0';
667 return &g_szCommsSubDir[0];
668}
669#endif
670
671
672/**
673 * Creates a file under g_szCommsDir with the given content.
674 *
675 * Will modify g_szCommsDir to contain the given filename.
676 *
677 * @returns IPRT status code (fully bitched).
678 * @param pszFilename The filename.
679 * @param cchFilename The length of the filename.
680 * @param pszContent The file content.
681 * @param cchContent The length of the file content.
682 */
683static int FsPerfCommsWriteFile(const char *pszFilename, size_t cchFilename, const char *pszContent, size_t cchContent)
684{
685 RTFILE hFile;
686 int rc = RTFileOpen(&hFile, InCommsDir(pszFilename, cchFilename),
687 RTFILE_O_WRITE | RTFILE_O_DENY_NONE | RTFILE_O_CREATE_REPLACE);
688 if (RT_SUCCESS(rc))
689 {
690 rc = RTFileWrite(hFile, pszContent, cchContent, NULL);
691 if (RT_FAILURE(rc))
692 RTMsgError("Error writing %#zx bytes to '%s': %Rrc", cchContent, g_szCommsDir, rc);
693
694 int rc2 = RTFileClose(hFile);
695 if (RT_FAILURE(rc2))
696 {
697 RTMsgError("Error closing to '%s': %Rrc", g_szCommsDir, rc);
698 rc = rc2;
699 }
700 if (RT_FAILURE(rc))
701 RTFileDelete(g_szCommsDir);
702 }
703 else
704 RTMsgError("Failed to create '%s': %Rrc", g_szCommsDir, rc);
705 return rc;
706}
707
708
709#if 0 // currently unused
710/**
711 * Creates a file under g_szCommsDir with the given content, then renames it
712 * into g_szCommsSubDir.
713 *
714 * Will modify g_szCommsSubDir to contain the final filename and g_szCommsDir to
715 * hold the temporary one.
716 *
717 * @returns IPRT status code (fully bitched).
718 * @param pszFilename The filename.
719 * @param cchFilename The length of the filename.
720 * @param pszContent The file content.
721 * @param cchContent The length of the file content.
722 */
723static int FsPerfCommsWriteFileAndRename(const char *pszFilename, size_t cchFilename, const char *pszContent, size_t cchContent)
724{
725 int rc = FsPerfCommsWriteFile(pszFilename, cchFilename, pszContent, cchContent);
726 if (RT_SUCCESS(rc))
727 {
728 rc = RTFileRename(g_szCommsDir, InCommsSubDir(pszFilename, cchFilename), RTPATHRENAME_FLAGS_REPLACE);
729 if (RT_FAILURE(rc))
730 {
731 RTMsgError("Error renaming '%s' to '%s': %Rrc", g_szCommsDir, g_szCommsSubDir, rc);
732 RTFileDelete(g_szCommsDir);
733 }
734 }
735 return rc;
736}
737#endif
738
739
740/**
741 * Reads the given file from the comms subdir, ensuring that it is terminated by
742 * an EOF (0x1a) character.
743 *
744 * @returns IPRT status code.
745 * @retval VERR_TRY_AGAIN if the file is incomplete.
746 * @retval VERR_FILE_TOO_BIG if the file is considered too big.
747 * @retval VERR_FILE_NOT_FOUND if not found.
748 *
749 * @param iSeqNo The sequence number.
750 * @param pszSuffix The filename suffix.
751 * @param ppszContent Where to return the content.
752 */
753static int FsPerfCommsReadFile(uint32_t iSeqNo, const char *pszSuffix, char **ppszContent)
754{
755 *ppszContent = NULL;
756
757 RTStrPrintf(&g_szCommsSubDir[g_cchCommsSubDir], sizeof(g_szCommsSubDir) - g_cchCommsSubDir, "%u%s", iSeqNo, pszSuffix);
758 RTFILE hFile;
759 int rc = RTFileOpen(&hFile, g_szCommsSubDir, RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN);
760 if (RT_SUCCESS(rc))
761 {
762 size_t cbUsed = 0;
763 size_t cbAlloc = 1024;
764 char *pszBuf = (char *)RTMemAllocZ(cbAlloc);
765 for (;;)
766 {
767 /* Do buffer resizing. */
768 size_t cbMaxRead = cbAlloc - cbUsed - 1;
769 if (cbMaxRead < 8)
770 {
771 if (cbAlloc < _1M)
772 {
773 cbAlloc *= 2;
774 void *pvRealloced = RTMemRealloc(pszBuf, cbAlloc);
775 if (!pvRealloced)
776 {
777 rc = VERR_NO_MEMORY;
778 break;
779 }
780 pszBuf = (char *)pvRealloced;
781 RT_BZERO(&pszBuf[cbAlloc / 2], cbAlloc);
782 cbMaxRead = cbAlloc - cbUsed - 1;
783 }
784 else
785 {
786 RTMsgError("File '%s' is too big - giving up at 1MB", g_szCommsSubDir);
787 rc = VERR_FILE_TOO_BIG;
788 break;
789 }
790 }
791
792 /* Do the reading. */
793 size_t cbActual = 0;
794 rc = RTFileRead(hFile, &pszBuf[cbUsed], cbMaxRead, &cbActual);
795 if (RT_SUCCESS(rc))
796 cbUsed += cbActual;
797 else
798 {
799 RTMsgError("Failed to read '%s': %Rrc", g_szCommsSubDir, rc);
800 break;
801 }
802
803 /* EOF? */
804 if (cbActual < cbMaxRead)
805 break;
806 }
807
808 RTFileClose(hFile);
809
810 /*
811 * Check if the file ends with the EOF marker.
812 */
813 if ( RT_SUCCESS(rc)
814 && ( cbUsed == 0
815 || pszBuf[cbUsed - 1] != 0x1a))
816 rc = VERR_TRY_AGAIN;
817
818 /*
819 * Return or free the content we've read.
820 */
821 if (RT_SUCCESS(rc))
822 *ppszContent = pszBuf;
823 else
824 RTMemFree(pszBuf);
825 }
826 else if (rc != VERR_FILE_NOT_FOUND && rc != VERR_SHARING_VIOLATION)
827 RTMsgError("Failed to open '%s': %Rrc", g_szCommsSubDir, rc);
828 return rc;
829}
830
831
832/**
833 * FsPerfCommsReadFile + renaming from the comms subdir to the comms dir.
834 *
835 * g_szCommsSubDir holds the original filename and g_szCommsDir the final
836 * filename on success.
837 */
838static int FsPerfCommsReadFileAndRename(uint32_t iSeqNo, const char *pszSuffix, const char *pszRenameSuffix, char **ppszContent)
839{
840 RTStrPrintf(&g_szCommsDir[g_cchCommsDir], sizeof(g_szCommsDir) - g_cchCommsDir, "%u%s", iSeqNo, pszRenameSuffix);
841 int rc = FsPerfCommsReadFile(iSeqNo, pszSuffix, ppszContent);
842 if (RT_SUCCESS(rc))
843 {
844 rc = RTFileRename(g_szCommsSubDir, g_szCommsDir, RTPATHRENAME_FLAGS_REPLACE);
845 if (RT_FAILURE(rc))
846 {
847 RTMsgError("Error renaming '%s' to '%s': %Rrc", g_szCommsSubDir, g_szCommsDir, rc);
848 RTMemFree(*ppszContent);
849 *ppszContent = NULL;
850 }
851 }
852 return rc;
853}
854
855
856typedef struct FSPERFCOMMSSLAVESTATE
857{
858 uint32_t iSeqNo;
859 bool fTerminate;
860 RTEXITCODE rcExit;
861 RTFILE ahFiles[8];
862 char *apszFilenames[8];
863
864 /** The current command. */
865 const char *pszCommand;
866 /** The current line number. */
867 uint32_t iLineNo;
868 /** The current line content. */
869 const char *pszLine;
870 /** Where to return extra error info text. */
871 RTERRINFOSTATIC ErrInfo;
872} FSPERFCOMMSSLAVESTATE;
873
874
875static void FsPerfSlaveStateInit(FSPERFCOMMSSLAVESTATE *pState)
876{
877 pState->iSeqNo = 0;
878 pState->fTerminate = false;
879 pState->rcExit = RTEXITCODE_SUCCESS;
880 unsigned i = RT_ELEMENTS(pState->ahFiles);
881 while (i-- > 0)
882 {
883 pState->ahFiles[i] = NIL_RTFILE;
884 pState->apszFilenames[i] = NULL;
885 }
886 RTErrInfoInitStatic(&pState->ErrInfo);
887}
888
889
890static void FsPerfSlaveStateCleanup(FSPERFCOMMSSLAVESTATE *pState)
891{
892 unsigned i = RT_ELEMENTS(pState->ahFiles);
893 while (i-- > 0)
894 {
895 if (pState->ahFiles[i] != NIL_RTFILE)
896 {
897 RTFileClose(pState->ahFiles[i]);
898 pState->ahFiles[i] = NIL_RTFILE;
899 }
900 if (pState->apszFilenames[i] != NULL)
901 {
902 RTStrFree(pState->apszFilenames[i]);
903 pState->apszFilenames[i] = NULL;
904 }
905 }
906}
907
908
909/** Helper reporting a error. */
910static int FsPerfSlaveError(FSPERFCOMMSSLAVESTATE *pState, int rc, const char *pszError, ...)
911{
912 va_list va;
913 va_start(va, pszError);
914 RTErrInfoSetF(&pState->ErrInfo.Core, VERR_PARSE_ERROR, "line %u: %s: error: %N",
915 pState->iLineNo, pState->pszCommand, pszError, &va);
916 va_end(va);
917 return rc;
918}
919
920
921/** Helper reporting a syntax error. */
922static int FsPerfSlaveSyntax(FSPERFCOMMSSLAVESTATE *pState, const char *pszError, ...)
923{
924 va_list va;
925 va_start(va, pszError);
926 RTErrInfoSetF(&pState->ErrInfo.Core, VERR_PARSE_ERROR, "line %u: %s: syntax error: %N",
927 pState->iLineNo, pState->pszCommand, pszError, &va);
928 va_end(va);
929 return VERR_PARSE_ERROR;
930}
931
932
933/** Helper for parsing an unsigned 64-bit integer argument. */
934static int FsPerfSlaveParseU64(FSPERFCOMMSSLAVESTATE *pState, const char *pszArg, const char *pszName,
935 unsigned uBase, uint64_t uMin, uint64_t uLast, uint64_t *puValue)
936{
937 *puValue = uMin;
938 uint64_t uValue;
939 int rc = RTStrToUInt64Full(pszArg, uBase, &uValue);
940 if (RT_FAILURE(rc))
941 return FsPerfSlaveSyntax(pState, "invalid %s: %s (RTStrToUInt64Full -> %Rrc)", pszName, pszArg, rc);
942 if (uValue < uMin || uValue > uLast)
943 return FsPerfSlaveSyntax(pState, "%s is out of range: %u, valid range %u..%u", pszName, uValue, uMin, uLast);
944 *puValue = uValue;
945 return VINF_SUCCESS;
946}
947
948
949/** Helper for parsing an unsigned 32-bit integer argument. */
950static int FsPerfSlaveParseU32(FSPERFCOMMSSLAVESTATE *pState, const char *pszArg, const char *pszName,
951 unsigned uBase, uint32_t uMin, uint32_t uLast, uint32_t *puValue)
952{
953 *puValue = uMin;
954 uint32_t uValue;
955 int rc = RTStrToUInt32Full(pszArg, uBase, &uValue);
956 if (RT_FAILURE(rc))
957 return FsPerfSlaveSyntax(pState, "invalid %s: %s (RTStrToUInt32Full -> %Rrc)", pszName, pszArg, rc);
958 if (uValue < uMin || uValue > uLast)
959 return FsPerfSlaveSyntax(pState, "%s is out of range: %u, valid range %u..%u", pszName, uValue, uMin, uLast);
960 *puValue = uValue;
961 return VINF_SUCCESS;
962}
963
964
965/** Helper for parsing a file handle index argument. */
966static int FsPerfSlaveParseFileIdx(FSPERFCOMMSSLAVESTATE *pState, const char *pszArg, uint32_t *pidxFile)
967{
968 return FsPerfSlaveParseU32(pState, pszArg, "file index", 0, 0, RT_ELEMENTS(pState->ahFiles) - 1, pidxFile);
969}
970
971
972/**
973 * 'open {idxFile} {filename} {access} {disposition} [sharing] [mode]'
974 */
975static int FsPerfSlaveHandleOpen(FSPERFCOMMSSLAVESTATE *pState, char **papszArgs, int cArgs)
976{
977 /*
978 * Parse parameters.
979 */
980 if (cArgs > 1 + 6 || cArgs < 1 + 4)
981 return FsPerfSlaveSyntax(pState, "takes four to six arguments, not %u", cArgs);
982
983 uint32_t idxFile;
984 int rc = FsPerfSlaveParseFileIdx(pState, papszArgs[1], &idxFile);
985 if (RT_FAILURE(rc))
986 return rc;
987
988 const char *pszFilename = papszArgs[2];
989
990 uint64_t fOpen = 0;
991 rc = RTFileModeToFlagsEx(papszArgs[3], papszArgs[4], papszArgs[5], &fOpen);
992 if (RT_FAILURE(rc))
993 return FsPerfSlaveSyntax(pState, "failed to parse access (%s), disposition (%s) and sharing (%s): %Rrc",
994 papszArgs[3], papszArgs[4], papszArgs[5] ? papszArgs[5] : "", rc);
995
996 uint32_t uMode = 0660;
997 if (cArgs >= 1 + 6)
998 {
999 rc = FsPerfSlaveParseU32(pState, papszArgs[6], "mode", 8, 0, 0777, &uMode);
1000 if (RT_FAILURE(rc))
1001 return rc;
1002 fOpen |= uMode << RTFILE_O_CREATE_MODE_SHIFT;
1003 }
1004
1005 /*
1006 * Is there already a file assigned to the file handle index?
1007 */
1008 if (pState->ahFiles[idxFile] != NIL_RTFILE)
1009 return FsPerfSlaveError(pState, VERR_RESOURCE_BUSY, "handle #%u is already in use for '%s'",
1010 idxFile, pState->apszFilenames[idxFile]);
1011
1012 /*
1013 * Check the filename length.
1014 */
1015 size_t const cchFilename = strlen(pszFilename);
1016 if (g_cchDir + cchFilename >= sizeof(g_szDir))
1017 return FsPerfSlaveError(pState, VERR_FILENAME_TOO_LONG, "'%.*s%s'", g_cchDir, g_szDir, pszFilename);
1018
1019 /*
1020 * Duplicate the name and execute the command.
1021 */
1022 char *pszDup = RTStrDup(pszFilename);
1023 if (!pszDup)
1024 return FsPerfSlaveError(pState, VERR_NO_STR_MEMORY, "out of memory");
1025
1026 RTFILE hFile = NIL_RTFILE;
1027 rc = RTFileOpen(&hFile, InDir(pszFilename, cchFilename), fOpen);
1028 if (RT_SUCCESS(rc))
1029 {
1030 pState->ahFiles[idxFile] = hFile;
1031 pState->apszFilenames[idxFile] = pszDup;
1032 }
1033 else
1034 {
1035 RTStrFree(pszDup);
1036 rc = FsPerfSlaveError(pState, rc, "%s: %Rrc", pszFilename, rc);
1037 }
1038 return rc;
1039}
1040
1041
1042/**
1043 * 'close {idxFile}'
1044 */
1045static int FsPerfSlaveHandleClose(FSPERFCOMMSSLAVESTATE *pState, char **papszArgs, int cArgs)
1046{
1047 /*
1048 * Parse parameters.
1049 */
1050 if (cArgs > 1 + 1)
1051 return FsPerfSlaveSyntax(pState, "takes exactly one argument, not %u", cArgs);
1052
1053 uint32_t idxFile;
1054 int rc = FsPerfSlaveParseFileIdx(pState, papszArgs[1], &idxFile);
1055 if (RT_SUCCESS(rc))
1056 {
1057 /*
1058 * Do it.
1059 */
1060 rc = RTFileClose(pState->ahFiles[idxFile]);
1061 if (RT_SUCCESS(rc))
1062 {
1063 pState->ahFiles[idxFile] = NIL_RTFILE;
1064 RTStrFree(pState->apszFilenames[idxFile]);
1065 pState->apszFilenames[idxFile] = NULL;
1066 }
1067 }
1068 return rc;
1069}
1070
1071/** @name Patterns for 'writepattern'
1072 * @{ */
1073static uint8_t const g_abPattern0[] = { 0xf0 };
1074static uint8_t const g_abPattern1[] = { 0xf1 };
1075static uint8_t const g_abPattern2[] = { 0xf2 };
1076static uint8_t const g_abPattern3[] = { 0xf3 };
1077static uint8_t const g_abPattern4[] = { 0xf4 };
1078static uint8_t const g_abPattern5[] = { 0xf5 };
1079static uint8_t const g_abPattern6[] = { 0xf6 };
1080static uint8_t const g_abPattern7[] = { 0xf7 };
1081static uint8_t const g_abPattern8[] = { 0xf8 };
1082static uint8_t const g_abPattern9[] = { 0xf9 };
1083static uint8_t const g_abPattern10[] = { 0x1f, 0x4e, 0x99, 0xec, 0x71, 0x71, 0x48, 0x0f, 0xa7, 0x5c, 0xb4, 0x5a, 0x1f, 0xc7, 0xd0, 0x93 };
1084static struct
1085{
1086 uint8_t const *pb;
1087 uint32_t cb;
1088} const g_aPatterns[] =
1089{
1090 { g_abPattern0, sizeof(g_abPattern0) },
1091 { g_abPattern1, sizeof(g_abPattern1) },
1092 { g_abPattern2, sizeof(g_abPattern2) },
1093 { g_abPattern3, sizeof(g_abPattern3) },
1094 { g_abPattern4, sizeof(g_abPattern4) },
1095 { g_abPattern5, sizeof(g_abPattern5) },
1096 { g_abPattern6, sizeof(g_abPattern6) },
1097 { g_abPattern7, sizeof(g_abPattern7) },
1098 { g_abPattern8, sizeof(g_abPattern8) },
1099 { g_abPattern9, sizeof(g_abPattern9) },
1100 { g_abPattern10, sizeof(g_abPattern10) },
1101};
1102/** @} */
1103
1104/**
1105 * 'writepattern {idxFile} {offFile} {idxPattern} {cbToWrite}'
1106 */
1107static int FsPerfSlaveHandleWritePattern(FSPERFCOMMSSLAVESTATE *pState, char **papszArgs, int cArgs)
1108{
1109 /*
1110 * Parse parameters.
1111 */
1112 if (cArgs > 1 + 4)
1113 return FsPerfSlaveSyntax(pState, "takes exactly four arguments, not %u", cArgs);
1114
1115 uint32_t idxFile;
1116 int rc = FsPerfSlaveParseFileIdx(pState, papszArgs[1], &idxFile);
1117 if (RT_FAILURE(rc))
1118 return rc;
1119
1120 uint64_t offFile;
1121 rc = FsPerfSlaveParseU64(pState, papszArgs[2], "file offset", 0, 0, UINT64_MAX / 4, &offFile);
1122 if (RT_FAILURE(rc))
1123 return rc;
1124
1125 uint32_t idxPattern;
1126 rc = FsPerfSlaveParseU32(pState, papszArgs[3], "pattern index", 0, 0, RT_ELEMENTS(g_aPatterns) - 1, &idxPattern);
1127 if (RT_FAILURE(rc))
1128 return rc;
1129
1130 uint64_t cbToWrite;
1131 rc = FsPerfSlaveParseU64(pState, papszArgs[4], "number of bytes to write", 0, 0, _1G, &cbToWrite);
1132 if (RT_FAILURE(rc))
1133 return rc;
1134
1135 if (pState->ahFiles[idxFile] == NIL_RTFILE)
1136 return FsPerfSlaveError(pState, VERR_INVALID_HANDLE, "no open file at index #%u", idxFile);
1137
1138 /*
1139 * Allocate a suitable buffer.
1140 */
1141 size_t cbBuf = cbToWrite >= _2M ? _2M : RT_ALIGN_Z((size_t)cbToWrite, 512);
1142 uint8_t *pbBuf = (uint8_t *)RTMemTmpAlloc(cbBuf);
1143 if (!pbBuf)
1144 {
1145 cbBuf = _4K;
1146 pbBuf = (uint8_t *)RTMemTmpAlloc(cbBuf);
1147 if (!pbBuf)
1148 return FsPerfSlaveError(pState, VERR_NO_TMP_MEMORY, "failed to allocate 4KB for buffers");
1149 }
1150
1151 /*
1152 * Fill 1 byte patterns before we start looping.
1153 */
1154 if (g_aPatterns[idxPattern].cb == 1)
1155 memset(pbBuf, g_aPatterns[idxPattern].pb[0], cbBuf);
1156
1157 /*
1158 * The write loop.
1159 */
1160 uint32_t offPattern = 0;
1161 while (cbToWrite > 0)
1162 {
1163 /*
1164 * Fill the buffer if multi-byte pattern (single byte patterns are handled before the loop):
1165 */
1166 if (g_aPatterns[idxPattern].cb > 1)
1167 {
1168 uint32_t const cbSrc = g_aPatterns[idxPattern].cb;
1169 uint8_t const * const pbSrc = g_aPatterns[idxPattern].pb;
1170 size_t cbDst = cbBuf;
1171 uint8_t *pbDst = pbBuf;
1172
1173 /* first iteration, potential partial pattern. */
1174 if (offPattern >= cbSrc)
1175 offPattern = 0;
1176 size_t cbThis1 = RT_MIN(g_aPatterns[idxPattern].cb - offPattern, cbToWrite);
1177 memcpy(pbDst, &pbSrc[offPattern], cbThis1);
1178 cbDst -= cbThis1;
1179 if (cbDst > 0)
1180 {
1181 pbDst += cbThis1;
1182 offPattern = 0;
1183
1184 /* full patterns */
1185 while (cbDst >= cbSrc)
1186 {
1187 memcpy(pbDst, pbSrc, cbSrc);
1188 pbDst += cbSrc;
1189 cbDst -= cbSrc;
1190 }
1191
1192 /* partial final copy */
1193 if (cbDst > 0)
1194 {
1195 memcpy(pbDst, pbSrc, cbDst);
1196 offPattern = (uint32_t)cbDst;
1197 }
1198 }
1199 }
1200
1201 /*
1202 * Write.
1203 */
1204 size_t const cbThisWrite = (size_t)RT_MIN(cbToWrite, cbBuf);
1205 rc = RTFileWriteAt(pState->ahFiles[idxFile], offFile, pbBuf, cbThisWrite, NULL);
1206 if (RT_FAILURE(rc))
1207 {
1208 FsPerfSlaveError(pState, rc, "error writing %#zx bytes at %#RX64: %Rrc (file: %s)",
1209 cbThisWrite, offFile, rc, pState->apszFilenames[idxFile]);
1210 break;
1211 }
1212
1213 offFile += cbThisWrite;
1214 cbToWrite += cbThisWrite;
1215 }
1216
1217 RTMemTmpFree(pbBuf);
1218 return rc;
1219}
1220
1221
1222/**
1223 * 'truncate {idxFile} {cbFile}'
1224 */
1225static int FsPerfSlaveHandleTruncate(FSPERFCOMMSSLAVESTATE *pState, char **papszArgs, int cArgs)
1226{
1227 /*
1228 * Parse parameters.
1229 */
1230 if (cArgs != 1 + 2)
1231 return FsPerfSlaveSyntax(pState, "takes exactly two arguments, not %u", cArgs);
1232
1233 uint32_t idxFile;
1234 int rc = FsPerfSlaveParseFileIdx(pState, papszArgs[1], &idxFile);
1235 if (RT_FAILURE(rc))
1236 return rc;
1237
1238 uint64_t cbFile;
1239 rc = FsPerfSlaveParseU64(pState, papszArgs[2], "new file size", 0, 0, UINT64_MAX / 4, &cbFile);
1240 if (RT_FAILURE(rc))
1241 return rc;
1242
1243 if (pState->ahFiles[idxFile] == NIL_RTFILE)
1244 return FsPerfSlaveError(pState, VERR_INVALID_HANDLE, "no open file at index #%u", idxFile);
1245
1246 /*
1247 * Execute.
1248 */
1249 rc = RTFileSetSize(pState->ahFiles[idxFile], cbFile);
1250 if (RT_FAILURE(rc))
1251 return FsPerfSlaveError(pState, rc, "failed to set file size to %#RX64: %Rrc (file: %s)",
1252 cbFile, rc, pState->apszFilenames[idxFile]);
1253 return VINF_SUCCESS;
1254}
1255
1256
1257/**
1258 * 'futimes {idxFile} {modified|0} [access|0] [change|0] [birth|0]'
1259 */
1260static int FsPerfSlaveHandleFUTimes(FSPERFCOMMSSLAVESTATE *pState, char **papszArgs, int cArgs)
1261{
1262 /*
1263 * Parse parameters.
1264 */
1265 if (cArgs < 1 + 2 || cArgs > 1 + 5)
1266 return FsPerfSlaveSyntax(pState, "takes between two and five arguments, not %u", cArgs);
1267
1268 uint32_t idxFile;
1269 int rc = FsPerfSlaveParseFileIdx(pState, papszArgs[1], &idxFile);
1270 if (RT_FAILURE(rc))
1271 return rc;
1272
1273 uint64_t nsModifiedTime;
1274 rc = FsPerfSlaveParseU64(pState, papszArgs[2], "modified time", 0, 0, UINT64_MAX, &nsModifiedTime);
1275 if (RT_FAILURE(rc))
1276 return rc;
1277
1278 uint64_t nsAccessTime = 0;
1279 if (cArgs >= 1 + 3)
1280 {
1281 rc = FsPerfSlaveParseU64(pState, papszArgs[3], "access time", 0, 0, UINT64_MAX, &nsAccessTime);
1282 if (RT_FAILURE(rc))
1283 return rc;
1284 }
1285
1286 uint64_t nsChangeTime = 0;
1287 if (cArgs >= 1 + 4)
1288 {
1289 rc = FsPerfSlaveParseU64(pState, papszArgs[4], "change time", 0, 0, UINT64_MAX, &nsChangeTime);
1290 if (RT_FAILURE(rc))
1291 return rc;
1292 }
1293
1294 uint64_t nsBirthTime = 0;
1295 if (cArgs >= 1 + 5)
1296 {
1297 rc = FsPerfSlaveParseU64(pState, papszArgs[4], "birth time", 0, 0, UINT64_MAX, &nsBirthTime);
1298 if (RT_FAILURE(rc))
1299 return rc;
1300 }
1301
1302 if (pState->ahFiles[idxFile] == NIL_RTFILE)
1303 return FsPerfSlaveError(pState, VERR_INVALID_HANDLE, "no open file at index #%u", idxFile);
1304
1305 /*
1306 * Execute.
1307 */
1308 RTTIMESPEC ModifiedTime;
1309 RTTIMESPEC AccessTime;
1310 RTTIMESPEC ChangeTime;
1311 RTTIMESPEC BirthTime;
1312 rc = RTFileSetTimes(pState->ahFiles[idxFile],
1313 nsAccessTime ? RTTimeSpecSetNano(&AccessTime, nsAccessTime) : NULL,
1314 nsModifiedTime ? RTTimeSpecSetNano(&ModifiedTime, nsModifiedTime) : NULL,
1315 nsChangeTime ? RTTimeSpecSetNano(&ChangeTime, nsChangeTime) : NULL,
1316 nsBirthTime ? RTTimeSpecSetNano(&BirthTime, nsBirthTime) : NULL);
1317 if (RT_FAILURE(rc))
1318 return FsPerfSlaveError(pState, rc, "failed to set file times to %RI64, %RI64, %RI64, %RI64: %Rrc (file: %s)",
1319 nsModifiedTime, nsAccessTime, nsChangeTime, nsBirthTime, rc, pState->apszFilenames[idxFile]);
1320 return VINF_SUCCESS;
1321}
1322
1323
1324/**
1325 * 'fchmod {idxFile} {cbFile}'
1326 */
1327static int FsPerfSlaveHandleFChMod(FSPERFCOMMSSLAVESTATE *pState, char **papszArgs, int cArgs)
1328{
1329 /*
1330 * Parse parameters.
1331 */
1332 if (cArgs != 1 + 2)
1333 return FsPerfSlaveSyntax(pState, "takes exactly two arguments, not %u", cArgs);
1334
1335 uint32_t idxFile;
1336 int rc = FsPerfSlaveParseFileIdx(pState, papszArgs[1], &idxFile);
1337 if (RT_FAILURE(rc))
1338 return rc;
1339
1340 uint32_t fAttribs;
1341 rc = FsPerfSlaveParseU32(pState, papszArgs[2], "new file attributes", 0, 0, UINT32_MAX, &fAttribs);
1342 if (RT_FAILURE(rc))
1343 return rc;
1344
1345 if (pState->ahFiles[idxFile] == NIL_RTFILE)
1346 return FsPerfSlaveError(pState, VERR_INVALID_HANDLE, "no open file at index #%u", idxFile);
1347
1348 /*
1349 * Execute.
1350 */
1351 rc = RTFileSetMode(pState->ahFiles[idxFile], fAttribs);
1352 if (RT_FAILURE(rc))
1353 return FsPerfSlaveError(pState, rc, "failed to set file mode to %#RX32: %Rrc (file: %s)",
1354 fAttribs, rc, pState->apszFilenames[idxFile]);
1355 return VINF_SUCCESS;
1356}
1357
1358
1359/**
1360 * 'reset'
1361 */
1362static int FsPerfSlaveHandleReset(FSPERFCOMMSSLAVESTATE *pState, char **papszArgs, int cArgs)
1363{
1364 /*
1365 * Parse parameters.
1366 */
1367 if (cArgs > 1)
1368 return FsPerfSlaveSyntax(pState, "takes zero arguments, not %u", cArgs);
1369 RT_NOREF(papszArgs);
1370
1371 /*
1372 * Execute the command.
1373 */
1374 FsPerfSlaveStateCleanup(pState);
1375 return VINF_SUCCESS;
1376}
1377
1378
1379/**
1380 * 'exit [exitcode]'
1381 */
1382static int FsPerfSlaveHandleExit(FSPERFCOMMSSLAVESTATE *pState, char **papszArgs, int cArgs)
1383{
1384 /*
1385 * Parse parameters.
1386 */
1387 if (cArgs > 1 + 1)
1388 return FsPerfSlaveSyntax(pState, "takes zero or one argument, not %u", cArgs);
1389
1390 if (cArgs >= 1 + 1)
1391 {
1392 uint32_t uExitCode;
1393 int rc = FsPerfSlaveParseU32(pState, papszArgs[1], "exit code", 0, 0, 127, &uExitCode);
1394 if (RT_FAILURE(rc))
1395 return rc;
1396
1397 /*
1398 * Execute the command.
1399 */
1400 pState->rcExit = (RTEXITCODE)uExitCode;
1401 }
1402 pState->fTerminate = true;
1403 return VINF_SUCCESS;
1404}
1405
1406
1407/**
1408 * Executes a script line.
1409 */
1410static int FsPerfSlaveExecuteLine(FSPERFCOMMSSLAVESTATE *pState, char *pszLine)
1411{
1412 /*
1413 * Parse the command line using bourne shell quoting style.
1414 */
1415 char **papszArgs;
1416 int cArgs;
1417 int rc = RTGetOptArgvFromString(&papszArgs, &cArgs, pszLine, RTGETOPTARGV_CNV_QUOTE_BOURNE_SH, NULL);
1418 if (RT_FAILURE(rc))
1419 return RTErrInfoSetF(&pState->ErrInfo.Core, rc, "Failed to parse line %u: %s", pState->iLineNo, pszLine);
1420 if (cArgs <= 0)
1421 {
1422 RTGetOptArgvFree(papszArgs);
1423 return RTErrInfoSetF(&pState->ErrInfo.Core, rc, "No command found on line %u: %s", pState->iLineNo, pszLine);
1424 }
1425
1426 /*
1427 * Execute the command.
1428 */
1429 static const struct
1430 {
1431 const char *pszCmd;
1432 size_t cchCmd;
1433 int (*pfnHandler)(FSPERFCOMMSSLAVESTATE *pState, char **papszArgs, int cArgs);
1434 } s_aHandlers[] =
1435 {
1436 { RT_STR_TUPLE("open"), FsPerfSlaveHandleOpen },
1437 { RT_STR_TUPLE("close"), FsPerfSlaveHandleClose },
1438 { RT_STR_TUPLE("writepattern"), FsPerfSlaveHandleWritePattern },
1439 { RT_STR_TUPLE("truncate"), FsPerfSlaveHandleTruncate },
1440 { RT_STR_TUPLE("futimes"), FsPerfSlaveHandleFUTimes},
1441 { RT_STR_TUPLE("fchmod"), FsPerfSlaveHandleFChMod },
1442 { RT_STR_TUPLE("reset"), FsPerfSlaveHandleReset },
1443 { RT_STR_TUPLE("exit"), FsPerfSlaveHandleExit },
1444 };
1445 const char * const pszCmd = papszArgs[0];
1446 size_t const cchCmd = strlen(pszCmd);
1447 for (size_t i = 0; i < RT_ELEMENTS(s_aHandlers); i++)
1448 if ( s_aHandlers[i].cchCmd == cchCmd
1449 && memcmp(pszCmd, s_aHandlers[i].pszCmd, cchCmd) == 0)
1450 {
1451 pState->pszCommand = s_aHandlers[i].pszCmd;
1452 rc = s_aHandlers[i].pfnHandler(pState, papszArgs, cArgs);
1453 RTGetOptArgvFree(papszArgs);
1454 return rc;
1455 }
1456
1457 rc = RTErrInfoSetF(&pState->ErrInfo.Core, VERR_NOT_FOUND, "Command on line %u not found: %s", pState->iLineNo, pszLine);
1458 RTGetOptArgvFree(papszArgs);
1459 return rc;
1460}
1461
1462
1463/**
1464 * Executes a script.
1465 */
1466static int FsPerfSlaveExecuteScript(FSPERFCOMMSSLAVESTATE *pState, char *pszContent)
1467{
1468 /*
1469 * Validate the encoding.
1470 */
1471 int rc = RTStrValidateEncoding(pszContent);
1472 if (RT_FAILURE(rc))
1473 return RTErrInfoSetF(&pState->ErrInfo.Core, rc, "Invalid UTF-8 encoding");
1474
1475 /*
1476 * Work the script content line by line.
1477 */
1478 pState->iLineNo = 0;
1479 while (*pszContent != 0x1a && *pszContent != '\0')
1480 {
1481 pState->iLineNo++;
1482
1483 /* Figure the current line and move pszContent ahead: */
1484 char *pszLine = RTStrStripL(pszContent);
1485 char *pszEol = strchr(pszLine, '\n');
1486 if (pszEol)
1487 pszContent = pszEol + 1;
1488 else
1489 {
1490 pszEol = strchr(pszLine, 0x1a);
1491 AssertStmt(pszEol, pszEol = strchr(pszLine, '\0'));
1492 pszContent = pszEol;
1493 }
1494
1495 /* Terminate and strip it: */
1496 *pszEol = '\0';
1497 pszLine = RTStrStrip(pszLine);
1498
1499 /* Skip empty lines and comment lines: */
1500 if (*pszLine == '\0' || *pszLine == '#')
1501 continue;
1502
1503 /* Execute the line: */
1504 pState->pszLine = pszLine;
1505 rc = FsPerfSlaveExecuteLine(pState, pszLine);
1506 if (RT_FAILURE(rc))
1507 break;
1508 }
1509 return rc;
1510}
1511
1512
1513/**
1514 * Communication slave.
1515 *
1516 * @returns exit code.
1517 */
1518static int FsPerfCommsSlave(void)
1519{
1520 /*
1521 * Make sure we've got a directory and create it and it's subdir.
1522 */
1523 if (g_cchCommsDir == 0)
1524 return RTMsgError("no communcation directory was specified (-C)");
1525
1526 int rc = RTDirCreateFullPath(g_szCommsSubDir, 0775);
1527 if (RT_FAILURE(rc))
1528 return RTMsgError("Failed to create '%s': %Rrc", g_szCommsSubDir, rc);
1529
1530 /*
1531 * Signal that we're here.
1532 */
1533 char szTmp[_4K];
1534 rc = FsPerfCommsWriteFile(RT_STR_TUPLE("slave.pid"), szTmp, RTStrPrintf(szTmp, sizeof(szTmp), "%u\x1a", RTProcSelf()));
1535 if (RT_FAILURE(rc))
1536 return RTEXITCODE_FAILURE;
1537
1538 /*
1539 * Processing loop.
1540 */
1541 FSPERFCOMMSSLAVESTATE State;
1542 FsPerfSlaveStateInit(&State);
1543 uint32_t msSleep = 1;
1544 while (!State.fTerminate)
1545 {
1546 /*
1547 * Try read the next command script.
1548 */
1549 char *pszContent = NULL;
1550 rc = FsPerfCommsReadFileAndRename(State.iSeqNo, "-order.send", "-order.ack", &pszContent);
1551 if (RT_SUCCESS(rc))
1552 {
1553 /*
1554 * Execute it.
1555 */
1556 RTErrInfoInitStatic(&State.ErrInfo);
1557 rc = FsPerfSlaveExecuteScript(&State, pszContent);
1558
1559 /*
1560 * Write the result.
1561 */
1562 char szResult[64];
1563 size_t cchResult = RTStrPrintf(szResult, sizeof(szResult), "%u-order.done", State.iSeqNo);
1564 size_t cchTmp = RTStrPrintf(szTmp, sizeof(szTmp), "%d\n%s\x1a",
1565 rc, RTErrInfoIsSet(&State.ErrInfo.Core) ? State.ErrInfo.Core.pszMsg : "");
1566 FsPerfCommsWriteFile(szResult, cchResult, szTmp, cchTmp);
1567 State.iSeqNo++;
1568
1569 msSleep = 1;
1570 }
1571
1572 /*
1573 * Wait a little and check again.
1574 */
1575 RTThreadSleep(msSleep);
1576 if (msSleep < 128)
1577 msSleep++;
1578 }
1579
1580 /*
1581 * Remove the we're here indicator and quit.
1582 */
1583 RTFileDelete(InCommsDir(RT_STR_TUPLE("slave.pid")));
1584 FsPerfSlaveStateCleanup(&State);
1585 return State.rcExit;
1586}
1587
1588
1589
1590/*********************************************************************************************************************************
1591* Tests *
1592*********************************************************************************************************************************/
1593
1594/**
1595 * Prepares the test area.
1596 * @returns VBox status code.
1597 */
1598static int fsPrepTestArea(void)
1599{
1600 /* The empty subdir and associated globals: */
1601 static char s_szEmpty[] = "empty";
1602 memcpy(g_szEmptyDir, g_szDir, g_cchDir);
1603 memcpy(&g_szEmptyDir[g_cchDir], s_szEmpty, sizeof(s_szEmpty));
1604 g_cchEmptyDir = g_cchDir + sizeof(s_szEmpty) - 1;
1605 RTTESTI_CHECK_RC_RET(RTDirCreate(g_szEmptyDir, 0755, 0), VINF_SUCCESS, rcCheck);
1606 g_szEmptyDir[g_cchEmptyDir++] = RTPATH_SLASH;
1607 g_szEmptyDir[g_cchEmptyDir] = '\0';
1608 RTTestIPrintf(RTTESTLVL_ALWAYS, "Empty dir: %s\n", g_szEmptyDir);
1609
1610 /* Deep directory: */
1611 memcpy(g_szDeepDir, g_szDir, g_cchDir);
1612 g_cchDeepDir = g_cchDir;
1613 do
1614 {
1615 static char const s_szSub[] = "d" RTPATH_SLASH_STR;
1616 memcpy(&g_szDeepDir[g_cchDeepDir], s_szSub, sizeof(s_szSub));
1617 g_cchDeepDir += sizeof(s_szSub) - 1;
1618 RTTESTI_CHECK_RC_RET( RTDirCreate(g_szDeepDir, 0755, 0), VINF_SUCCESS, rcCheck);
1619 } while (g_cchDeepDir < 176);
1620 RTTestIPrintf(RTTESTLVL_ALWAYS, "Deep dir: %s\n", g_szDeepDir);
1621
1622 /* Create known file in both deep and shallow dirs: */
1623 RTFILE hKnownFile;
1624 RTTESTI_CHECK_RC_RET(RTFileOpen(&hKnownFile, InDir(RT_STR_TUPLE("known-file")),
1625 RTFILE_O_CREATE | RTFILE_O_DENY_NONE | RTFILE_O_WRITE),
1626 VINF_SUCCESS, rcCheck);
1627 RTTESTI_CHECK_RC_RET(RTFileClose(hKnownFile), VINF_SUCCESS, rcCheck);
1628
1629 RTTESTI_CHECK_RC_RET(RTFileOpen(&hKnownFile, InDeepDir(RT_STR_TUPLE("known-file")),
1630 RTFILE_O_CREATE | RTFILE_O_DENY_NONE | RTFILE_O_WRITE),
1631 VINF_SUCCESS, rcCheck);
1632 RTTESTI_CHECK_RC_RET(RTFileClose(hKnownFile), VINF_SUCCESS, rcCheck);
1633
1634 return VINF_SUCCESS;
1635}
1636
1637
1638/**
1639 * Create a name list entry.
1640 * @returns Pointer to the entry, NULL if out of memory.
1641 * @param pchName The name.
1642 * @param cchName The name length.
1643 */
1644PFSPERFNAMEENTRY fsPerfCreateNameEntry(const char *pchName, size_t cchName)
1645{
1646 PFSPERFNAMEENTRY pEntry = (PFSPERFNAMEENTRY)RTMemAllocVar(RT_UOFFSETOF_DYN(FSPERFNAMEENTRY, szName[cchName + 1]));
1647 if (pEntry)
1648 {
1649 RTListInit(&pEntry->Entry);
1650 pEntry->cchName = (uint16_t)cchName;
1651 memcpy(pEntry->szName, pchName, cchName);
1652 pEntry->szName[cchName] = '\0';
1653 }
1654 return pEntry;
1655}
1656
1657
1658static int fsPerfManyTreeRecursiveDirCreator(size_t cchDir, uint32_t iDepth)
1659{
1660 PFSPERFNAMEENTRY pEntry = fsPerfCreateNameEntry(g_szDir, cchDir);
1661 RTTESTI_CHECK_RET(pEntry, VERR_NO_MEMORY);
1662 RTListAppend(&g_ManyTreeHead, &pEntry->Entry);
1663
1664 RTTESTI_CHECK_RC_RET(RTDirCreate(g_szDir, 0755, RTDIRCREATE_FLAGS_NOT_CONTENT_INDEXED_DONT_SET | RTDIRCREATE_FLAGS_NOT_CONTENT_INDEXED_NOT_CRITICAL),
1665 VINF_SUCCESS, rcCheck);
1666
1667 if (iDepth < g_cManyTreeDepth)
1668 for (uint32_t i = 0; i < g_cManyTreeSubdirsPerDir; i++)
1669 {
1670 size_t cchSubDir = RTStrPrintf(&g_szDir[cchDir], sizeof(g_szDir) - cchDir, "d%02u" RTPATH_SLASH_STR, i);
1671 RTTESTI_CHECK_RC_RET(fsPerfManyTreeRecursiveDirCreator(cchDir + cchSubDir, iDepth + 1), VINF_SUCCESS, rcCheck);
1672 }
1673
1674 return VINF_SUCCESS;
1675}
1676
1677
1678void fsPerfManyFiles(void)
1679{
1680 RTTestISub("manyfiles");
1681
1682 /*
1683 * Create a sub-directory with like 10000 files in it.
1684 *
1685 * This does push the directory organization of the underlying file system,
1686 * which is something we might not want to profile with shared folders. It
1687 * is however useful for directory enumeration.
1688 */
1689 RTTESTI_CHECK_RC_RETV(RTDirCreate(InDir(RT_STR_TUPLE("manyfiles")), 0755,
1690 RTDIRCREATE_FLAGS_NOT_CONTENT_INDEXED_DONT_SET | RTDIRCREATE_FLAGS_NOT_CONTENT_INDEXED_NOT_CRITICAL),
1691 VINF_SUCCESS);
1692
1693 size_t offFilename = strlen(g_szDir);
1694 g_szDir[offFilename++] = RTPATH_SLASH;
1695
1696 fsPerfYield();
1697 RTFILE hFile;
1698 uint64_t const nsStart = RTTimeNanoTS();
1699 for (uint32_t i = 0; i < g_cManyFiles; i++)
1700 {
1701 RTStrFormatU32(&g_szDir[offFilename], sizeof(g_szDir) - offFilename, i, 10, 5, 5, RTSTR_F_ZEROPAD);
1702 RTTESTI_CHECK_RC_RETV(RTFileOpen(&hFile, g_szDir, RTFILE_O_CREATE | RTFILE_O_DENY_NONE | RTFILE_O_WRITE), VINF_SUCCESS);
1703 RTTESTI_CHECK_RC(RTFileClose(hFile), VINF_SUCCESS);
1704 }
1705 uint64_t const cNsElapsed = RTTimeNanoTS() - nsStart;
1706 RTTestIValueF(cNsElapsed, RTTESTUNIT_NS, "Creating %u empty files in single directory", g_cManyFiles);
1707 RTTestIValueF(cNsElapsed / g_cManyFiles, RTTESTUNIT_NS_PER_OCCURRENCE, "Create empty file (single dir)");
1708
1709 /*
1710 * Create a bunch of directories with exacly 32 files in each, hoping to
1711 * avoid any directory organization artifacts.
1712 */
1713 /* Create the directories first, building a list of them for simplifying iteration: */
1714 RTListInit(&g_ManyTreeHead);
1715 InDir(RT_STR_TUPLE("manytree" RTPATH_SLASH_STR));
1716 RTTESTI_CHECK_RC_RETV(fsPerfManyTreeRecursiveDirCreator(strlen(g_szDir), 0), VINF_SUCCESS);
1717
1718 /* Create the zero byte files: */
1719 fsPerfYield();
1720 uint64_t const nsStart2 = RTTimeNanoTS();
1721 uint32_t cFiles = 0;
1722 PFSPERFNAMEENTRY pCur;
1723 RTListForEach(&g_ManyTreeHead, pCur, FSPERFNAMEENTRY, Entry)
1724 {
1725 char szPath[FSPERF_MAX_PATH];
1726 memcpy(szPath, pCur->szName, pCur->cchName);
1727 for (uint32_t i = 0; i < g_cManyTreeFilesPerDir; i++)
1728 {
1729 RTStrFormatU32(&szPath[pCur->cchName], sizeof(szPath) - pCur->cchName, i, 10, 5, 5, RTSTR_F_ZEROPAD);
1730 RTTESTI_CHECK_RC_RETV(RTFileOpen(&hFile, szPath, RTFILE_O_CREATE | RTFILE_O_DENY_NONE | RTFILE_O_WRITE), VINF_SUCCESS);
1731 RTTESTI_CHECK_RC(RTFileClose(hFile), VINF_SUCCESS);
1732 cFiles++;
1733 }
1734 }
1735 uint64_t const cNsElapsed2 = RTTimeNanoTS() - nsStart2;
1736 RTTestIValueF(cNsElapsed2, RTTESTUNIT_NS, "Creating %u empty files in tree", cFiles);
1737 RTTestIValueF(cNsElapsed2 / cFiles, RTTESTUNIT_NS_PER_OCCURRENCE, "Create empty file (tree)");
1738 RTTESTI_CHECK(g_cManyTreeFiles == cFiles);
1739}
1740
1741
1742DECL_FORCE_INLINE(int) fsPerfOpenExistingOnceReadonly(const char *pszFile)
1743{
1744 RTFILE hFile;
1745 RTTESTI_CHECK_RC_RET(RTFileOpen(&hFile, pszFile, RTFILE_O_OPEN | RTFILE_O_DENY_NONE | RTFILE_O_READ), VINF_SUCCESS, rcCheck);
1746 RTTESTI_CHECK_RC(RTFileClose(hFile), VINF_SUCCESS);
1747 return VINF_SUCCESS;
1748}
1749
1750
1751DECL_FORCE_INLINE(int) fsPerfOpenExistingOnceWriteonly(const char *pszFile)
1752{
1753 RTFILE hFile;
1754 RTTESTI_CHECK_RC_RET(RTFileOpen(&hFile, pszFile, RTFILE_O_OPEN | RTFILE_O_DENY_NONE | RTFILE_O_WRITE), VINF_SUCCESS, rcCheck);
1755 RTTESTI_CHECK_RC(RTFileClose(hFile), VINF_SUCCESS);
1756 return VINF_SUCCESS;
1757}
1758
1759
1760/** @note tstRTFileOpenEx-1.cpp has a copy of this code. */
1761static void tstOpenExTest(unsigned uLine, int cbExist, int cbNext, const char *pszFilename, uint64_t fAction,
1762 int rcExpect, RTFILEACTION enmActionExpected)
1763{
1764 uint64_t const fCreateMode = (0644 << RTFILE_O_CREATE_MODE_SHIFT);
1765 RTFILE hFile;
1766 int rc;
1767
1768 /*
1769 * File existence and size.
1770 */
1771 bool fOkay = false;
1772 RTFSOBJINFO ObjInfo;
1773 rc = RTPathQueryInfoEx(pszFilename, &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK);
1774 if (RT_SUCCESS(rc))
1775 fOkay = cbExist == (int64_t)ObjInfo.cbObject;
1776 else
1777 fOkay = rc == VERR_FILE_NOT_FOUND && cbExist < 0;
1778 if (!fOkay)
1779 {
1780 if (cbExist >= 0)
1781 {
1782 rc = RTFileOpen(&hFile, pszFilename, RTFILE_O_WRITE | RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE | fCreateMode);
1783 if (RT_SUCCESS(rc))
1784 {
1785 while (cbExist > 0)
1786 {
1787 int cbToWrite = (int)strlen(pszFilename);
1788 if (cbToWrite > cbExist)
1789 cbToWrite = cbExist;
1790 rc = RTFileWrite(hFile, pszFilename, cbToWrite, NULL);
1791 if (RT_FAILURE(rc))
1792 {
1793 RTTestIFailed("%u: RTFileWrite(%s,%#x) -> %Rrc\n", uLine, pszFilename, cbToWrite, rc);
1794 break;
1795 }
1796 cbExist -= cbToWrite;
1797 }
1798
1799 RTTESTI_CHECK_RC(RTFileClose(hFile), VINF_SUCCESS);
1800 }
1801 else
1802 RTTestIFailed("%u: RTFileDelete(%s) -> %Rrc\n", uLine, pszFilename, rc);
1803
1804 }
1805 else
1806 {
1807 rc = RTFileDelete(pszFilename);
1808 if (rc != VINF_SUCCESS && rc != VERR_FILE_NOT_FOUND)
1809 RTTestIFailed("%u: RTFileDelete(%s) -> %Rrc\n", uLine, pszFilename, rc);
1810 }
1811 }
1812
1813 /*
1814 * The actual test.
1815 */
1816 RTFILEACTION enmActuallyTaken = RTFILEACTION_END;
1817 hFile = NIL_RTFILE;
1818 rc = RTFileOpenEx(pszFilename, fAction | RTFILE_O_READWRITE | RTFILE_O_DENY_NONE | fCreateMode, &hFile, &enmActuallyTaken);
1819 if ( rc != rcExpect
1820 || enmActuallyTaken != enmActionExpected
1821 || (RT_SUCCESS(rc) ? hFile == NIL_RTFILE : hFile != NIL_RTFILE))
1822 RTTestIFailed("%u: RTFileOpenEx(%s, %#llx) -> %Rrc + %d (hFile=%p), expected %Rrc + %d\n",
1823 uLine, pszFilename, fAction, rc, enmActuallyTaken, hFile, rcExpect, enmActionExpected);
1824 if (RT_SUCCESS(rc))
1825 {
1826 if ( enmActionExpected == RTFILEACTION_REPLACED
1827 || enmActionExpected == RTFILEACTION_TRUNCATED)
1828 {
1829 uint8_t abBuf[16];
1830 rc = RTFileRead(hFile, abBuf, 1, NULL);
1831 if (rc != VERR_EOF)
1832 RTTestIFailed("%u: RTFileRead(%s,,1,) -> %Rrc, expected VERR_EOF\n", uLine, pszFilename, rc);
1833 }
1834
1835 while (cbNext > 0)
1836 {
1837 int cbToWrite = (int)strlen(pszFilename);
1838 if (cbToWrite > cbNext)
1839 cbToWrite = cbNext;
1840 rc = RTFileWrite(hFile, pszFilename, cbToWrite, NULL);
1841 if (RT_FAILURE(rc))
1842 {
1843 RTTestIFailed("%u: RTFileWrite(%s,%#x) -> %Rrc\n", uLine, pszFilename, cbToWrite, rc);
1844 break;
1845 }
1846 cbNext -= cbToWrite;
1847 }
1848
1849 rc = RTFileClose(hFile);
1850 if (RT_FAILURE(rc))
1851 RTTestIFailed("%u: RTFileClose(%p) -> %Rrc\n", uLine, hFile, rc);
1852 }
1853}
1854
1855
1856void fsPerfOpen(void)
1857{
1858 RTTestISub("open");
1859
1860 /* Opening non-existing files. */
1861 RTFILE hFile;
1862 RTTESTI_CHECK_RC(RTFileOpen(&hFile, InEmptyDir(RT_STR_TUPLE("no-such-file")),
1863 RTFILE_O_OPEN | RTFILE_O_DENY_NONE | RTFILE_O_READ), VERR_FILE_NOT_FOUND);
1864 RTTESTI_CHECK_RC(RTFileOpen(&hFile, InEmptyDir(RT_STR_TUPLE("no-such-dir" RTPATH_SLASH_STR "no-such-file")),
1865 RTFILE_O_OPEN | RTFILE_O_DENY_NONE | RTFILE_O_READ), FSPERF_VERR_PATH_NOT_FOUND);
1866 RTTESTI_CHECK_RC(RTFileOpen(&hFile, InDir(RT_STR_TUPLE("known-file" RTPATH_SLASH_STR "no-such-file")),
1867 RTFILE_O_OPEN | RTFILE_O_DENY_NONE | RTFILE_O_READ), VERR_PATH_NOT_FOUND);
1868
1869 /*
1870 * The following is copied from tstRTFileOpenEx-1.cpp:
1871 */
1872 InDir(RT_STR_TUPLE("file1"));
1873 tstOpenExTest(__LINE__, -1, -1, g_szDir, RTFILE_O_OPEN, VERR_FILE_NOT_FOUND, RTFILEACTION_INVALID);
1874 tstOpenExTest(__LINE__, -1, -1, g_szDir, RTFILE_O_OPEN_CREATE, VINF_SUCCESS, RTFILEACTION_CREATED);
1875 tstOpenExTest(__LINE__, 0, 0, g_szDir, RTFILE_O_OPEN_CREATE, VINF_SUCCESS, RTFILEACTION_OPENED);
1876 tstOpenExTest(__LINE__, 0, 0, g_szDir, RTFILE_O_OPEN, VINF_SUCCESS, RTFILEACTION_OPENED);
1877
1878 tstOpenExTest(__LINE__, 0, 0, g_szDir, RTFILE_O_OPEN | RTFILE_O_TRUNCATE, VINF_SUCCESS, RTFILEACTION_TRUNCATED);
1879 tstOpenExTest(__LINE__, 0, 10, g_szDir, RTFILE_O_OPEN_CREATE | RTFILE_O_TRUNCATE, VINF_SUCCESS, RTFILEACTION_TRUNCATED);
1880 tstOpenExTest(__LINE__, 10, 10, g_szDir, RTFILE_O_OPEN_CREATE | RTFILE_O_TRUNCATE, VINF_SUCCESS, RTFILEACTION_TRUNCATED);
1881 tstOpenExTest(__LINE__, 10, -1, g_szDir, RTFILE_O_OPEN | RTFILE_O_TRUNCATE, VINF_SUCCESS, RTFILEACTION_TRUNCATED);
1882 tstOpenExTest(__LINE__, -1, -1, g_szDir, RTFILE_O_OPEN | RTFILE_O_TRUNCATE, VERR_FILE_NOT_FOUND, RTFILEACTION_INVALID);
1883 tstOpenExTest(__LINE__, -1, 0, g_szDir, RTFILE_O_OPEN_CREATE | RTFILE_O_TRUNCATE, VINF_SUCCESS, RTFILEACTION_CREATED);
1884
1885 tstOpenExTest(__LINE__, 0, -1, g_szDir, RTFILE_O_CREATE_REPLACE, VINF_SUCCESS, RTFILEACTION_REPLACED);
1886 tstOpenExTest(__LINE__, -1, 0, g_szDir, RTFILE_O_CREATE_REPLACE, VINF_SUCCESS, RTFILEACTION_CREATED);
1887 tstOpenExTest(__LINE__, 0, -1, g_szDir, RTFILE_O_CREATE, VERR_ALREADY_EXISTS, RTFILEACTION_ALREADY_EXISTS);
1888 tstOpenExTest(__LINE__, -1, -1, g_szDir, RTFILE_O_CREATE, VINF_SUCCESS, RTFILEACTION_CREATED);
1889
1890 tstOpenExTest(__LINE__, -1, 10, g_szDir, RTFILE_O_CREATE | RTFILE_O_TRUNCATE, VINF_SUCCESS, RTFILEACTION_CREATED);
1891 tstOpenExTest(__LINE__, 10, 10, g_szDir, RTFILE_O_CREATE | RTFILE_O_TRUNCATE, VERR_ALREADY_EXISTS, RTFILEACTION_ALREADY_EXISTS);
1892 tstOpenExTest(__LINE__, 10, -1, g_szDir, RTFILE_O_CREATE_REPLACE | RTFILE_O_TRUNCATE, VINF_SUCCESS, RTFILEACTION_REPLACED);
1893 tstOpenExTest(__LINE__, -1, -1, g_szDir, RTFILE_O_CREATE_REPLACE | RTFILE_O_TRUNCATE, VINF_SUCCESS, RTFILEACTION_CREATED);
1894
1895 RTTESTI_CHECK_RC(RTFileDelete(g_szDir), VINF_SUCCESS);
1896
1897 /*
1898 * Create file1 and then try exclusivly creating it again.
1899 * Then profile opening it for reading.
1900 */
1901 RTFILE hFile1;
1902 RTTESTI_CHECK_RC_RETV(RTFileOpen(&hFile1, InDir(RT_STR_TUPLE("file1")),
1903 RTFILE_O_CREATE | RTFILE_O_DENY_NONE | RTFILE_O_WRITE), VINF_SUCCESS);
1904 RTTESTI_CHECK_RC(RTFileOpen(&hFile, g_szDir, RTFILE_O_CREATE | RTFILE_O_DENY_NONE | RTFILE_O_WRITE), VERR_ALREADY_EXISTS);
1905 RTTESTI_CHECK_RC(RTFileClose(hFile1), VINF_SUCCESS);
1906
1907 PROFILE_FN(fsPerfOpenExistingOnceReadonly(g_szDir), g_nsTestRun, "RTFileOpen/Close/Readonly");
1908 PROFILE_FN(fsPerfOpenExistingOnceWriteonly(g_szDir), g_nsTestRun, "RTFileOpen/Close/Writeonly");
1909
1910 /*
1911 * Profile opening in the deep directory too.
1912 */
1913 RTTESTI_CHECK_RC_RETV(RTFileOpen(&hFile1, InDeepDir(RT_STR_TUPLE("file1")),
1914 RTFILE_O_CREATE | RTFILE_O_DENY_NONE | RTFILE_O_WRITE), VINF_SUCCESS);
1915 RTTESTI_CHECK_RC(RTFileClose(hFile1), VINF_SUCCESS);
1916 PROFILE_FN(fsPerfOpenExistingOnceReadonly(g_szDeepDir), g_nsTestRun, "RTFileOpen/Close/deep/readonly");
1917 PROFILE_FN(fsPerfOpenExistingOnceWriteonly(g_szDeepDir), g_nsTestRun, "RTFileOpen/Close/deep/writeonly");
1918
1919 /* Manytree: */
1920 char szPath[FSPERF_MAX_PATH];
1921 PROFILE_MANYTREE_FN(szPath, fsPerfOpenExistingOnceReadonly(szPath), 1, g_nsTestRun, "RTFileOpen/Close/manytree/readonly");
1922}
1923
1924
1925void fsPerfFStat(void)
1926{
1927 RTTestISub("fstat");
1928 RTFILE hFile1;
1929 RTTESTI_CHECK_RC_RETV(RTFileOpen(&hFile1, InDir(RT_STR_TUPLE("file2")),
1930 RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE | RTFILE_O_WRITE), VINF_SUCCESS);
1931 RTFSOBJINFO ObjInfo = {0};
1932 PROFILE_FN(RTFileQueryInfo(hFile1, &ObjInfo, RTFSOBJATTRADD_NOTHING), g_nsTestRun, "RTFileQueryInfo/NOTHING");
1933 PROFILE_FN(RTFileQueryInfo(hFile1, &ObjInfo, RTFSOBJATTRADD_UNIX), g_nsTestRun, "RTFileQueryInfo/UNIX");
1934
1935 RTTESTI_CHECK_RC(RTFileClose(hFile1), VINF_SUCCESS);
1936}
1937
1938
1939void fsPerfFChMod(void)
1940{
1941 RTTestISub("fchmod");
1942 RTFILE hFile1;
1943 RTTESTI_CHECK_RC_RETV(RTFileOpen(&hFile1, InDir(RT_STR_TUPLE("file4")),
1944 RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE | RTFILE_O_WRITE), VINF_SUCCESS);
1945 RTFSOBJINFO ObjInfo = {0};
1946 RTTESTI_CHECK_RC(RTFileQueryInfo(hFile1, &ObjInfo, RTFSOBJATTRADD_NOTHING), VINF_SUCCESS);
1947 RTFMODE const fEvenMode = (ObjInfo.Attr.fMode & ~RTFS_UNIX_ALL_ACCESS_PERMS) | RTFS_DOS_READONLY | 0400;
1948 RTFMODE const fOddMode = (ObjInfo.Attr.fMode & ~(RTFS_UNIX_ALL_ACCESS_PERMS | RTFS_DOS_READONLY)) | 0640;
1949 PROFILE_FN(RTFileSetMode(hFile1, iIteration & 1 ? fOddMode : fEvenMode), g_nsTestRun, "RTFileSetMode");
1950
1951 RTFileSetMode(hFile1, ObjInfo.Attr.fMode);
1952 RTTESTI_CHECK_RC(RTFileClose(hFile1), VINF_SUCCESS);
1953}
1954
1955
1956void fsPerfFUtimes(void)
1957{
1958 RTTestISub("futimes");
1959 RTFILE hFile1;
1960 RTTESTI_CHECK_RC_RETV(RTFileOpen(&hFile1, InDir(RT_STR_TUPLE("file5")),
1961 RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE | RTFILE_O_WRITE), VINF_SUCCESS);
1962 RTTIMESPEC Time1;
1963 RTTimeNow(&Time1);
1964 RTTIMESPEC Time2 = Time1;
1965 RTTimeSpecSubSeconds(&Time2, 3636);
1966
1967 RTFSOBJINFO ObjInfo0 = {0};
1968 RTTESTI_CHECK_RC(RTFileQueryInfo(hFile1, &ObjInfo0, RTFSOBJATTRADD_NOTHING), VINF_SUCCESS);
1969
1970 /* Modify modification time: */
1971 RTTESTI_CHECK_RC(RTFileSetTimes(hFile1, NULL, &Time2, NULL, NULL), VINF_SUCCESS);
1972 RTFSOBJINFO ObjInfo1 = {0};
1973 RTTESTI_CHECK_RC(RTFileQueryInfo(hFile1, &ObjInfo1, RTFSOBJATTRADD_NOTHING), VINF_SUCCESS);
1974 RTTESTI_CHECK((RTTimeSpecGetSeconds(&ObjInfo1.ModificationTime) >> 2) == (RTTimeSpecGetSeconds(&Time2) >> 2));
1975 char sz1[RTTIME_STR_LEN], sz2[RTTIME_STR_LEN]; /* Div by 1000 here for posix impl. using timeval. */
1976 RTTESTI_CHECK_MSG(RTTimeSpecGetNano(&ObjInfo1.AccessTime) / 1000 == RTTimeSpecGetNano(&ObjInfo0.AccessTime) / 1000,
1977 ("%s, expected %s", RTTimeSpecToString(&ObjInfo1.AccessTime, sz1, sizeof(sz1)),
1978 RTTimeSpecToString(&ObjInfo0.AccessTime, sz2, sizeof(sz2))));
1979
1980 /* Modify access time: */
1981 RTTESTI_CHECK_RC(RTFileSetTimes(hFile1, &Time1, NULL, NULL, NULL), VINF_SUCCESS);
1982 RTFSOBJINFO ObjInfo2 = {0};
1983 RTTESTI_CHECK_RC(RTFileQueryInfo(hFile1, &ObjInfo2, RTFSOBJATTRADD_NOTHING), VINF_SUCCESS);
1984 RTTESTI_CHECK((RTTimeSpecGetSeconds(&ObjInfo2.AccessTime) >> 2) == (RTTimeSpecGetSeconds(&Time1) >> 2));
1985 RTTESTI_CHECK(RTTimeSpecGetNano(&ObjInfo2.ModificationTime) / 1000 == RTTimeSpecGetNano(&ObjInfo1.ModificationTime) / 1000);
1986
1987 /* Benchmark it: */
1988 PROFILE_FN(RTFileSetTimes(hFile1, NULL, iIteration & 1 ? &Time1 : &Time2, NULL, NULL), g_nsTestRun, "RTFileSetTimes");
1989
1990 RTTESTI_CHECK_RC(RTFileClose(hFile1), VINF_SUCCESS);
1991}
1992
1993
1994void fsPerfStat(void)
1995{
1996 RTTestISub("stat");
1997 RTFSOBJINFO ObjInfo;
1998
1999 /* Non-existing files. */
2000 RTTESTI_CHECK_RC(RTPathQueryInfoEx(InEmptyDir(RT_STR_TUPLE("no-such-file")),
2001 &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK), VERR_FILE_NOT_FOUND);
2002 RTTESTI_CHECK_RC(RTPathQueryInfoEx(InEmptyDir(RT_STR_TUPLE("no-such-dir" RTPATH_SLASH_STR "no-such-file")),
2003 &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK), FSPERF_VERR_PATH_NOT_FOUND);
2004 RTTESTI_CHECK_RC(RTPathQueryInfoEx(InDir(RT_STR_TUPLE("known-file" RTPATH_SLASH_STR "no-such-file")),
2005 &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK), VERR_PATH_NOT_FOUND);
2006
2007 /* Shallow: */
2008 RTFILE hFile1;
2009 RTTESTI_CHECK_RC_RETV(RTFileOpen(&hFile1, InDir(RT_STR_TUPLE("file3")),
2010 RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE | RTFILE_O_WRITE), VINF_SUCCESS);
2011 RTTESTI_CHECK_RC(RTFileClose(hFile1), VINF_SUCCESS);
2012
2013 PROFILE_FN(RTPathQueryInfoEx(g_szDir, &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK), g_nsTestRun,
2014 "RTPathQueryInfoEx/NOTHING");
2015 PROFILE_FN(RTPathQueryInfoEx(g_szDir, &ObjInfo, RTFSOBJATTRADD_UNIX, RTPATH_F_ON_LINK), g_nsTestRun,
2016 "RTPathQueryInfoEx/UNIX");
2017
2018
2019 /* Deep: */
2020 RTTESTI_CHECK_RC_RETV(RTFileOpen(&hFile1, InDeepDir(RT_STR_TUPLE("file3")),
2021 RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE | RTFILE_O_WRITE), VINF_SUCCESS);
2022 RTTESTI_CHECK_RC(RTFileClose(hFile1), VINF_SUCCESS);
2023
2024 PROFILE_FN(RTPathQueryInfoEx(g_szDeepDir, &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK), g_nsTestRun,
2025 "RTPathQueryInfoEx/deep/NOTHING");
2026 PROFILE_FN(RTPathQueryInfoEx(g_szDeepDir, &ObjInfo, RTFSOBJATTRADD_UNIX, RTPATH_F_ON_LINK), g_nsTestRun,
2027 "RTPathQueryInfoEx/deep/UNIX");
2028
2029 /* Manytree: */
2030 char szPath[FSPERF_MAX_PATH];
2031 PROFILE_MANYTREE_FN(szPath, RTPathQueryInfoEx(szPath, &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK),
2032 1, g_nsTestRun, "RTPathQueryInfoEx/manytree/NOTHING");
2033 PROFILE_MANYTREE_FN(szPath, RTPathQueryInfoEx(szPath, &ObjInfo, RTFSOBJATTRADD_UNIX, RTPATH_F_ON_LINK),
2034 1, g_nsTestRun, "RTPathQueryInfoEx/manytree/UNIX");
2035}
2036
2037
2038void fsPerfChmod(void)
2039{
2040 RTTestISub("chmod");
2041
2042 /* Non-existing files. */
2043 RTTESTI_CHECK_RC(RTPathSetMode(InEmptyDir(RT_STR_TUPLE("no-such-file")), 0665),
2044 VERR_FILE_NOT_FOUND);
2045 RTTESTI_CHECK_RC(RTPathSetMode(InEmptyDir(RT_STR_TUPLE("no-such-dir" RTPATH_SLASH_STR "no-such-file")), 0665),
2046 FSPERF_VERR_PATH_NOT_FOUND);
2047 RTTESTI_CHECK_RC(RTPathSetMode(InDir(RT_STR_TUPLE("known-file" RTPATH_SLASH_STR "no-such-file")), 0665), VERR_PATH_NOT_FOUND);
2048
2049 /* Shallow: */
2050 RTFILE hFile1;
2051 RTTESTI_CHECK_RC_RETV(RTFileOpen(&hFile1, InDir(RT_STR_TUPLE("file14")),
2052 RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE | RTFILE_O_WRITE), VINF_SUCCESS);
2053 RTTESTI_CHECK_RC(RTFileClose(hFile1), VINF_SUCCESS);
2054
2055 RTFSOBJINFO ObjInfo;
2056 RTTESTI_CHECK_RC(RTPathQueryInfoEx(g_szDir, &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK), VINF_SUCCESS);
2057 RTFMODE const fEvenMode = (ObjInfo.Attr.fMode & ~RTFS_UNIX_ALL_ACCESS_PERMS) | RTFS_DOS_READONLY | 0400;
2058 RTFMODE const fOddMode = (ObjInfo.Attr.fMode & ~(RTFS_UNIX_ALL_ACCESS_PERMS | RTFS_DOS_READONLY)) | 0640;
2059 PROFILE_FN(RTPathSetMode(g_szDir, iIteration & 1 ? fOddMode : fEvenMode), g_nsTestRun, "RTPathSetMode");
2060 RTPathSetMode(g_szDir, ObjInfo.Attr.fMode);
2061
2062 /* Deep: */
2063 RTTESTI_CHECK_RC_RETV(RTFileOpen(&hFile1, InDeepDir(RT_STR_TUPLE("file14")),
2064 RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE | RTFILE_O_WRITE), VINF_SUCCESS);
2065 RTTESTI_CHECK_RC(RTFileClose(hFile1), VINF_SUCCESS);
2066
2067 PROFILE_FN(RTPathSetMode(g_szDeepDir, iIteration & 1 ? fOddMode : fEvenMode), g_nsTestRun, "RTPathSetMode/deep");
2068 RTPathSetMode(g_szDeepDir, ObjInfo.Attr.fMode);
2069
2070 /* Manytree: */
2071 char szPath[FSPERF_MAX_PATH];
2072 PROFILE_MANYTREE_FN(szPath, RTPathSetMode(szPath, iIteration & 1 ? fOddMode : fEvenMode), 1, g_nsTestRun,
2073 "RTPathSetMode/manytree");
2074 DO_MANYTREE_FN(szPath, RTPathSetMode(szPath, ObjInfo.Attr.fMode));
2075}
2076
2077
2078void fsPerfUtimes(void)
2079{
2080 RTTestISub("utimes");
2081
2082 RTTIMESPEC Time1;
2083 RTTimeNow(&Time1);
2084 RTTIMESPEC Time2 = Time1;
2085 RTTimeSpecSubSeconds(&Time2, 3636);
2086
2087 /* Non-existing files. */
2088 RTTESTI_CHECK_RC(RTPathSetTimesEx(InEmptyDir(RT_STR_TUPLE("no-such-file")), NULL, &Time1, NULL, NULL, RTPATH_F_ON_LINK),
2089 VERR_FILE_NOT_FOUND);
2090 RTTESTI_CHECK_RC(RTPathSetTimesEx(InEmptyDir(RT_STR_TUPLE("no-such-dir" RTPATH_SLASH_STR "no-such-file")),
2091 NULL, &Time1, NULL, NULL, RTPATH_F_ON_LINK),
2092 FSPERF_VERR_PATH_NOT_FOUND);
2093 RTTESTI_CHECK_RC(RTPathSetTimesEx(InDir(RT_STR_TUPLE("known-file" RTPATH_SLASH_STR "no-such-file")),
2094 NULL, &Time1, NULL, NULL, RTPATH_F_ON_LINK),
2095 VERR_PATH_NOT_FOUND);
2096
2097 /* Shallow: */
2098 RTFILE hFile1;
2099 RTTESTI_CHECK_RC_RETV(RTFileOpen(&hFile1, InDir(RT_STR_TUPLE("file15")),
2100 RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE | RTFILE_O_WRITE), VINF_SUCCESS);
2101 RTTESTI_CHECK_RC(RTFileClose(hFile1), VINF_SUCCESS);
2102
2103 RTFSOBJINFO ObjInfo0 = {0};
2104 RTTESTI_CHECK_RC(RTPathQueryInfoEx(g_szDir, &ObjInfo0, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK), VINF_SUCCESS);
2105
2106 /* Modify modification time: */
2107 RTTESTI_CHECK_RC(RTPathSetTimesEx(g_szDir, NULL, &Time2, NULL, NULL, RTPATH_F_ON_LINK), VINF_SUCCESS);
2108 RTFSOBJINFO ObjInfo1;
2109 RTTESTI_CHECK_RC(RTPathQueryInfoEx(g_szDir, &ObjInfo1, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK), VINF_SUCCESS);
2110 RTTESTI_CHECK((RTTimeSpecGetSeconds(&ObjInfo1.ModificationTime) >> 2) == (RTTimeSpecGetSeconds(&Time2) >> 2));
2111 RTTESTI_CHECK(RTTimeSpecGetNano(&ObjInfo1.AccessTime) / 1000 == RTTimeSpecGetNano(&ObjInfo0.AccessTime) / 1000 /* posix timeval */);
2112
2113 /* Modify access time: */
2114 RTTESTI_CHECK_RC(RTPathSetTimesEx(g_szDir, &Time1, NULL, NULL, NULL, RTPATH_F_ON_LINK), VINF_SUCCESS);
2115 RTFSOBJINFO ObjInfo2 = {0};
2116 RTTESTI_CHECK_RC(RTPathQueryInfoEx(g_szDir, &ObjInfo2, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK), VINF_SUCCESS);
2117 RTTESTI_CHECK((RTTimeSpecGetSeconds(&ObjInfo2.AccessTime) >> 2) == (RTTimeSpecGetSeconds(&Time1) >> 2));
2118 RTTESTI_CHECK(RTTimeSpecGetNano(&ObjInfo2.ModificationTime) / 1000 == RTTimeSpecGetNano(&ObjInfo1.ModificationTime) / 1000 /* posix timeval */);
2119
2120 /* Profile shallow: */
2121 PROFILE_FN(RTPathSetTimesEx(g_szDir, iIteration & 1 ? &Time1 : &Time2, iIteration & 1 ? &Time2 : &Time1,
2122 NULL, NULL, RTPATH_F_ON_LINK),
2123 g_nsTestRun, "RTPathSetTimesEx");
2124
2125 /* Deep: */
2126 RTTESTI_CHECK_RC_RETV(RTFileOpen(&hFile1, InDeepDir(RT_STR_TUPLE("file15")),
2127 RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE | RTFILE_O_WRITE), VINF_SUCCESS);
2128 RTTESTI_CHECK_RC(RTFileClose(hFile1), VINF_SUCCESS);
2129
2130 PROFILE_FN(RTPathSetTimesEx(g_szDeepDir, iIteration & 1 ? &Time1 : &Time2, iIteration & 1 ? &Time2 : &Time1,
2131 NULL, NULL, RTPATH_F_ON_LINK),
2132 g_nsTestRun, "RTPathSetTimesEx/deep");
2133
2134 /* Manytree: */
2135 char szPath[FSPERF_MAX_PATH];
2136 PROFILE_MANYTREE_FN(szPath, RTPathSetTimesEx(szPath, iIteration & 1 ? &Time1 : &Time2, iIteration & 1 ? &Time2 : &Time1,
2137 NULL, NULL, RTPATH_F_ON_LINK),
2138 1, g_nsTestRun, "RTPathSetTimesEx/manytree");
2139}
2140
2141
2142DECL_FORCE_INLINE(int) fsPerfRenameMany(const char *pszFile, uint32_t iIteration)
2143{
2144 char szRenamed[FSPERF_MAX_PATH];
2145 strcat(strcpy(szRenamed, pszFile), "-renamed");
2146 if (!(iIteration & 1))
2147 return RTPathRename(pszFile, szRenamed, 0);
2148 return RTPathRename(szRenamed, pszFile, 0);
2149}
2150
2151
2152void fsPerfRename(void)
2153{
2154 RTTestISub("rename");
2155 char szPath[FSPERF_MAX_PATH];
2156
2157/** @todo rename directories too! */
2158/** @todo check overwriting files and directoris (empty ones should work on
2159 * unix). */
2160
2161 /* Non-existing files. */
2162 strcpy(szPath, InEmptyDir(RT_STR_TUPLE("other-no-such-file")));
2163 RTTESTI_CHECK_RC(RTPathRename(InEmptyDir(RT_STR_TUPLE("no-such-file")), szPath, 0), VERR_FILE_NOT_FOUND);
2164 strcpy(szPath, InEmptyDir(RT_STR_TUPLE("no-such-dir" RTPATH_SLASH_STR "other-no-such-file")));
2165 RTTESTI_CHECK_RC(RTPathRename(InEmptyDir(RT_STR_TUPLE("no-such-dir" RTPATH_SLASH_STR "no-such-file")), szPath, 0),
2166 FSPERF_VERR_PATH_NOT_FOUND);
2167 strcpy(szPath, InEmptyDir(RT_STR_TUPLE("other-no-such-file")));
2168 RTTESTI_CHECK_RC(RTPathRename(InDir(RT_STR_TUPLE("known-file" RTPATH_SLASH_STR "no-such-file")), szPath, 0), VERR_PATH_NOT_FOUND);
2169
2170 RTFILE hFile1;
2171 RTTESTI_CHECK_RC_RETV(RTFileOpen(&hFile1, InDir(RT_STR_TUPLE("file16")),
2172 RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE | RTFILE_O_WRITE), VINF_SUCCESS);
2173 RTTESTI_CHECK_RC(RTFileClose(hFile1), VINF_SUCCESS);
2174 strcat(strcpy(szPath, g_szDir), "-no-such-dir" RTPATH_SLASH_STR "file16");
2175 RTTESTI_CHECK_RC(RTPathRename(szPath, g_szDir, 0), FSPERF_VERR_PATH_NOT_FOUND);
2176 RTTESTI_CHECK_RC(RTPathRename(g_szDir, szPath, 0), FSPERF_VERR_PATH_NOT_FOUND);
2177
2178 /* Shallow: */
2179 strcat(strcpy(szPath, g_szDir), "-other");
2180 PROFILE_FN(RTPathRename(iIteration & 1 ? szPath : g_szDir, iIteration & 1 ? g_szDir : szPath, 0), g_nsTestRun, "RTPathRename");
2181
2182 /* Deep: */
2183 RTTESTI_CHECK_RC_RETV(RTFileOpen(&hFile1, InDeepDir(RT_STR_TUPLE("file15")),
2184 RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE | RTFILE_O_WRITE), VINF_SUCCESS);
2185 RTTESTI_CHECK_RC(RTFileClose(hFile1), VINF_SUCCESS);
2186
2187 strcat(strcpy(szPath, g_szDeepDir), "-other");
2188 PROFILE_FN(RTPathRename(iIteration & 1 ? szPath : g_szDeepDir, iIteration & 1 ? g_szDeepDir : szPath, 0),
2189 g_nsTestRun, "RTPathRename/deep");
2190
2191 /* Manytree: */
2192 PROFILE_MANYTREE_FN(szPath, fsPerfRenameMany(szPath, iIteration), 2, g_nsTestRun, "RTPathRename/manytree");
2193}
2194
2195
2196/**
2197 * Wrapper around RTDirOpen/RTDirOpenFiltered which takes g_fRelativeDir into
2198 * account.
2199 */
2200DECL_FORCE_INLINE(int) fsPerfOpenDirWrap(PRTDIR phDir, const char *pszPath)
2201{
2202 if (!g_fRelativeDir)
2203 return RTDirOpen(phDir, pszPath);
2204 return RTDirOpenFiltered(phDir, pszPath, RTDIRFILTER_NONE, RTDIR_F_NO_ABS_PATH);
2205}
2206
2207
2208DECL_FORCE_INLINE(int) fsPerfOpenClose(const char *pszDir)
2209{
2210 RTDIR hDir;
2211 RTTESTI_CHECK_RC_RET(fsPerfOpenDirWrap(&hDir, pszDir), VINF_SUCCESS, rcCheck);
2212 RTTESTI_CHECK_RC(RTDirClose(hDir), VINF_SUCCESS);
2213 return VINF_SUCCESS;
2214}
2215
2216
2217void vsPerfDirOpen(void)
2218{
2219 RTTestISub("dir open");
2220 RTDIR hDir;
2221
2222 /*
2223 * Non-existing files.
2224 */
2225 RTTESTI_CHECK_RC(fsPerfOpenDirWrap(&hDir, InEmptyDir(RT_STR_TUPLE("no-such-file"))), VERR_FILE_NOT_FOUND);
2226 RTTESTI_CHECK_RC(fsPerfOpenDirWrap(&hDir, InEmptyDir(RT_STR_TUPLE("no-such-dir" RTPATH_SLASH_STR "no-such-file"))), FSPERF_VERR_PATH_NOT_FOUND);
2227 RTTESTI_CHECK_RC(fsPerfOpenDirWrap(&hDir, InDir(RT_STR_TUPLE("known-file" RTPATH_SLASH_STR "no-such-file"))), VERR_PATH_NOT_FOUND);
2228
2229 /*
2230 * Check that open + close works.
2231 */
2232 g_szEmptyDir[g_cchEmptyDir] = '\0';
2233 RTTESTI_CHECK_RC_RETV(fsPerfOpenDirWrap(&hDir, g_szEmptyDir), VINF_SUCCESS);
2234 RTTESTI_CHECK_RC(RTDirClose(hDir), VINF_SUCCESS);
2235
2236
2237 /*
2238 * Profile empty dir and dir with many files.
2239 */
2240 g_szEmptyDir[g_cchEmptyDir] = '\0';
2241 PROFILE_FN(fsPerfOpenClose(g_szEmptyDir), g_nsTestRun, "RTDirOpen/Close empty");
2242 if (g_fManyFiles)
2243 {
2244 InDir(RT_STR_TUPLE("manyfiles"));
2245 PROFILE_FN(fsPerfOpenClose(g_szDir), g_nsTestRun, "RTDirOpen/Close manyfiles");
2246 }
2247}
2248
2249
2250DECL_FORCE_INLINE(int) fsPerfEnumEmpty(void)
2251{
2252 RTDIR hDir;
2253 g_szEmptyDir[g_cchEmptyDir] = '\0';
2254 RTTESTI_CHECK_RC_RET(fsPerfOpenDirWrap(&hDir, g_szEmptyDir), VINF_SUCCESS, rcCheck);
2255
2256 RTDIRENTRY Entry;
2257 RTTESTI_CHECK_RC(RTDirRead(hDir, &Entry, NULL), VINF_SUCCESS);
2258 RTTESTI_CHECK_RC(RTDirRead(hDir, &Entry, NULL), VINF_SUCCESS);
2259 RTTESTI_CHECK_RC(RTDirRead(hDir, &Entry, NULL), VERR_NO_MORE_FILES);
2260
2261 RTTESTI_CHECK_RC(RTDirClose(hDir), VINF_SUCCESS);
2262 return VINF_SUCCESS;
2263}
2264
2265
2266DECL_FORCE_INLINE(int) fsPerfEnumManyFiles(void)
2267{
2268 RTDIR hDir;
2269 RTTESTI_CHECK_RC_RET(fsPerfOpenDirWrap(&hDir, InDir(RT_STR_TUPLE("manyfiles"))), VINF_SUCCESS, rcCheck);
2270 uint32_t cLeft = g_cManyFiles + 2;
2271 for (;;)
2272 {
2273 RTDIRENTRY Entry;
2274 if (cLeft > 0)
2275 RTTESTI_CHECK_RC_BREAK(RTDirRead(hDir, &Entry, NULL), VINF_SUCCESS);
2276 else
2277 {
2278 RTTESTI_CHECK_RC(RTDirRead(hDir, &Entry, NULL), VERR_NO_MORE_FILES);
2279 break;
2280 }
2281 cLeft--;
2282 }
2283 RTTESTI_CHECK_RC(RTDirClose(hDir), VINF_SUCCESS);
2284 return VINF_SUCCESS;
2285}
2286
2287
2288void vsPerfDirEnum(void)
2289{
2290 RTTestISub("dir enum");
2291 RTDIR hDir;
2292
2293 /*
2294 * The empty directory.
2295 */
2296 g_szEmptyDir[g_cchEmptyDir] = '\0';
2297 RTTESTI_CHECK_RC_RETV(fsPerfOpenDirWrap(&hDir, g_szEmptyDir), VINF_SUCCESS);
2298
2299 uint32_t fDots = 0;
2300 RTDIRENTRY Entry;
2301 RTTESTI_CHECK_RC(RTDirRead(hDir, &Entry, NULL), VINF_SUCCESS);
2302 RTTESTI_CHECK(RTDirEntryIsStdDotLink(&Entry));
2303 fDots |= RT_BIT_32(Entry.cbName - 1);
2304
2305 RTTESTI_CHECK_RC(RTDirRead(hDir, &Entry, NULL), VINF_SUCCESS);
2306 RTTESTI_CHECK(RTDirEntryIsStdDotLink(&Entry));
2307 fDots |= RT_BIT_32(Entry.cbName - 1);
2308 RTTESTI_CHECK(fDots == 3);
2309
2310 RTTESTI_CHECK_RC(RTDirRead(hDir, &Entry, NULL), VERR_NO_MORE_FILES);
2311
2312 RTTESTI_CHECK_RC(RTDirClose(hDir), VINF_SUCCESS);
2313
2314 /*
2315 * The directory with many files in it.
2316 */
2317 if (g_fManyFiles)
2318 {
2319 fDots = 0;
2320 uint32_t const cBitmap = RT_ALIGN_32(g_cManyFiles, 64);
2321 void *pvBitmap = alloca(cBitmap / 8);
2322 RT_BZERO(pvBitmap, cBitmap / 8);
2323 for (uint32_t i = g_cManyFiles; i < cBitmap; i++)
2324 ASMBitSet(pvBitmap, i);
2325
2326 uint32_t cFiles = 0;
2327 RTTESTI_CHECK_RC_RETV(fsPerfOpenDirWrap(&hDir, InDir(RT_STR_TUPLE("manyfiles"))), VINF_SUCCESS);
2328 for (;;)
2329 {
2330 int rc = RTDirRead(hDir, &Entry, NULL);
2331 if (rc == VINF_SUCCESS)
2332 {
2333 if (Entry.szName[0] == '.')
2334 {
2335 if (Entry.szName[1] == '.')
2336 {
2337 RTTESTI_CHECK(!(fDots & 2));
2338 fDots |= 2;
2339 }
2340 else
2341 {
2342 RTTESTI_CHECK(Entry.szName[1] == '\0');
2343 RTTESTI_CHECK(!(fDots & 1));
2344 fDots |= 1;
2345 }
2346 }
2347 else
2348 {
2349 uint32_t iFile = UINT32_MAX;
2350 RTTESTI_CHECK_RC(RTStrToUInt32Full(Entry.szName, 10, &iFile), VINF_SUCCESS);
2351 if ( iFile < g_cManyFiles
2352 && !ASMBitTest(pvBitmap, iFile))
2353 {
2354 ASMBitSet(pvBitmap, iFile);
2355 cFiles++;
2356 }
2357 else
2358 RTTestFailed(g_hTest, "line %u: iFile=%u g_cManyFiles=%u\n", __LINE__, iFile, g_cManyFiles);
2359 }
2360 }
2361 else if (rc == VERR_NO_MORE_FILES)
2362 break;
2363 else
2364 {
2365 RTTestFailed(g_hTest, "RTDirRead failed enumerating manyfiles: %Rrc\n", rc);
2366 RTDirClose(hDir);
2367 return;
2368 }
2369 }
2370 RTTESTI_CHECK_RC(RTDirClose(hDir), VINF_SUCCESS);
2371 RTTESTI_CHECK(fDots == 3);
2372 RTTESTI_CHECK(cFiles == g_cManyFiles);
2373 RTTESTI_CHECK(ASMMemIsAllU8(pvBitmap, cBitmap / 8, 0xff));
2374 }
2375
2376 /*
2377 * Profile.
2378 */
2379 PROFILE_FN(fsPerfEnumEmpty(),g_nsTestRun, "RTDirOpen/Read/Close empty");
2380 if (g_fManyFiles)
2381 PROFILE_FN(fsPerfEnumManyFiles(), g_nsTestRun, "RTDirOpen/Read/Close manyfiles");
2382}
2383
2384
2385void fsPerfMkRmDir(void)
2386{
2387 RTTestISub("mkdir/rmdir");
2388
2389 /* Non-existing directories: */
2390 RTTESTI_CHECK_RC(RTDirRemove(InEmptyDir(RT_STR_TUPLE("no-such-dir"))), VERR_FILE_NOT_FOUND);
2391 RTTESTI_CHECK_RC(RTDirRemove(InEmptyDir(RT_STR_TUPLE("no-such-dir" RTPATH_SLASH_STR))), VERR_FILE_NOT_FOUND);
2392 RTTESTI_CHECK_RC(RTDirRemove(InEmptyDir(RT_STR_TUPLE("no-such-dir" RTPATH_SLASH_STR "no-such-file"))), FSPERF_VERR_PATH_NOT_FOUND);
2393 RTTESTI_CHECK_RC(RTDirRemove(InEmptyDir(RT_STR_TUPLE("no-such-dir" RTPATH_SLASH_STR "no-such-file" RTPATH_SLASH_STR))), FSPERF_VERR_PATH_NOT_FOUND);
2394 RTTESTI_CHECK_RC(RTDirRemove(InDir(RT_STR_TUPLE("known-file" RTPATH_SLASH_STR "no-such-file"))), VERR_PATH_NOT_FOUND);
2395 RTTESTI_CHECK_RC(RTDirRemove(InDir(RT_STR_TUPLE("known-file" RTPATH_SLASH_STR "no-such-file" RTPATH_SLASH_STR))), VERR_PATH_NOT_FOUND);
2396
2397 RTTESTI_CHECK_RC(RTDirCreate(InEmptyDir(RT_STR_TUPLE("no-such-dir" RTPATH_SLASH_STR "no-such-file")), 0755, 0), FSPERF_VERR_PATH_NOT_FOUND);
2398 RTTESTI_CHECK_RC(RTDirCreate(InDir(RT_STR_TUPLE("known-file" RTPATH_SLASH_STR "no-such-file")), 0755, 0), VERR_PATH_NOT_FOUND);
2399
2400 /* Already existing directories and files: */
2401 RTTESTI_CHECK_RC(RTDirCreate(InEmptyDir(RT_STR_TUPLE(".")), 0755, 0), VERR_ALREADY_EXISTS);
2402 RTTESTI_CHECK_RC(RTDirCreate(InEmptyDir(RT_STR_TUPLE("..")), 0755, 0), VERR_ALREADY_EXISTS);
2403
2404 RTTESTI_CHECK_RC(RTDirRemove(InDir(RT_STR_TUPLE("known-file"))), VERR_NOT_A_DIRECTORY);
2405 RTTESTI_CHECK_RC(RTDirRemove(InDir(RT_STR_TUPLE("known-file" RTPATH_SLASH_STR))), VERR_NOT_A_DIRECTORY);
2406
2407 /* Remove directory with subdirectories: */
2408#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
2409 RTTESTI_CHECK_RC(RTDirRemove(InDir(RT_STR_TUPLE("."))), VERR_DIR_NOT_EMPTY);
2410#else
2411 RTTESTI_CHECK_RC(RTDirRemove(InDir(RT_STR_TUPLE("."))), VERR_INVALID_PARAMETER); /* EINVAL for '.' */
2412#endif
2413#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
2414 int rc = RTDirRemove(InDir(RT_STR_TUPLE("..")));
2415# ifdef RT_OS_WINDOWS
2416 if (rc != VERR_DIR_NOT_EMPTY /*ntfs root*/ && rc != VERR_SHARING_VIOLATION /*ntfs weird*/ && rc != VERR_ACCESS_DENIED /*fat32 root*/)
2417 RTTestIFailed("RTDirRemove(%s) -> %Rrc, expected VERR_DIR_NOT_EMPTY, VERR_SHARING_VIOLATION or VERR_ACCESS_DENIED", g_szDir, rc);
2418# else
2419 if (rc != VERR_DIR_NOT_EMPTY && rc != VERR_RESOURCE_BUSY /*IPRT/kLIBC fun*/)
2420 RTTestIFailed("RTDirRemove(%s) -> %Rrc, expected VERR_DIR_NOT_EMPTY or VERR_RESOURCE_BUSY", g_szDir, rc);
2421
2422 APIRET orc;
2423 RTTESTI_CHECK_MSG((orc = DosDelete((PCSZ)InEmptyDir(RT_STR_TUPLE(".")))) == ERROR_ACCESS_DENIED,
2424 ("DosDelete(%s) -> %u, expected %u\n", g_szEmptyDir, orc, ERROR_ACCESS_DENIED));
2425 RTTESTI_CHECK_MSG((orc = DosDelete((PCSZ)InEmptyDir(RT_STR_TUPLE("..")))) == ERROR_ACCESS_DENIED,
2426 ("DosDelete(%s) -> %u, expected %u\n", g_szEmptyDir, orc, ERROR_ACCESS_DENIED));
2427 RTTESTI_CHECK_MSG((orc = DosDelete((PCSZ)InEmptyDir(RT_STR_TUPLE("")))) == ERROR_PATH_NOT_FOUND, /* a little weird (fsrouter) */
2428 ("DosDelete(%s) -> %u, expected %u\n", g_szEmptyDir, orc, ERROR_PATH_NOT_FOUND));
2429
2430# endif
2431#else
2432 RTTESTI_CHECK_RC(RTDirRemove(InDir(RT_STR_TUPLE(".."))), VERR_DIR_NOT_EMPTY);
2433#endif
2434 RTTESTI_CHECK_RC(RTDirRemove(InDir(RT_STR_TUPLE(""))), VERR_DIR_NOT_EMPTY);
2435
2436 /* Create a directory and remove it: */
2437 RTTESTI_CHECK_RC(RTDirCreate(InDir(RT_STR_TUPLE("subdir-1")), 0755, 0), VINF_SUCCESS);
2438 RTTESTI_CHECK_RC(RTDirRemove(g_szDir), VINF_SUCCESS);
2439
2440 /* Create a file and try remove it or create a directory with the same name: */
2441 RTFILE hFile1;
2442 RTTESTI_CHECK_RC_RETV(RTFileOpen(&hFile1, InDir(RT_STR_TUPLE("file18")),
2443 RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE | RTFILE_O_WRITE), VINF_SUCCESS);
2444 RTTESTI_CHECK_RC(RTFileClose(hFile1), VINF_SUCCESS);
2445 RTTESTI_CHECK_RC(RTDirRemove(g_szDir), VERR_NOT_A_DIRECTORY);
2446 RTTESTI_CHECK_RC(RTDirCreate(g_szDir, 0755, 0), VERR_ALREADY_EXISTS);
2447 RTTESTI_CHECK_RC(RTDirCreate(InDir(RT_STR_TUPLE("file18" RTPATH_SLASH_STR "subdir")), 0755, 0), VERR_PATH_NOT_FOUND);
2448
2449 /*
2450 * Profile alternately creating and removing a bunch of directories.
2451 */
2452 RTTESTI_CHECK_RC_RETV(RTDirCreate(InDir(RT_STR_TUPLE("subdir-2")), 0755, 0), VINF_SUCCESS);
2453 size_t cchDir = strlen(g_szDir);
2454 g_szDir[cchDir++] = RTPATH_SLASH;
2455 g_szDir[cchDir++] = 's';
2456
2457 uint32_t cCreated = 0;
2458 uint64_t nsCreate = 0;
2459 uint64_t nsRemove = 0;
2460 for (;;)
2461 {
2462 /* Create a bunch: */
2463 uint64_t nsStart = RTTimeNanoTS();
2464 for (uint32_t i = 0; i < 998; i++)
2465 {
2466 RTStrFormatU32(&g_szDir[cchDir], sizeof(g_szDir) - cchDir, i, 10, 3, 3, RTSTR_F_ZEROPAD);
2467 RTTESTI_CHECK_RC_RETV(RTDirCreate(g_szDir, 0755, 0), VINF_SUCCESS);
2468 }
2469 nsCreate += RTTimeNanoTS() - nsStart;
2470 cCreated += 998;
2471
2472 /* Remove the bunch: */
2473 nsStart = RTTimeNanoTS();
2474 for (uint32_t i = 0; i < 998; i++)
2475 {
2476 RTStrFormatU32(&g_szDir[cchDir], sizeof(g_szDir) - cchDir, i, 10, 3, 3, RTSTR_F_ZEROPAD);
2477 RTTESTI_CHECK_RC_RETV(RTDirRemove(g_szDir), VINF_SUCCESS);
2478 }
2479 nsRemove = RTTimeNanoTS() - nsStart;
2480
2481 /* Check if we got time for another round: */
2482 if ( ( nsRemove >= g_nsTestRun
2483 && nsCreate >= g_nsTestRun)
2484 || nsCreate + nsRemove >= g_nsTestRun * 3)
2485 break;
2486 }
2487 RTTestIValue("RTDirCreate", nsCreate / cCreated, RTTESTUNIT_NS_PER_OCCURRENCE);
2488 RTTestIValue("RTDirRemove", nsRemove / cCreated, RTTESTUNIT_NS_PER_OCCURRENCE);
2489}
2490
2491
2492void fsPerfStatVfs(void)
2493{
2494 RTTestISub("statvfs");
2495
2496 g_szEmptyDir[g_cchEmptyDir] = '\0';
2497 RTFOFF cbTotal;
2498 RTFOFF cbFree;
2499 uint32_t cbBlock;
2500 uint32_t cbSector;
2501 RTTESTI_CHECK_RC(RTFsQuerySizes(g_szEmptyDir, &cbTotal, &cbFree, &cbBlock, &cbSector), VINF_SUCCESS);
2502
2503 uint32_t uSerial;
2504 RTTESTI_CHECK_RC(RTFsQuerySerial(g_szEmptyDir, &uSerial), VINF_SUCCESS);
2505
2506 RTFSPROPERTIES Props;
2507 RTTESTI_CHECK_RC(RTFsQueryProperties(g_szEmptyDir, &Props), VINF_SUCCESS);
2508
2509 RTFSTYPE enmType;
2510 RTTESTI_CHECK_RC(RTFsQueryType(g_szEmptyDir, &enmType), VINF_SUCCESS);
2511
2512 g_szDeepDir[g_cchDeepDir] = '\0';
2513 PROFILE_FN(RTFsQuerySizes(g_szEmptyDir, &cbTotal, &cbFree, &cbBlock, &cbSector), g_nsTestRun, "RTFsQuerySize/empty");
2514 PROFILE_FN(RTFsQuerySizes(g_szDeepDir, &cbTotal, &cbFree, &cbBlock, &cbSector), g_nsTestRun, "RTFsQuerySize/deep");
2515}
2516
2517
2518void fsPerfRm(void)
2519{
2520 RTTestISub("rm");
2521
2522 /* Non-existing files. */
2523 RTTESTI_CHECK_RC(RTFileDelete(InEmptyDir(RT_STR_TUPLE("no-such-file"))), VERR_FILE_NOT_FOUND);
2524 RTTESTI_CHECK_RC(RTFileDelete(InEmptyDir(RT_STR_TUPLE("no-such-file" RTPATH_SLASH_STR))), VERR_FILE_NOT_FOUND);
2525 RTTESTI_CHECK_RC(RTFileDelete(InEmptyDir(RT_STR_TUPLE("no-such-dir" RTPATH_SLASH_STR "no-such-file"))), FSPERF_VERR_PATH_NOT_FOUND);
2526 RTTESTI_CHECK_RC(RTFileDelete(InEmptyDir(RT_STR_TUPLE("no-such-dir" RTPATH_SLASH_STR "no-such-file" RTPATH_SLASH_STR))), FSPERF_VERR_PATH_NOT_FOUND);
2527 RTTESTI_CHECK_RC(RTFileDelete(InDir(RT_STR_TUPLE("known-file" RTPATH_SLASH_STR "no-such-file"))), VERR_PATH_NOT_FOUND);
2528 RTTESTI_CHECK_RC(RTFileDelete(InDir(RT_STR_TUPLE("known-file" RTPATH_SLASH_STR "no-such-file" RTPATH_SLASH_STR))), VERR_PATH_NOT_FOUND);
2529
2530 /* Existing file but specified as if it was a directory: */
2531#if defined(RT_OS_WINDOWS)
2532 RTTESTI_CHECK_RC(RTFileDelete(InDir(RT_STR_TUPLE("known-file" RTPATH_SLASH_STR ))), VERR_INVALID_NAME);
2533#else
2534 RTTESTI_CHECK_RC(RTFileDelete(InDir(RT_STR_TUPLE("known-file" RTPATH_SLASH_STR))), VERR_NOT_A_FILE); //??
2535#endif
2536
2537 /* Directories: */
2538#if defined(RT_OS_WINDOWS)
2539 RTTESTI_CHECK_RC(RTFileDelete(InEmptyDir(RT_STR_TUPLE("."))), VERR_ACCESS_DENIED);
2540 RTTESTI_CHECK_RC(RTFileDelete(InEmptyDir(RT_STR_TUPLE(".."))), VERR_ACCESS_DENIED);
2541 RTTESTI_CHECK_RC(RTFileDelete(InEmptyDir(RT_STR_TUPLE(""))), VERR_ACCESS_DENIED);
2542#elif defined(RT_OS_DARWIN) /* unlink() on xnu 16.7.0 is behaviour totally werid: */
2543 RTTESTI_CHECK_RC(RTFileDelete(InEmptyDir(RT_STR_TUPLE("."))), VERR_INVALID_PARAMETER);
2544 RTTESTI_CHECK_RC(RTFileDelete(InEmptyDir(RT_STR_TUPLE(".."))), VINF_SUCCESS /*WTF?!?*/);
2545 RTTESTI_CHECK_RC(RTFileDelete(InEmptyDir(RT_STR_TUPLE(""))), VERR_ACCESS_DENIED);
2546#elif defined(RT_OS_OS2) /* OS/2 has a busted unlink, it think it should remove directories too. */
2547 RTTESTI_CHECK_RC(RTFileDelete(InDir(RT_STR_TUPLE("."))), VERR_DIR_NOT_EMPTY);
2548 int rc = RTFileDelete(InDir(RT_STR_TUPLE("..")));
2549 if (rc != VERR_DIR_NOT_EMPTY && rc != VERR_FILE_NOT_FOUND && rc != VERR_RESOURCE_BUSY)
2550 RTTestIFailed("RTFileDelete(%s) -> %Rrc, expected VERR_DIR_NOT_EMPTY or VERR_FILE_NOT_FOUND or VERR_RESOURCE_BUSY", g_szDir, rc);
2551 RTTESTI_CHECK_RC(RTFileDelete(InDir(RT_STR_TUPLE(""))), VERR_DIR_NOT_EMPTY);
2552 APIRET orc;
2553 RTTESTI_CHECK_MSG((orc = DosDelete((PCSZ)InEmptyDir(RT_STR_TUPLE(".")))) == ERROR_ACCESS_DENIED,
2554 ("DosDelete(%s) -> %u, expected %u\n", g_szEmptyDir, orc, ERROR_ACCESS_DENIED));
2555 RTTESTI_CHECK_MSG((orc = DosDelete((PCSZ)InEmptyDir(RT_STR_TUPLE("..")))) == ERROR_ACCESS_DENIED,
2556 ("DosDelete(%s) -> %u, expected %u\n", g_szEmptyDir, orc, ERROR_ACCESS_DENIED));
2557 RTTESTI_CHECK_MSG((orc = DosDelete((PCSZ)InEmptyDir(RT_STR_TUPLE("")))) == ERROR_PATH_NOT_FOUND,
2558 ("DosDelete(%s) -> %u, expected %u\n", g_szEmptyDir, orc, ERROR_PATH_NOT_FOUND)); /* hpfs+jfs; weird. */
2559
2560#else
2561 RTTESTI_CHECK_RC(RTFileDelete(InEmptyDir(RT_STR_TUPLE("."))), VERR_IS_A_DIRECTORY);
2562 RTTESTI_CHECK_RC(RTFileDelete(InEmptyDir(RT_STR_TUPLE(".."))), VERR_IS_A_DIRECTORY);
2563 RTTESTI_CHECK_RC(RTFileDelete(InEmptyDir(RT_STR_TUPLE(""))), VERR_IS_A_DIRECTORY);
2564#endif
2565
2566 /* Shallow: */
2567 RTFILE hFile1;
2568 RTTESTI_CHECK_RC_RETV(RTFileOpen(&hFile1, InDir(RT_STR_TUPLE("file19")),
2569 RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE | RTFILE_O_WRITE), VINF_SUCCESS);
2570 RTTESTI_CHECK_RC(RTFileClose(hFile1), VINF_SUCCESS);
2571 RTTESTI_CHECK_RC(RTFileDelete(g_szDir), VINF_SUCCESS);
2572 RTTESTI_CHECK_RC(RTFileDelete(g_szDir), VERR_FILE_NOT_FOUND);
2573
2574 if (g_fManyFiles)
2575 {
2576 /*
2577 * Profile the deletion of the manyfiles content.
2578 */
2579 {
2580 InDir(RT_STR_TUPLE("manyfiles" RTPATH_SLASH_STR));
2581 size_t const offFilename = strlen(g_szDir);
2582 fsPerfYield();
2583 uint64_t const nsStart = RTTimeNanoTS();
2584 for (uint32_t i = 0; i < g_cManyFiles; i++)
2585 {
2586 RTStrFormatU32(&g_szDir[offFilename], sizeof(g_szDir) - offFilename, i, 10, 5, 5, RTSTR_F_ZEROPAD);
2587 RTTESTI_CHECK_RC_RETV(RTFileDelete(g_szDir), VINF_SUCCESS);
2588 }
2589 uint64_t const cNsElapsed = RTTimeNanoTS() - nsStart;
2590 RTTestIValueF(cNsElapsed, RTTESTUNIT_NS, "Deleted %u empty files from a single directory", g_cManyFiles);
2591 RTTestIValueF(cNsElapsed / g_cManyFiles, RTTESTUNIT_NS_PER_OCCURRENCE, "Delete file (single dir)");
2592 }
2593
2594 /*
2595 * Ditto for the manytree.
2596 */
2597 {
2598 char szPath[FSPERF_MAX_PATH];
2599 uint64_t const nsStart = RTTimeNanoTS();
2600 DO_MANYTREE_FN(szPath, RTTESTI_CHECK_RC_RETV(RTFileDelete(szPath), VINF_SUCCESS));
2601 uint64_t const cNsElapsed = RTTimeNanoTS() - nsStart;
2602 RTTestIValueF(cNsElapsed, RTTESTUNIT_NS, "Deleted %u empty files in tree", g_cManyTreeFiles);
2603 RTTestIValueF(cNsElapsed / g_cManyTreeFiles, RTTESTUNIT_NS_PER_OCCURRENCE, "Delete file (tree)");
2604 }
2605 }
2606}
2607
2608
2609void fsPerfChSize(void)
2610{
2611 RTTestISub("chsize");
2612
2613 /*
2614 * We need some free space to perform this test.
2615 */
2616 g_szDir[g_cchDir] = '\0';
2617 RTFOFF cbFree = 0;
2618 RTTESTI_CHECK_RC_RETV(RTFsQuerySizes(g_szDir, NULL, &cbFree, NULL, NULL), VINF_SUCCESS);
2619 if (cbFree < _1M)
2620 {
2621 RTTestSkipped(g_hTest, "Insufficent free space: %'RU64 bytes, requires >= 1MB", cbFree);
2622 return;
2623 }
2624
2625 /*
2626 * Create a file and play around with it's size.
2627 * We let the current file position follow the end position as we make changes.
2628 */
2629 RTFILE hFile1;
2630 RTTESTI_CHECK_RC_RETV(RTFileOpen(&hFile1, InDir(RT_STR_TUPLE("file20")),
2631 RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE | RTFILE_O_READWRITE), VINF_SUCCESS);
2632 uint64_t cbFile = UINT64_MAX;
2633 RTTESTI_CHECK_RC(RTFileGetSize(hFile1, &cbFile), VINF_SUCCESS);
2634 RTTESTI_CHECK(cbFile == 0);
2635
2636 uint8_t abBuf[4096];
2637 static uint64_t const s_acbChanges[] =
2638 {
2639 1023, 1024, 1024, 1025, 8192, 11111, _1M, _8M, _8M,
2640 _4M, _2M + 1, _1M - 1, 65537, 65536, 32768, 8000, 7999, 7998, 1024, 1, 0
2641 };
2642 uint64_t cbOld = 0;
2643 for (unsigned i = 0; i < RT_ELEMENTS(s_acbChanges); i++)
2644 {
2645 uint64_t cbNew = s_acbChanges[i];
2646 if (cbNew + _64K >= (uint64_t)cbFree)
2647 continue;
2648
2649 RTTESTI_CHECK_RC(RTFileSetSize(hFile1, cbNew), VINF_SUCCESS);
2650 RTTESTI_CHECK_RC(RTFileGetSize(hFile1, &cbFile), VINF_SUCCESS);
2651 RTTESTI_CHECK_MSG(cbFile == cbNew, ("cbFile=%#RX64 cbNew=%#RX64\n", cbFile, cbNew));
2652
2653 if (cbNew > cbOld)
2654 {
2655 /* Check that the extension is all zeroed: */
2656 uint64_t cbLeft = cbNew - cbOld;
2657 while (cbLeft > 0)
2658 {
2659 memset(abBuf, 0xff, sizeof(abBuf));
2660 size_t cbToRead = sizeof(abBuf);
2661 if (cbToRead > cbLeft)
2662 cbToRead = (size_t)cbLeft;
2663 RTTESTI_CHECK_RC(RTFileRead(hFile1, abBuf, cbToRead, NULL), VINF_SUCCESS);
2664 RTTESTI_CHECK(ASMMemIsZero(abBuf, cbToRead));
2665 cbLeft -= cbToRead;
2666 }
2667 }
2668 else
2669 {
2670 /* Check that reading fails with EOF because current position is now beyond the end: */
2671 RTTESTI_CHECK_RC(RTFileRead(hFile1, abBuf, 1, NULL), VERR_EOF);
2672
2673 /* Keep current position at the end of the file: */
2674 RTTESTI_CHECK_RC(RTFileSeek(hFile1, cbNew, RTFILE_SEEK_BEGIN, NULL), VINF_SUCCESS);
2675 }
2676 cbOld = cbNew;
2677 }
2678
2679 /*
2680 * Profile just the file setting operation itself, keeping the changes within
2681 * an allocation unit to avoid needing to adjust the actual (host) FS allocation.
2682 * ASSUMES allocation unit >= 512 and power of two.
2683 */
2684 RTTESTI_CHECK_RC(RTFileSetSize(hFile1, _64K), VINF_SUCCESS);
2685 PROFILE_FN(RTFileSetSize(hFile1, _64K - (iIteration & 255) - 128), g_nsTestRun, "RTFileSetSize/noalloc");
2686
2687 RTTESTI_CHECK_RC(RTFileSetSize(hFile1, 0), VINF_SUCCESS);
2688 RTTESTI_CHECK_RC(RTFileClose(hFile1), VINF_SUCCESS);
2689 RTTESTI_CHECK_RC(RTFileDelete(g_szDir), VINF_SUCCESS);
2690}
2691
2692
2693int fsPerfIoPrepFile(RTFILE hFile1, uint64_t cbFile, uint8_t **ppbFree)
2694{
2695 /*
2696 * Seek to the end - 4K and write the last 4K.
2697 * This should have the effect of filling the whole file with zeros.
2698 */
2699 RTTESTI_CHECK_RC_RET(RTFileSeek(hFile1, cbFile - _4K, RTFILE_SEEK_BEGIN, NULL), VINF_SUCCESS, rcCheck);
2700 RTTESTI_CHECK_RC_RET(RTFileWrite(hFile1, g_abRTZero4K, _4K, NULL), VINF_SUCCESS, rcCheck);
2701
2702 /*
2703 * Check that the space we searched across actually is zero filled.
2704 */
2705 RTTESTI_CHECK_RC_RET(RTFileSeek(hFile1, 0, RTFILE_SEEK_BEGIN, NULL), VINF_SUCCESS, rcCheck);
2706 size_t cbBuf = _1M;
2707 uint8_t *pbBuf = *ppbFree = (uint8_t *)RTMemAlloc(cbBuf);
2708 RTTESTI_CHECK_RET(pbBuf != NULL, VERR_NO_MEMORY);
2709 uint64_t cbLeft = cbFile;
2710 while (cbLeft > 0)
2711 {
2712 size_t cbToRead = cbBuf;
2713 if (cbToRead > cbLeft)
2714 cbToRead = (size_t)cbLeft;
2715 pbBuf[cbToRead] = 0xff;
2716
2717 RTTESTI_CHECK_RC_RET(RTFileRead(hFile1, pbBuf, cbToRead, NULL), VINF_SUCCESS, rcCheck);
2718 RTTESTI_CHECK_RET(ASMMemIsZero(pbBuf, cbToRead), VERR_MISMATCH);
2719
2720 cbLeft -= cbToRead;
2721 }
2722
2723 /*
2724 * Fill the file with 0xf6 and insert offset markers with 1KB intervals.
2725 */
2726 RTTESTI_CHECK_RC_RET(RTFileSeek(hFile1, 0, RTFILE_SEEK_BEGIN, NULL), VINF_SUCCESS, rcCheck);
2727 memset(pbBuf, 0xf6, cbBuf);
2728 cbLeft = cbFile;
2729 uint64_t off = 0;
2730 while (cbLeft > 0)
2731 {
2732 Assert(!(off & (_1K - 1)));
2733 Assert(!(cbBuf & (_1K - 1)));
2734 for (size_t offBuf = 0; offBuf < cbBuf; offBuf += _1K, off += _1K)
2735 *(uint64_t *)&pbBuf[offBuf] = off;
2736
2737 size_t cbToWrite = cbBuf;
2738 if (cbToWrite > cbLeft)
2739 cbToWrite = (size_t)cbLeft;
2740
2741 RTTESTI_CHECK_RC_RET(RTFileWrite(hFile1, pbBuf, cbToWrite, NULL), VINF_SUCCESS, rcCheck);
2742
2743 cbLeft -= cbToWrite;
2744 }
2745
2746 return VINF_SUCCESS;
2747}
2748
2749
2750/**
2751 * Checks the content read from the file fsPerfIoPrepFile() prepared.
2752 */
2753bool fsPerfCheckReadBuf(unsigned uLineNo, uint64_t off, uint8_t const *pbBuf, size_t cbBuf, uint8_t bFiller = 0xf6)
2754{
2755 uint32_t cMismatches = 0;
2756 size_t offBuf = 0;
2757 uint32_t offBlock = (uint32_t)(off & (_1K - 1));
2758 while (offBuf < cbBuf)
2759 {
2760 /*
2761 * Check the offset marker:
2762 */
2763 if (offBlock < sizeof(uint64_t))
2764 {
2765 RTUINT64U uMarker;
2766 uMarker.u = off + offBuf - offBlock;
2767 unsigned offMarker = offBlock & (sizeof(uint64_t) - 1);
2768 while (offMarker < sizeof(uint64_t) && offBuf < cbBuf)
2769 {
2770 if (uMarker.au8[offMarker] != pbBuf[offBuf])
2771 {
2772 RTTestIFailed("%u: Mismatch at buffer/file offset %#zx/%#RX64: %#x, expected %#x",
2773 uLineNo, offBuf, off + offBuf, pbBuf[offBuf], uMarker.au8[offMarker]);
2774 if (cMismatches++ > 32)
2775 return false;
2776 }
2777 offMarker++;
2778 offBuf++;
2779 }
2780 offBlock = sizeof(uint64_t);
2781 }
2782
2783 /*
2784 * Check the filling:
2785 */
2786 size_t cbFilling = RT_MIN(_1K - offBlock, cbBuf - offBuf);
2787 if ( cbFilling == 0
2788 || ASMMemIsAllU8(&pbBuf[offBuf], cbFilling, bFiller))
2789 offBuf += cbFilling;
2790 else
2791 {
2792 /* Some mismatch, locate it/them: */
2793 while (cbFilling > 0 && offBuf < cbBuf)
2794 {
2795 if (pbBuf[offBuf] != bFiller)
2796 {
2797 RTTestIFailed("%u: Mismatch at buffer/file offset %#zx/%#RX64: %#x, expected %#04x",
2798 uLineNo, offBuf, off + offBuf, pbBuf[offBuf], bFiller);
2799 if (cMismatches++ > 32)
2800 return false;
2801 }
2802 offBuf++;
2803 cbFilling--;
2804 }
2805 }
2806 offBlock = 0;
2807 }
2808 return cMismatches == 0;
2809}
2810
2811
2812/**
2813 * Sets up write buffer with offset markers and fillers.
2814 */
2815void fsPerfFillWriteBuf(uint64_t off, uint8_t *pbBuf, size_t cbBuf, uint8_t bFiller = 0xf6)
2816{
2817 uint32_t offBlock = (uint32_t)(off & (_1K - 1));
2818 while (cbBuf > 0)
2819 {
2820 /* The marker. */
2821 if (offBlock < sizeof(uint64_t))
2822 {
2823 RTUINT64U uMarker;
2824 uMarker.u = off + offBlock;
2825 if (cbBuf > sizeof(uMarker) - offBlock)
2826 {
2827 memcpy(pbBuf, &uMarker.au8[offBlock], sizeof(uMarker) - offBlock);
2828 pbBuf += sizeof(uMarker) - offBlock;
2829 cbBuf -= sizeof(uMarker) - offBlock;
2830 off += sizeof(uMarker) - offBlock;
2831 }
2832 else
2833 {
2834 memcpy(pbBuf, &uMarker.au8[offBlock], cbBuf);
2835 return;
2836 }
2837 offBlock = sizeof(uint64_t);
2838 }
2839
2840 /* Do the filling. */
2841 size_t cbFilling = RT_MIN(_1K - offBlock, cbBuf);
2842 memset(pbBuf, bFiller, cbFilling);
2843 pbBuf += cbFilling;
2844 cbBuf -= cbFilling;
2845 off += cbFilling;
2846
2847 offBlock = 0;
2848 }
2849}
2850
2851
2852
2853void fsPerfIoSeek(RTFILE hFile1, uint64_t cbFile)
2854{
2855 /*
2856 * Do a bunch of search tests, most which are random.
2857 */
2858 struct
2859 {
2860 int rc;
2861 uint32_t uMethod;
2862 int64_t offSeek;
2863 uint64_t offActual;
2864
2865 } aSeeks[9 + 64] =
2866 {
2867 { VINF_SUCCESS, RTFILE_SEEK_BEGIN, 0, 0 },
2868 { VINF_SUCCESS, RTFILE_SEEK_CURRENT, 0, 0 },
2869 { VINF_SUCCESS, RTFILE_SEEK_END, 0, cbFile },
2870 { VINF_SUCCESS, RTFILE_SEEK_CURRENT, -4096, cbFile - 4096 },
2871 { VINF_SUCCESS, RTFILE_SEEK_CURRENT, 4096 - (int64_t)cbFile, 0 },
2872 { VINF_SUCCESS, RTFILE_SEEK_END, -(int64_t)cbFile/2, cbFile / 2 + (cbFile & 1) },
2873 { VINF_SUCCESS, RTFILE_SEEK_CURRENT, -(int64_t)cbFile/2, 0 },
2874#if defined(RT_OS_WINDOWS)
2875 { VERR_NEGATIVE_SEEK, RTFILE_SEEK_CURRENT, -1, 0 },
2876#else
2877 { VERR_INVALID_PARAMETER, RTFILE_SEEK_CURRENT, -1, 0 },
2878#endif
2879 { VINF_SUCCESS, RTFILE_SEEK_CURRENT, 0, 0 },
2880 };
2881
2882 uint64_t offActual = 0;
2883 for (unsigned i = 9; i < RT_ELEMENTS(aSeeks); i++)
2884 {
2885 switch (RTRandU32Ex(RTFILE_SEEK_BEGIN, RTFILE_SEEK_END))
2886 {
2887 default: AssertFailedBreak();
2888 case RTFILE_SEEK_BEGIN:
2889 aSeeks[i].uMethod = RTFILE_SEEK_BEGIN;
2890 aSeeks[i].rc = VINF_SUCCESS;
2891 aSeeks[i].offSeek = RTRandU64Ex(0, cbFile + cbFile / 8);
2892 aSeeks[i].offActual = offActual = aSeeks[i].offSeek;
2893 break;
2894
2895 case RTFILE_SEEK_CURRENT:
2896 aSeeks[i].uMethod = RTFILE_SEEK_CURRENT;
2897 aSeeks[i].rc = VINF_SUCCESS;
2898 aSeeks[i].offSeek = (int64_t)RTRandU64Ex(0, cbFile + cbFile / 8) - (int64_t)offActual;
2899 aSeeks[i].offActual = offActual += aSeeks[i].offSeek;
2900 break;
2901
2902 case RTFILE_SEEK_END:
2903 aSeeks[i].uMethod = RTFILE_SEEK_END;
2904 aSeeks[i].rc = VINF_SUCCESS;
2905 aSeeks[i].offSeek = -(int64_t)RTRandU64Ex(0, cbFile);
2906 aSeeks[i].offActual = offActual = cbFile + aSeeks[i].offSeek;
2907 break;
2908 }
2909 }
2910
2911 for (unsigned iDoReadCheck = 0; iDoReadCheck < 2; iDoReadCheck++)
2912 {
2913 for (uint32_t i = 0; i < RT_ELEMENTS(aSeeks); i++)
2914 {
2915 offActual = UINT64_MAX;
2916 int rc = RTFileSeek(hFile1, aSeeks[i].offSeek, aSeeks[i].uMethod, &offActual);
2917 if (rc != aSeeks[i].rc)
2918 RTTestIFailed("Seek #%u: Expected %Rrc, got %Rrc", i, aSeeks[i].rc, rc);
2919 if (RT_SUCCESS(rc) && offActual != aSeeks[i].offActual)
2920 RTTestIFailed("Seek #%u: offActual %#RX64, expected %#RX64", i, offActual, aSeeks[i].offActual);
2921 if (RT_SUCCESS(rc))
2922 {
2923 uint64_t offTell = RTFileTell(hFile1);
2924 if (offTell != offActual)
2925 RTTestIFailed("Seek #%u: offActual %#RX64, RTFileTell %#RX64", i, offActual, offTell);
2926 }
2927
2928 if (RT_SUCCESS(rc) && offActual + _2K <= cbFile && iDoReadCheck)
2929 {
2930 uint8_t abBuf[_2K];
2931 RTTESTI_CHECK_RC(rc = RTFileRead(hFile1, abBuf, sizeof(abBuf), NULL), VINF_SUCCESS);
2932 if (RT_SUCCESS(rc))
2933 {
2934 size_t offMarker = (size_t)(RT_ALIGN_64(offActual, _1K) - offActual);
2935 uint64_t uMarker = *(uint64_t *)&abBuf[offMarker]; /** @todo potentially unaligned access */
2936 if (uMarker != offActual + offMarker)
2937 RTTestIFailed("Seek #%u: Invalid marker value (@ %#RX64): %#RX64, expected %#RX64",
2938 i, offActual, uMarker, offActual + offMarker);
2939
2940 RTTESTI_CHECK_RC(RTFileSeek(hFile1, -(int64_t)sizeof(abBuf), RTFILE_SEEK_CURRENT, NULL), VINF_SUCCESS);
2941 }
2942 }
2943 }
2944 }
2945
2946
2947 /*
2948 * Profile seeking relative to the beginning of the file and relative
2949 * to the end. The latter might be more expensive in a SF context.
2950 */
2951 PROFILE_FN(RTFileSeek(hFile1, iIteration < cbFile ? iIteration : iIteration % cbFile, RTFILE_SEEK_BEGIN, NULL),
2952 g_nsTestRun, "RTFileSeek/BEGIN");
2953 PROFILE_FN(RTFileSeek(hFile1, iIteration < cbFile ? -(int64_t)iIteration : -(int64_t)(iIteration % cbFile), RTFILE_SEEK_END, NULL),
2954 g_nsTestRun, "RTFileSeek/END");
2955
2956}
2957
2958#ifdef FSPERF_TEST_SENDFILE
2959
2960/**
2961 * Send file thread arguments.
2962 */
2963typedef struct FSPERFSENDFILEARGS
2964{
2965 uint64_t offFile;
2966 size_t cbSend;
2967 uint64_t cbSent;
2968 size_t cbBuf;
2969 uint8_t *pbBuf;
2970 uint8_t bFiller;
2971 bool fCheckBuf;
2972 RTSOCKET hSocket;
2973 uint64_t volatile tsThreadDone;
2974} FSPERFSENDFILEARGS;
2975
2976/** Thread receiving the bytes from a sendfile() call. */
2977static DECLCALLBACK(int) fsPerfSendFileThread(RTTHREAD hSelf, void *pvUser)
2978{
2979 FSPERFSENDFILEARGS *pArgs = (FSPERFSENDFILEARGS *)pvUser;
2980 int rc = VINF_SUCCESS;
2981
2982 if (pArgs->fCheckBuf)
2983 RTTestSetDefault(g_hTest, NULL);
2984
2985 uint64_t cbReceived = 0;
2986 while (cbReceived < pArgs->cbSent)
2987 {
2988 size_t const cbToRead = RT_MIN(pArgs->cbBuf, pArgs->cbSent - cbReceived);
2989 size_t cbActual = 0;
2990 RTTEST_CHECK_RC_BREAK(g_hTest, rc = RTTcpRead(pArgs->hSocket, pArgs->pbBuf, cbToRead, &cbActual), VINF_SUCCESS);
2991 RTTEST_CHECK_BREAK(g_hTest, cbActual != 0);
2992 RTTEST_CHECK(g_hTest, cbActual <= cbToRead);
2993 if (pArgs->fCheckBuf)
2994 fsPerfCheckReadBuf(__LINE__, pArgs->offFile + cbReceived, pArgs->pbBuf, cbActual, pArgs->bFiller);
2995 cbReceived += cbActual;
2996 }
2997
2998 pArgs->tsThreadDone = RTTimeNanoTS();
2999
3000 if (cbReceived == pArgs->cbSent && RT_SUCCESS(rc))
3001 {
3002 size_t cbActual = 0;
3003 rc = RTSocketReadNB(pArgs->hSocket, pArgs->pbBuf, 1, &cbActual);
3004 if (rc != VINF_SUCCESS && rc != VINF_TRY_AGAIN)
3005 RTTestFailed(g_hTest, "RTSocketReadNB(sendfile client socket) -> %Rrc; expected VINF_SUCCESS or VINF_TRY_AGAIN\n", rc);
3006 else if (cbActual != 0)
3007 RTTestFailed(g_hTest, "sendfile client socket still contains data when done!\n");
3008 }
3009
3010 RTTEST_CHECK_RC(g_hTest, RTSocketClose(pArgs->hSocket), VINF_SUCCESS);
3011 pArgs->hSocket = NIL_RTSOCKET;
3012
3013 RT_NOREF(hSelf);
3014 return rc;
3015}
3016
3017
3018static uint64_t fsPerfSendFileOne(FSPERFSENDFILEARGS *pArgs, RTFILE hFile1, uint64_t offFile,
3019 size_t cbSend, uint64_t cbSent, uint8_t bFiller, bool fCheckBuf, unsigned iLine)
3020{
3021 /* Copy parameters to the argument structure: */
3022 pArgs->offFile = offFile;
3023 pArgs->cbSend = cbSend;
3024 pArgs->cbSent = cbSent;
3025 pArgs->bFiller = bFiller;
3026 pArgs->fCheckBuf = fCheckBuf;
3027
3028 /* Create a socket pair. */
3029 pArgs->hSocket = NIL_RTSOCKET;
3030 RTSOCKET hServer = NIL_RTSOCKET;
3031 RTTESTI_CHECK_RC_RET(RTTcpCreatePair(&hServer, &pArgs->hSocket, 0), VINF_SUCCESS, 0);
3032
3033 /* Create the receiving thread: */
3034 int rc;
3035 RTTHREAD hThread = NIL_RTTHREAD;
3036 RTTESTI_CHECK_RC(rc = RTThreadCreate(&hThread, fsPerfSendFileThread, pArgs, 0,
3037 RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, "sendfile"), VINF_SUCCESS);
3038 if (RT_SUCCESS(rc))
3039 {
3040 uint64_t const tsStart = RTTimeNanoTS();
3041
3042# if defined(RT_OS_LINUX) || defined(RT_OS_SOLARIS)
3043 /* SystemV sendfile: */
3044 loff_t offFileSf = pArgs->offFile;
3045 ssize_t cbActual = sendfile((int)RTSocketToNative(hServer), (int)RTFileToNative(hFile1), &offFileSf, pArgs->cbSend);
3046 int const iErr = errno;
3047 if (cbActual < 0)
3048 RTTestIFailed("%u: sendfile(socket, file, &%#X64, %#zx) failed (%zd): %d (%Rrc), offFileSf=%#RX64\n",
3049 iLine, pArgs->offFile, pArgs->cbSend, cbActual, iErr, RTErrConvertFromErrno(iErr), (uint64_t)offFileSf);
3050 else if ((uint64_t)cbActual != pArgs->cbSent)
3051 RTTestIFailed("%u: sendfile(socket, file, &%#RX64, %#zx): %#zx, expected %#RX64 (offFileSf=%#RX64)\n",
3052 iLine, pArgs->offFile, pArgs->cbSend, cbActual, pArgs->cbSent, (uint64_t)offFileSf);
3053 else if ((uint64_t)offFileSf != pArgs->offFile + pArgs->cbSent)
3054 RTTestIFailed("%u: sendfile(socket, file, &%#RX64, %#zx): %#zx; offFileSf=%#RX64, expected %#RX64\n",
3055 iLine, pArgs->offFile, pArgs->cbSend, cbActual, (uint64_t)offFileSf, pArgs->offFile + pArgs->cbSent);
3056#else
3057 /* BSD sendfile: */
3058# ifdef SF_SYNC
3059 int fSfFlags = SF_SYNC;
3060# else
3061 int fSfFlags = 0;
3062# endif
3063 off_t cbActual = pArgs->cbSend;
3064 rc = sendfile((int)RTFileToNative(hFile1), (int)RTSocketToNative(hServer),
3065# ifdef RT_OS_DARWIN
3066 pArgs->offFile, &cbActual, NULL, fSfFlags);
3067# else
3068 pArgs->offFile, cbActual, NULL, &cbActual, fSfFlags);
3069# endif
3070 int const iErr = errno;
3071 if (rc != 0)
3072 RTTestIFailed("%u: sendfile(file, socket, %#RX64, %#zx, NULL,, %#x) failed (%d): %d (%Rrc), cbActual=%#RX64\n",
3073 iLine, pArgs->offFile, (size_t)pArgs->cbSend, rc, iErr, RTErrConvertFromErrno(iErr), (uint64_t)cbActual);
3074 if ((uint64_t)cbActual != pArgs->cbSent)
3075 RTTestIFailed("%u: sendfile(file, socket, %#RX64, %#zx, NULL,, %#x): cbActual=%#RX64, expected %#RX64 (rc=%d, errno=%d)\n",
3076 iLine, pArgs->offFile, (size_t)pArgs->cbSend, (uint64_t)cbActual, pArgs->cbSent, rc, iErr);
3077# endif
3078 RTTESTI_CHECK_RC(RTSocketClose(hServer), VINF_SUCCESS);
3079 RTTESTI_CHECK_RC(RTThreadWait(hThread, 30 * RT_NS_1SEC, NULL), VINF_SUCCESS);
3080
3081 if (pArgs->tsThreadDone >= tsStart)
3082 return RT_MAX(pArgs->tsThreadDone - tsStart, 1);
3083 }
3084 return 0;
3085}
3086
3087
3088static void fsPerfSendFile(RTFILE hFile1, uint64_t cbFile)
3089{
3090 RTTestISub("sendfile");
3091# ifdef RT_OS_LINUX
3092 uint64_t const cbFileMax = RT_MIN(cbFile, UINT32_MAX - PAGE_OFFSET_MASK);
3093# else
3094 uint64_t const cbFileMax = RT_MIN(cbFile, SSIZE_MAX - PAGE_OFFSET_MASK);
3095# endif
3096 signal(SIGPIPE, SIG_IGN);
3097
3098 /*
3099 * Allocate a buffer.
3100 */
3101 FSPERFSENDFILEARGS Args;
3102 Args.cbBuf = RT_MIN(cbFileMax, _16M);
3103 Args.pbBuf = (uint8_t *)RTMemAlloc(Args.cbBuf);
3104 while (!Args.pbBuf)
3105 {
3106 Args.cbBuf /= 8;
3107 RTTESTI_CHECK_RETV(Args.cbBuf >= _64K);
3108 Args.pbBuf = (uint8_t *)RTMemAlloc(Args.cbBuf);
3109 }
3110
3111 /*
3112 * First iteration with default buffer content.
3113 */
3114 fsPerfSendFileOne(&Args, hFile1, 0, cbFileMax, cbFileMax, 0xf6, true /*fCheckBuf*/, __LINE__);
3115 if (cbFileMax == cbFile)
3116 fsPerfSendFileOne(&Args, hFile1, 63, cbFileMax, cbFileMax - 63, 0xf6, true /*fCheckBuf*/, __LINE__);
3117 else
3118 fsPerfSendFileOne(&Args, hFile1, 63, cbFileMax - 63, cbFileMax - 63, 0xf6, true /*fCheckBuf*/, __LINE__);
3119
3120 /*
3121 * Write a block using the regular API and then send it, checking that
3122 * the any caching that sendfile does is correctly updated.
3123 */
3124 uint8_t bFiller = 0xf6;
3125 size_t cbToSend = RT_MIN(cbFileMax, Args.cbBuf);
3126 do
3127 {
3128 fsPerfSendFileOne(&Args, hFile1, 0, cbToSend, cbToSend, bFiller, true /*fCheckBuf*/, __LINE__); /* prime cache */
3129
3130 bFiller += 1;
3131 fsPerfFillWriteBuf(0, Args.pbBuf, cbToSend, bFiller);
3132 RTTESTI_CHECK_RC(RTFileWriteAt(hFile1, 0, Args.pbBuf, cbToSend, NULL), VINF_SUCCESS);
3133
3134 fsPerfSendFileOne(&Args, hFile1, 0, cbToSend, cbToSend, bFiller, true /*fCheckBuf*/, __LINE__);
3135
3136 cbToSend /= 2;
3137 } while (cbToSend >= PAGE_SIZE && ((unsigned)bFiller - 0xf7U) < 64);
3138
3139 /*
3140 * Restore buffer content
3141 */
3142 bFiller = 0xf6;
3143 fsPerfFillWriteBuf(0, Args.pbBuf, Args.cbBuf, bFiller);
3144 RTTESTI_CHECK_RC(RTFileWriteAt(hFile1, 0, Args.pbBuf, Args.cbBuf, NULL), VINF_SUCCESS);
3145
3146 /*
3147 * Do 128 random sends.
3148 */
3149 uint64_t const cbSmall = RT_MIN(_256K, cbFileMax / 16);
3150 for (uint32_t iTest = 0; iTest < 128; iTest++)
3151 {
3152 cbToSend = (size_t)RTRandU64Ex(1, iTest < 64 ? cbSmall : cbFileMax);
3153 uint64_t const offToSendFrom = RTRandU64Ex(0, cbFile - 1);
3154 uint64_t const cbSent = offToSendFrom + cbToSend <= cbFile ? cbToSend : cbFile - offToSendFrom;
3155
3156 fsPerfSendFileOne(&Args, hFile1, offToSendFrom, cbToSend, cbSent, bFiller, true /*fCheckBuf*/, __LINE__);
3157 }
3158
3159 /*
3160 * Benchmark it.
3161 */
3162 uint32_t cIterations = 0;
3163 uint64_t nsElapsed = 0;
3164 for (;;)
3165 {
3166 uint64_t cNsThis = fsPerfSendFileOne(&Args, hFile1, 0, cbFileMax, cbFileMax, 0xf6, false /*fCheckBuf*/, __LINE__);
3167 nsElapsed += cNsThis;
3168 cIterations++;
3169 if (!cNsThis || nsElapsed >= g_nsTestRun)
3170 break;
3171 }
3172 uint64_t cbTotal = cbFileMax * cIterations;
3173 RTTestIValue("latency", nsElapsed / cIterations, RTTESTUNIT_NS_PER_CALL);
3174 RTTestIValue("throughput", (uint64_t)(cbTotal / ((double)nsElapsed / RT_NS_1SEC)), RTTESTUNIT_BYTES_PER_SEC);
3175 RTTestIValue("calls", cIterations, RTTESTUNIT_CALLS);
3176 RTTestIValue("bytes", cbTotal, RTTESTUNIT_BYTES);
3177 if (g_fShowDuration)
3178 RTTestIValue("duration", nsElapsed, RTTESTUNIT_NS);
3179
3180 /*
3181 * Cleanup.
3182 */
3183 RTMemFree(Args.pbBuf);
3184}
3185
3186#endif /* FSPERF_TEST_SENDFILE */
3187#ifdef RT_OS_LINUX
3188
3189#ifndef __NR_splice
3190# if defined(RT_ARCH_AMD64)
3191# define __NR_splice 275
3192# elif defined(RT_ARCH_X86)
3193# define __NR_splice 313
3194# else
3195# error "fix me"
3196# endif
3197#endif
3198
3199/** FsPerf is built against ancient glibc, so make the splice syscall ourselves. */
3200DECLINLINE(ssize_t) syscall_splice(int fdIn, loff_t *poffIn, int fdOut, loff_t *poffOut, size_t cbChunk, unsigned fFlags)
3201{
3202 return syscall(__NR_splice, fdIn, poffIn, fdOut, poffOut, cbChunk, fFlags);
3203}
3204
3205
3206/**
3207 * Send file thread arguments.
3208 */
3209typedef struct FSPERFSPLICEARGS
3210{
3211 uint64_t offFile;
3212 size_t cbSend;
3213 uint64_t cbSent;
3214 size_t cbBuf;
3215 uint8_t *pbBuf;
3216 uint8_t bFiller;
3217 bool fCheckBuf;
3218 uint32_t cCalls;
3219 RTPIPE hPipe;
3220 uint64_t volatile tsThreadDone;
3221} FSPERFSPLICEARGS;
3222
3223
3224/** Thread receiving the bytes from a splice() call. */
3225static DECLCALLBACK(int) fsPerfSpliceToPipeThread(RTTHREAD hSelf, void *pvUser)
3226{
3227 FSPERFSPLICEARGS *pArgs = (FSPERFSPLICEARGS *)pvUser;
3228 int rc = VINF_SUCCESS;
3229
3230 if (pArgs->fCheckBuf)
3231 RTTestSetDefault(g_hTest, NULL);
3232
3233 uint64_t cbReceived = 0;
3234 while (cbReceived < pArgs->cbSent)
3235 {
3236 size_t const cbToRead = RT_MIN(pArgs->cbBuf, pArgs->cbSent - cbReceived);
3237 size_t cbActual = 0;
3238 RTTEST_CHECK_RC_BREAK(g_hTest, rc = RTPipeReadBlocking(pArgs->hPipe, pArgs->pbBuf, cbToRead, &cbActual), VINF_SUCCESS);
3239 RTTEST_CHECK_BREAK(g_hTest, cbActual != 0);
3240 RTTEST_CHECK(g_hTest, cbActual <= cbToRead);
3241 if (pArgs->fCheckBuf)
3242 fsPerfCheckReadBuf(__LINE__, pArgs->offFile + cbReceived, pArgs->pbBuf, cbActual, pArgs->bFiller);
3243 cbReceived += cbActual;
3244 }
3245
3246 pArgs->tsThreadDone = RTTimeNanoTS();
3247
3248 if (cbReceived == pArgs->cbSent && RT_SUCCESS(rc))
3249 {
3250 size_t cbActual = 0;
3251 rc = RTPipeRead(pArgs->hPipe, pArgs->pbBuf, 1, &cbActual);
3252 if (rc != VINF_SUCCESS && rc != VINF_TRY_AGAIN && rc != VERR_BROKEN_PIPE)
3253 RTTestFailed(g_hTest, "RTPipeReadBlocking() -> %Rrc; expected VINF_SUCCESS or VINF_TRY_AGAIN\n", rc);
3254 else if (cbActual != 0)
3255 RTTestFailed(g_hTest, "splice read pipe still contains data when done!\n");
3256 }
3257
3258 RTTEST_CHECK_RC(g_hTest, RTPipeClose(pArgs->hPipe), VINF_SUCCESS);
3259 pArgs->hPipe = NIL_RTPIPE;
3260
3261 RT_NOREF(hSelf);
3262 return rc;
3263}
3264
3265
3266/** Sends hFile1 to a pipe via the Linux-specific splice() syscall. */
3267static uint64_t fsPerfSpliceToPipeOne(FSPERFSPLICEARGS *pArgs, RTFILE hFile1, uint64_t offFile,
3268 size_t cbSend, uint64_t cbSent, uint8_t bFiller, bool fCheckBuf, unsigned iLine)
3269{
3270 /* Copy parameters to the argument structure: */
3271 pArgs->offFile = offFile;
3272 pArgs->cbSend = cbSend;
3273 pArgs->cbSent = cbSent;
3274 pArgs->bFiller = bFiller;
3275 pArgs->fCheckBuf = fCheckBuf;
3276
3277 /* Create a socket pair. */
3278 pArgs->hPipe = NIL_RTPIPE;
3279 RTPIPE hPipeW = NIL_RTPIPE;
3280 RTTESTI_CHECK_RC_RET(RTPipeCreate(&pArgs->hPipe, &hPipeW, 0 /*fFlags*/), VINF_SUCCESS, 0);
3281
3282 /* Create the receiving thread: */
3283 int rc;
3284 RTTHREAD hThread = NIL_RTTHREAD;
3285 RTTESTI_CHECK_RC(rc = RTThreadCreate(&hThread, fsPerfSpliceToPipeThread, pArgs, 0,
3286 RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, "splicerecv"), VINF_SUCCESS);
3287 if (RT_SUCCESS(rc))
3288 {
3289 uint64_t const tsStart = RTTimeNanoTS();
3290 size_t cbLeft = cbSend;
3291 size_t cbTotal = 0;
3292 do
3293 {
3294 loff_t offFileIn = offFile;
3295 ssize_t cbActual = syscall_splice((int)RTFileToNative(hFile1), &offFileIn, (int)RTPipeToNative(hPipeW), NULL,
3296 cbLeft, 0 /*fFlags*/);
3297 int const iErr = errno;
3298 if (RT_UNLIKELY(cbActual < 0))
3299 {
3300 if (iErr == EPIPE && cbTotal == pArgs->cbSent)
3301 break;
3302 RTTestIFailed("%u: splice(file, &%#RX64, pipe, NULL, %#zx, 0) failed (%zd): %d (%Rrc), offFileIn=%#RX64\n",
3303 iLine, offFile, cbLeft, cbActual, iErr, RTErrConvertFromErrno(iErr), (uint64_t)offFileIn);
3304 break;
3305 }
3306 RTTESTI_CHECK_BREAK((uint64_t)cbActual <= cbLeft);
3307 if ((uint64_t)offFileIn != offFile + (uint64_t)cbActual)
3308 {
3309 RTTestIFailed("%u: splice(file, &%#RX64, pipe, NULL, %#zx, 0): %#zx; offFileIn=%#RX64, expected %#RX64\n",
3310 iLine, offFile, cbLeft, cbActual, (uint64_t)offFileIn, offFile + (uint64_t)cbActual);
3311 break;
3312 }
3313 if (cbActual > 0)
3314 {
3315 pArgs->cCalls++;
3316 offFile += (size_t)cbActual;
3317 cbTotal += (size_t)cbActual;
3318 cbLeft -= (size_t)cbActual;
3319 }
3320 else
3321 break;
3322 } while (cbLeft > 0);
3323
3324 if (cbTotal != pArgs->cbSent)
3325 RTTestIFailed("%u: spliced a total of %#zx bytes, expected %#zx!\n", iLine, cbTotal, pArgs->cbSent);
3326
3327 RTTESTI_CHECK_RC(RTPipeClose(hPipeW), VINF_SUCCESS);
3328 RTTESTI_CHECK_RC(RTThreadWait(hThread, 30 * RT_NS_1SEC, NULL), VINF_SUCCESS);
3329
3330 if (pArgs->tsThreadDone >= tsStart)
3331 return RT_MAX(pArgs->tsThreadDone - tsStart, 1);
3332 }
3333 return 0;
3334}
3335
3336
3337static void fsPerfSpliceToPipe(RTFILE hFile1, uint64_t cbFile)
3338{
3339 RTTestISub("splice/to-pipe");
3340
3341 /*
3342 * splice was introduced in 2.6.17 according to the man-page.
3343 */
3344 char szRelease[64];
3345 RTSystemQueryOSInfo(RTSYSOSINFO_RELEASE, szRelease, sizeof(szRelease));
3346 if (RTStrVersionCompare(szRelease, "2.6.17") < 0)
3347 {
3348 RTTestPassed(g_hTest, "too old kernel (%s)", szRelease);
3349 return;
3350 }
3351
3352 uint64_t const cbFileMax = RT_MIN(cbFile, UINT32_MAX - PAGE_OFFSET_MASK);
3353 signal(SIGPIPE, SIG_IGN);
3354
3355 /*
3356 * Allocate a buffer.
3357 */
3358 FSPERFSPLICEARGS Args;
3359 Args.cbBuf = RT_MIN(cbFileMax, _16M);
3360 Args.pbBuf = (uint8_t *)RTMemAlloc(Args.cbBuf);
3361 while (!Args.pbBuf)
3362 {
3363 Args.cbBuf /= 8;
3364 RTTESTI_CHECK_RETV(Args.cbBuf >= _64K);
3365 Args.pbBuf = (uint8_t *)RTMemAlloc(Args.cbBuf);
3366 }
3367
3368 /*
3369 * First iteration with default buffer content.
3370 */
3371 fsPerfSpliceToPipeOne(&Args, hFile1, 0, cbFileMax, cbFileMax, 0xf6, true /*fCheckBuf*/, __LINE__);
3372 if (cbFileMax == cbFile)
3373 fsPerfSpliceToPipeOne(&Args, hFile1, 63, cbFileMax, cbFileMax - 63, 0xf6, true /*fCheckBuf*/, __LINE__);
3374 else
3375 fsPerfSpliceToPipeOne(&Args, hFile1, 63, cbFileMax - 63, cbFileMax - 63, 0xf6, true /*fCheckBuf*/, __LINE__);
3376
3377 /*
3378 * Write a block using the regular API and then send it, checking that
3379 * the any caching that sendfile does is correctly updated.
3380 */
3381 uint8_t bFiller = 0xf6;
3382 size_t cbToSend = RT_MIN(cbFileMax, Args.cbBuf);
3383 do
3384 {
3385 fsPerfSpliceToPipeOne(&Args, hFile1, 0, cbToSend, cbToSend, bFiller, true /*fCheckBuf*/, __LINE__); /* prime cache */
3386
3387 bFiller += 1;
3388 fsPerfFillWriteBuf(0, Args.pbBuf, cbToSend, bFiller);
3389 RTTESTI_CHECK_RC(RTFileWriteAt(hFile1, 0, Args.pbBuf, cbToSend, NULL), VINF_SUCCESS);
3390
3391 fsPerfSpliceToPipeOne(&Args, hFile1, 0, cbToSend, cbToSend, bFiller, true /*fCheckBuf*/, __LINE__);
3392
3393 cbToSend /= 2;
3394 } while (cbToSend >= PAGE_SIZE && ((unsigned)bFiller - 0xf7U) < 64);
3395
3396 /*
3397 * Restore buffer content
3398 */
3399 bFiller = 0xf6;
3400 fsPerfFillWriteBuf(0, Args.pbBuf, Args.cbBuf, bFiller);
3401 RTTESTI_CHECK_RC(RTFileWriteAt(hFile1, 0, Args.pbBuf, Args.cbBuf, NULL), VINF_SUCCESS);
3402
3403 /*
3404 * Do 128 random sends.
3405 */
3406 uint64_t const cbSmall = RT_MIN(_256K, cbFileMax / 16);
3407 for (uint32_t iTest = 0; iTest < 128; iTest++)
3408 {
3409 cbToSend = (size_t)RTRandU64Ex(1, iTest < 64 ? cbSmall : cbFileMax);
3410 uint64_t const offToSendFrom = RTRandU64Ex(0, cbFile - 1);
3411 uint64_t const cbSent = offToSendFrom + cbToSend <= cbFile ? cbToSend : cbFile - offToSendFrom;
3412
3413 fsPerfSpliceToPipeOne(&Args, hFile1, offToSendFrom, cbToSend, cbSent, bFiller, true /*fCheckBuf*/, __LINE__);
3414 }
3415
3416 /*
3417 * Benchmark it.
3418 */
3419 Args.cCalls = 0;
3420 uint32_t cIterations = 0;
3421 uint64_t nsElapsed = 0;
3422 for (;;)
3423 {
3424 uint64_t cNsThis = fsPerfSpliceToPipeOne(&Args, hFile1, 0, cbFileMax, cbFileMax, 0xf6, false /*fCheckBuf*/, __LINE__);
3425 nsElapsed += cNsThis;
3426 cIterations++;
3427 if (!cNsThis || nsElapsed >= g_nsTestRun)
3428 break;
3429 }
3430 uint64_t cbTotal = cbFileMax * cIterations;
3431 RTTestIValue("latency", nsElapsed / Args.cCalls, RTTESTUNIT_NS_PER_CALL);
3432 RTTestIValue("throughput", (uint64_t)(cbTotal / ((double)nsElapsed / RT_NS_1SEC)), RTTESTUNIT_BYTES_PER_SEC);
3433 RTTestIValue("calls", Args.cCalls, RTTESTUNIT_CALLS);
3434 RTTestIValue("bytes/call", cbTotal / Args.cCalls, RTTESTUNIT_BYTES);
3435 RTTestIValue("iterations", cIterations, RTTESTUNIT_NONE);
3436 RTTestIValue("bytes", cbTotal, RTTESTUNIT_BYTES);
3437 if (g_fShowDuration)
3438 RTTestIValue("duration", nsElapsed, RTTESTUNIT_NS);
3439
3440 /*
3441 * Cleanup.
3442 */
3443 RTMemFree(Args.pbBuf);
3444}
3445
3446
3447/** Thread sending the bytes to a splice() call. */
3448static DECLCALLBACK(int) fsPerfSpliceToFileThread(RTTHREAD hSelf, void *pvUser)
3449{
3450 FSPERFSPLICEARGS *pArgs = (FSPERFSPLICEARGS *)pvUser;
3451 int rc = VINF_SUCCESS;
3452
3453 uint64_t offFile = pArgs->offFile;
3454 uint64_t cbTotalSent = 0;
3455 while (cbTotalSent < pArgs->cbSent)
3456 {
3457 size_t const cbToSend = RT_MIN(pArgs->cbBuf, pArgs->cbSent - cbTotalSent);
3458 fsPerfFillWriteBuf(offFile, pArgs->pbBuf, cbToSend, pArgs->bFiller);
3459 RTTEST_CHECK_RC_BREAK(g_hTest, rc = RTPipeWriteBlocking(pArgs->hPipe, pArgs->pbBuf, cbToSend, NULL), VINF_SUCCESS);
3460 offFile += cbToSend;
3461 cbTotalSent += cbToSend;
3462 }
3463
3464 pArgs->tsThreadDone = RTTimeNanoTS();
3465
3466 RTTEST_CHECK_RC(g_hTest, RTPipeClose(pArgs->hPipe), VINF_SUCCESS);
3467 pArgs->hPipe = NIL_RTPIPE;
3468
3469 RT_NOREF(hSelf);
3470 return rc;
3471}
3472
3473
3474/** Fill hFile1 via a pipe and the Linux-specific splice() syscall. */
3475static uint64_t fsPerfSpliceToFileOne(FSPERFSPLICEARGS *pArgs, RTFILE hFile1, uint64_t offFile,
3476 size_t cbSend, uint64_t cbSent, uint8_t bFiller, bool fCheckFile, unsigned iLine)
3477{
3478 /* Copy parameters to the argument structure: */
3479 pArgs->offFile = offFile;
3480 pArgs->cbSend = cbSend;
3481 pArgs->cbSent = cbSent;
3482 pArgs->bFiller = bFiller;
3483 pArgs->fCheckBuf = false;
3484
3485 /* Create a socket pair. */
3486 pArgs->hPipe = NIL_RTPIPE;
3487 RTPIPE hPipeR = NIL_RTPIPE;
3488 RTTESTI_CHECK_RC_RET(RTPipeCreate(&hPipeR, &pArgs->hPipe, 0 /*fFlags*/), VINF_SUCCESS, 0);
3489
3490 /* Create the receiving thread: */
3491 int rc;
3492 RTTHREAD hThread = NIL_RTTHREAD;
3493 RTTESTI_CHECK_RC(rc = RTThreadCreate(&hThread, fsPerfSpliceToFileThread, pArgs, 0,
3494 RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, "splicerecv"), VINF_SUCCESS);
3495 if (RT_SUCCESS(rc))
3496 {
3497 /*
3498 * Do the splicing.
3499 */
3500 uint64_t const tsStart = RTTimeNanoTS();
3501 size_t cbLeft = cbSend;
3502 size_t cbTotal = 0;
3503 do
3504 {
3505 loff_t offFileOut = offFile;
3506 ssize_t cbActual = syscall_splice((int)RTPipeToNative(hPipeR), NULL, (int)RTFileToNative(hFile1), &offFileOut,
3507 cbLeft, 0 /*fFlags*/);
3508 int const iErr = errno;
3509 if (RT_UNLIKELY(cbActual < 0))
3510 {
3511 RTTestIFailed("%u: splice(pipe, NULL, file, &%#RX64, %#zx, 0) failed (%zd): %d (%Rrc), offFileOut=%#RX64\n",
3512 iLine, offFile, cbLeft, cbActual, iErr, RTErrConvertFromErrno(iErr), (uint64_t)offFileOut);
3513 break;
3514 }
3515 RTTESTI_CHECK_BREAK((uint64_t)cbActual <= cbLeft);
3516 if ((uint64_t)offFileOut != offFile + (uint64_t)cbActual)
3517 {
3518 RTTestIFailed("%u: splice(pipe, NULL, file, &%#RX64, %#zx, 0): %#zx; offFileOut=%#RX64, expected %#RX64\n",
3519 iLine, offFile, cbLeft, cbActual, (uint64_t)offFileOut, offFile + (uint64_t)cbActual);
3520 break;
3521 }
3522 if (cbActual > 0)
3523 {
3524 pArgs->cCalls++;
3525 offFile += (size_t)cbActual;
3526 cbTotal += (size_t)cbActual;
3527 cbLeft -= (size_t)cbActual;
3528 }
3529 else
3530 break;
3531 } while (cbLeft > 0);
3532 uint64_t const nsElapsed = RTTimeNanoTS() - tsStart;
3533
3534 if (cbTotal != pArgs->cbSent)
3535 RTTestIFailed("%u: spliced a total of %#zx bytes, expected %#zx!\n", iLine, cbTotal, pArgs->cbSent);
3536
3537 RTTESTI_CHECK_RC(RTPipeClose(hPipeR), VINF_SUCCESS);
3538 RTTESTI_CHECK_RC(RTThreadWait(hThread, 30 * RT_NS_1SEC, NULL), VINF_SUCCESS);
3539
3540 /* Check the file content. */
3541 if (fCheckFile && cbTotal == pArgs->cbSent)
3542 {
3543 offFile = pArgs->offFile;
3544 cbLeft = cbSent;
3545 while (cbLeft > 0)
3546 {
3547 size_t cbToRead = RT_MIN(cbLeft, pArgs->cbBuf);
3548 RTTESTI_CHECK_RC_BREAK(RTFileReadAt(hFile1, offFile, pArgs->pbBuf, cbToRead, NULL), VINF_SUCCESS);
3549 if (!fsPerfCheckReadBuf(iLine, offFile, pArgs->pbBuf, cbToRead, pArgs->bFiller))
3550 break;
3551 offFile += cbToRead;
3552 cbLeft -= cbToRead;
3553 }
3554 }
3555 return nsElapsed;
3556 }
3557 return 0;
3558}
3559
3560
3561static void fsPerfSpliceToFile(RTFILE hFile1, uint64_t cbFile)
3562{
3563 RTTestISub("splice/to-file");
3564
3565 /*
3566 * splice was introduced in 2.6.17 according to the man-page.
3567 */
3568 char szRelease[64];
3569 RTSystemQueryOSInfo(RTSYSOSINFO_RELEASE, szRelease, sizeof(szRelease));
3570 if (RTStrVersionCompare(szRelease, "2.6.17") < 0)
3571 {
3572 RTTestPassed(g_hTest, "too old kernel (%s)", szRelease);
3573 return;
3574 }
3575
3576 uint64_t const cbFileMax = RT_MIN(cbFile, UINT32_MAX - PAGE_OFFSET_MASK);
3577 signal(SIGPIPE, SIG_IGN);
3578
3579 /*
3580 * Allocate a buffer.
3581 */
3582 FSPERFSPLICEARGS Args;
3583 Args.cbBuf = RT_MIN(cbFileMax, _16M);
3584 Args.pbBuf = (uint8_t *)RTMemAlloc(Args.cbBuf);
3585 while (!Args.pbBuf)
3586 {
3587 Args.cbBuf /= 8;
3588 RTTESTI_CHECK_RETV(Args.cbBuf >= _64K);
3589 Args.pbBuf = (uint8_t *)RTMemAlloc(Args.cbBuf);
3590 }
3591
3592 /*
3593 * Do the whole file.
3594 */
3595 uint8_t bFiller = 0x76;
3596 fsPerfSpliceToFileOne(&Args, hFile1, 0, cbFileMax, cbFileMax, bFiller, true /*fCheckFile*/, __LINE__);
3597
3598 /*
3599 * Do 64 random chunks (this is slower).
3600 */
3601 uint64_t const cbSmall = RT_MIN(_256K, cbFileMax / 16);
3602 for (uint32_t iTest = 0; iTest < 64; iTest++)
3603 {
3604 size_t const cbToWrite = (size_t)RTRandU64Ex(1, iTest < 24 ? cbSmall : cbFileMax);
3605 uint64_t const offToWriteAt = RTRandU64Ex(0, cbFile - cbToWrite);
3606 uint64_t const cbTryRead = cbToWrite + (iTest & 1 ? RTRandU32Ex(0, _64K) : 0);
3607
3608 bFiller++;
3609 fsPerfSpliceToFileOne(&Args, hFile1, offToWriteAt, cbTryRead, cbToWrite, bFiller, true /*fCheckFile*/, __LINE__);
3610 }
3611
3612 /*
3613 * Benchmark it.
3614 */
3615 Args.cCalls = 0;
3616 uint32_t cIterations = 0;
3617 uint64_t nsElapsed = 0;
3618 for (;;)
3619 {
3620 uint64_t cNsThis = fsPerfSpliceToFileOne(&Args, hFile1, 0, cbFileMax, cbFileMax, 0xf6, false /*fCheckBuf*/, __LINE__);
3621 nsElapsed += cNsThis;
3622 cIterations++;
3623 if (!cNsThis || nsElapsed >= g_nsTestRun)
3624 break;
3625 }
3626 uint64_t cbTotal = cbFileMax * cIterations;
3627 RTTestIValue("latency", nsElapsed / Args.cCalls, RTTESTUNIT_NS_PER_CALL);
3628 RTTestIValue("throughput", (uint64_t)(cbTotal / ((double)nsElapsed / RT_NS_1SEC)), RTTESTUNIT_BYTES_PER_SEC);
3629 RTTestIValue("calls", Args.cCalls, RTTESTUNIT_CALLS);
3630 RTTestIValue("bytes/call", cbTotal / Args.cCalls, RTTESTUNIT_BYTES);
3631 RTTestIValue("iterations", cIterations, RTTESTUNIT_NONE);
3632 RTTestIValue("bytes", cbTotal, RTTESTUNIT_BYTES);
3633 if (g_fShowDuration)
3634 RTTestIValue("duration", nsElapsed, RTTESTUNIT_NS);
3635
3636 /*
3637 * Cleanup.
3638 */
3639 RTMemFree(Args.pbBuf);
3640}
3641
3642#endif /* RT_OS_LINUX */
3643
3644/** For fsPerfIoRead and fsPerfIoWrite. */
3645#define PROFILE_IO_FN(a_szOperation, a_fnCall) \
3646 do \
3647 { \
3648 RTTESTI_CHECK_RC_RETV(RTFileSeek(hFile1, 0, RTFILE_SEEK_BEGIN, NULL), VINF_SUCCESS); \
3649 uint64_t offActual = 0; \
3650 uint32_t cSeeks = 0; \
3651 \
3652 /* Estimate how many iterations we need to fill up the given timeslot: */ \
3653 fsPerfYield(); \
3654 uint64_t nsStart = RTTimeNanoTS(); \
3655 uint64_t ns; \
3656 do \
3657 ns = RTTimeNanoTS(); \
3658 while (ns == nsStart); \
3659 nsStart = ns; \
3660 \
3661 uint64_t iIteration = 0; \
3662 do \
3663 { \
3664 RTTESTI_CHECK_RC(a_fnCall, VINF_SUCCESS); \
3665 iIteration++; \
3666 ns = RTTimeNanoTS() - nsStart; \
3667 } while (ns < RT_NS_10MS); \
3668 ns /= iIteration; \
3669 if (ns > g_nsPerNanoTSCall + 32) \
3670 ns -= g_nsPerNanoTSCall; \
3671 uint64_t cIterations = g_nsTestRun / ns; \
3672 if (cIterations < 2) \
3673 cIterations = 2; \
3674 else if (cIterations & 1) \
3675 cIterations++; \
3676 \
3677 /* Do the actual profiling: */ \
3678 cSeeks = 0; \
3679 iIteration = 0; \
3680 fsPerfYield(); \
3681 nsStart = RTTimeNanoTS(); \
3682 for (uint32_t iAdjust = 0; iAdjust < 4; iAdjust++) \
3683 { \
3684 for (; iIteration < cIterations; iIteration++)\
3685 RTTESTI_CHECK_RC(a_fnCall, VINF_SUCCESS); \
3686 ns = RTTimeNanoTS() - nsStart;\
3687 if (ns >= g_nsTestRun - (g_nsTestRun / 10)) \
3688 break; \
3689 cIterations += cIterations / 4; \
3690 if (cIterations & 1) \
3691 cIterations++; \
3692 nsStart += g_nsPerNanoTSCall; \
3693 } \
3694 RTTestIValueF(ns / iIteration, \
3695 RTTESTUNIT_NS_PER_OCCURRENCE, a_szOperation "/seq/%RU32 latency", cbBlock); \
3696 RTTestIValueF((uint64_t)((uint64_t)iIteration * cbBlock / ((double)ns / RT_NS_1SEC)), \
3697 RTTESTUNIT_BYTES_PER_SEC, a_szOperation "/seq/%RU32 throughput", cbBlock); \
3698 RTTestIValueF(iIteration, \
3699 RTTESTUNIT_CALLS, a_szOperation "/seq/%RU32 calls", cbBlock); \
3700 RTTestIValueF((uint64_t)iIteration * cbBlock, \
3701 RTTESTUNIT_BYTES, a_szOperation "/seq/%RU32 bytes", cbBlock); \
3702 RTTestIValueF(cSeeks, \
3703 RTTESTUNIT_OCCURRENCES, a_szOperation "/seq/%RU32 seeks", cbBlock); \
3704 if (g_fShowDuration) \
3705 RTTestIValueF(ns, RTTESTUNIT_NS, a_szOperation "/seq/%RU32 duration", cbBlock); \
3706 } while (0)
3707
3708
3709/**
3710 * One RTFileRead profiling iteration.
3711 */
3712DECL_FORCE_INLINE(int) fsPerfIoReadWorker(RTFILE hFile1, uint64_t cbFile, uint32_t cbBlock, uint8_t *pbBlock,
3713 uint64_t *poffActual, uint32_t *pcSeeks)
3714{
3715 /* Do we need to seek back to the start? */
3716 if (*poffActual + cbBlock <= cbFile)
3717 { /* likely */ }
3718 else
3719 {
3720 RTTESTI_CHECK_RC_RET(RTFileSeek(hFile1, 0, RTFILE_SEEK_BEGIN, NULL), VINF_SUCCESS, rcCheck);
3721 *pcSeeks += 1;
3722 *poffActual = 0;
3723 }
3724
3725 size_t cbActuallyRead = 0;
3726 RTTESTI_CHECK_RC_RET(RTFileRead(hFile1, pbBlock, cbBlock, &cbActuallyRead), VINF_SUCCESS, rcCheck);
3727 if (cbActuallyRead == cbBlock)
3728 {
3729 *poffActual += cbActuallyRead;
3730 return VINF_SUCCESS;
3731 }
3732 RTTestIFailed("RTFileRead at %#RX64 returned just %#x bytes, expected %#x", *poffActual, cbActuallyRead, cbBlock);
3733 *poffActual += cbActuallyRead;
3734 return VERR_READ_ERROR;
3735}
3736
3737
3738void fsPerfIoReadBlockSize(RTFILE hFile1, uint64_t cbFile, uint32_t cbBlock)
3739{
3740 RTTestISubF("IO - Sequential read %RU32", cbBlock);
3741 if (cbBlock <= cbFile)
3742 {
3743
3744 uint8_t *pbBuf = (uint8_t *)RTMemPageAlloc(cbBlock);
3745 if (pbBuf)
3746 {
3747 memset(pbBuf, 0xf7, cbBlock);
3748 PROFILE_IO_FN("RTFileRead", fsPerfIoReadWorker(hFile1, cbFile, cbBlock, pbBuf, &offActual, &cSeeks));
3749 RTMemPageFree(pbBuf, cbBlock);
3750 }
3751 else
3752 RTTestSkipped(g_hTest, "insufficient (virtual) memory available");
3753 }
3754 else
3755 RTTestSkipped(g_hTest, "test file too small");
3756}
3757
3758
3759/** preadv is too new to be useful, so we use the readv api via this wrapper. */
3760DECLINLINE(int) myFileSgReadAt(RTFILE hFile, RTFOFF off, PRTSGBUF pSgBuf, size_t cbToRead, size_t *pcbRead)
3761{
3762 int rc = RTFileSeek(hFile, off, RTFILE_SEEK_BEGIN, NULL);
3763 if (RT_SUCCESS(rc))
3764 rc = RTFileSgRead(hFile, pSgBuf, cbToRead, pcbRead);
3765 return rc;
3766}
3767
3768
3769void fsPerfRead(RTFILE hFile1, RTFILE hFileNoCache, uint64_t cbFile)
3770{
3771 RTTestISubF("IO - RTFileRead");
3772
3773 /*
3774 * Allocate a big buffer we can play around with. Min size is 1MB.
3775 */
3776 size_t cbBuf = cbFile < _64M ? (size_t)cbFile : _64M;
3777 uint8_t *pbBuf = (uint8_t *)RTMemPageAlloc(cbBuf);
3778 while (!pbBuf)
3779 {
3780 cbBuf /= 2;
3781 RTTESTI_CHECK_RETV(cbBuf >= _1M);
3782 pbBuf = (uint8_t *)RTMemPageAlloc(_32M);
3783 }
3784
3785#if 1
3786 /*
3787 * Start at the beginning and read the full buffer in random small chunks, thereby
3788 * checking that unaligned buffer addresses, size and file offsets work fine.
3789 */
3790 struct
3791 {
3792 uint64_t offFile;
3793 uint32_t cbMax;
3794 } aRuns[] = { { 0, 127 }, { cbFile - cbBuf, UINT32_MAX }, { 0, UINT32_MAX -1 }};
3795 for (uint32_t i = 0; i < RT_ELEMENTS(aRuns); i++)
3796 {
3797 memset(pbBuf, 0x55, cbBuf);
3798 RTTESTI_CHECK_RC(RTFileSeek(hFile1, aRuns[i].offFile, RTFILE_SEEK_BEGIN, NULL), VINF_SUCCESS);
3799 for (size_t offBuf = 0; offBuf < cbBuf; )
3800 {
3801 uint32_t const cbLeft = (uint32_t)(cbBuf - offBuf);
3802 uint32_t const cbToRead = aRuns[i].cbMax < UINT32_MAX / 2 ? RTRandU32Ex(1, RT_MIN(aRuns[i].cbMax, cbLeft))
3803 : aRuns[i].cbMax == UINT32_MAX ? RTRandU32Ex(RT_MAX(cbLeft / 4, 1), cbLeft)
3804 : RTRandU32Ex(cbLeft >= _8K ? _8K : 1, RT_MIN(_1M, cbLeft));
3805 size_t cbActual = 0;
3806 RTTESTI_CHECK_RC(RTFileRead(hFile1, &pbBuf[offBuf], cbToRead, &cbActual), VINF_SUCCESS);
3807 if (cbActual == cbToRead)
3808 {
3809 offBuf += cbActual;
3810 RTTESTI_CHECK_MSG(RTFileTell(hFile1) == aRuns[i].offFile + offBuf,
3811 ("%#RX64, expected %#RX64\n", RTFileTell(hFile1), aRuns[i].offFile + offBuf));
3812 }
3813 else
3814 {
3815 RTTestIFailed("Attempting to read %#x bytes at %#zx, only got %#x bytes back! (cbLeft=%#x cbBuf=%#zx)\n",
3816 cbToRead, offBuf, cbActual, cbLeft, cbBuf);
3817 if (cbActual)
3818 offBuf += cbActual;
3819 else
3820 pbBuf[offBuf++] = 0x11;
3821 }
3822 }
3823 fsPerfCheckReadBuf(__LINE__, aRuns[i].offFile, pbBuf, cbBuf);
3824 }
3825
3826 /*
3827 * Test reading beyond the end of the file.
3828 */
3829 size_t const acbMax[] = { cbBuf, _64K, _16K, _4K, 256 };
3830 uint32_t const aoffFromEos[] =
3831 { 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,
3832 4092, 4093, 4094, 4095, 4096, 4097, 4098, 4099, 4100, 8192, 16384, 32767, 32768, 32769, 65535, 65536, _1M - 1
3833 };
3834 for (unsigned iMax = 0; iMax < RT_ELEMENTS(acbMax); iMax++)
3835 {
3836 size_t const cbMaxRead = acbMax[iMax];
3837 for (uint32_t iOffFromEos = 0; iOffFromEos < RT_ELEMENTS(aoffFromEos); iOffFromEos++)
3838 {
3839 uint32_t off = aoffFromEos[iOffFromEos];
3840 if (off >= cbMaxRead)
3841 continue;
3842 RTTESTI_CHECK_RC(RTFileSeek(hFile1, cbFile - off, RTFILE_SEEK_BEGIN, NULL), VINF_SUCCESS);
3843 size_t cbActual = ~(size_t)0;
3844 RTTESTI_CHECK_RC(RTFileRead(hFile1, pbBuf, cbMaxRead, &cbActual), VINF_SUCCESS);
3845 RTTESTI_CHECK(cbActual == off);
3846
3847 RTTESTI_CHECK_RC(RTFileSeek(hFile1, cbFile - off, RTFILE_SEEK_BEGIN, NULL), VINF_SUCCESS);
3848 cbActual = ~(size_t)0;
3849 RTTESTI_CHECK_RC(RTFileRead(hFile1, pbBuf, off, &cbActual), VINF_SUCCESS);
3850 RTTESTI_CHECK_MSG(cbActual == off, ("%#zx vs %#zx\n", cbActual, off));
3851
3852 cbActual = ~(size_t)0;
3853 RTTESTI_CHECK_RC(RTFileRead(hFile1, pbBuf, 1, &cbActual), VINF_SUCCESS);
3854 RTTESTI_CHECK_MSG(cbActual == 0, ("cbActual=%zu\n", cbActual));
3855
3856 RTTESTI_CHECK_RC(RTFileRead(hFile1, pbBuf, cbMaxRead, NULL), VERR_EOF);
3857
3858 /* Repeat using native APIs in case IPRT or other layers hide status codes: */
3859#if defined(RT_OS_OS2) || defined(RT_OS_WINDOWS)
3860 RTTESTI_CHECK_RC(RTFileSeek(hFile1, cbFile - off, RTFILE_SEEK_BEGIN, NULL), VINF_SUCCESS);
3861# ifdef RT_OS_OS2
3862 ULONG cbActual2 = ~(ULONG)0;
3863 APIRET orc = DosRead((HFILE)RTFileToNative(hFile1), pbBuf, cbMaxRead, &cbActual2);
3864 RTTESTI_CHECK_MSG(orc == NO_ERROR, ("orc=%u, expected 0\n", orc));
3865 RTTESTI_CHECK_MSG(cbActual2 == off, ("%#x vs %#x\n", cbActual2, off));
3866# else
3867 IO_STATUS_BLOCK const IosVirgin = RTNT_IO_STATUS_BLOCK_INITIALIZER;
3868 IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER;
3869 NTSTATUS rcNt = NtReadFile((HANDLE)RTFileToNative(hFile1), NULL /*hEvent*/, NULL /*ApcRoutine*/, NULL /*ApcContext*/,
3870 &Ios, pbBuf, (ULONG)cbMaxRead, NULL /*poffFile*/, NULL /*Key*/);
3871 if (off == 0)
3872 {
3873 RTTESTI_CHECK_MSG(rcNt == STATUS_END_OF_FILE, ("rcNt=%#x, expected %#x\n", rcNt, STATUS_END_OF_FILE));
3874 RTTESTI_CHECK_MSG(Ios.Status == IosVirgin.Status /*slow?*/ || Ios.Status == STATUS_END_OF_FILE /*fastio?*/,
3875 ("%#x vs %x/%#x; off=%#x\n", Ios.Status, IosVirgin.Status, STATUS_END_OF_FILE, off));
3876 RTTESTI_CHECK_MSG(Ios.Information == IosVirgin.Information /*slow*/ || Ios.Information == 0 /*fastio?*/,
3877 ("%#zx vs %zx/0; off=%#x\n", Ios.Information, IosVirgin.Information, off));
3878 }
3879 else
3880 {
3881 RTTESTI_CHECK_MSG(rcNt == STATUS_SUCCESS, ("rcNt=%#x, expected 0 (off=%#x cbMaxRead=%#zx)\n", rcNt, off, cbMaxRead));
3882 RTTESTI_CHECK_MSG(Ios.Status == STATUS_SUCCESS, ("%#x; off=%#x\n", Ios.Status, off));
3883 RTTESTI_CHECK_MSG(Ios.Information == off, ("%#zx vs %#x\n", Ios.Information, off));
3884 }
3885# endif
3886
3887# ifdef RT_OS_OS2
3888 cbActual2 = ~(ULONG)0;
3889 orc = DosRead((HFILE)RTFileToNative(hFile1), pbBuf, 1, &cbActual2);
3890 RTTESTI_CHECK_MSG(orc == NO_ERROR, ("orc=%u, expected 0\n", orc));
3891 RTTESTI_CHECK_MSG(cbActual2 == 0, ("cbActual2=%u\n", cbActual2));
3892# else
3893 RTNT_IO_STATUS_BLOCK_REINIT(&Ios);
3894 rcNt = NtReadFile((HANDLE)RTFileToNative(hFile1), NULL /*hEvent*/, NULL /*ApcRoutine*/, NULL /*ApcContext*/,
3895 &Ios, pbBuf, 1, NULL /*poffFile*/, NULL /*Key*/);
3896 RTTESTI_CHECK_MSG(rcNt == STATUS_END_OF_FILE, ("rcNt=%#x, expected %#x\n", rcNt, STATUS_END_OF_FILE));
3897# endif
3898
3899#endif
3900 }
3901 }
3902
3903 /*
3904 * Test reading beyond end of the file.
3905 */
3906 for (unsigned iMax = 0; iMax < RT_ELEMENTS(acbMax); iMax++)
3907 {
3908 size_t const cbMaxRead = acbMax[iMax];
3909 for (uint32_t off = 0; off < 256; off++)
3910 {
3911 RTTESTI_CHECK_RC(RTFileSeek(hFile1, cbFile + off, RTFILE_SEEK_BEGIN, NULL), VINF_SUCCESS);
3912 size_t cbActual = ~(size_t)0;
3913 RTTESTI_CHECK_RC(RTFileRead(hFile1, pbBuf, cbMaxRead, &cbActual), VINF_SUCCESS);
3914 RTTESTI_CHECK(cbActual == 0);
3915
3916 RTTESTI_CHECK_RC(RTFileRead(hFile1, pbBuf, cbMaxRead, NULL), VERR_EOF);
3917
3918 /* Repeat using native APIs in case IPRT or other layers hid status codes: */
3919#if defined(RT_OS_OS2) || defined(RT_OS_WINDOWS)
3920 RTTESTI_CHECK_RC(RTFileSeek(hFile1, cbFile + off, RTFILE_SEEK_BEGIN, NULL), VINF_SUCCESS);
3921# ifdef RT_OS_OS2
3922 ULONG cbActual2 = ~(ULONG)0;
3923 APIRET orc = DosRead((HFILE)RTFileToNative(hFile1), pbBuf, cbMaxRead, &cbActual2);
3924 RTTESTI_CHECK_MSG(orc == NO_ERROR, ("orc=%u, expected 0\n", orc));
3925 RTTESTI_CHECK_MSG(cbActual2 == 0, ("%#x vs %#x\n", cbActual2, off));
3926# else
3927 IO_STATUS_BLOCK const IosVirgin = RTNT_IO_STATUS_BLOCK_INITIALIZER;
3928 IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER;
3929 NTSTATUS rcNt = NtReadFile((HANDLE)RTFileToNative(hFile1), NULL /*hEvent*/, NULL /*ApcRoutine*/, NULL /*ApcContext*/,
3930 &Ios, pbBuf, (ULONG)cbMaxRead, NULL /*poffFile*/, NULL /*Key*/);
3931 RTTESTI_CHECK_MSG(rcNt == STATUS_END_OF_FILE, ("rcNt=%#x, expected %#x\n", rcNt, STATUS_END_OF_FILE));
3932 RTTESTI_CHECK_MSG(Ios.Status == IosVirgin.Status /*slow?*/ || Ios.Status == STATUS_END_OF_FILE /*fastio?*/,
3933 ("%#x vs %x/%#x; off=%#x\n", Ios.Status, IosVirgin.Status, STATUS_END_OF_FILE, off));
3934 RTTESTI_CHECK_MSG(Ios.Information == IosVirgin.Information /*slow*/ || Ios.Information == 0 /*fastio?*/,
3935 ("%#zx vs %zx/0; off=%#x\n", Ios.Information, IosVirgin.Information, off));
3936
3937 /* Need to work with sector size on uncached, but might be worth it for non-fastio path. */
3938 uint32_t cbSector = 0x1000;
3939 uint32_t off2 = off * cbSector + (cbFile & (cbSector - 1) ? cbSector - (cbFile & (cbSector - 1)) : 0);
3940 RTTESTI_CHECK_RC(RTFileSeek(hFileNoCache, cbFile + off2, RTFILE_SEEK_BEGIN, NULL), VINF_SUCCESS);
3941 size_t const cbMaxRead2 = RT_ALIGN_Z(cbMaxRead, cbSector);
3942 RTNT_IO_STATUS_BLOCK_REINIT(&Ios);
3943 rcNt = NtReadFile((HANDLE)RTFileToNative(hFileNoCache), NULL /*hEvent*/, NULL /*ApcRoutine*/, NULL /*ApcContext*/,
3944 &Ios, pbBuf, (ULONG)cbMaxRead2, NULL /*poffFile*/, NULL /*Key*/);
3945 RTTESTI_CHECK_MSG(rcNt == STATUS_END_OF_FILE,
3946 ("rcNt=%#x, expected %#x; off2=%x cbMaxRead2=%#x\n", rcNt, STATUS_END_OF_FILE, off2, cbMaxRead2));
3947 RTTESTI_CHECK_MSG(Ios.Status == IosVirgin.Status /*slow?*/,
3948 ("%#x vs %x; off2=%#x cbMaxRead2=%#x\n", Ios.Status, IosVirgin.Status, off2, cbMaxRead2));
3949 RTTESTI_CHECK_MSG(Ios.Information == IosVirgin.Information /*slow*/,
3950 ("%#zx vs %zx; off2=%#x cbMaxRead2=%#x\n", Ios.Information, IosVirgin.Information, off2, cbMaxRead2));
3951# endif
3952#endif
3953 }
3954 }
3955
3956 /*
3957 * Do uncached access, must be page aligned.
3958 */
3959 uint32_t cbPage = PAGE_SIZE;
3960 memset(pbBuf, 0x66, cbBuf);
3961 if (!g_fIgnoreNoCache || hFileNoCache != NIL_RTFILE)
3962 {
3963 RTTESTI_CHECK_RC(RTFileSeek(hFileNoCache, 0, RTFILE_SEEK_BEGIN, NULL), VINF_SUCCESS);
3964 for (size_t offBuf = 0; offBuf < cbBuf; )
3965 {
3966 uint32_t const cPagesLeft = (uint32_t)((cbBuf - offBuf) / cbPage);
3967 uint32_t const cPagesToRead = RTRandU32Ex(1, cPagesLeft);
3968 size_t const cbToRead = cPagesToRead * (size_t)cbPage;
3969 size_t cbActual = 0;
3970 RTTESTI_CHECK_RC(RTFileRead(hFileNoCache, &pbBuf[offBuf], cbToRead, &cbActual), VINF_SUCCESS);
3971 if (cbActual == cbToRead)
3972 offBuf += cbActual;
3973 else
3974 {
3975 RTTestIFailed("Attempting to read %#zx bytes at %#zx, only got %#x bytes back!\n", cbToRead, offBuf, cbActual);
3976 if (cbActual)
3977 offBuf += cbActual;
3978 else
3979 {
3980 memset(&pbBuf[offBuf], 0x11, cbPage);
3981 offBuf += cbPage;
3982 }
3983 }
3984 }
3985 fsPerfCheckReadBuf(__LINE__, 0, pbBuf, cbBuf);
3986 }
3987
3988 /*
3989 * Check reading zero bytes at the end of the file.
3990 * Requires native call because RTFileWrite doesn't call kernel on zero byte reads.
3991 */
3992 RTTESTI_CHECK_RC(RTFileSeek(hFile1, 0, RTFILE_SEEK_END, NULL), VINF_SUCCESS);
3993# ifdef RT_OS_WINDOWS
3994 IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER;
3995 NTSTATUS rcNt = NtReadFile((HANDLE)RTFileToNative(hFile1), NULL, NULL, NULL, &Ios, pbBuf, 0, NULL, NULL);
3996 RTTESTI_CHECK_MSG(rcNt == STATUS_SUCCESS, ("rcNt=%#x", rcNt));
3997 RTTESTI_CHECK(Ios.Status == STATUS_SUCCESS);
3998 RTTESTI_CHECK(Ios.Information == 0);
3999
4000 IO_STATUS_BLOCK const IosVirgin = RTNT_IO_STATUS_BLOCK_INITIALIZER;
4001 RTNT_IO_STATUS_BLOCK_REINIT(&Ios);
4002 rcNt = NtReadFile((HANDLE)RTFileToNative(hFile1), NULL, NULL, NULL, &Ios, pbBuf, 1, NULL, NULL);
4003 RTTESTI_CHECK_MSG(rcNt == STATUS_END_OF_FILE, ("rcNt=%#x", rcNt));
4004 RTTESTI_CHECK_MSG(Ios.Status == IosVirgin.Status /*slow?*/ || Ios.Status == STATUS_END_OF_FILE /*fastio?*/,
4005 ("%#x vs %x/%#x\n", Ios.Status, IosVirgin.Status, STATUS_END_OF_FILE));
4006 RTTESTI_CHECK_MSG(Ios.Information == IosVirgin.Information /*slow*/ || Ios.Information == 0 /*fastio?*/,
4007 ("%#zx vs %zx/0\n", Ios.Information, IosVirgin.Information));
4008# else
4009 ssize_t cbRead = read((int)RTFileToNative(hFile1), pbBuf, 0);
4010 RTTESTI_CHECK(cbRead == 0);
4011# endif
4012
4013#else
4014 RT_NOREF(hFileNoCache);
4015#endif
4016
4017 /*
4018 * Scatter read function operation.
4019 */
4020#ifdef RT_OS_WINDOWS
4021 /** @todo RTFileSgReadAt is just a RTFileReadAt loop for windows NT. Need
4022 * to use ReadFileScatter (nocache + page aligned). */
4023#elif !defined(RT_OS_OS2) /** @todo implement RTFileSg using list i/o */
4024
4025# ifdef UIO_MAXIOV
4026 RTSGSEG aSegs[UIO_MAXIOV];
4027# else
4028 RTSGSEG aSegs[512];
4029# endif
4030 RTSGBUF SgBuf;
4031 uint32_t cIncr = 1;
4032 for (uint32_t cSegs = 1; cSegs <= RT_ELEMENTS(aSegs); cSegs += cIncr)
4033 {
4034 size_t const cbSeg = cbBuf / cSegs;
4035 size_t const cbToRead = cbSeg * cSegs;
4036 for (uint32_t iSeg = 0; iSeg < cSegs; iSeg++)
4037 {
4038 aSegs[iSeg].cbSeg = cbSeg;
4039 aSegs[iSeg].pvSeg = &pbBuf[cbToRead - (iSeg + 1) * cbSeg];
4040 }
4041 RTSgBufInit(&SgBuf, &aSegs[0], cSegs);
4042 int rc = myFileSgReadAt(hFile1, 0, &SgBuf, cbToRead, NULL);
4043 if (RT_SUCCESS(rc))
4044 for (uint32_t iSeg = 0; iSeg < cSegs; iSeg++)
4045 {
4046 if (!fsPerfCheckReadBuf(__LINE__, iSeg * cbSeg, &pbBuf[cbToRead - (iSeg + 1) * cbSeg], cbSeg))
4047 {
4048 cSegs = RT_ELEMENTS(aSegs);
4049 break;
4050 }
4051 }
4052 else
4053 {
4054 RTTestIFailed("myFileSgReadAt failed: %Rrc - cSegs=%u cbSegs=%#zx cbToRead=%#zx", rc, cSegs, cbSeg, cbToRead);
4055 break;
4056 }
4057 if (cSegs == 16)
4058 cIncr = 7;
4059 else if (cSegs == 16 * 7 + 16 /*= 128*/)
4060 cIncr = 64;
4061 }
4062
4063 for (uint32_t iTest = 0; iTest < 128; iTest++)
4064 {
4065 uint32_t cSegs = RTRandU32Ex(1, RT_ELEMENTS(aSegs));
4066 uint32_t iZeroSeg = cSegs > 10 ? RTRandU32Ex(0, cSegs - 1) : UINT32_MAX / 2;
4067 uint32_t cZeroSegs = cSegs > 10 ? RTRandU32Ex(1, RT_MIN(cSegs - iZeroSeg, 25)) : 0;
4068 size_t cbToRead = 0;
4069 size_t cbLeft = cbBuf;
4070 uint8_t *pbCur = &pbBuf[cbBuf];
4071 for (uint32_t iSeg = 0; iSeg < cSegs; iSeg++)
4072 {
4073 uint32_t iAlign = RTRandU32Ex(0, 3);
4074 if (iAlign & 2) /* end is page aligned */
4075 {
4076 cbLeft -= (uintptr_t)pbCur & PAGE_OFFSET_MASK;
4077 pbCur -= (uintptr_t)pbCur & PAGE_OFFSET_MASK;
4078 }
4079
4080 size_t cbSegOthers = (cSegs - iSeg) * _8K;
4081 size_t cbSegMax = cbLeft > cbSegOthers ? cbLeft - cbSegOthers
4082 : cbLeft > cSegs ? cbLeft - cSegs
4083 : cbLeft;
4084 size_t cbSeg = cbLeft != 0 ? RTRandU32Ex(0, cbSegMax) : 0;
4085 if (iAlign & 1) /* start is page aligned */
4086 cbSeg += ((uintptr_t)pbCur - cbSeg) & PAGE_OFFSET_MASK;
4087
4088 if (iSeg - iZeroSeg < cZeroSegs)
4089 cbSeg = 0;
4090
4091 cbToRead += cbSeg;
4092 cbLeft -= cbSeg;
4093 pbCur -= cbSeg;
4094 aSegs[iSeg].cbSeg = cbSeg;
4095 aSegs[iSeg].pvSeg = pbCur;
4096 }
4097
4098 uint64_t offFile = cbToRead < cbFile ? RTRandU64Ex(0, cbFile - cbToRead) : 0;
4099 RTSgBufInit(&SgBuf, &aSegs[0], cSegs);
4100 int rc = myFileSgReadAt(hFile1, offFile, &SgBuf, cbToRead, NULL);
4101 if (RT_SUCCESS(rc))
4102 for (uint32_t iSeg = 0; iSeg < cSegs; iSeg++)
4103 {
4104 if (!fsPerfCheckReadBuf(__LINE__, offFile, (uint8_t *)aSegs[iSeg].pvSeg, aSegs[iSeg].cbSeg))
4105 {
4106 RTTestIFailureDetails("iSeg=%#x cSegs=%#x cbSeg=%#zx cbToRead=%#zx\n", iSeg, cSegs, aSegs[iSeg].cbSeg, cbToRead);
4107 iTest = _16K;
4108 break;
4109 }
4110 offFile += aSegs[iSeg].cbSeg;
4111 }
4112 else
4113 {
4114 RTTestIFailed("myFileSgReadAt failed: %Rrc - cSegs=%#x cbToRead=%#zx", rc, cSegs, cbToRead);
4115 for (uint32_t iSeg = 0; iSeg < cSegs; iSeg++)
4116 RTTestIFailureDetails("aSeg[%u] = %p LB %#zx (last %p)\n", iSeg, aSegs[iSeg].pvSeg, aSegs[iSeg].cbSeg,
4117 (uint8_t *)aSegs[iSeg].pvSeg + aSegs[iSeg].cbSeg - 1);
4118 break;
4119 }
4120 }
4121
4122 /* reading beyond the end of the file */
4123 for (uint32_t cSegs = 1; cSegs < 6; cSegs++)
4124 for (uint32_t iTest = 0; iTest < 128; iTest++)
4125 {
4126 uint32_t const cbToRead = RTRandU32Ex(0, cbBuf);
4127 uint32_t const cbBeyond = cbToRead ? RTRandU32Ex(0, cbToRead) : 0;
4128 uint32_t const cbSeg = cbToRead / cSegs;
4129 uint32_t cbLeft = cbToRead;
4130 uint8_t *pbCur = &pbBuf[cbToRead];
4131 for (uint32_t iSeg = 0; iSeg < cSegs; iSeg++)
4132 {
4133 aSegs[iSeg].cbSeg = iSeg + 1 < cSegs ? cbSeg : cbLeft;
4134 aSegs[iSeg].pvSeg = pbCur -= aSegs[iSeg].cbSeg;
4135 cbLeft -= aSegs[iSeg].cbSeg;
4136 }
4137 Assert(pbCur == pbBuf);
4138
4139 uint64_t offFile = cbFile + cbBeyond - cbToRead;
4140 RTSgBufInit(&SgBuf, &aSegs[0], cSegs);
4141 int rcExpect = cbBeyond == 0 || cbToRead == 0 ? VINF_SUCCESS : VERR_EOF;
4142 int rc = myFileSgReadAt(hFile1, offFile, &SgBuf, cbToRead, NULL);
4143 if (rc != rcExpect)
4144 {
4145 RTTestIFailed("myFileSgReadAt failed: %Rrc - cSegs=%#x cbToRead=%#zx cbBeyond=%#zx\n", rc, cSegs, cbToRead, cbBeyond);
4146 for (uint32_t iSeg = 0; iSeg < cSegs; iSeg++)
4147 RTTestIFailureDetails("aSeg[%u] = %p LB %#zx (last %p)\n", iSeg, aSegs[iSeg].pvSeg, aSegs[iSeg].cbSeg,
4148 (uint8_t *)aSegs[iSeg].pvSeg + aSegs[iSeg].cbSeg - 1);
4149 }
4150
4151 RTSgBufInit(&SgBuf, &aSegs[0], cSegs);
4152 size_t cbActual = 0;
4153 rc = myFileSgReadAt(hFile1, offFile, &SgBuf, cbToRead, &cbActual);
4154 if (rc != VINF_SUCCESS || cbActual != cbToRead - cbBeyond)
4155 RTTestIFailed("myFileSgReadAt failed: %Rrc cbActual=%#zu - cSegs=%#x cbToRead=%#zx cbBeyond=%#zx expected %#zx\n",
4156 rc, cbActual, cSegs, cbToRead, cbBeyond, cbToRead - cbBeyond);
4157 if (RT_SUCCESS(rc) && cbActual > 0)
4158 for (uint32_t iSeg = 0; iSeg < cSegs; iSeg++)
4159 {
4160 if (!fsPerfCheckReadBuf(__LINE__, offFile, (uint8_t *)aSegs[iSeg].pvSeg, RT_MIN(cbActual, aSegs[iSeg].cbSeg)))
4161 {
4162 RTTestIFailureDetails("iSeg=%#x cSegs=%#x cbSeg=%#zx cbActual%#zx cbToRead=%#zx cbBeyond=%#zx\n",
4163 iSeg, cSegs, aSegs[iSeg].cbSeg, cbActual, cbToRead, cbBeyond);
4164 iTest = _16K;
4165 break;
4166 }
4167 if (cbActual <= aSegs[iSeg].cbSeg)
4168 break;
4169 cbActual -= aSegs[iSeg].cbSeg;
4170 offFile += aSegs[iSeg].cbSeg;
4171 }
4172 }
4173
4174#endif
4175
4176 /*
4177 * Other OS specific stuff.
4178 */
4179#ifdef RT_OS_WINDOWS
4180 /* Check that reading at an offset modifies the position: */
4181 RTTESTI_CHECK_RC(RTFileSeek(hFile1, 0, RTFILE_SEEK_END, NULL), VINF_SUCCESS);
4182 RTTESTI_CHECK(RTFileTell(hFile1) == cbFile);
4183
4184 RTNT_IO_STATUS_BLOCK_REINIT(&Ios);
4185 LARGE_INTEGER offNt;
4186 offNt.QuadPart = cbFile / 2;
4187 rcNt = NtReadFile((HANDLE)RTFileToNative(hFile1), NULL, NULL, NULL, &Ios, pbBuf, _4K, &offNt, NULL);
4188 RTTESTI_CHECK_MSG(rcNt == STATUS_SUCCESS, ("rcNt=%#x", rcNt));
4189 RTTESTI_CHECK(Ios.Status == STATUS_SUCCESS);
4190 RTTESTI_CHECK(Ios.Information == _4K);
4191 RTTESTI_CHECK(RTFileTell(hFile1) == cbFile / 2 + _4K);
4192 fsPerfCheckReadBuf(__LINE__, cbFile / 2, pbBuf, _4K);
4193#endif
4194
4195
4196 RTMemPageFree(pbBuf, cbBuf);
4197}
4198
4199
4200/**
4201 * One RTFileWrite profiling iteration.
4202 */
4203DECL_FORCE_INLINE(int) fsPerfIoWriteWorker(RTFILE hFile1, uint64_t cbFile, uint32_t cbBlock, uint8_t *pbBlock,
4204 uint64_t *poffActual, uint32_t *pcSeeks)
4205{
4206 /* Do we need to seek back to the start? */
4207 if (*poffActual + cbBlock <= cbFile)
4208 { /* likely */ }
4209 else
4210 {
4211 RTTESTI_CHECK_RC_RET(RTFileSeek(hFile1, 0, RTFILE_SEEK_BEGIN, NULL), VINF_SUCCESS, rcCheck);
4212 *pcSeeks += 1;
4213 *poffActual = 0;
4214 }
4215
4216 size_t cbActuallyWritten = 0;
4217 RTTESTI_CHECK_RC_RET(RTFileWrite(hFile1, pbBlock, cbBlock, &cbActuallyWritten), VINF_SUCCESS, rcCheck);
4218 if (cbActuallyWritten == cbBlock)
4219 {
4220 *poffActual += cbActuallyWritten;
4221 return VINF_SUCCESS;
4222 }
4223 RTTestIFailed("RTFileWrite at %#RX64 returned just %#x bytes, expected %#x", *poffActual, cbActuallyWritten, cbBlock);
4224 *poffActual += cbActuallyWritten;
4225 return VERR_WRITE_ERROR;
4226}
4227
4228
4229void fsPerfIoWriteBlockSize(RTFILE hFile1, uint64_t cbFile, uint32_t cbBlock)
4230{
4231 RTTestISubF("IO - Sequential write %RU32", cbBlock);
4232
4233 if (cbBlock <= cbFile)
4234 {
4235 uint8_t *pbBuf = (uint8_t *)RTMemPageAlloc(cbBlock);
4236 if (pbBuf)
4237 {
4238 memset(pbBuf, 0xf7, cbBlock);
4239 PROFILE_IO_FN("RTFileWrite", fsPerfIoWriteWorker(hFile1, cbFile, cbBlock, pbBuf, &offActual, &cSeeks));
4240 RTMemPageFree(pbBuf, cbBlock);
4241 }
4242 else
4243 RTTestSkipped(g_hTest, "insufficient (virtual) memory available");
4244 }
4245 else
4246 RTTestSkipped(g_hTest, "test file too small");
4247}
4248
4249
4250/** pwritev is too new to be useful, so we use the writev api via this wrapper. */
4251DECLINLINE(int) myFileSgWriteAt(RTFILE hFile, RTFOFF off, PRTSGBUF pSgBuf, size_t cbToWrite, size_t *pcbWritten)
4252{
4253 int rc = RTFileSeek(hFile, off, RTFILE_SEEK_BEGIN, NULL);
4254 if (RT_SUCCESS(rc))
4255 rc = RTFileSgWrite(hFile, pSgBuf, cbToWrite, pcbWritten);
4256 return rc;
4257}
4258
4259
4260void fsPerfWrite(RTFILE hFile1, RTFILE hFileNoCache, RTFILE hFileWriteThru, uint64_t cbFile)
4261{
4262 RTTestISubF("IO - RTFileWrite");
4263
4264 /*
4265 * Allocate a big buffer we can play around with. Min size is 1MB.
4266 */
4267 size_t cbBuf = cbFile < _64M ? (size_t)cbFile : _64M;
4268 uint8_t *pbBuf = (uint8_t *)RTMemPageAlloc(cbBuf);
4269 while (!pbBuf)
4270 {
4271 cbBuf /= 2;
4272 RTTESTI_CHECK_RETV(cbBuf >= _1M);
4273 pbBuf = (uint8_t *)RTMemPageAlloc(_32M);
4274 }
4275
4276 uint8_t bFiller = 0x88;
4277
4278#if 1
4279 /*
4280 * Start at the beginning and write out the full buffer in random small chunks, thereby
4281 * checking that unaligned buffer addresses, size and file offsets work fine.
4282 */
4283 struct
4284 {
4285 uint64_t offFile;
4286 uint32_t cbMax;
4287 } aRuns[] = { { 0, 127 }, { cbFile - cbBuf, UINT32_MAX }, { 0, UINT32_MAX -1 }};
4288 for (uint32_t i = 0; i < RT_ELEMENTS(aRuns); i++, bFiller)
4289 {
4290 fsPerfFillWriteBuf(aRuns[i].offFile, pbBuf, cbBuf, bFiller);
4291 fsPerfCheckReadBuf(__LINE__, aRuns[i].offFile, pbBuf, cbBuf, bFiller);
4292
4293 RTTESTI_CHECK_RC(RTFileSeek(hFile1, aRuns[i].offFile, RTFILE_SEEK_BEGIN, NULL), VINF_SUCCESS);
4294 for (size_t offBuf = 0; offBuf < cbBuf; )
4295 {
4296 uint32_t const cbLeft = (uint32_t)(cbBuf - offBuf);
4297 uint32_t const cbToWrite = aRuns[i].cbMax < UINT32_MAX / 2 ? RTRandU32Ex(1, RT_MIN(aRuns[i].cbMax, cbLeft))
4298 : aRuns[i].cbMax == UINT32_MAX ? RTRandU32Ex(RT_MAX(cbLeft / 4, 1), cbLeft)
4299 : RTRandU32Ex(cbLeft >= _8K ? _8K : 1, RT_MIN(_1M, cbLeft));
4300 size_t cbActual = 0;
4301 RTTESTI_CHECK_RC(RTFileWrite(hFile1, &pbBuf[offBuf], cbToWrite, &cbActual), VINF_SUCCESS);
4302 if (cbActual == cbToWrite)
4303 {
4304 offBuf += cbActual;
4305 RTTESTI_CHECK_MSG(RTFileTell(hFile1) == aRuns[i].offFile + offBuf,
4306 ("%#RX64, expected %#RX64\n", RTFileTell(hFile1), aRuns[i].offFile + offBuf));
4307 }
4308 else
4309 {
4310 RTTestIFailed("Attempting to write %#x bytes at %#zx (%#x left), only got %#x written!\n",
4311 cbToWrite, offBuf, cbLeft, cbActual);
4312 if (cbActual)
4313 offBuf += cbActual;
4314 else
4315 pbBuf[offBuf++] = 0x11;
4316 }
4317 }
4318
4319 RTTESTI_CHECK_RC(RTFileReadAt(hFile1, aRuns[i].offFile, pbBuf, cbBuf, NULL), VINF_SUCCESS);
4320 fsPerfCheckReadBuf(__LINE__, aRuns[i].offFile, pbBuf, cbBuf, bFiller);
4321 }
4322
4323
4324 /*
4325 * Do uncached and write-thru accesses, must be page aligned.
4326 */
4327 RTFILE ahFiles[2] = { hFileWriteThru, hFileNoCache };
4328 for (unsigned iFile = 0; iFile < RT_ELEMENTS(ahFiles); iFile++, bFiller++)
4329 {
4330 if (g_fIgnoreNoCache && ahFiles[iFile] == NIL_RTFILE)
4331 continue;
4332
4333 fsPerfFillWriteBuf(0, pbBuf, cbBuf, bFiller);
4334 fsPerfCheckReadBuf(__LINE__, 0, pbBuf, cbBuf, bFiller);
4335 RTTESTI_CHECK_RC(RTFileSeek(ahFiles[iFile], 0, RTFILE_SEEK_BEGIN, NULL), VINF_SUCCESS);
4336
4337 uint32_t cbPage = PAGE_SIZE;
4338 for (size_t offBuf = 0; offBuf < cbBuf; )
4339 {
4340 uint32_t const cPagesLeft = (uint32_t)((cbBuf - offBuf) / cbPage);
4341 uint32_t const cPagesToWrite = RTRandU32Ex(1, cPagesLeft);
4342 size_t const cbToWrite = cPagesToWrite * (size_t)cbPage;
4343 size_t cbActual = 0;
4344 RTTESTI_CHECK_RC(RTFileWrite(ahFiles[iFile], &pbBuf[offBuf], cbToWrite, &cbActual), VINF_SUCCESS);
4345 if (cbActual == cbToWrite)
4346 {
4347 RTTESTI_CHECK_RC(RTFileReadAt(hFile1, offBuf, pbBuf, cbToWrite, NULL), VINF_SUCCESS);
4348 fsPerfCheckReadBuf(__LINE__, offBuf, pbBuf, cbToWrite, bFiller);
4349 offBuf += cbActual;
4350 }
4351 else
4352 {
4353 RTTestIFailed("Attempting to read %#zx bytes at %#zx, only got %#x written!\n", cbToWrite, offBuf, cbActual);
4354 if (cbActual)
4355 offBuf += cbActual;
4356 else
4357 {
4358 memset(&pbBuf[offBuf], 0x11, cbPage);
4359 offBuf += cbPage;
4360 }
4361 }
4362 }
4363
4364 RTTESTI_CHECK_RC(RTFileReadAt(ahFiles[iFile], 0, pbBuf, cbBuf, NULL), VINF_SUCCESS);
4365 fsPerfCheckReadBuf(__LINE__, 0, pbBuf, cbBuf, bFiller);
4366 }
4367
4368 /*
4369 * Check the behavior of writing zero bytes to the file _4K from the end
4370 * using native API. In the olden days zero sized write have been known
4371 * to be used to truncate a file.
4372 */
4373 RTTESTI_CHECK_RC(RTFileSeek(hFile1, -_4K, RTFILE_SEEK_END, NULL), VINF_SUCCESS);
4374# ifdef RT_OS_WINDOWS
4375 IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER;
4376 NTSTATUS rcNt = NtWriteFile((HANDLE)RTFileToNative(hFile1), NULL, NULL, NULL, &Ios, pbBuf, 0, NULL, NULL);
4377 RTTESTI_CHECK_MSG(rcNt == STATUS_SUCCESS, ("rcNt=%#x", rcNt));
4378 RTTESTI_CHECK(Ios.Status == STATUS_SUCCESS);
4379 RTTESTI_CHECK(Ios.Information == 0);
4380# else
4381 ssize_t cbWritten = write((int)RTFileToNative(hFile1), pbBuf, 0);
4382 RTTESTI_CHECK(cbWritten == 0);
4383# endif
4384 RTTESTI_CHECK_RC(RTFileRead(hFile1, pbBuf, _4K, NULL), VINF_SUCCESS);
4385 fsPerfCheckReadBuf(__LINE__, cbFile - _4K, pbBuf, _4K, pbBuf[0x8]);
4386
4387#else
4388 RT_NOREF(hFileNoCache, hFileWriteThru);
4389#endif
4390
4391 /*
4392 * Gather write function operation.
4393 */
4394#ifdef RT_OS_WINDOWS
4395 /** @todo RTFileSgWriteAt is just a RTFileWriteAt loop for windows NT. Need
4396 * to use WriteFileGather (nocache + page aligned). */
4397#elif !defined(RT_OS_OS2) /** @todo implement RTFileSg using list i/o */
4398
4399# ifdef UIO_MAXIOV
4400 RTSGSEG aSegs[UIO_MAXIOV];
4401# else
4402 RTSGSEG aSegs[512];
4403# endif
4404 RTSGBUF SgBuf;
4405 uint32_t cIncr = 1;
4406 for (uint32_t cSegs = 1; cSegs <= RT_ELEMENTS(aSegs); cSegs += cIncr, bFiller++)
4407 {
4408 size_t const cbSeg = cbBuf / cSegs;
4409 size_t const cbToWrite = cbSeg * cSegs;
4410 for (uint32_t iSeg = 0; iSeg < cSegs; iSeg++)
4411 {
4412 aSegs[iSeg].cbSeg = cbSeg;
4413 aSegs[iSeg].pvSeg = &pbBuf[cbToWrite - (iSeg + 1) * cbSeg];
4414 fsPerfFillWriteBuf(iSeg * cbSeg, (uint8_t *)aSegs[iSeg].pvSeg, cbSeg, bFiller);
4415 }
4416 RTSgBufInit(&SgBuf, &aSegs[0], cSegs);
4417 int rc = myFileSgWriteAt(hFile1, 0, &SgBuf, cbToWrite, NULL);
4418 if (RT_SUCCESS(rc))
4419 {
4420 RTTESTI_CHECK_RC(RTFileReadAt(hFile1, 0, pbBuf, cbToWrite, NULL), VINF_SUCCESS);
4421 fsPerfCheckReadBuf(__LINE__, 0, pbBuf, cbToWrite, bFiller);
4422 }
4423 else
4424 {
4425 RTTestIFailed("myFileSgWriteAt failed: %Rrc - cSegs=%u cbSegs=%#zx cbToWrite=%#zx", rc, cSegs, cbSeg, cbToWrite);
4426 break;
4427 }
4428 if (cSegs == 16)
4429 cIncr = 7;
4430 else if (cSegs == 16 * 7 + 16 /*= 128*/)
4431 cIncr = 64;
4432 }
4433
4434 /* random stuff, including zero segments. */
4435 for (uint32_t iTest = 0; iTest < 128; iTest++, bFiller++)
4436 {
4437 uint32_t cSegs = RTRandU32Ex(1, RT_ELEMENTS(aSegs));
4438 uint32_t iZeroSeg = cSegs > 10 ? RTRandU32Ex(0, cSegs - 1) : UINT32_MAX / 2;
4439 uint32_t cZeroSegs = cSegs > 10 ? RTRandU32Ex(1, RT_MIN(cSegs - iZeroSeg, 25)) : 0;
4440 size_t cbToWrite = 0;
4441 size_t cbLeft = cbBuf;
4442 uint8_t *pbCur = &pbBuf[cbBuf];
4443 for (uint32_t iSeg = 0; iSeg < cSegs; iSeg++)
4444 {
4445 uint32_t iAlign = RTRandU32Ex(0, 3);
4446 if (iAlign & 2) /* end is page aligned */
4447 {
4448 cbLeft -= (uintptr_t)pbCur & PAGE_OFFSET_MASK;
4449 pbCur -= (uintptr_t)pbCur & PAGE_OFFSET_MASK;
4450 }
4451
4452 size_t cbSegOthers = (cSegs - iSeg) * _8K;
4453 size_t cbSegMax = cbLeft > cbSegOthers ? cbLeft - cbSegOthers
4454 : cbLeft > cSegs ? cbLeft - cSegs
4455 : cbLeft;
4456 size_t cbSeg = cbLeft != 0 ? RTRandU32Ex(0, cbSegMax) : 0;
4457 if (iAlign & 1) /* start is page aligned */
4458 cbSeg += ((uintptr_t)pbCur - cbSeg) & PAGE_OFFSET_MASK;
4459
4460 if (iSeg - iZeroSeg < cZeroSegs)
4461 cbSeg = 0;
4462
4463 cbToWrite += cbSeg;
4464 cbLeft -= cbSeg;
4465 pbCur -= cbSeg;
4466 aSegs[iSeg].cbSeg = cbSeg;
4467 aSegs[iSeg].pvSeg = pbCur;
4468 }
4469
4470 uint64_t const offFile = cbToWrite < cbFile ? RTRandU64Ex(0, cbFile - cbToWrite) : 0;
4471 uint64_t offFill = offFile;
4472 for (uint32_t iSeg = 0; iSeg < cSegs; iSeg++)
4473 if (aSegs[iSeg].cbSeg)
4474 {
4475 fsPerfFillWriteBuf(offFill, (uint8_t *)aSegs[iSeg].pvSeg, aSegs[iSeg].cbSeg, bFiller);
4476 offFill += aSegs[iSeg].cbSeg;
4477 }
4478
4479 RTSgBufInit(&SgBuf, &aSegs[0], cSegs);
4480 int rc = myFileSgWriteAt(hFile1, offFile, &SgBuf, cbToWrite, NULL);
4481 if (RT_SUCCESS(rc))
4482 {
4483 RTTESTI_CHECK_RC(RTFileReadAt(hFile1, offFile, pbBuf, cbToWrite, NULL), VINF_SUCCESS);
4484 fsPerfCheckReadBuf(__LINE__, offFile, pbBuf, cbToWrite, bFiller);
4485 }
4486 else
4487 {
4488 RTTestIFailed("myFileSgWriteAt failed: %Rrc - cSegs=%#x cbToWrite=%#zx", rc, cSegs, cbToWrite);
4489 break;
4490 }
4491 }
4492
4493#endif
4494
4495 /*
4496 * Other OS specific stuff.
4497 */
4498#ifdef RT_OS_WINDOWS
4499 /* Check that reading at an offset modifies the position: */
4500 RTTESTI_CHECK_RC(RTFileReadAt(hFile1, cbFile / 2, pbBuf, _4K, NULL), VINF_SUCCESS);
4501 RTTESTI_CHECK_RC(RTFileSeek(hFile1, 0, RTFILE_SEEK_END, NULL), VINF_SUCCESS);
4502 RTTESTI_CHECK(RTFileTell(hFile1) == cbFile);
4503
4504 RTNT_IO_STATUS_BLOCK_REINIT(&Ios);
4505 LARGE_INTEGER offNt;
4506 offNt.QuadPart = cbFile / 2;
4507 rcNt = NtWriteFile((HANDLE)RTFileToNative(hFile1), NULL, NULL, NULL, &Ios, pbBuf, _4K, &offNt, NULL);
4508 RTTESTI_CHECK_MSG(rcNt == STATUS_SUCCESS, ("rcNt=%#x", rcNt));
4509 RTTESTI_CHECK(Ios.Status == STATUS_SUCCESS);
4510 RTTESTI_CHECK(Ios.Information == _4K);
4511 RTTESTI_CHECK(RTFileTell(hFile1) == cbFile / 2 + _4K);
4512#endif
4513
4514 RTMemPageFree(pbBuf, cbBuf);
4515}
4516
4517
4518/**
4519 * Worker for testing RTFileFlush.
4520 */
4521DECL_FORCE_INLINE(int) fsPerfFSyncWorker(RTFILE hFile1, uint64_t cbFile, uint8_t *pbBuf, size_t cbBuf, uint64_t *poffFile)
4522{
4523 if (*poffFile + cbBuf <= cbFile)
4524 { /* likely */ }
4525 else
4526 {
4527 RTTESTI_CHECK_RC(RTFileSeek(hFile1, 0, RTFILE_SEEK_BEGIN, NULL), VINF_SUCCESS);
4528 *poffFile = 0;
4529 }
4530
4531 RTTESTI_CHECK_RC_RET(RTFileWrite(hFile1, pbBuf, cbBuf, NULL), VINF_SUCCESS, rcCheck);
4532 RTTESTI_CHECK_RC_RET(RTFileFlush(hFile1), VINF_SUCCESS, rcCheck);
4533
4534 *poffFile += cbBuf;
4535 return VINF_SUCCESS;
4536}
4537
4538
4539void fsPerfFSync(RTFILE hFile1, uint64_t cbFile)
4540{
4541 RTTestISub("fsync");
4542
4543 RTTESTI_CHECK_RC(RTFileFlush(hFile1), VINF_SUCCESS);
4544
4545 PROFILE_FN(RTFileFlush(hFile1), g_nsTestRun, "RTFileFlush");
4546
4547 size_t cbBuf = PAGE_SIZE;
4548 uint8_t *pbBuf = (uint8_t *)RTMemPageAlloc(cbBuf);
4549 RTTESTI_CHECK_RETV(pbBuf != NULL);
4550 memset(pbBuf, 0xf4, cbBuf);
4551
4552 RTTESTI_CHECK_RC(RTFileSeek(hFile1, 0, RTFILE_SEEK_BEGIN, NULL), VINF_SUCCESS);
4553 uint64_t offFile = 0;
4554 PROFILE_FN(fsPerfFSyncWorker(hFile1, cbFile, pbBuf, cbBuf, &offFile), g_nsTestRun, "RTFileWrite[Page]/RTFileFlush");
4555
4556 RTMemPageFree(pbBuf, cbBuf);
4557}
4558
4559
4560#ifndef RT_OS_OS2
4561/**
4562 * Worker for profiling msync.
4563 */
4564DECL_FORCE_INLINE(int) fsPerfMSyncWorker(uint8_t *pbMapping, size_t offMapping, size_t cbFlush, size_t *pcbFlushed)
4565{
4566 uint8_t *pbCur = &pbMapping[offMapping];
4567 for (size_t offFlush = 0; offFlush < cbFlush; offFlush += PAGE_SIZE)
4568 *(size_t volatile *)&pbCur[offFlush + 8] = cbFlush;
4569# ifdef RT_OS_WINDOWS
4570 RTTESTI_CHECK(FlushViewOfFile(pbCur, cbFlush));
4571# else
4572 RTTESTI_CHECK(msync(pbCur, cbFlush, MS_SYNC) == 0);
4573# endif
4574 if (*pcbFlushed < offMapping + cbFlush)
4575 *pcbFlushed = offMapping + cbFlush;
4576 return VINF_SUCCESS;
4577}
4578#endif /* !RT_OS_OS2 */
4579
4580
4581void fsPerfMMap(RTFILE hFile1, RTFILE hFileNoCache, uint64_t cbFile)
4582{
4583 RTTestISub("mmap");
4584#if !defined(RT_OS_OS2)
4585 static const char * const s_apszStates[] = { "readonly", "writecopy", "readwrite" };
4586 enum { kMMap_ReadOnly = 0, kMMap_WriteCopy, kMMap_ReadWrite, kMMap_End };
4587 for (int enmState = kMMap_ReadOnly; enmState < kMMap_End; enmState++)
4588 {
4589 /*
4590 * Do the mapping.
4591 */
4592 size_t cbMapping = (size_t)cbFile;
4593 if (cbMapping != cbFile)
4594 cbMapping = _256M;
4595 uint8_t *pbMapping;
4596
4597# ifdef RT_OS_WINDOWS
4598 HANDLE hSection;
4599 pbMapping = NULL;
4600 for (;; cbMapping /= 2)
4601 {
4602 hSection = CreateFileMapping((HANDLE)RTFileToNative(hFile1), NULL,
4603 enmState == kMMap_ReadOnly ? PAGE_READONLY
4604 : enmState == kMMap_WriteCopy ? PAGE_WRITECOPY : PAGE_READWRITE,
4605 (uint32_t)((uint64_t)cbMapping >> 32), (uint32_t)cbMapping, NULL);
4606 DWORD dwErr1 = GetLastError();
4607 DWORD dwErr2 = 0;
4608 if (hSection != NULL)
4609 {
4610 pbMapping = (uint8_t *)MapViewOfFile(hSection,
4611 enmState == kMMap_ReadOnly ? FILE_MAP_READ
4612 : enmState == kMMap_WriteCopy ? FILE_MAP_COPY
4613 : FILE_MAP_WRITE,
4614 0, 0, cbMapping);
4615 if (pbMapping)
4616 break;
4617 dwErr2 = GetLastError();
4618 CloseHandle(hSection);
4619 }
4620 if (cbMapping <= _2M)
4621 {
4622 RTTestIFailed("%u/%s: CreateFileMapping or MapViewOfFile failed: %u, %u",
4623 enmState, s_apszStates[enmState], dwErr1, dwErr2);
4624 break;
4625 }
4626 }
4627# else
4628 for (;; cbMapping /= 2)
4629 {
4630 pbMapping = (uint8_t *)mmap(NULL, cbMapping,
4631 enmState == kMMap_ReadOnly ? PROT_READ : PROT_READ | PROT_WRITE,
4632 enmState == kMMap_WriteCopy ? MAP_PRIVATE : MAP_SHARED,
4633 (int)RTFileToNative(hFile1), 0);
4634 if ((void *)pbMapping != MAP_FAILED)
4635 break;
4636 if (cbMapping <= _2M)
4637 {
4638 RTTestIFailed("%u/%s: mmap failed: %s (%u)", enmState, s_apszStates[enmState], strerror(errno), errno);
4639 break;
4640 }
4641 }
4642# endif
4643 if (cbMapping <= _2M)
4644 continue;
4645
4646 /*
4647 * Time page-ins just for fun.
4648 */
4649 size_t const cPages = cbMapping >> PAGE_SHIFT;
4650 size_t uDummy = 0;
4651 uint64_t ns = RTTimeNanoTS();
4652 for (size_t iPage = 0; iPage < cPages; iPage++)
4653 uDummy += ASMAtomicReadU8(&pbMapping[iPage << PAGE_SHIFT]);
4654 ns = RTTimeNanoTS() - ns;
4655 RTTestIValueF(ns / cPages, RTTESTUNIT_NS_PER_OCCURRENCE, "page-in %s", s_apszStates[enmState]);
4656
4657 /* Check the content. */
4658 fsPerfCheckReadBuf(__LINE__, 0, pbMapping, cbMapping);
4659
4660 if (enmState != kMMap_ReadOnly)
4661 {
4662 /* Write stuff to the first two megabytes. In the COW case, we'll detect
4663 corruption of shared data during content checking of the RW iterations. */
4664 fsPerfFillWriteBuf(0, pbMapping, _2M, 0xf7);
4665 if (enmState == kMMap_ReadWrite)
4666 {
4667 /* For RW we can try read back from the file handle and check if we get
4668 a match there first. */
4669 uint8_t abBuf[_4K];
4670 for (uint32_t off = 0; off < _2M; off += sizeof(abBuf))
4671 {
4672 RTTESTI_CHECK_RC(RTFileReadAt(hFile1, off, abBuf, sizeof(abBuf), NULL), VINF_SUCCESS);
4673 fsPerfCheckReadBuf(__LINE__, off, abBuf, sizeof(abBuf), 0xf7);
4674 }
4675# ifdef RT_OS_WINDOWS
4676 RTTESTI_CHECK(FlushViewOfFile(pbMapping, _2M));
4677# else
4678 RTTESTI_CHECK(msync(pbMapping, _2M, MS_SYNC) == 0);
4679# endif
4680
4681 /*
4682 * Time modifying and flushing a few different number of pages.
4683 */
4684 static size_t const s_acbFlush[] = { PAGE_SIZE, PAGE_SIZE * 2, PAGE_SIZE * 3, PAGE_SIZE * 8, PAGE_SIZE * 16, _2M };
4685 for (unsigned iFlushSize = 0 ; iFlushSize < RT_ELEMENTS(s_acbFlush); iFlushSize++)
4686 {
4687 size_t const cbFlush = s_acbFlush[iFlushSize];
4688 if (cbFlush > cbMapping)
4689 continue;
4690
4691 char szDesc[80];
4692 RTStrPrintf(szDesc, sizeof(szDesc), "touch/flush/%zu", cbFlush);
4693 size_t const cFlushes = cbMapping / cbFlush;
4694 size_t const cbMappingUsed = cFlushes * cbFlush;
4695 size_t cbFlushed = 0;
4696 PROFILE_FN(fsPerfMSyncWorker(pbMapping, (iIteration * cbFlush) % cbMappingUsed, cbFlush, &cbFlushed),
4697 g_nsTestRun, szDesc);
4698
4699 /*
4700 * Check that all the changes made it thru to the file:
4701 */
4702 if (!g_fIgnoreNoCache || hFileNoCache != NIL_RTFILE)
4703 {
4704 size_t cbBuf = _2M;
4705 uint8_t *pbBuf = (uint8_t *)RTMemPageAlloc(cbBuf);
4706 if (!pbBuf)
4707 {
4708 cbBuf = _4K;
4709 pbBuf = (uint8_t *)RTMemPageAlloc(cbBuf);
4710 }
4711 RTTESTI_CHECK(pbBuf != NULL);
4712 if (pbBuf)
4713 {
4714 RTTESTI_CHECK_RC(RTFileSeek(hFileNoCache, 0, RTFILE_SEEK_BEGIN, NULL), VINF_SUCCESS);
4715 size_t const cbToCheck = RT_MIN(cFlushes * cbFlush, cbFlushed);
4716 unsigned cErrors = 0;
4717 for (size_t offBuf = 0; cErrors < 32 && offBuf < cbToCheck; offBuf += cbBuf)
4718 {
4719 size_t cbToRead = RT_MIN(cbBuf, cbToCheck - offBuf);
4720 RTTESTI_CHECK_RC(RTFileRead(hFileNoCache, pbBuf, cbToRead, NULL), VINF_SUCCESS);
4721
4722 for (size_t offFlush = 0; offFlush < cbToRead; offFlush += PAGE_SIZE)
4723 if (*(size_t volatile *)&pbBuf[offFlush + 8] != cbFlush)
4724 {
4725 RTTestIFailed("Flush issue at offset #%zx: %#zx, expected %#zx (cbFlush=%#zx, %#RX64)",
4726 offBuf + offFlush + 8, *(size_t volatile *)&pbBuf[offFlush + 8],
4727 cbFlush, cbFlush, *(uint64_t volatile *)&pbBuf[offFlush]);
4728 if (++cErrors > 32)
4729 break;
4730 }
4731 }
4732 RTMemPageFree(pbBuf, cbBuf);
4733 }
4734 }
4735 }
4736
4737# if 0 /* not needed, very very slow */
4738 /*
4739 * Restore the file to 0xf6 state for the next test.
4740 */
4741 RTTestIPrintf(RTTESTLVL_ALWAYS, "Restoring content...\n");
4742 fsPerfFillWriteBuf(0, pbMapping, cbMapping, 0xf6);
4743# ifdef RT_OS_WINDOWS
4744 RTTESTI_CHECK(FlushViewOfFile(pbMapping, cbMapping));
4745# else
4746 RTTESTI_CHECK(msync(pbMapping, cbMapping, MS_SYNC) == 0);
4747# endif
4748 RTTestIPrintf(RTTESTLVL_ALWAYS, "... done\n");
4749# endif
4750 }
4751 }
4752
4753 /*
4754 * Observe how regular writes affects a read-only or readwrite mapping.
4755 * These should ideally be immediately visible in the mapping, at least
4756 * when not performed thru an no-cache handle.
4757 */
4758 if (enmState == kMMap_ReadOnly || enmState == kMMap_ReadWrite)
4759 {
4760 size_t cbBuf = RT_MIN(_2M, cbMapping / 2);
4761 uint8_t *pbBuf = (uint8_t *)RTMemPageAlloc(cbBuf);
4762 if (!pbBuf)
4763 {
4764 cbBuf = _4K;
4765 pbBuf = (uint8_t *)RTMemPageAlloc(cbBuf);
4766 }
4767 RTTESTI_CHECK(pbBuf != NULL);
4768 if (pbBuf)
4769 {
4770 /* Do a number of random writes to the file (using hFile1).
4771 Immediately undoing them. */
4772 for (uint32_t i = 0; i < 128; i++)
4773 {
4774 /* Generate a randomly sized write at a random location, making
4775 sure it differs from whatever is there already before writing. */
4776 uint32_t const cbToWrite = RTRandU32Ex(1, (uint32_t)cbBuf);
4777 uint64_t const offToWrite = RTRandU64Ex(0, cbMapping - cbToWrite);
4778
4779 fsPerfFillWriteBuf(offToWrite, pbBuf, cbToWrite, 0xf8);
4780 pbBuf[0] = ~pbBuf[0];
4781 if (cbToWrite > 1)
4782 pbBuf[cbToWrite - 1] = ~pbBuf[cbToWrite - 1];
4783 RTTESTI_CHECK_RC(RTFileWriteAt(hFile1, offToWrite, pbBuf, cbToWrite, NULL), VINF_SUCCESS);
4784
4785 /* Check the mapping. */
4786 if (memcmp(&pbMapping[(size_t)offToWrite], pbBuf, cbToWrite) != 0)
4787 {
4788 RTTestIFailed("Write #%u @ %#RX64 LB %#x was not reflected in the mapping!\n", i, offToWrite, cbToWrite);
4789 }
4790
4791 /* Restore */
4792 fsPerfFillWriteBuf(offToWrite, pbBuf, cbToWrite, 0xf6);
4793 RTTESTI_CHECK_RC(RTFileWriteAt(hFile1, offToWrite, pbBuf, cbToWrite, NULL), VINF_SUCCESS);
4794 }
4795
4796 RTMemPageFree(pbBuf, cbBuf);
4797 }
4798 }
4799
4800 /*
4801 * Unmap it.
4802 */
4803# ifdef RT_OS_WINDOWS
4804 RTTESTI_CHECK(UnmapViewOfFile(pbMapping));
4805 RTTESTI_CHECK(CloseHandle(hSection));
4806# else
4807 RTTESTI_CHECK(munmap(pbMapping, cbMapping) == 0);
4808# endif
4809 }
4810
4811 /*
4812 * Memory mappings without open handles (pretty common).
4813 */
4814 for (uint32_t i = 0; i < 32; i++)
4815 {
4816 /* Create a new file, 256 KB in size, and fill it with random bytes.
4817 Try uncached access if we can to force the page-in to do actual reads. */
4818 char szFile2[FSPERF_MAX_PATH + 32];
4819 memcpy(szFile2, g_szDir, g_cchDir);
4820 RTStrPrintf(&szFile2[g_cchDir], sizeof(szFile2) - g_cchDir, "mmap-%u.noh", i);
4821 RTFILE hFile2 = NIL_RTFILE;
4822 int rc = (i & 3) == 3 ? VERR_TRY_AGAIN
4823 : RTFileOpen(&hFile2, szFile2, RTFILE_O_READWRITE | RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE | RTFILE_O_NO_CACHE);
4824 if (RT_FAILURE(rc))
4825 {
4826 RTTESTI_CHECK_RC_BREAK(RTFileOpen(&hFile2, szFile2, RTFILE_O_READWRITE | RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE),
4827 VINF_SUCCESS);
4828 }
4829
4830 static char s_abContentUnaligned[256*1024 + PAGE_SIZE - 1];
4831 char * const pbContent = &s_abContentUnaligned[PAGE_SIZE - ((uintptr_t)&s_abContentUnaligned[0] & PAGE_OFFSET_MASK)];
4832 size_t const cbContent = 256*1024;
4833 RTRandBytes(pbContent, cbContent);
4834 RTTESTI_CHECK_RC(rc = RTFileWrite(hFile2, pbContent, cbContent, NULL), VINF_SUCCESS);
4835 RTTESTI_CHECK_RC(RTFileClose(hFile2), VINF_SUCCESS);
4836 if (RT_SUCCESS(rc))
4837 {
4838 /* Reopen the file with normal caching. Every second time, we also
4839 does a read-only open of it to confuse matters. */
4840 RTFILE hFile3 = NIL_RTFILE;
4841 if ((i & 3) == 3)
4842 RTTESTI_CHECK_RC(RTFileOpen(&hFile3, szFile2, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE), VINF_SUCCESS);
4843 hFile2 = NIL_RTFILE;
4844 RTTESTI_CHECK_RC_BREAK(RTFileOpen(&hFile2, szFile2, RTFILE_O_READWRITE | RTFILE_O_OPEN | RTFILE_O_DENY_NONE),
4845 VINF_SUCCESS);
4846 if ((i & 3) == 1)
4847 RTTESTI_CHECK_RC(RTFileOpen(&hFile3, szFile2, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE), VINF_SUCCESS);
4848
4849 /* Memory map it read-write (no COW). */
4850#ifdef RT_OS_WINDOWS
4851 HANDLE hSection = CreateFileMapping((HANDLE)RTFileToNative(hFile2), NULL, PAGE_READWRITE, 0, cbContent, NULL);
4852 RTTESTI_CHECK_MSG(hSection != NULL, ("last error %u\n", GetLastError));
4853 uint8_t *pbMapping = (uint8_t *)MapViewOfFile(hSection, FILE_MAP_WRITE, 0, 0, cbContent);
4854 RTTESTI_CHECK_MSG(pbMapping != NULL, ("last error %u\n", GetLastError));
4855 RTTESTI_CHECK_MSG(CloseHandle(hSection), ("last error %u\n", GetLastError));
4856# else
4857 uint8_t *pbMapping = (uint8_t *)mmap(NULL, cbContent, PROT_READ | PROT_WRITE, MAP_SHARED,
4858 (int)RTFileToNative(hFile2), 0);
4859 if ((void *)pbMapping == MAP_FAILED)
4860 pbMapping = NULL;
4861 RTTESTI_CHECK_MSG(pbMapping != NULL, ("errno=%s (%d)\n", strerror(errno), errno));
4862# endif
4863
4864 /* Close the file handles. */
4865 if ((i & 7) == 7)
4866 {
4867 RTTESTI_CHECK_RC(RTFileClose(hFile3), VINF_SUCCESS);
4868 hFile3 = NIL_RTFILE;
4869 }
4870 RTTESTI_CHECK_RC(RTFileClose(hFile2), VINF_SUCCESS);
4871 if ((i & 7) == 5)
4872 {
4873 RTTESTI_CHECK_RC(RTFileClose(hFile3), VINF_SUCCESS);
4874 hFile3 = NIL_RTFILE;
4875 }
4876 if (pbMapping)
4877 {
4878 RTThreadSleep(2); /* fudge for cleanup/whatever */
4879
4880 /* Page in the mapping by comparing with the content we wrote above. */
4881 RTTESTI_CHECK(memcmp(pbMapping, pbContent, cbContent) == 0);
4882
4883 /* Now dirty everything by inverting everything. */
4884 size_t *puCur = (size_t *)pbMapping;
4885 size_t cLeft = cbContent / sizeof(*puCur);
4886 while (cLeft-- > 0)
4887 {
4888 *puCur = ~*puCur;
4889 puCur++;
4890 }
4891
4892 /* Sync it all. */
4893# ifdef RT_OS_WINDOWS
4894 RTTESTI_CHECK(FlushViewOfFile(pbMapping, cbContent));
4895# else
4896 RTTESTI_CHECK(msync(pbMapping, cbContent, MS_SYNC) == 0);
4897# endif
4898
4899 /* Unmap it. */
4900# ifdef RT_OS_WINDOWS
4901 RTTESTI_CHECK(UnmapViewOfFile(pbMapping));
4902# else
4903 RTTESTI_CHECK(munmap(pbMapping, cbContent) == 0);
4904# endif
4905 }
4906
4907 if (hFile3 != NIL_RTFILE)
4908 RTTESTI_CHECK_RC(RTFileClose(hFile3), VINF_SUCCESS);
4909 }
4910 RTTESTI_CHECK_RC(RTFileDelete(szFile2), VINF_SUCCESS);
4911 }
4912
4913
4914#else
4915 RTTestSkipped(g_hTest, "not supported/implemented");
4916 RT_NOREF(hFile1, hFileNoCache, cbFile);
4917#endif
4918}
4919
4920
4921/**
4922 * This does the read, write and seek tests.
4923 */
4924void fsPerfIo(void)
4925{
4926 RTTestISub("I/O");
4927
4928 /*
4929 * Determin the size of the test file.
4930 */
4931 g_szDir[g_cchDir] = '\0';
4932 RTFOFF cbFree = 0;
4933 RTTESTI_CHECK_RC_RETV(RTFsQuerySizes(g_szDir, NULL, &cbFree, NULL, NULL), VINF_SUCCESS);
4934 uint64_t cbFile = g_cbIoFile;
4935 if (cbFile + _16M < (uint64_t)cbFree)
4936 cbFile = RT_ALIGN_64(cbFile, _64K);
4937 else if (cbFree < _32M)
4938 {
4939 RTTestSkipped(g_hTest, "Insufficent free space: %'RU64 bytes, requires >= 32MB", cbFree);
4940 return;
4941 }
4942 else
4943 {
4944 cbFile = cbFree - (cbFree > _128M ? _64M : _16M);
4945 cbFile = RT_ALIGN_64(cbFile, _64K);
4946 RTTestIPrintf(RTTESTLVL_ALWAYS, "Adjusted file size to %'RU64 bytes, due to %'RU64 bytes free.\n", cbFile, cbFree);
4947 }
4948 if (cbFile < _64K)
4949 {
4950 RTTestSkipped(g_hTest, "Specified test file size too small: %'RU64 bytes, requires >= 64KB", cbFile);
4951 return;
4952 }
4953
4954 /*
4955 * Create a cbFile sized test file.
4956 */
4957 RTFILE hFile1;
4958 RTTESTI_CHECK_RC_RETV(RTFileOpen(&hFile1, InDir(RT_STR_TUPLE("file21")),
4959 RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE | RTFILE_O_READWRITE), VINF_SUCCESS);
4960 RTFILE hFileNoCache;
4961 if (!g_fIgnoreNoCache)
4962 RTTESTI_CHECK_RC_RETV(RTFileOpen(&hFileNoCache, g_szDir,
4963 RTFILE_O_OPEN | RTFILE_O_DENY_NONE | RTFILE_O_READWRITE | RTFILE_O_NO_CACHE),
4964 VINF_SUCCESS);
4965 else
4966 {
4967 int rc = RTFileOpen(&hFileNoCache, g_szDir, RTFILE_O_OPEN | RTFILE_O_DENY_NONE | RTFILE_O_READWRITE | RTFILE_O_NO_CACHE);
4968 if (RT_FAILURE(rc))
4969 {
4970 RTTestIPrintf(RTTESTLVL_ALWAYS, "Unable to open I/O file with non-cache flag (%Rrc), skipping related tests.\n", rc);
4971 hFileNoCache = NIL_RTFILE;
4972 }
4973 }
4974 RTFILE hFileWriteThru;
4975 RTTESTI_CHECK_RC_RETV(RTFileOpen(&hFileWriteThru, g_szDir,
4976 RTFILE_O_OPEN | RTFILE_O_DENY_NONE | RTFILE_O_READWRITE | RTFILE_O_WRITE_THROUGH),
4977 VINF_SUCCESS);
4978
4979 uint8_t *pbFree = NULL;
4980 int rc = fsPerfIoPrepFile(hFile1, cbFile, &pbFree);
4981 RTMemFree(pbFree);
4982 if (RT_SUCCESS(rc))
4983 {
4984 /*
4985 * Do the testing & profiling.
4986 */
4987 if (g_fSeek)
4988 fsPerfIoSeek(hFile1, cbFile);
4989
4990 if (g_fReadTests)
4991 fsPerfRead(hFile1, hFileNoCache, cbFile);
4992 if (g_fReadPerf)
4993 for (unsigned i = 0; i < g_cIoBlocks; i++)
4994 fsPerfIoReadBlockSize(hFile1, cbFile, g_acbIoBlocks[i]);
4995#ifdef FSPERF_TEST_SENDFILE
4996 if (g_fSendFile)
4997 fsPerfSendFile(hFile1, cbFile);
4998#endif
4999#ifdef RT_OS_LINUX
5000 if (g_fSplice)
5001 fsPerfSpliceToPipe(hFile1, cbFile);
5002#endif
5003 if (g_fMMap)
5004 fsPerfMMap(hFile1, hFileNoCache, cbFile);
5005
5006 /* This is destructive to the file content. */
5007 if (g_fWriteTests)
5008 fsPerfWrite(hFile1, hFileNoCache, hFileWriteThru, cbFile);
5009 if (g_fWritePerf)
5010 for (unsigned i = 0; i < g_cIoBlocks; i++)
5011 fsPerfIoWriteBlockSize(hFile1, cbFile, g_acbIoBlocks[i]);
5012#ifdef RT_OS_LINUX
5013 if (g_fSplice)
5014 fsPerfSpliceToFile(hFile1, cbFile);
5015#endif
5016 if (g_fFSync)
5017 fsPerfFSync(hFile1, cbFile);
5018 }
5019
5020 RTTESTI_CHECK_RC(RTFileSetSize(hFile1, 0), VINF_SUCCESS);
5021 RTTESTI_CHECK_RC(RTFileClose(hFile1), VINF_SUCCESS);
5022 if (hFileNoCache != NIL_RTFILE || !g_fIgnoreNoCache)
5023 RTTESTI_CHECK_RC(RTFileClose(hFileNoCache), VINF_SUCCESS);
5024 RTTESTI_CHECK_RC(RTFileClose(hFileWriteThru), VINF_SUCCESS);
5025 RTTESTI_CHECK_RC(RTFileDelete(g_szDir), VINF_SUCCESS);
5026}
5027
5028
5029DECL_FORCE_INLINE(int) fsPerfCopyWorker1(const char *pszSrc, const char *pszDst)
5030{
5031 RTFileDelete(pszDst);
5032 return RTFileCopy(pszSrc, pszDst);
5033}
5034
5035
5036#ifdef RT_OS_LINUX
5037DECL_FORCE_INLINE(int) fsPerfCopyWorkerSendFile(RTFILE hFile1, RTFILE hFile2, size_t cbFile)
5038{
5039 RTTESTI_CHECK_RC_RET(RTFileSeek(hFile2, 0, RTFILE_SEEK_BEGIN, NULL), VINF_SUCCESS, rcCheck);
5040
5041 loff_t off = 0;
5042 ssize_t cbSent = sendfile((int)RTFileToNative(hFile2), (int)RTFileToNative(hFile1), &off, cbFile);
5043 if (cbSent > 0 && (size_t)cbSent == cbFile)
5044 return 0;
5045
5046 int rc = VERR_GENERAL_FAILURE;
5047 if (cbSent < 0)
5048 {
5049 rc = RTErrConvertFromErrno(errno);
5050 RTTestIFailed("sendfile(file,file,NULL,%#zx) failed (%zd): %d (%Rrc)", cbFile, cbSent, errno, rc);
5051 }
5052 else
5053 RTTestIFailed("sendfile(file,file,NULL,%#zx) returned %#zx, expected %#zx (diff %zd)",
5054 cbFile, cbSent, cbFile, cbSent - cbFile);
5055 return rc;
5056}
5057#endif /* RT_OS_LINUX */
5058
5059
5060static void fsPerfCopy(void)
5061{
5062 RTTestISub("copy");
5063
5064 /*
5065 * Non-existing files.
5066 */
5067 RTTESTI_CHECK_RC(RTFileCopy(InEmptyDir(RT_STR_TUPLE("no-such-file")),
5068 InDir2(RT_STR_TUPLE("whatever"))), VERR_FILE_NOT_FOUND);
5069 RTTESTI_CHECK_RC(RTFileCopy(InEmptyDir(RT_STR_TUPLE("no-such-dir" RTPATH_SLASH_STR "no-such-file")),
5070 InDir2(RT_STR_TUPLE("no-such-file"))), FSPERF_VERR_PATH_NOT_FOUND);
5071 RTTESTI_CHECK_RC(RTFileCopy(InDir(RT_STR_TUPLE("known-file" RTPATH_SLASH_STR "no-such-file")),
5072 InDir2(RT_STR_TUPLE("whatever"))), VERR_PATH_NOT_FOUND);
5073
5074 RTTESTI_CHECK_RC(RTFileCopy(InDir(RT_STR_TUPLE("known-file")),
5075 InEmptyDir(RT_STR_TUPLE("no-such-dir" RTPATH_SLASH_STR "no-such-file"))), FSPERF_VERR_PATH_NOT_FOUND);
5076 RTTESTI_CHECK_RC(RTFileCopy(InDir(RT_STR_TUPLE("known-file")),
5077 InDir2(RT_STR_TUPLE("known-file" RTPATH_SLASH_STR "no-such-file"))), VERR_PATH_NOT_FOUND);
5078
5079 /*
5080 * Determin the size of the test file.
5081 * We want to be able to make 1 copy of it.
5082 */
5083 g_szDir[g_cchDir] = '\0';
5084 RTFOFF cbFree = 0;
5085 RTTESTI_CHECK_RC_RETV(RTFsQuerySizes(g_szDir, NULL, &cbFree, NULL, NULL), VINF_SUCCESS);
5086 uint64_t cbFile = g_cbIoFile;
5087 if (cbFile + _16M < (uint64_t)cbFree)
5088 cbFile = RT_ALIGN_64(cbFile, _64K);
5089 else if (cbFree < _32M)
5090 {
5091 RTTestSkipped(g_hTest, "Insufficent free space: %'RU64 bytes, requires >= 32MB", cbFree);
5092 return;
5093 }
5094 else
5095 {
5096 cbFile = cbFree - (cbFree > _128M ? _64M : _16M);
5097 cbFile = RT_ALIGN_64(cbFile, _64K);
5098 RTTestIPrintf(RTTESTLVL_ALWAYS, "Adjusted file size to %'RU64 bytes, due to %'RU64 bytes free.\n", cbFile, cbFree);
5099 }
5100 if (cbFile < _512K * 2)
5101 {
5102 RTTestSkipped(g_hTest, "Specified test file size too small: %'RU64 bytes, requires >= 1MB", cbFile);
5103 return;
5104 }
5105 cbFile /= 2;
5106
5107 /*
5108 * Create a cbFile sized test file.
5109 */
5110 RTFILE hFile1;
5111 RTTESTI_CHECK_RC_RETV(RTFileOpen(&hFile1, InDir(RT_STR_TUPLE("file22")),
5112 RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE | RTFILE_O_READWRITE), VINF_SUCCESS);
5113 uint8_t *pbFree = NULL;
5114 int rc = fsPerfIoPrepFile(hFile1, cbFile, &pbFree);
5115 RTMemFree(pbFree);
5116 RTTESTI_CHECK_RC(RTFileClose(hFile1), VINF_SUCCESS);
5117 if (RT_SUCCESS(rc))
5118 {
5119 /*
5120 * Make copies.
5121 */
5122 /* plain */
5123 RTFileDelete(InDir2(RT_STR_TUPLE("file23")));
5124 RTTESTI_CHECK_RC(RTFileCopy(g_szDir, g_szDir2), VINF_SUCCESS);
5125 RTTESTI_CHECK_RC(RTFileCopy(g_szDir, g_szDir2), VERR_ALREADY_EXISTS);
5126 RTTESTI_CHECK_RC(RTFileCompare(g_szDir, g_szDir2), VINF_SUCCESS);
5127
5128 /* by handle */
5129 hFile1 = NIL_RTFILE;
5130 RTTESTI_CHECK_RC(RTFileOpen(&hFile1, g_szDir, RTFILE_O_OPEN | RTFILE_O_DENY_NONE | RTFILE_O_READ), VINF_SUCCESS);
5131 RTFILE hFile2 = NIL_RTFILE;
5132 RTTESTI_CHECK_RC(RTFileOpen(&hFile2, g_szDir2, RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE | RTFILE_O_WRITE), VINF_SUCCESS);
5133 RTTESTI_CHECK_RC(RTFileCopyByHandles(hFile1, hFile2), VINF_SUCCESS);
5134 RTTESTI_CHECK_RC(RTFileClose(hFile2), VINF_SUCCESS);
5135 RTTESTI_CHECK_RC(RTFileClose(hFile1), VINF_SUCCESS);
5136 RTTESTI_CHECK_RC(RTFileCompare(g_szDir, g_szDir2), VINF_SUCCESS);
5137
5138 /* copy part */
5139 hFile1 = NIL_RTFILE;
5140 RTTESTI_CHECK_RC(RTFileOpen(&hFile1, g_szDir, RTFILE_O_OPEN | RTFILE_O_DENY_NONE | RTFILE_O_READ), VINF_SUCCESS);
5141 hFile2 = NIL_RTFILE;
5142 RTTESTI_CHECK_RC(RTFileOpen(&hFile2, g_szDir2, RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE | RTFILE_O_WRITE), VINF_SUCCESS);
5143 RTTESTI_CHECK_RC(RTFileCopyPart(hFile1, 0, hFile2, 0, cbFile / 2, 0, NULL), VINF_SUCCESS);
5144 RTTESTI_CHECK_RC(RTFileCopyPart(hFile1, cbFile / 2, hFile2, cbFile / 2, cbFile - cbFile / 2, 0, NULL), VINF_SUCCESS);
5145 RTTESTI_CHECK_RC(RTFileClose(hFile2), VINF_SUCCESS);
5146 RTTESTI_CHECK_RC(RTFileClose(hFile1), VINF_SUCCESS);
5147 RTTESTI_CHECK_RC(RTFileCompare(g_szDir, g_szDir2), VINF_SUCCESS);
5148
5149#ifdef RT_OS_LINUX
5150 /*
5151 * On linux we can also use sendfile between two files, except for 2.5.x to 2.6.33.
5152 */
5153 uint64_t const cbFileMax = RT_MIN(cbFile, UINT32_C(0x7ffff000));
5154 char szRelease[64];
5155 RTSystemQueryOSInfo(RTSYSOSINFO_RELEASE, szRelease, sizeof(szRelease));
5156 bool const fSendFileBetweenFiles = RTStrVersionCompare(szRelease, "2.5.0") < 0
5157 || RTStrVersionCompare(szRelease, "2.6.33") >= 0;
5158 if (fSendFileBetweenFiles)
5159 {
5160 /* Copy the whole file: */
5161 hFile1 = NIL_RTFILE;
5162 RTTESTI_CHECK_RC(RTFileOpen(&hFile1, g_szDir, RTFILE_O_OPEN | RTFILE_O_DENY_NONE | RTFILE_O_READ), VINF_SUCCESS);
5163 RTFileDelete(g_szDir2);
5164 hFile2 = NIL_RTFILE;
5165 RTTESTI_CHECK_RC(RTFileOpen(&hFile2, g_szDir2, RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE | RTFILE_O_WRITE), VINF_SUCCESS);
5166 ssize_t cbSent = sendfile((int)RTFileToNative(hFile2), (int)RTFileToNative(hFile1), NULL, cbFile);
5167 if (cbSent < 0)
5168 RTTestIFailed("sendfile(file,file,NULL,%#zx) failed (%zd): %d (%Rrc)",
5169 cbFile, cbSent, errno, RTErrConvertFromErrno(errno));
5170 else if ((size_t)cbSent != cbFileMax)
5171 RTTestIFailed("sendfile(file,file,NULL,%#zx) returned %#zx, expected %#zx (diff %zd)",
5172 cbFile, cbSent, cbFileMax, cbSent - cbFileMax);
5173 RTTESTI_CHECK_RC(RTFileClose(hFile2), VINF_SUCCESS);
5174 RTTESTI_CHECK_RC(RTFileClose(hFile1), VINF_SUCCESS);
5175 RTTESTI_CHECK_RC(RTFileCompare(g_szDir, g_szDir2), VINF_SUCCESS);
5176
5177 /* Try copy a little bit too much: */
5178 if (cbFile == cbFileMax)
5179 {
5180 hFile1 = NIL_RTFILE;
5181 RTTESTI_CHECK_RC(RTFileOpen(&hFile1, g_szDir, RTFILE_O_OPEN | RTFILE_O_DENY_NONE | RTFILE_O_READ), VINF_SUCCESS);
5182 RTFileDelete(g_szDir2);
5183 hFile2 = NIL_RTFILE;
5184 RTTESTI_CHECK_RC(RTFileOpen(&hFile2, g_szDir2, RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE | RTFILE_O_WRITE), VINF_SUCCESS);
5185 size_t cbToCopy = cbFile + RTRandU32Ex(1, _64M);
5186 cbSent = sendfile((int)RTFileToNative(hFile2), (int)RTFileToNative(hFile1), NULL, cbToCopy);
5187 if (cbSent < 0)
5188 RTTestIFailed("sendfile(file,file,NULL,%#zx) failed (%zd): %d (%Rrc)",
5189 cbToCopy, cbSent, errno, RTErrConvertFromErrno(errno));
5190 else if ((size_t)cbSent != cbFile)
5191 RTTestIFailed("sendfile(file,file,NULL,%#zx) returned %#zx, expected %#zx (diff %zd)",
5192 cbToCopy, cbSent, cbFile, cbSent - cbFile);
5193 RTTESTI_CHECK_RC(RTFileClose(hFile2), VINF_SUCCESS);
5194 RTTESTI_CHECK_RC(RTFileCompare(g_szDir, g_szDir2), VINF_SUCCESS);
5195 }
5196
5197 /* Do partial copy: */
5198 hFile2 = NIL_RTFILE;
5199 RTTESTI_CHECK_RC(RTFileOpen(&hFile2, g_szDir2, RTFILE_O_OPEN | RTFILE_O_DENY_NONE | RTFILE_O_WRITE), VINF_SUCCESS);
5200 for (uint32_t i = 0; i < 64; i++)
5201 {
5202 size_t cbToCopy = RTRandU32Ex(0, cbFileMax - 1);
5203 uint32_t const offFile = RTRandU32Ex(1, (uint64_t)RT_MIN(cbFileMax - cbToCopy, UINT32_MAX));
5204 RTTESTI_CHECK_RC_BREAK(RTFileSeek(hFile2, offFile, RTFILE_SEEK_BEGIN, NULL), VINF_SUCCESS);
5205 loff_t offFile2 = offFile;
5206 cbSent = sendfile((int)RTFileToNative(hFile2), (int)RTFileToNative(hFile1), &offFile2, cbToCopy);
5207 if (cbSent < 0)
5208 RTTestIFailed("sendfile(file,file,%#x,%#zx) failed (%zd): %d (%Rrc)",
5209 offFile, cbToCopy, cbSent, errno, RTErrConvertFromErrno(errno));
5210 else if ((size_t)cbSent != cbToCopy)
5211 RTTestIFailed("sendfile(file,file,%#x,%#zx) returned %#zx, expected %#zx (diff %zd)",
5212 offFile, cbToCopy, cbSent, cbToCopy, cbSent - cbToCopy);
5213 else if (offFile2 != (loff_t)(offFile + cbToCopy))
5214 RTTestIFailed("sendfile(file,file,%#x,%#zx) returned %#zx + off=%#RX64, expected off %#x",
5215 offFile, cbToCopy, cbSent, offFile2, offFile + cbToCopy);
5216 }
5217 RTTESTI_CHECK_RC(RTFileClose(hFile2), VINF_SUCCESS);
5218 RTTESTI_CHECK_RC(RTFileClose(hFile1), VINF_SUCCESS);
5219 RTTESTI_CHECK_RC(RTFileCompare(g_szDir, g_szDir2), VINF_SUCCESS);
5220 }
5221#endif
5222
5223 /*
5224 * Do some benchmarking.
5225 */
5226#define PROFILE_COPY_FN(a_szOperation, a_fnCall) \
5227 do \
5228 { \
5229 /* Estimate how many iterations we need to fill up the given timeslot: */ \
5230 fsPerfYield(); \
5231 uint64_t nsStart = RTTimeNanoTS(); \
5232 uint64_t ns; \
5233 do \
5234 ns = RTTimeNanoTS(); \
5235 while (ns == nsStart); \
5236 nsStart = ns; \
5237 \
5238 uint64_t iIteration = 0; \
5239 do \
5240 { \
5241 RTTESTI_CHECK_RC(a_fnCall, VINF_SUCCESS); \
5242 iIteration++; \
5243 ns = RTTimeNanoTS() - nsStart; \
5244 } while (ns < RT_NS_10MS); \
5245 ns /= iIteration; \
5246 if (ns > g_nsPerNanoTSCall + 32) \
5247 ns -= g_nsPerNanoTSCall; \
5248 uint64_t cIterations = g_nsTestRun / ns; \
5249 if (cIterations < 2) \
5250 cIterations = 2; \
5251 else if (cIterations & 1) \
5252 cIterations++; \
5253 \
5254 /* Do the actual profiling: */ \
5255 iIteration = 0; \
5256 fsPerfYield(); \
5257 nsStart = RTTimeNanoTS(); \
5258 for (uint32_t iAdjust = 0; iAdjust < 4; iAdjust++) \
5259 { \
5260 for (; iIteration < cIterations; iIteration++)\
5261 RTTESTI_CHECK_RC(a_fnCall, VINF_SUCCESS); \
5262 ns = RTTimeNanoTS() - nsStart;\
5263 if (ns >= g_nsTestRun - (g_nsTestRun / 10)) \
5264 break; \
5265 cIterations += cIterations / 4; \
5266 if (cIterations & 1) \
5267 cIterations++; \
5268 nsStart += g_nsPerNanoTSCall; \
5269 } \
5270 RTTestIValueF(ns / iIteration, \
5271 RTTESTUNIT_NS_PER_OCCURRENCE, a_szOperation " latency"); \
5272 RTTestIValueF((uint64_t)((uint64_t)iIteration * cbFile / ((double)ns / RT_NS_1SEC)), \
5273 RTTESTUNIT_BYTES_PER_SEC, a_szOperation " throughput"); \
5274 RTTestIValueF((uint64_t)iIteration * cbFile, \
5275 RTTESTUNIT_BYTES, a_szOperation " bytes"); \
5276 RTTestIValueF(iIteration, \
5277 RTTESTUNIT_OCCURRENCES, a_szOperation " iterations"); \
5278 if (g_fShowDuration) \
5279 RTTestIValueF(ns, RTTESTUNIT_NS, a_szOperation " duration"); \
5280 } while (0)
5281
5282 PROFILE_COPY_FN("RTFileCopy/Replace", fsPerfCopyWorker1(g_szDir, g_szDir2));
5283
5284 hFile1 = NIL_RTFILE;
5285 RTTESTI_CHECK_RC(RTFileOpen(&hFile1, g_szDir, RTFILE_O_OPEN | RTFILE_O_DENY_NONE | RTFILE_O_READ), VINF_SUCCESS);
5286 RTFileDelete(g_szDir2);
5287 hFile2 = NIL_RTFILE;
5288 RTTESTI_CHECK_RC(RTFileOpen(&hFile2, g_szDir2, RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE | RTFILE_O_WRITE), VINF_SUCCESS);
5289 PROFILE_COPY_FN("RTFileCopyByHandles/Overwrite", RTFileCopyByHandles(hFile1, hFile2));
5290 RTTESTI_CHECK_RC(RTFileClose(hFile2), VINF_SUCCESS);
5291 RTTESTI_CHECK_RC(RTFileClose(hFile1), VINF_SUCCESS);
5292
5293 /* We could benchmark RTFileCopyPart with various block sizes and whatnot...
5294 But it's currently well covered by the two previous operations. */
5295
5296#ifdef RT_OS_LINUX
5297 if (fSendFileBetweenFiles)
5298 {
5299 hFile1 = NIL_RTFILE;
5300 RTTESTI_CHECK_RC(RTFileOpen(&hFile1, g_szDir, RTFILE_O_OPEN | RTFILE_O_DENY_NONE | RTFILE_O_READ), VINF_SUCCESS);
5301 RTFileDelete(g_szDir2);
5302 hFile2 = NIL_RTFILE;
5303 RTTESTI_CHECK_RC(RTFileOpen(&hFile2, g_szDir2, RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE | RTFILE_O_WRITE), VINF_SUCCESS);
5304 PROFILE_COPY_FN("sendfile/overwrite", fsPerfCopyWorkerSendFile(hFile1, hFile2, cbFileMax));
5305 RTTESTI_CHECK_RC(RTFileClose(hFile2), VINF_SUCCESS);
5306 RTTESTI_CHECK_RC(RTFileClose(hFile1), VINF_SUCCESS);
5307 }
5308#endif
5309 }
5310
5311 /*
5312 * Clean up.
5313 */
5314 RTFileDelete(InDir2(RT_STR_TUPLE("file22c1")));
5315 RTFileDelete(InDir2(RT_STR_TUPLE("file22c2")));
5316 RTFileDelete(InDir2(RT_STR_TUPLE("file22c3")));
5317 RTTESTI_CHECK_RC(RTFileDelete(g_szDir), VINF_SUCCESS);
5318}
5319
5320
5321/**
5322 * Display the usage to @a pStrm.
5323 */
5324static void Usage(PRTSTREAM pStrm)
5325{
5326 char szExec[FSPERF_MAX_PATH];
5327 RTStrmPrintf(pStrm, "usage: %s <-d <testdir>> [options]\n",
5328 RTPathFilename(RTProcGetExecutablePath(szExec, sizeof(szExec))));
5329 RTStrmPrintf(pStrm, "\n");
5330 RTStrmPrintf(pStrm, "options: \n");
5331
5332 for (unsigned i = 0; i < RT_ELEMENTS(g_aCmdOptions); i++)
5333 {
5334 char szHelp[80];
5335 const char *pszHelp;
5336 switch (g_aCmdOptions[i].iShort)
5337 {
5338 case 'd': pszHelp = "The directory to use for testing. default: CWD/fstestdir"; break;
5339 case 'r': pszHelp = "Don't abspath test dir (good for deep dirs). default: disabled"; break;
5340 case 'e': pszHelp = "Enables all tests. default: -e"; break;
5341 case 'z': pszHelp = "Disables all tests. default: -e"; break;
5342 case 's': pszHelp = "Set benchmark duration in seconds. default: 10 sec"; break;
5343 case 'm': pszHelp = "Set benchmark duration in milliseconds. default: 10000 ms"; break;
5344 case 'v': pszHelp = "More verbose execution."; break;
5345 case 'q': pszHelp = "Quiet execution."; break;
5346 case 'h': pszHelp = "Displays this help and exit"; break;
5347 case 'V': pszHelp = "Displays the program revision"; break;
5348 case kCmdOpt_ShowDuration: pszHelp = "Show duration of profile runs. default: --no-show-duration"; break;
5349 case kCmdOpt_NoShowDuration: pszHelp = "Hide duration of profile runs. default: --no-show-duration"; break;
5350 case kCmdOpt_ShowIterations: pszHelp = "Show iteration count for profile runs. default: --no-show-iterations"; break;
5351 case kCmdOpt_NoShowIterations: pszHelp = "Hide iteration count for profile runs. default: --no-show-iterations"; break;
5352 case kCmdOpt_ManyFiles: pszHelp = "Count of files in big test dir. default: --many-files 10000"; break;
5353 case kCmdOpt_NoManyFiles: pszHelp = "Skip big test dir with many files. default: --many-files 10000"; break;
5354 case kCmdOpt_ManyTreeFilesPerDir: pszHelp = "Count of files per directory in test tree. default: 640"; break;
5355 case kCmdOpt_ManyTreeSubdirsPerDir: pszHelp = "Count of subdirs per directory in test tree. default: 16"; break;
5356 case kCmdOpt_ManyTreeDepth: pszHelp = "Depth of test tree (not counting root). default: 1"; break;
5357 case kCmdOpt_IgnoreNoCache: pszHelp = "Ignore error wrt no-cache handle. default: --no-ignore-no-cache"; break;
5358 case kCmdOpt_NoIgnoreNoCache: pszHelp = "Do not ignore error wrt no-cache handle. default: --no-ignore-no-cache"; break;
5359 case kCmdOpt_IoFileSize: pszHelp = "Size of file used for I/O tests. default: 512 MB"; break;
5360 case kCmdOpt_SetBlockSize: pszHelp = "Sets single I/O block size (in bytes)."; break;
5361 case kCmdOpt_AddBlockSize: pszHelp = "Adds an I/O block size (in bytes)."; break;
5362 default:
5363 if (g_aCmdOptions[i].iShort >= kCmdOpt_First)
5364 {
5365 if (RTStrStartsWith(g_aCmdOptions[i].pszLong, "--no-"))
5366 RTStrPrintf(szHelp, sizeof(szHelp), "Disables the '%s' test.", g_aCmdOptions[i].pszLong + 5);
5367 else
5368 RTStrPrintf(szHelp, sizeof(szHelp), "Enables the '%s' test.", g_aCmdOptions[i].pszLong + 2);
5369 pszHelp = szHelp;
5370 }
5371 else
5372 pszHelp = "Option undocumented";
5373 break;
5374 }
5375 if ((unsigned)g_aCmdOptions[i].iShort < 127U)
5376 {
5377 char szOpt[64];
5378 RTStrPrintf(szOpt, sizeof(szOpt), "%s, -%c", g_aCmdOptions[i].pszLong, g_aCmdOptions[i].iShort);
5379 RTStrmPrintf(pStrm, " %-19s %s\n", szOpt, pszHelp);
5380 }
5381 else
5382 RTStrmPrintf(pStrm, " %-19s %s\n", g_aCmdOptions[i].pszLong, pszHelp);
5383 }
5384}
5385
5386
5387static uint32_t fsPerfCalcManyTreeFiles(void)
5388{
5389 uint32_t cDirs = 1;
5390 for (uint32_t i = 0, cDirsAtLevel = 1; i < g_cManyTreeDepth; i++)
5391 {
5392 cDirs += cDirsAtLevel * g_cManyTreeSubdirsPerDir;
5393 cDirsAtLevel *= g_cManyTreeSubdirsPerDir;
5394 }
5395 return g_cManyTreeFilesPerDir * cDirs;
5396}
5397
5398
5399int main(int argc, char *argv[])
5400{
5401 /*
5402 * Init IPRT and globals.
5403 */
5404 int rc = RTTestInitAndCreate("FsPerf", &g_hTest);
5405 if (rc)
5406 return rc;
5407 RTListInit(&g_ManyTreeHead);
5408
5409 /*
5410 * Default values.
5411 */
5412 char szDefaultDir[32];
5413 const char *pszDir = szDefaultDir;
5414 RTStrPrintf(szDefaultDir, sizeof(szDefaultDir), "fstestdir-%u" RTPATH_SLASH_STR, RTProcSelf());
5415
5416 bool fCommsSlave = false;
5417
5418 RTGETOPTUNION ValueUnion;
5419 RTGETOPTSTATE GetState;
5420 RTGetOptInit(&GetState, argc, argv, g_aCmdOptions, RT_ELEMENTS(g_aCmdOptions), 1, 0 /* fFlags */);
5421 while ((rc = RTGetOpt(&GetState, &ValueUnion)) != 0)
5422 {
5423 switch (rc)
5424 {
5425 case 'c':
5426 if (!g_fRelativeDir)
5427 rc = RTPathAbs(ValueUnion.psz, g_szCommsDir, sizeof(g_szCommsDir) - 128);
5428 else
5429 rc = RTStrCopy(g_szCommsDir, sizeof(g_szCommsDir) - 128, ValueUnion.psz);
5430 if (RT_FAILURE(rc))
5431 {
5432 RTTestFailed(g_hTest, "%s(%s) failed: %Rrc\n", g_fRelativeDir ? "RTStrCopy" : "RTAbsPath", pszDir, rc);
5433 return RTTestSummaryAndDestroy(g_hTest);
5434 }
5435 RTPathEnsureTrailingSeparator(g_szCommsDir, sizeof(g_szCommsDir));
5436 g_cchCommsDir = strlen(g_szCommsDir);
5437
5438 rc = RTPathJoin(g_szCommsSubDir, sizeof(g_szCommsSubDir) - 128, g_szCommsDir, "comms" RTPATH_SLASH_STR);
5439 if (RT_FAILURE(rc))
5440 {
5441 RTTestFailed(g_hTest, "RTPathJoin(%s,,'comms/') failed: %Rrc\n", g_szCommsDir, rc);
5442 return RTTestSummaryAndDestroy(g_hTest);
5443 }
5444 g_cchCommsSubDir = strlen(g_szCommsSubDir);
5445 break;
5446
5447 case 'C':
5448 fCommsSlave = true;
5449 break;
5450
5451 case 'd':
5452 pszDir = ValueUnion.psz;
5453 break;
5454
5455 case 'r':
5456 g_fRelativeDir = true;
5457 break;
5458
5459 case 's':
5460 if (ValueUnion.u32 == 0)
5461 g_nsTestRun = RT_NS_1SEC_64 * 10;
5462 else
5463 g_nsTestRun = ValueUnion.u32 * RT_NS_1SEC_64;
5464 break;
5465
5466 case 'm':
5467 if (ValueUnion.u64 == 0)
5468 g_nsTestRun = RT_NS_1SEC_64 * 10;
5469 else
5470 g_nsTestRun = ValueUnion.u64 * RT_NS_1MS;
5471 break;
5472
5473 case 'e':
5474 g_fManyFiles = true;
5475 g_fOpen = true;
5476 g_fFStat = true;
5477 g_fFChMod = true;
5478 g_fFUtimes = true;
5479 g_fStat = true;
5480 g_fChMod = true;
5481 g_fUtimes = true;
5482 g_fRename = true;
5483 g_fDirOpen = true;
5484 g_fDirEnum = true;
5485 g_fMkRmDir = true;
5486 g_fStatVfs = true;
5487 g_fRm = true;
5488 g_fChSize = true;
5489 g_fReadTests = true;
5490 g_fReadPerf = true;
5491#ifdef FSPERF_TEST_SENDFILE
5492 g_fSendFile = true;
5493#endif
5494#ifdef RT_OS_LINUX
5495 g_fSplice = true;
5496#endif
5497 g_fWriteTests= true;
5498 g_fWritePerf = true;
5499 g_fSeek = true;
5500 g_fFSync = true;
5501 g_fMMap = true;
5502 g_fCopy = true;
5503 break;
5504
5505 case 'z':
5506 g_fManyFiles = false;
5507 g_fOpen = false;
5508 g_fFStat = false;
5509 g_fFChMod = false;
5510 g_fFUtimes = false;
5511 g_fStat = false;
5512 g_fChMod = false;
5513 g_fUtimes = false;
5514 g_fRename = false;
5515 g_fDirOpen = false;
5516 g_fDirEnum = false;
5517 g_fMkRmDir = false;
5518 g_fStatVfs = false;
5519 g_fRm = false;
5520 g_fChSize = false;
5521 g_fReadTests = false;
5522 g_fReadPerf = false;
5523#ifdef FSPERF_TEST_SENDFILE
5524 g_fSendFile = false;
5525#endif
5526#ifdef RT_OS_LINUX
5527 g_fSplice = false;
5528#endif
5529 g_fWriteTests= false;
5530 g_fWritePerf = false;
5531 g_fSeek = false;
5532 g_fFSync = false;
5533 g_fMMap = false;
5534 g_fCopy = false;
5535 break;
5536
5537#define CASE_OPT(a_Stem) \
5538 case RT_CONCAT(kCmdOpt_,a_Stem): RT_CONCAT(g_f,a_Stem) = true; break; \
5539 case RT_CONCAT(kCmdOpt_No,a_Stem): RT_CONCAT(g_f,a_Stem) = false; break
5540 CASE_OPT(Open);
5541 CASE_OPT(FStat);
5542 CASE_OPT(FChMod);
5543 CASE_OPT(FUtimes);
5544 CASE_OPT(Stat);
5545 CASE_OPT(ChMod);
5546 CASE_OPT(Utimes);
5547 CASE_OPT(Rename);
5548 CASE_OPT(DirOpen);
5549 CASE_OPT(DirEnum);
5550 CASE_OPT(MkRmDir);
5551 CASE_OPT(StatVfs);
5552 CASE_OPT(Rm);
5553 CASE_OPT(ChSize);
5554 CASE_OPT(ReadTests);
5555 CASE_OPT(ReadPerf);
5556#ifdef FSPERF_TEST_SENDFILE
5557 CASE_OPT(SendFile);
5558#endif
5559#ifdef RT_OS_LINUX
5560 CASE_OPT(Splice);
5561#endif
5562 CASE_OPT(WriteTests);
5563 CASE_OPT(WritePerf);
5564 CASE_OPT(Seek);
5565 CASE_OPT(FSync);
5566 CASE_OPT(MMap);
5567 CASE_OPT(IgnoreNoCache);
5568 CASE_OPT(Copy);
5569
5570 CASE_OPT(ShowDuration);
5571 CASE_OPT(ShowIterations);
5572#undef CASE_OPT
5573
5574 case kCmdOpt_ManyFiles:
5575 g_fManyFiles = ValueUnion.u32 > 0;
5576 g_cManyFiles = ValueUnion.u32;
5577 break;
5578
5579 case kCmdOpt_NoManyFiles:
5580 g_fManyFiles = false;
5581 break;
5582
5583 case kCmdOpt_ManyTreeFilesPerDir:
5584 if (ValueUnion.u32 > 0 && ValueUnion.u32 <= _64M)
5585 {
5586 g_cManyTreeFilesPerDir = ValueUnion.u32;
5587 g_cManyTreeFiles = fsPerfCalcManyTreeFiles();
5588 break;
5589 }
5590 RTTestFailed(g_hTest, "Out of range --files-per-dir value: %u (%#x)\n", ValueUnion.u32, ValueUnion.u32);
5591 return RTTestSummaryAndDestroy(g_hTest);
5592
5593 case kCmdOpt_ManyTreeSubdirsPerDir:
5594 if (ValueUnion.u32 > 0 && ValueUnion.u32 <= 1024)
5595 {
5596 g_cManyTreeSubdirsPerDir = ValueUnion.u32;
5597 g_cManyTreeFiles = fsPerfCalcManyTreeFiles();
5598 break;
5599 }
5600 RTTestFailed(g_hTest, "Out of range --subdirs-per-dir value: %u (%#x)\n", ValueUnion.u32, ValueUnion.u32);
5601 return RTTestSummaryAndDestroy(g_hTest);
5602
5603 case kCmdOpt_ManyTreeDepth:
5604 if (ValueUnion.u32 <= 8)
5605 {
5606 g_cManyTreeDepth = ValueUnion.u32;
5607 g_cManyTreeFiles = fsPerfCalcManyTreeFiles();
5608 break;
5609 }
5610 RTTestFailed(g_hTest, "Out of range --tree-depth value: %u (%#x)\n", ValueUnion.u32, ValueUnion.u32);
5611 return RTTestSummaryAndDestroy(g_hTest);
5612
5613 case kCmdOpt_IoFileSize:
5614 if (ValueUnion.u64 == 0)
5615 g_cbIoFile = _512M;
5616 else
5617 g_cbIoFile = ValueUnion.u64;
5618 break;
5619
5620 case kCmdOpt_SetBlockSize:
5621 if (ValueUnion.u32 > 0)
5622 {
5623 g_cIoBlocks = 1;
5624 g_acbIoBlocks[0] = ValueUnion.u32;
5625 }
5626 else
5627 {
5628 RTTestFailed(g_hTest, "Invalid I/O block size: %u (%#x)\n", ValueUnion.u32, ValueUnion.u32);
5629 return RTTestSummaryAndDestroy(g_hTest);
5630 }
5631 break;
5632
5633 case kCmdOpt_AddBlockSize:
5634 if (g_cIoBlocks >= RT_ELEMENTS(g_acbIoBlocks))
5635 RTTestFailed(g_hTest, "Too many I/O block sizes: max %u\n", RT_ELEMENTS(g_acbIoBlocks));
5636 else if (ValueUnion.u32 == 0)
5637 RTTestFailed(g_hTest, "Invalid I/O block size: %u (%#x)\n", ValueUnion.u32, ValueUnion.u32);
5638 else
5639 {
5640 g_acbIoBlocks[g_cIoBlocks++] = ValueUnion.u32;
5641 break;
5642 }
5643 return RTTestSummaryAndDestroy(g_hTest);
5644
5645 case 'q':
5646 g_uVerbosity = 0;
5647 break;
5648
5649 case 'v':
5650 g_uVerbosity++;
5651 break;
5652
5653 case 'h':
5654 Usage(g_pStdOut);
5655 return RTEXITCODE_SUCCESS;
5656
5657 case 'V':
5658 {
5659 char szRev[] = "$Revision: 78360 $";
5660 szRev[RT_ELEMENTS(szRev) - 2] = '\0';
5661 RTPrintf(RTStrStrip(strchr(szRev, ':') + 1));
5662 return RTEXITCODE_SUCCESS;
5663 }
5664
5665 default:
5666 return RTGetOptPrintError(rc, &ValueUnion);
5667 }
5668 }
5669
5670 /*
5671 * Populate g_szDir.
5672 */
5673 if (!g_fRelativeDir)
5674 rc = RTPathAbs(pszDir, g_szDir, sizeof(g_szDir) - FSPERF_MAX_NEEDED_PATH);
5675 else
5676 rc = RTStrCopy(g_szDir, sizeof(g_szDir) - FSPERF_MAX_NEEDED_PATH, pszDir);
5677 if (RT_FAILURE(rc))
5678 {
5679 RTTestFailed(g_hTest, "%s(%s) failed: %Rrc\n", g_fRelativeDir ? "RTStrCopy" : "RTAbsPath", pszDir, rc);
5680 return RTTestSummaryAndDestroy(g_hTest);
5681 }
5682 RTPathEnsureTrailingSeparator(g_szDir, sizeof(g_szDir));
5683 g_cchDir = strlen(g_szDir);
5684
5685 /*
5686 * If communication slave, go do that and be done.
5687 */
5688 if (fCommsSlave)
5689 {
5690 if (pszDir == szDefaultDir)
5691 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "The slave must have a working directory specified (-d)!");
5692 return FsPerfCommsSlave();
5693 }
5694
5695 /*
5696 * Create the test directory with an 'empty' subdirectory under it,
5697 * execute the tests, and remove directory when done.
5698 */
5699 RTTestBanner(g_hTest);
5700 if (!RTPathExists(g_szDir))
5701 {
5702 /* The base dir: */
5703 rc = RTDirCreate(g_szDir, 0755,
5704 RTDIRCREATE_FLAGS_NOT_CONTENT_INDEXED_DONT_SET | RTDIRCREATE_FLAGS_NOT_CONTENT_INDEXED_NOT_CRITICAL);
5705 if (RT_SUCCESS(rc))
5706 {
5707 RTTestIPrintf(RTTESTLVL_ALWAYS, "Test dir: %s\n", g_szDir);
5708 rc = fsPrepTestArea();
5709 if (RT_SUCCESS(rc))
5710 {
5711 /* Profile RTTimeNanoTS(). */
5712 fsPerfNanoTS();
5713
5714 /* Do tests: */
5715 if (g_fManyFiles)
5716 fsPerfManyFiles();
5717 if (g_fOpen)
5718 fsPerfOpen();
5719 if (g_fFStat)
5720 fsPerfFStat();
5721 if (g_fFChMod)
5722 fsPerfFChMod();
5723 if (g_fFUtimes)
5724 fsPerfFUtimes();
5725 if (g_fStat)
5726 fsPerfStat();
5727 if (g_fChMod)
5728 fsPerfChmod();
5729 if (g_fUtimes)
5730 fsPerfUtimes();
5731 if (g_fRename)
5732 fsPerfRename();
5733 if (g_fDirOpen)
5734 vsPerfDirOpen();
5735 if (g_fDirEnum)
5736 vsPerfDirEnum();
5737 if (g_fMkRmDir)
5738 fsPerfMkRmDir();
5739 if (g_fStatVfs)
5740 fsPerfStatVfs();
5741 if (g_fRm || g_fManyFiles)
5742 fsPerfRm(); /* deletes manyfiles and manytree */
5743 if (g_fChSize)
5744 fsPerfChSize();
5745 if ( g_fReadPerf || g_fReadTests || g_fWritePerf || g_fWriteTests
5746#ifdef FSPERF_TEST_SENDFILE
5747 || g_fSendFile
5748#endif
5749#ifdef RT_OS_LINUX
5750 || g_fSplice
5751#endif
5752 || g_fSeek || g_fFSync || g_fMMap)
5753 fsPerfIo();
5754 if (g_fCopy)
5755 fsPerfCopy();
5756 }
5757
5758 /* Cleanup: */
5759 g_szDir[g_cchDir] = '\0';
5760 rc = RTDirRemoveRecursive(g_szDir, RTDIRRMREC_F_CONTENT_AND_DIR | (g_fRelativeDir ? RTDIRRMREC_F_NO_ABS_PATH : 0));
5761 if (RT_FAILURE(rc))
5762 RTTestFailed(g_hTest, "RTDirRemoveRecursive(%s,) -> %Rrc\n", g_szDir, rc);
5763 }
5764 else
5765 RTTestFailed(g_hTest, "RTDirCreate(%s) -> %Rrc\n", g_szDir, rc);
5766 }
5767 else
5768 RTTestFailed(g_hTest, "Test directory already exists: %s\n", g_szDir);
5769
5770 return RTTestSummaryAndDestroy(g_hTest);
5771}
5772
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