VirtualBox

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

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

FsPerf: Working on adding a slave that runs on the host and so we can check whether the guest sees host changes as they occur. [build fix] bugref:9172

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

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