diff --git a/CmdLineParser/CmdLineParser.cpp b/CmdLineParser/CmdLineParser.cpp index 075c0bd..c614dfb 100644 --- a/CmdLineParser/CmdLineParser.cpp +++ b/CmdLineParser/CmdLineParser.cpp @@ -1,1959 +1,2070 @@ -/* - -DISKSPD - -Copyright(c) Microsoft Corporation -All rights reserved. - -MIT License - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - -*/ - -#include "CmdLineParser.h" -#include "Common.h" -#include "XmlProfileParser.h" -#include -#include -#include - -CmdLineParser::CmdLineParser() : - _dwBlockSize(64 * 1024), - _ulWriteRatio(0), - _hEventStarted(nullptr), - _hEventFinished(nullptr) -{ -} - -CmdLineParser::~CmdLineParser() -{ -} - -// Get size in bytes from a string (KMGTb) -bool CmdLineParser::_GetSizeInBytes(const char *pszSize, UINT64& ullSize, const char **pszRest) const -{ - bool fOk = true; - UINT64 ullResult = 0; - UINT64 ullMultiplier = 1; - const char *rest = nullptr; - - fOk = Util::ParseUInt(pszSize, ullResult, rest); - - if (fOk) - { - char ch = static_cast(toupper(*rest)); - - switch (ch) - { - case '\0': { break; } - case 'T': { ullMultiplier *= 1024; } - case 'G': { ullMultiplier *= 1024; } - case 'M': { ullMultiplier *= 1024; } - case 'K': { ullMultiplier *= 1024; ++rest; break; } - case 'B': { ullMultiplier = _dwBlockSize; ++rest; break; } - default: { - - // - // If caller is not expecting continuation, we know this is malformed now and - // can say so with respect to size specifiers. - // If there is continuation, caller is responsible for validating. - // - - if (!pszRest) - { - fOk = false; - fprintf(stderr, "Invalid size '%c'. Valid: K - KiB, M - MiB, G - GiB, T - TiB, B - block\n", *rest); - } - } - } - - if (fOk) - { - // - // Second chance after parsing valid size qualifier. - // - - if (!pszRest && *rest != '\0') - { - fOk = false; - fprintf(stderr, "Unrecognized characters after size specification\n"); - } - - // - // Now apply size specifier. - // - - else if (ullResult <= MAXUINT64 / ullMultiplier) - { - ullResult *= ullMultiplier; - } - else - { - // overflow - fOk = false; - fprintf(stderr, "Overflow applying multipler '%c'\n", ch); - } - } - } - else - { - fprintf(stderr, "Invalid integer\n"); - } - - if (fOk) - { - ullSize = ullResult; - - if (pszRest) - { - *pszRest = rest; - } - } - - return fOk; -} - -bool CmdLineParser::_GetRandomDataWriteBufferData(const string& sArg, UINT64& cb, string& sPath) -{ - bool fOk = true; - size_t iComma = sArg.find(','); - if (iComma == sArg.npos) - { - fOk = _GetSizeInBytes(sArg.c_str(), cb, nullptr); - sPath = ""; - } - else - { - fOk = _GetSizeInBytes(sArg.substr(0, iComma).c_str(), cb, nullptr); - sPath = sArg.substr(iComma + 1); - } - return fOk; -} - -void CmdLineParser::_DisplayUsageInfo(const char *pszFilename) const -{ - // ISSUE-REVIEW: this formats badly in the default 80 column command prompt - printf("\n"); - printf("Usage: %s [options] target1 [ target2 [ target3 ...] ]\n", pszFilename); - printf("version %s (%s)\n", DISKSPD_NUMERIC_VERSION_STRING, DISKSPD_DATE_VERSION_STRING); - printf("\n"); - - printf( - "Valid targets:\n" - " file_path\n" - " #\n" - " :\n" - "\n" - "Available options:\n" - " -? display usage information\n" - " -ag group affinity - affinitize threads round-robin to cores in Processor Groups 0 - n.\n" - " Group 0 is filled before Group 1, and so forth.\n" - " [default; use -n to disable default affinity]\n" - " -ag#,#[,#,...]> advanced CPU affinity - affinitize threads round-robin to the CPUs provided. The g# notation\n" - " specifies Processor Groups for the following CPU core #s. Multiple Processor Groups\n" - " may be specified, and groups/cores may be repeated. If no group is specified, 0 is assumed.\n" - " Additional groups/processors may be added, comma separated, or on separate parameters.\n" - " Examples: -a0,1,2 and -ag0,0,1,2 are equivalent.\n" - " -ag0,0,1,2,g1,0,1,2 specifies the first three cores in groups 0 and 1.\n" - " -ag0,0,1,2 -ag1,0,1,2 is equivalent.\n" - " -b[KMGT] block size in bytes or KiB/MiB/GiB/TiB [default=64K]\n" - " -B[KMGTb] base target offset in bytes or KiB/MiB/GiB/TiB/blocks [default=0]\n" - " (offset from the beginning of the file)\n" - " -c[KMGTb] create files of the given size.\n" - " Size can be stated in bytes or KiB/MiB/GiB/TiB/blocks\n" - " -C cool down time - duration of the test after measurements finished [default=0s].\n" - " -D Capture IOPs statistics in intervals of ; these are per-thread\n" - " per-target: text output provides IOPs standard deviation, XML provides the full\n" - " IOPs time series in addition. [default=1000, 1 second].\n" - " -d duration (in seconds) to run test [default=10s]\n" - " -f[KMGTb] target size - use only the first bytes or KiB/MiB/GiB/TiB/blocks of the file/disk/partition,\n" - " for example to test only the first sectors of a disk\n" - " -f open file with one or more additional access hints\n" - " r : the FILE_FLAG_RANDOM_ACCESS hint\n" - " s : the FILE_FLAG_SEQUENTIAL_SCAN hint\n" - " t : the FILE_ATTRIBUTE_TEMPORARY hint\n" - " [default: none]\n" - " -F total number of threads (conflicts with -t)\n" - " -g[i] throughput per-thread per-target throttled to given value; defaults to bytes per millisecond\n" - " With the optional i qualifier the value is IOPS of the specified block size (-b).\n" - " Throughput limits cannot be specified when using completion routines (-x)\n" - " [default: no limit]\n" - " -h deprecated, see -Sh\n" - " -i number of IOs per burst; see -j [default: inactive]\n" - " -j interval in between issuing IO bursts; see -i [default: inactive]\n" - " -I Set IO priority to . Available values are: 1-very low, 2-low, 3-normal (default)\n" - " -l Use large pages for IO buffers\n" - " -L measure latency statistics\n" - " -n disable default affinity (-a)\n" - " -N specify the flush mode for memory mapped I/O\n" - " v : uses the FlushViewOfFile API\n" - " n : uses the RtlFlushNonVolatileMemory API\n" - " i : uses RtlFlushNonVolatileMemory without waiting for the flush to drain\n" - " [default: none]\n" - " -o number of outstanding I/O requests per target per thread\n" - " (1=synchronous I/O, unless more than 1 thread is specified with -F)\n" - " [default=2]\n" - " -O number of outstanding I/O requests per thread - for use with -F\n" - " (1=synchronous I/O)\n" - " -p start parallel sequential I/O operations with the same offset\n" - " (ignored if -r is specified, makes sense only with -o2 or greater)\n" - " -P enable printing a progress dot after each [default=65536]\n" - " completed I/O operations, counted separately by each thread \n" - " -r[align[KMGTb]] random I/O aligned to in bytes/KiB/MiB/GiB/TiB/blocks (overrides -s)\n" - " [default alignment=block size (-b)]\n" - " -rd[params] specify an non-uniform distribution for random IO in the target\n" - " [default uniformly random]\n" - " distributions: pct, abs\n" - " all: IO%% and %%Target/Size are cumulative. If the sum of IO%% is less than 100%% the\n" - " remainder is applied to the remainder of the target. An IO%% of 0 indicates a gap -\n" - " no IO will be issued to that range of the target.\n" - " pct : parameter is a combination of IO%%/%%Target separated by : (colon)\n" - " Example: -rdpct90/10:0/10:5/20 specifies 90%% of IO in 10%% of the target, no IO\n" - " next 10%%, 5%% IO in the next 20%% and the remaining 5%% of IO in the last 60%%\n" - " abs : parameter is a combination of IO%%/Target Size separated by : (colon)\n" - " If the actual target size is smaller than the distribution, the relative values of IO%%\n" - " for the valid elements define the effective distribution.\n" - " Example: -rdabs90/10G:0/10G:5/20G specifies 90%% of IO in 10GiB of the target, no IO\n" - " next 10GiB, 5%% IO in the next 20GiB and the remaining 5%% of IO in the remaining\n" - " capacity of the target. If the target is only 20G, the distribution truncates at\n" - " 90/10G:0:10G and all IO is directed to the first 10G (equivalent to -f10G).\n" - " -rs percentage of requests which should be issued randomly. When used, -r may be used to\n" - " specify IO alignment (applies to both the random and sequential portions of the load).\n" - " Sequential IO runs will be homogeneous if a mixed ratio is specified (-w), and run\n" - " lengths will follow a geometric distribution based on the percentage split.\n" - " -R[p] output format. With the p prefix, the input profile (command line or XML) is validated and\n" - " re-output in the specified format without running load, useful for checking or building\n" - " complex profiles.\n" - " [default: text]\n" - " -s[i][align[KMGTb]] stride size of in bytes/KiB/MiB/GiB/TiB/blocks, alignment/offset between operations\n" - " [default=non-interlocked, default alignment=block size (-b)]\n" - " By default threads track independent sequential IO offsets starting at offset 0 of the target.\n" - " With multiple threads this results in threads overlapping their IOs - see -T to divide\n" - " them into multiple separate sequential streams on the target.\n" - " With the optional i qualifier (-si) threads interlock on a shared sequential offset.\n" - " Interlocked operations may introduce overhead but make it possible to issue a single\n" - " sequential stream to a target which responds faster than a one thread can drive.\n" - " (ignored if -r specified, -si conflicts with -p, -rs and -T)\n" - " -S[bhmruw] control caching behavior [default: caching is enabled, no writethrough]\n" - " non-conflicting flags may be combined in any order; ex: -Sbw, -Suw, -Swu\n" - " -S equivalent to -Su\n" - " -Sb enable caching (default, explicitly stated)\n" - " -Sh equivalent -Suw\n" - " -Sm enable memory mapped I/O\n" - " -Su disable software caching, equivalent to FILE_FLAG_NO_BUFFERING\n" - " -Sr disable local caching, with remote sw caching enabled; only valid for remote filesystems\n" - " -Sw enable writethrough (no hardware write caching), equivalent to FILE_FLAG_WRITE_THROUGH or\n" - " non-temporal writes for memory mapped I/O (-Sm)\n" - " -t number of threads per target (conflicts with -F)\n" - " -T[KMGTb] starting stride between I/O operations performed on the same target by different threads\n" - " [default=0] (starting offset = base file offset + (thread number * )\n" - " only applies with #threads > 1\n" - " -v verbose mode\n" - " -w percentage of write requests (-w and -w0 are equivalent and result in a read-only workload).\n" - " absence of this switch indicates 100%% reads\n" - " IMPORTANT: a write test will destroy existing data without a warning\n" - " -W warm up time - duration of the test before measurements start [default=5s]\n" - " -x use completion routines instead of I/O Completion Ports\n" - " -X use an XML file to configure the workload. Combine with -R, -v and -z to override profile defaults.\n" - " Targets can be defined in XML profiles as template paths of the form * (*1, *2, ...).\n" - " When run, specify the paths to substitute for the template paths in order on the command line.\n" - " The first specified target is *1, second is *2, and so on.\n" - " Example: diskspd -Xprof.xml first.bin second.bin (prof.xml using *1 and *2)\n" - " -z[seed] set random seed [with no -z, seed=0; with plain -z, seed is based on system run time]\n" - "\n" - "Write buffers:\n" - " -Z zero buffers used for write tests\n" - " -Zr per IO random buffers used for write tests - this incurrs additional run-time\n" - " overhead to create random content and shouln't be compared to results run\n" - " without -Zr\n" - " -Z[KMGb] use a buffer filled with random data as a source for write operations.\n" - " -Z[KMGb], use a buffer filled with data from as a source for write operations.\n" - "\n" - " By default, the write buffers are filled with a repeating pattern (0, 1, 2, ..., 255, 0, 1, ...)\n" - "\n" - "Synchronization:\n" - " -ys signals event before starting the actual run (no warmup)\n" - " (creates a notification event if does not exist)\n" - " -yf signals event after the actual run finishes (no cooldown)\n" - " (creates a notification event if does not exist)\n" - " -yr waits on event before starting the run (including warmup)\n" - " (creates a notification event if does not exist)\n" - " -yp stops the run when event is set; CTRL+C is bound to this event\n" - " (creates a notification event if does not exist)\n" - " -ye sets event and quits\n" - "\n" - "Event Tracing:\n" - " -e Use query perf timer (qpc), cycle count, or system timer respectively.\n" - " [default = q, query perf timer (qpc)]\n" - " -ep use paged memory for the NT Kernel Logger [default=non-paged memory]\n" - " -ePROCESS process start & end\n" - " -eTHREAD thread start & end\n" - " -eIMAGE_LOAD image load\n" - " -eDISK_IO physical disk IO\n" - " -eMEMORY_PAGE_FAULTS all page faults\n" - " -eMEMORY_HARD_FAULTS hard faults only\n" - " -eNETWORK TCP/IP, UDP/IP send & receive\n" - " -eREGISTRY registry calls\n" - "\n\n"); - - printf("Examples:\n\n"); - printf("Create 8192KB file and run read test on it for 1 second:\n\n"); - printf(" %s -c8192K -d1 testfile.dat\n", pszFilename); - printf("\n"); - printf("Set block size to 4KB, create 2 threads per file, 32 overlapped (outstanding)\n"); - printf("I/O operations per thread, disable all caching mechanisms and run block-aligned random\n"); - printf("access read test lasting 10 seconds:\n\n"); - printf(" %s -b4K -t2 -r -o32 -d10 -Sh testfile.dat\n\n", pszFilename); - printf("Create two 1GB files, set block size to 4KB, create 2 threads per file, affinitize threads\n"); - printf("to CPUs 0 and 1 (each file will have threads affinitized to both CPUs) and run read test\n"); - printf("lasting 10 seconds:\n\n"); - printf(" %s -c1G -b4K -t2 -d10 -a0,1 testfile1.dat testfile2.dat\n", pszFilename); - - printf("\n"); -} - -bool CmdLineParser::_ParseETWParameter(const char *arg, Profile *pProfile) -{ - assert(nullptr != arg); - assert(0 != *arg); - - bool fOk = true; - pProfile->SetEtwEnabled(true); - if (*(arg + 1) != '\0') - { - const char *c = arg + 1; - if (*c == 'p') - { - pProfile->SetEtwUsePagedMemory(true); - } - else if (*c == 'q') - { - pProfile->SetEtwUsePerfTimer(true); - } - else if (*c == 's') - { - pProfile->SetEtwUseSystemTimer(true); //default - } - else if (*c == 'c') - { - pProfile->SetEtwUseCyclesCounter(true); - } - else if (strcmp(c, "PROCESS") == 0) //process start & end - { - pProfile->SetEtwProcess(true); - } - else if (strcmp(c, "THREAD") == 0) //thread start & end - { - pProfile->SetEtwThread(true); - } - else if (strcmp(c, "IMAGE_LOAD") == 0) //image load - { - pProfile->SetEtwImageLoad(true); - } - else if (strcmp(c, "DISK_IO") == 0) //physical disk IO - { - pProfile->SetEtwDiskIO(true); - } - else if (strcmp(c, "MEMORY_PAGE_FAULTS") == 0) //all page faults - { - pProfile->SetEtwMemoryPageFaults(true); - } - else if (strcmp(c, "MEMORY_HARD_FAULTS") == 0) //hard faults only - { - pProfile->SetEtwMemoryHardFaults(true); - } - else if (strcmp(c, "NETWORK") == 0) //tcpip send & receive - { - pProfile->SetEtwNetwork(true); - } - else if (strcmp(c, "REGISTRY") == 0) //registry calls - { - pProfile->SetEtwRegistry(true); - } - else - { - fOk = false; - } - } - else - { - fOk = false; - } - - return fOk; -} - -bool CmdLineParser::_ParseAffinity(const char *arg, TimeSpan *pTimeSpan) -{ - bool fOk = true; - - assert(nullptr != arg); - assert('\0' != *arg); - - const char *c = arg + 1; - - // -a and -ag are functionally equivalent; group-aware affinity. - // Note that group-aware affinity is default. - - // look for the -a simple case - if (*c == '\0') - { - return true; - } - - // look for the -ag simple case - if (*c == 'g') - { - // peek ahead, done? - if (*(c + 1) == '\0') - { - return true; - } - - // leave the parser at the g; this is the start of a group number - } - - // more complex affinity -ag0,0,1,2,g1,0,1,2,... OR -a0,1,2,.. - // n counts the -a prefix, the first parsed character is string index 2 - DWORD nGroup = 0, nNum = 0, n = 2; - bool fGroup = false, fNum = false; - while (*c != '\0') - { - if ((*c >= '0') && (*c <= '9')) - { - // accumulating a number - fNum = true; - nNum = 10 * nNum + (*c - '0'); - } - else if (*c == 'g') - { - // bad: ggggg - if (fGroup) - { - fOk = false; - } - - // now parsing a group number - fGroup = true; - } - else if (*c == ',') - { - // separator; if parsing group and have a number, now have the group - if (fGroup && fNum) - { - if (nNum > MAXWORD) - { - fprintf(stderr, "ERROR: group %u is out of range\n", nNum); - fOk = false; - } - else - { - nGroup = nNum; - nNum = 0; - fGroup = false; - } - } - // at a split but don't have a parsed number, error - else if (!fNum) - { - fOk = false; - } - // have a parsed core number - else - { - if (nNum > MAXBYTE) - { - fprintf(stderr, "ERROR: core %u is out of range\n", nNum); - fOk = false; - } - else - { - pTimeSpan->AddAffinityAssignment((WORD)nGroup, (BYTE)nNum); - nNum = 0; - fNum = false; - } - } - } - else - { - fOk = false; - } - - // bail out to error pretty print on error - if (!fOk) - { - break; - } - - c++; - n++; - } - - // if parsing a group or don't have a final number, error - if (fGroup || !fNum) - { - fOk = false; - } - - if (fOk && nNum > MAXBYTE) - { - fprintf(stderr, "ERROR: core %u is out of range\n", nNum); - fOk = false; - } - - if (!fOk) - { - // mid-parse error, show the point at which it occured - if (*c != '\0') { - fprintf(stderr, "ERROR: syntax error parsing affinity at highlighted character\n-%s\n", arg); - while (n-- > 0) - { - fprintf(stderr, " "); - } - fprintf(stderr, "^\n"); - } - else - { - fprintf(stderr, "ERROR: incomplete affinity specification\n"); - } - } - - if (fOk) - { - // fprintf(stderr, "FINAL parsed group %d core %d\n", nGroup, nNum); - pTimeSpan->AddAffinityAssignment((WORD)nGroup, (BYTE)nNum); - } - - return fOk; -} - -bool CmdLineParser::_ParseFlushParameter(const char *arg, MemoryMappedIoFlushMode *FlushMode) -{ - assert(nullptr != arg); - assert(0 != *arg); - - bool fOk = true; - if (*(arg + 1) != '\0') - { - const char *c = arg + 1; - if (_stricmp(c, "v") == 0) - { - *FlushMode = MemoryMappedIoFlushMode::ViewOfFile; - } - else if (_stricmp(c, "n") == 0) - { - *FlushMode = MemoryMappedIoFlushMode::NonVolatileMemory; - } - else if (_stricmp(c, "i") == 0) - { - *FlushMode = MemoryMappedIoFlushMode::NonVolatileMemoryNoDrain; - } - else - { - fOk = false; - } - } - else - { - fOk = false; - } - return fOk; -} - -bool CmdLineParser::_ParseRandomDistribution(const char *arg, vector& vTargets) -{ - vector vOr; - DistributionType dType; - bool fOk = false; - UINT32 pctAcc = 0, pctCur; // accumulated/cur pct io - UINT64 targetAcc = 0, targetCur; // accumulated/cur target - - if (!strncmp(arg, "pct", 3)) - { - dType = DistributionType::Percent; - } - else if (strncmp(arg, "abs", 3)) - { - fprintf(stderr, "Unrecognized random distribution type\n"); - return false; - } - else - { - dType = DistributionType::Absolute; - } - - arg += 3; - - // - // Parse pairs of - // - // * pct: percentage/target percentage - // * abs: percentage/absolute range of target - // - // Ex: 90/10:5/5 => [0,90) -> [0, 10) :: [90, 95) -> [10, 15) - // a remainder of [95, 100) -> [15, 100) would be applied. - // - // Percentages are cumulative and successively define the span of - // the preceding definition. Absolute ranges are also cumulative: - // 10/1G:90/1G puts 90% of accesses in the second 1G range of the - // target. - // - // A single percentage can be 100 but is of limited value since it - // would only be valid as a single element distribution. - // - // Basic numeric validations are done here (similar to XSD for XML). - // Cross validation with other workload parameters (blocksize) and whole - // distribution validation is delayed to common code. - // - - while (true) - { - // Consume IO% integer - fOk = Util::ParseUInt(arg, pctCur, arg); - if (!fOk) - { - fprintf(stderr, "Invalid integer IO%%: must be > 0 and <= %u\n", 100 - pctAcc); - return false; - } - // hole is ok - else if (pctCur > 100) - { - fprintf(stderr, "Invalid IO%% %u: must be >= 0 and <= %u\n", pctCur, 100 - pctAcc); - return false; - } - - // Expect separator - if (*arg++ != '/') - { - fprintf(stderr, "Expected / separator after %u\n", pctCur); - return false; - } - - // Consume Target%/Absolute range integer - if (dType == DistributionType::Percent) - { - // Percent specification - fOk = Util::ParseUInt(arg, targetCur, arg); - if (!fOk) - { - fprintf(stderr, "Invalid integer Target%%: must be > 0 and <= %I64u\n", 100 - targetAcc); - return false; - } - // no hole - else if (targetCur == 0 || targetCur > 100) - { - fprintf(stderr, "Invalid Target%% %I64u: must be > 0 and <= %I64u\n", targetCur, 100 - targetAcc); - return false; - } - } - else - { - // Size specification - fOk = CmdLineParser::_GetSizeInBytes(arg, targetCur, &arg); - if (!fOk) - { - // error already emitted - return fOk; - } - - if (targetCur == 0) - { - fprintf(stderr, "Invalid zero length target range\n"); - return false; - } - } - - // Add range from [accumulator - accumulator + current) => ... - // Note that zero pctCur indicates a hole where no IO is desired - this is recorded - // for fidelity of display/profile but will never match on lookup, as intended. - vOr.emplace_back(pctAcc, pctCur, make_pair(targetAcc, targetCur)); - - // Now move accumulators for the next tuple/completion - pctAcc += pctCur; - targetAcc += targetCur; - - // Expect/consume separator for next tuple? - if (*arg == ':') - { - ++arg; - continue; - } - - // Done? - if (*arg == '\0') - { - break; - } - - fprintf(stderr, "Unexpected characters in specification '%s'\n", arg); - return false; - } - - // Apply to all targets - for (auto& t : vTargets) - { - t.SetDistributionRange(vOr, dType); - } - - return true; -} - -bool CmdLineParser::_ReadParametersFromCmdLine(const int argc, const char *argv[], Profile *pProfile, struct Synchronization *synch, bool& fXMLProfile) -{ - int nParamCnt = argc - 1; - const char** args = argv + 1; - bool fError = false; - - TimeSpan timeSpan; - - // - // Pass 1 - determine parameter set type: cmdline specification or XML, and preparse targets/blocksize - // - - ParseState isXMLSet = ParseState::Unknown; - - ParseState isXMLResultFormat = ParseState::Unknown; - ParseState isProfileOnly = ParseState::Unknown; - ParseState isVerbose = ParseState::Unknown; - ParseState isRandomSeed = ParseState::Unknown; - ParseState isWarmupTime = ParseState::Unknown; - ParseState isDurationTime = ParseState::Unknown; - ParseState isCooldownTime = ParseState::Unknown; - - ULONG randomSeedValue = 0; - ULONG warmupTime = 0; - ULONG durationTime = 0; - ULONG cooldownTime = 0; - const char *xmlProfile = nullptr; - - // - // Find all target specifications. Note that this assumes all non-target - // parameters are single tokens; e.g. "-Hsomevalue" and never "-H somevalue". - // Targets follow parameter specifications. - // - - vector vTargets; - for (int i = 0, inTargets = false; i < nParamCnt; ++i) - { - if (!_IsSwitchChar(args[i][0])) - { - inTargets = true; - - Target target; - target.SetPath(args[i]); - vTargets.push_back(target); - } - else if (inTargets) - { - fprintf(stderr, "ERROR: parameters (%s) must come before targets on the command line\n", args[i]); - return false; - } - } - - // - // Find composable and dependent parameters as we resolve the parameter set. - // - - for (int i = 0; i < nParamCnt; ++i) - { - if (_IsSwitchChar(args[i][0])) - { - const char *arg = &args[i][2]; - - switch(args[i][1]) - { - - case 'b': - - // Block size does not compose with XML profile spec - if (isXMLSet == ParseState::True) - { - fprintf(stderr, "ERROR: -b is not compatible with -X XML profile specification\n"); - return false; - } - else - { - UINT64 ullBlockSize; - if (_GetSizeInBytes(arg, ullBlockSize, nullptr) && ullBlockSize < MAXUINT32) - { - for (auto i = vTargets.begin(); i != vTargets.end(); i++) - { - i->SetBlockSizeInBytes((DWORD)ullBlockSize); - } - } - else - { - fprintf(stderr, "ERROR: invalid block size passed to -b\n"); - return false; - } - _dwBlockSize = (DWORD)ullBlockSize; - - isXMLSet = ParseState::False; - } - break; - - case 'C': - { - int c = atoi(arg); - if (c >= 0) - { - cooldownTime = c; - isCooldownTime = ParseState::True; - } - else - { - fprintf(stderr, "ERROR: invalid cooldown time (-C): '%s'\n", arg); - return false; - } - } - break; - - case 'd': - { - int c = atoi(arg); - if (c >= 0) - { - durationTime = c; - isDurationTime = ParseState::True; - } - else - { - fprintf(stderr, "ERROR: invalid measured duration time (-d): '%s'\n", arg); - return false; - } - } - break; - - case 'W': - { - int c = atoi(arg); - if (c >= 0) - { - warmupTime = c; - isWarmupTime = ParseState::True; - } - else - { - fprintf(stderr, "ERROR: invalid warmup time (-W): '%s'\n", arg); - return false; - } - } - break; - - case 'R': - - // re-output profile only (no run) - if ('p' == *arg) - { - isProfileOnly = ParseState::True; - ++arg; - } - - if ('\0' != *arg) - { - // Explicit results format - if (strcmp(arg, "xml") == 0) - { - isXMLResultFormat = ParseState::True; - } - else if (strcmp(arg, "text") != 0) - { - fprintf(stderr, "ERROR: invalid results format (-R): '%s'\n", arg); - return false; - } - else - { - isXMLResultFormat = ParseState::False; - } - } - else - { - // allow for -Rp shorthand for default profile-only format - if (isProfileOnly != ParseState::True) - { - fprintf(stderr, "ERROR: unspecified results format -R: use [p]\n"); - return false; - } - } - break; - - case 'v': - - isVerbose = ParseState::True; - break; - - case 'X': - - if (isXMLSet == ParseState::Unknown) - { - isXMLSet = ParseState::True; - } - else - { - fprintf(stderr, "ERROR: multiple XML profiles specified (-X)\n"); - return false; - } - xmlProfile = arg; - break; - - case 'z': - { - char *endPtr = nullptr; - - if (*arg == '\0') - { - randomSeedValue = (ULONG) GetTickCount64(); - } - else - { - randomSeedValue = strtoul(arg, &endPtr, 10); - if (*endPtr != '\0') - { - fprintf(stderr, "ERROR: invalid random seed value '%s' specified - must be a valid 32 bit integer\n", arg); - return false; - } - } - - isRandomSeed = ParseState::True; - } - break; - - default: - // no other switches are valid in combination with -X - // if we've seen X, this means it is bad - // if not, we know it will not be X - if (isXMLSet == ParseState::True) - { - fprintf(stderr, "ERROR: invalid XML profile specification; parameter %s not compatible with -X\n", args[i]); - return false; - } - else - { - isXMLSet = ParseState::False; - } - } - } - } - - // XML profile? - if (isXMLSet == ParseState::True) - { - if (!_ReadParametersFromXmlFile(xmlProfile, pProfile, &vTargets)) - { - return false; - } - } - - // - // Apply profile common parameters - note that results format is unmodified if R not explicitly provided - // - - if (isXMLResultFormat == ParseState::True) - { - pProfile->SetResultsFormat(ResultsFormat::Xml); - } - else if (isXMLResultFormat == ParseState::False) - { - pProfile->SetResultsFormat(ResultsFormat::Text); - } - - if (isProfileOnly == ParseState::True) - { - pProfile->SetProfileOnly(true); - } - - if (isVerbose == ParseState::True) - { - pProfile->SetVerbose(true); - } - - // - // Apply timespan common composable parameters - // - - if (isXMLSet == ParseState::True) - { - for (auto& ts : const_cast &>(pProfile->GetTimeSpans())) - { - if (isRandomSeed == ParseState::True) { ts.SetRandSeed(randomSeedValue); } - if (isWarmupTime == ParseState::True) { ts.SetWarmup(warmupTime); } - if (isDurationTime == ParseState::True) { ts.SetDuration(durationTime); } - if (isCooldownTime == ParseState::True) { ts.SetCooldown(cooldownTime); } - } - } - else - { - if (isRandomSeed == ParseState::True) { timeSpan.SetRandSeed(randomSeedValue); } - if (isWarmupTime == ParseState::True) { timeSpan.SetWarmup(warmupTime); } - if (isDurationTime == ParseState::True) { timeSpan.SetDuration(durationTime); } - if (isCooldownTime == ParseState::True) { timeSpan.SetCooldown(cooldownTime); } - } - - // Now done if XML profile - if (isXMLSet == ParseState::True) - { - fXMLProfile = true; - return true; - } - - // - // Parse full command line for profile - // - - // initial parse for cache/writethrough - // these are built up across the entire cmd line and applied at the end. - // this allows for conflicts to be thrown for mixed -h/-S as needed. - TargetCacheMode t = TargetCacheMode::Undefined; - WriteThroughMode w = WriteThroughMode::Undefined; - MemoryMappedIoMode m = MemoryMappedIoMode::Undefined; - MemoryMappedIoFlushMode f = MemoryMappedIoFlushMode::Undefined; - - bool bExit = false; - while (nParamCnt) - { - const char* arg = *args; - const char* const carg = arg; // save for error reporting, arg is modified during parse - - // Targets follow parameters on command line. If this is a target, we are done now. - if (!_IsSwitchChar(*arg)) - { - break; - } - - // skip switch character, provide length - ++arg; - const size_t argLen = strlen(arg); - - switch (*arg) - { - case '?': - _DisplayUsageInfo(argv[0]); - exit(0); - - case 'a': //affinity - //-a1,2,3,4 (assign threads to cpus 1,2,3,4 (round robin)) - if (!_ParseAffinity(arg, &timeSpan)) - { - fError = true; - } - break; - - case 'b': //block size - // nop - block size has been taken care of before the loop - break; - - case 'B': //base file offset (offset from the beginning of the file) - if (*(arg + 1) != '\0') - { - UINT64 cb; - if (_GetSizeInBytes(arg + 1, cb, nullptr)) - { - for (auto i = vTargets.begin(); i != vTargets.end(); i++) - { - i->SetBaseFileOffsetInBytes(cb); - } - } - else - { - fprintf(stderr, "ERROR: invalid base file offset passed to -B\n"); - fError = true; - } - } - else - { - fError = true; - } - break; - - case 'c': //create file of the given size - if (*(arg + 1) != '\0') - { - UINT64 cb; - if (_GetSizeInBytes(arg + 1, cb, nullptr)) - { - for (auto i = vTargets.begin(); i != vTargets.end(); i++) - { - i->SetFileSize(cb); - i->SetCreateFile(true); - } - } - else - { - fprintf(stderr, "ERROR: invalid file size passed to -c\n"); - fError = true; - } - } - else - { - fError = true; - } - break; - - case 'C': //cool down time - pass 1 composable - break; - - case 'd': //duration - pass 1 composable - break; - - case 'D': //standard deviation - { - timeSpan.SetCalculateIopsStdDev(true); - - int x = atoi(arg + 1); - if (x > 0) - { - timeSpan.SetIoBucketDurationInMilliseconds(x); - } - } - break; - - case 'e': //etw - if (!_ParseETWParameter(arg, pProfile)) - { - fError = true; - } - break; - - case 'f': - if (isdigit(*(arg + 1))) - { - UINT64 cb; - if (_GetSizeInBytes(arg + 1, cb, nullptr)) - { - for (auto i = vTargets.begin(); i != vTargets.end(); i++) - { - i->SetMaxFileSize(cb); - } - } - else - { - fprintf(stderr, "ERROR: invalid max file size passed to -f\n"); - fError = true; - } - } - else - { - if ('\0' == *(arg + 1)) - { - fError = true; - } - else - { - // while -frs (or -fsr) are generally conflicting intentions as far as - // the OS is concerned, do not enforce - while (*(++arg) != '\0') - { - switch (*arg) - { - case 'r': - for (auto i = vTargets.begin(); i != vTargets.end(); i++) - { - i->SetRandomAccessHint(true); - } - break; - case 's': - for (auto i = vTargets.begin(); i != vTargets.end(); i++) - { - i->SetSequentialScanHint(true); - } - break; - case 't': - for (auto i = vTargets.begin(); i != vTargets.end(); i++) - { - i->SetTemporaryFileHint(true); - } - break; - default: - fError = true; - break; - } - } - } - } - break; - - case 'F': //total number of threads - { - int c = atoi(arg + 1); - if (c > 0) - { - timeSpan.SetThreadCount(c); - } - else - { - fError = true; - } - } - break; - - case 'g': //throughput in bytes per millisecond (gNNN) OR iops (gNNNi) - { - // units? - bool isBpms = false; - if (isdigit(arg[argLen - 1])) - { - isBpms = true; - } - else if (arg[argLen - 1] != 'i') - { - // not IOPS, so its bad - fError = true; - } - - if (!fError) - { - int c = atoi(arg + 1); - if (c > 0) - { - for (auto i = vTargets.begin(); i != vTargets.end(); i++) - { - if (isBpms) - { - i->SetThroughput(c); - } - else - { - i->SetThroughputIOPS(c); - } - } - } - else - { - fError = true; - } - } - } - break; - - case 'h': // compat: disable os cache and set writethrough; now equivalent to -Sh - if (t == TargetCacheMode::Undefined && - w == WriteThroughMode::Undefined) - { - t = TargetCacheMode::DisableOSCache; - w = WriteThroughMode::On; - } - else - { - fprintf(stderr, "ERROR: -h conflicts with earlier specification of cache/writethrough\n"); - fError = true; - } - break; - - case 'i': //number of IOs to issue before think time - { - int c = atoi(arg + 1); - if (c > 0) - { - for (auto i = vTargets.begin(); i != vTargets.end(); i++) - { - i->SetBurstSize(c); - i->SetUseBurstSize(true); - } - } - else - { - fError = true; - } - } - break; - - case 'j': //time to wait between bursts of IOs - { - int c = atoi(arg + 1); - if (c > 0) - { - for (auto i = vTargets.begin(); i != vTargets.end(); i++) - { - i->SetThinkTime(c); - i->SetEnableThinkTime(true); - } - } - else - { - fError = true; - } - } - break; - - case 'I': //io priority - { - int x = atoi(arg + 1); - if (x > 0 && x < 4) - { - PRIORITY_HINT hint[] = { IoPriorityHintVeryLow, IoPriorityHintLow, IoPriorityHintNormal }; - for (auto i = vTargets.begin(); i != vTargets.end(); i++) - { - i->SetIOPriorityHint(hint[x - 1]); - } - } - else - { - fError = true; - } - } - break; - - case 'l': //large pages - for (auto i = vTargets.begin(); i != vTargets.end(); i++) - { - i->SetUseLargePages(true); - } - break; - - case 'L': //measure latency - timeSpan.SetMeasureLatency(true); - break; - - case 'n': //disable affinity (by default simple affinity is turned on) - timeSpan.SetDisableAffinity(true); - break; - - case 'N': - if (!_ParseFlushParameter(arg, &f)) - { - fError = true; - } - break; - - case 'o': //request count (1==synchronous) - { - int c = atoi(arg + 1); - if (c > 0) - { - for (auto i = vTargets.begin(); i != vTargets.end(); i++) - { - i->SetRequestCount(c); - } - } - else - { - fError = true; - } - } - break; - - case 'O': //total number of IOs/thread - for use with -F - { - int c = atoi(arg + 1); - if (c > 0) - { - timeSpan.SetRequestCount(c); - } - else - { - fError = true; - } - } - break; - - case 'p': //start async IO operations with the same offset - //makes sense only for -o2 and greater - for (auto i = vTargets.begin(); i != vTargets.end(); i++) - { - i->SetUseParallelAsyncIO(true); - } - break; - - case 'P': //show progress every x IO operations - { - int c = atoi(arg + 1); - if (c < 1) - { - c = 65536; - } - pProfile->SetProgress(c); - } - break; - - case 'r': //random access - { - // mixed random/sequential pct split? - if (*(arg + 1) == 's') - { - int c = 0; - - ++arg; - if (*(arg + 1) == '\0') - { - fprintf(stderr, "ERROR: no random percentage passed to -rs\n"); - fError = true; - } - else - { - c = atoi(arg + 1); - if (c <= 0 || c > 100) - { - fprintf(stderr, "ERROR: random percentage passed to -rs should be between 1 and 100\n"); - fError = true; - } - } - if (!fError) - { - for (auto i = vTargets.begin(); i != vTargets.end(); i++) - { - // if random ratio is unset and actual alignment is already specified, - // -s was used: don't allow this for clarity of intent - if (!i->GetRandomRatio() && - i->GetBlockAlignmentInBytes(true)) - { - fprintf(stderr, "ERROR: use -r to specify IO alignment when using mixed random/sequential IO (-rs)\n"); - fError = true; - break; - } - // if random ratio was already set to something other than 100% (-r) - // then -rs was specified multiple times: catch and block this - if (i->GetRandomRatio() && - i->GetRandomRatio() != 100) - { - fprintf(stderr, "ERROR: mixed random/sequential IO (-rs) specified multiple times\n"); - fError = true; - break; - } - // Note that -rs100 is the same as -r. It will not result in the element - // in the XML profile; we will still only emit/accept 1-99 there. - // - // Saying -rs0 (sequential) would create an ambiguity between that and -r[nnn]. Rather - // than bend the intepretation of -r[nnn] for the special case of -rs0 we will error - // it out in the bounds check above. - i->SetRandomRatio(c); - } - } - } - - // random distribution - - else if (*(arg + 1) == 'd') - { - // advance past the d - arg += 2; - - fError = !_ParseRandomDistribution(arg, vTargets); - } - - // random block alignment - // if mixed random/sequential not already specified, set to 100% - else - { - - UINT64 cb = _dwBlockSize; - if (*(arg + 1) != '\0') - { - if (!_GetSizeInBytes(arg + 1, cb, nullptr) || (cb == 0)) - { - fprintf(stderr, "ERROR: invalid alignment passed to -r\n"); - fError = true; - } - } - if (!fError) - { - for (auto i = vTargets.begin(); i != vTargets.end(); i++) - { - // Do not override -rs specification - if (!i->GetRandomRatio()) - { - i->SetRandomRatio(100); - } - // Multiple -rNN? - // Note that -rs100 -r[NN] will pass since -rs does not set alignment. - // We are only validating a single -rNN specification. - else if (i->GetRandomRatio() == 100 && - i->GetBlockAlignmentInBytes(true)) - { - fprintf(stderr, "ERROR: random IO (-r) specified multiple times\n"); - fError = true; - break; - } - // -s already set the alignment? - if (i->GetBlockAlignmentInBytes(true)) - { - fprintf(stderr, "ERROR: sequential IO (-s) conflicts with random IO (-r/-rs)\n"); - fError = true; - break; - } - i->SetBlockAlignmentInBytes(cb); - } - } - } - } - break; - - case 'R': // output profile/results format engine - handled in pass 1 - break; - - case 's': //stride size - { - int idx = 1; - - if ('i' == *(arg + idx)) - { - // do interlocked sequential mode - // ISSUE-REVIEW: this does nothing if -r is specified - // ISSUE-REVIEW: this does nothing if -p is specified - // ISSUE-REVIEW: this does nothing if we are single-threaded - for (auto i = vTargets.begin(); i != vTargets.end(); i++) - { - i->SetUseInterlockedSequential(true); - } - - idx++; - } - - for (auto i = vTargets.begin(); i != vTargets.end(); i++) - { - // conflict -s with -rs/-s - if (i->GetRandomRatio()) - { - if (i->GetRandomRatio() == 100) { - fprintf(stderr, "ERROR: sequential IO (-s) conflicts with random IO (-r/-rs)\n"); - } - else - { - fprintf(stderr, "ERROR: use -r to specify IO alignment for -rs\n"); - } - fError = true; - break; - } - - // conflict with multiple -s - if (i->GetBlockAlignmentInBytes(true)) - { - fprintf(stderr, "ERROR: sequential IO (-s) specified multiple times\n"); - fError = true; - break; - } - } - - if (*(arg + idx) != '\0') - { - UINT64 cb; - // Note that we allow -s0, as unusual as that would be. - // The counter-case of -r0 is invalid and checked for. - if (_GetSizeInBytes(arg + idx, cb, nullptr)) - { - for (auto i = vTargets.begin(); i != vTargets.end(); i++) - { - i->SetBlockAlignmentInBytes(cb); - } - } - else - { - fprintf(stderr, "ERROR: invalid stride size passed to -s\n"); - fError = true; - } - } - else - { - // explicitly pass through the block size so that we can detect - // -rs/-s intent conflicts when attempting to set -rs - for (auto i = vTargets.begin(); i != vTargets.end(); i++) - { - i->SetBlockAlignmentInBytes(i->GetBlockSizeInBytes()); - } - } - } - break; - - case 'S': //control os/hw/remote caching and writethrough - { - // parse flags - it is an error to multiply specify either property, which - // can be detected simply by checking if we move one from !undefined. - // this also handles conflict cases. - int idx; - for (idx = 1; !fError && *(arg + idx) != '\0'; idx++) - { - switch (*(arg + idx)) - { - case 'b': - if (t == TargetCacheMode::Undefined) - { - t = TargetCacheMode::Cached; - } - else - { - fprintf(stderr, "ERROR: -Sb conflicts with earlier specification of cache mode\n"); - fError = true; - } - break; - case 'h': - if (t == TargetCacheMode::Undefined && - w == WriteThroughMode::Undefined && - m == MemoryMappedIoMode::Undefined) - { - t = TargetCacheMode::DisableOSCache; - w = WriteThroughMode::On; - } - else - { - fprintf(stderr, "ERROR: -Sh conflicts with earlier specification of cache/writethrough/memory mapped\n"); - fError = true; - } - break; - case 'm': - if (m == MemoryMappedIoMode::Undefined && - t != TargetCacheMode::DisableOSCache) - { - m = MemoryMappedIoMode::On; - } - else - { - fprintf(stderr, "ERROR: -Sm conflicts with earlier specification of memory mapped IO/unbuffered IO\n"); - fError = true; - } - break; - case 'r': - if (t == TargetCacheMode::Undefined) - { - t = TargetCacheMode::DisableLocalCache; - } - else - { - fprintf(stderr, "ERROR: -Sr conflicts with earlier specification of cache mode\n"); - fError = true; - } - break; - case 'u': - if (t == TargetCacheMode::Undefined && - m == MemoryMappedIoMode::Undefined) - { - t = TargetCacheMode::DisableOSCache; - } - else - { - fprintf(stderr, "ERROR: -Su conflicts with earlier specification of cache mode/memory mapped IO\n"); - fError = true; - } - break; - case 'w': - if (w == WriteThroughMode::Undefined) - { - w = WriteThroughMode::On; - } - else - { - fprintf(stderr, "ERROR -Sw conflicts with earlier specification of write through\n"); - fError = true; - } - break; - default: - fprintf(stderr, "ERROR: unrecognized option provided to -S\n"); - fError = true; - break; - } - } - - // bare -S, parse loop did not advance - if (!fError && idx == 1) - { - if (t == TargetCacheMode::Undefined && - m == MemoryMappedIoMode::Undefined) - { - t = TargetCacheMode::DisableOSCache; - } - else - { - fprintf(stderr, "ERROR: -S conflicts with earlier specification of cache mode\n"); - fError = true; - } - } - } - break; - - case 't': //number of threads per file - { - int c = atoi(arg + 1); - if (c > 0) - { - for (auto i = vTargets.begin(); i != vTargets.end(); i++) - { - i->SetThreadsPerFile(c); - } - } - else - { - fError = true; - } - } - break; - - case 'T': //offsets between threads reading the same file - { - UINT64 cb; - if (_GetSizeInBytes(arg + 1, cb, nullptr) && (cb > 0)) - { - for (auto i = vTargets.begin(); i != vTargets.end(); i++) - { - i->SetThreadStrideInBytes(cb); - } - } - else - { - fprintf(stderr, "ERROR: invalid offset passed to -T\n"); - fError = true; - } - } - break; - - case 'v': //verbose mode - handled in pass 1 - break; - - case 'w': //write test [default=read] - { - int c = 0; - - if (*(arg + 1) == '\0') - { - fprintf(stderr, "ERROR: no write ratio passed to -w\n"); - fError = true; - } - else - { - c = atoi(arg + 1); - if (c < 0 || c > 100) - { - fprintf(stderr, "ERROR: write ratio passed to -w must be between 0 and 100 (percent)\n"); - fError = true; - } - } - if (!fError) - { - for (auto i = vTargets.begin(); i != vTargets.end(); i++) - { - i->SetWriteRatio(c); - } - } - } - break; - - case 'W': //warm up time - pass 1 composable - break; - - case 'x': //completion routines - timeSpan.SetCompletionRoutines(true); - break; - - case 'y': //external synchronization - switch (*(arg + 1)) - { - - case 's': - _hEventStarted = CreateEvent(NULL, TRUE, FALSE, arg + 2); - if (NULL == _hEventStarted) - { - fprintf(stderr, "Error creating/opening start notification event: '%s'\n", arg + 2); - exit(1); // TODO: this class shouldn't terminate the process - } - break; - - case 'f': - _hEventFinished = CreateEvent(NULL, TRUE, FALSE, arg + 2); - if (NULL == _hEventFinished) - { - fprintf(stderr, "Error creating/opening finish notification event: '%s'\n", arg + 2); - exit(1); // TODO: this class shouldn't terminate the process - } - break; - - case 'r': - synch->hStartEvent = CreateEvent(NULL, TRUE, FALSE, arg + 2); - if (NULL == synch->hStartEvent) - { - fprintf(stderr, "Error creating/opening wait-for-start event: '%s'\n", arg + 2); - exit(1); // TODO: this class shouldn't terminate the process - } - break; - - case 'p': - synch->hStopEvent = CreateEvent(NULL, TRUE, FALSE, arg + 2); - if (NULL == synch->hStopEvent) - { - fprintf(stderr, "Error creating/opening force-stop event: '%s'\n", arg + 2); - exit(1); // TODO: this class shouldn't terminate the process - } - break; - - case 'e': - { - HANDLE hEvent = OpenEvent(EVENT_MODIFY_STATE, FALSE, arg + 2); - if (NULL == hEvent) - { - fprintf(stderr, "Error opening event '%s'\n", arg + 2); - exit(1); // TODO: this class shouldn't terminate the process - } - if (!SetEvent(hEvent)) - { - fprintf(stderr, "Error setting event '%s'\n", arg + 2); - exit(1); // TODO: this class shouldn't terminate the process - } - CloseHandle(hEvent); - printf("Succesfully set event: '%s'\n", arg + 2); - bExit = true; - break; - } - - default: - fError = true; - } - - case 'z': //random seed - pass 1 composable - break; - - case 'Z': //zero write buffers - if (*(arg + 1) == '\0') - { - for (auto i = vTargets.begin(); i != vTargets.end(); i++) - { - i->SetZeroWriteBuffers(true); - } - } - else if (*(arg + 1) == 'r' && *(arg + 2) == '\0') - { - timeSpan.SetRandomWriteData(true); - } - else - { - UINT64 cb = 0; - string sPath; - if (_GetRandomDataWriteBufferData(string(arg + 1), cb, sPath) && (cb > 0)) - { - for (auto i = vTargets.begin(); i != vTargets.end(); i++) - { - i->SetRandomDataWriteBufferSize(cb); - i->SetRandomDataWriteBufferSourcePath(sPath); - } - } - else - { - fprintf(stderr, "ERROR: invalid size passed to -Z\n"); - fError = true; - } - } - break; - - default: - fprintf(stderr, "ERROR: invalid option: '%s'\n", carg); - return false; - } - - if (fError) - { - // note: original pointer to the cmdline argument, without parse movement - fprintf(stderr, "ERROR: incorrectly provided option: '%s'\n", carg); - return false; - } - - --nParamCnt; - ++args; - } - - // - // exit if a user specified an action which was already satisfied and doesn't require running test - // - if (bExit) - { - printf("Now exiting...\n"); - exit(1); // TODO: this class shouldn't terminate the process - } - - if (vTargets.size() < 1) - { - fprintf(stderr, "ERROR: need to provide at least one filename\n"); - return false; - } - - // apply resultant cache/writethrough/memory mapped io modes to the targets - for (auto i = vTargets.begin(); i != vTargets.end(); i++) - { - if (t != TargetCacheMode::Undefined) - { - i->SetCacheMode(t); - } - if (w != WriteThroughMode::Undefined) - { - i->SetWriteThroughMode(w); - } - if (m != MemoryMappedIoMode::Undefined) - { - i->SetMemoryMappedIoMode(m); - } - if (f != MemoryMappedIoFlushMode::Undefined) - { - i->SetMemoryMappedIoFlushMode(f); - } - } - - // ... and apply targets to the timespan - for (auto i = vTargets.begin(); i != vTargets.end(); i++) - { - timeSpan.AddTarget(*i); - } - pProfile->AddTimeSpan(timeSpan); - - return true; -} - -bool CmdLineParser::_ReadParametersFromXmlFile(const char *pszPath, Profile *pProfile, vector *pvSubstTargets) -{ - XmlProfileParser parser; - - return parser.ParseFile(pszPath, pProfile, pvSubstTargets, NULL); -} - -bool CmdLineParser::ParseCmdLine(const int argc, const char *argv[], Profile *pProfile, struct Synchronization *synch, SystemInformation *pSystem) -{ - assert(nullptr != argv); - assert(nullptr != pProfile); - assert(NULL != synch); - - if (argc < 2) - { - _DisplayUsageInfo(argv[0]); - return false; - } - - string sCmdLine; - for (int i = 0; i < argc - 1; i++) - { - sCmdLine += argv[i]; - sCmdLine += ' '; - } - if (argc > 0) - { - sCmdLine += argv[argc - 1]; - } - pProfile->SetCmdLine(sCmdLine); - - bool fOk = true; - bool fXMLProfile = false; - - fOk = _ReadParametersFromCmdLine(argc, argv, pProfile, synch, fXMLProfile); - - // Check additional restrictions and conditions on the parsed profile. - // Note that on the current cmdline, all targets receive the same parameters - // so their mutual consistency only needs to be checked once. Do not check - // system consistency in profile-only operation (this is only required at - // execution time). - - if (fOk) - { - fOk = pProfile->Validate(!fXMLProfile, pProfile->GetProfileOnly() ? nullptr : pSystem); - } - - return fOk; -} +/* + +DISKSPD + +Copyright(c) Microsoft Corporation +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +#include "CmdLineParser.h" +#include "Common.h" +#include "XmlProfileParser.h" +#include +#include +#include + +CmdLineParser::CmdLineParser() : + _dwBlockSize(64 * 1024), + _ulWriteRatio(0), + _hEventStarted(nullptr), + _hEventFinished(nullptr) +{ +} + +CmdLineParser::~CmdLineParser() +{ +} + +// Get size in bytes from a string (KMGTb) +bool CmdLineParser::_GetSizeInBytes(const char *pszSize, UINT64& ullSize, const char **pszRest) const +{ + bool fOk = true; + UINT64 ullResult = 0; + UINT64 ullMultiplier = 1; + const char *rest = nullptr; + + fOk = Util::ParseUInt(pszSize, ullResult, rest); + + if (fOk) + { + char ch = static_cast(toupper(*rest)); + + switch (ch) + { + case '\0': { break; } + case 'T': { ullMultiplier *= 1024; } + case 'G': { ullMultiplier *= 1024; } + case 'M': { ullMultiplier *= 1024; } + case 'K': { ullMultiplier *= 1024; ++rest; break; } + case 'B': { ullMultiplier = _dwBlockSize; ++rest; break; } + default: { + + // + // If caller is not expecting continuation, we know this is malformed now and + // can say so with respect to size specifiers. + // If there is continuation, caller is responsible for validating. + // + + if (!pszRest) + { + fOk = false; + fprintf(stderr, "Invalid size '%c'. Valid: K - KiB, M - MiB, G - GiB, T - TiB, B - block\n", *rest); + } + } + } + + if (fOk) + { + // + // Second chance after parsing valid size qualifier. + // + + if (!pszRest && *rest != '\0') + { + fOk = false; + fprintf(stderr, "Unrecognized characters after size specification\n"); + } + + // + // Now apply size specifier. + // + + else if (ullResult <= MAXUINT64 / ullMultiplier) + { + ullResult *= ullMultiplier; + } + else + { + // overflow + fOk = false; + fprintf(stderr, "Overflow applying multipler '%c'\n", ch); + } + } + } + else + { + fprintf(stderr, "Invalid integer\n"); + } + + if (fOk) + { + ullSize = ullResult; + + if (pszRest) + { + *pszRest = rest; + } + } + + return fOk; +} + +bool CmdLineParser::_GetRandomDataWriteBufferData(const string& sArg, UINT64& cb, string& sPath) +{ + bool fOk = true; + size_t iComma = sArg.find(','); + if (iComma == sArg.npos) + { + fOk = _GetSizeInBytes(sArg.c_str(), cb, nullptr); + sPath = ""; + } + else + { + fOk = _GetSizeInBytes(sArg.substr(0, iComma).c_str(), cb, nullptr); + sPath = sArg.substr(iComma + 1); + } + return fOk; +} + +void CmdLineParser::_DisplayUsageInfo(const char *pszFilename) const +{ + // ISSUE-REVIEW: this formats badly in the default 80 column command prompt + printf("\n"); + printf("Usage: %s [options] target1 [ target2 [ target3 ...] ]\n", pszFilename); + printf("version %s (%s)\n", DISKSPD_NUMERIC_VERSION_STRING, DISKSPD_DATE_VERSION_STRING); + printf("\n"); + + printf( + "Valid targets:\n" + " file_path\n" + " #\n" + " :\n" + "\n" + "Sizes, offsets and lengths are specified as integer bytes, or with an\n" + "optional suffix of KMGT (KiB/MiB/GiB/TiB) or b (for blocks, see -b).\n" + "Examples: 4k = 4096\n" + " with -b4k, 8b = 32768 (8 * 4KiB)\n" + "\n" + "Available options:\n" + " -? display usage information\n" + " -: experimental behaviors, as a bitmask of flags. current:\n" + " 1 - allow throughput rate limit sleeps >1ms if indicated by rate\n" + " -ag group affinity - threads assigned round-robin to CPUs by processor groups, 0 - n.\n" + " Groups are filled from lowest to highest processor before moving to the next.\n" + " [default; use -n to disable default affinity]\n" + " -a[g#,]#[,#,...]> advanced CPU affinity - threads assigned round-robin to the CPUs stated, in order of\n" + " specification; g# is the processor group for the following CPUs. If no group is\n" + " stated, 0 is default. Additional groups/processors can be added, comma separated,\n" + " on the same or separate -a parameters.\n" + " Examples: -a0,1,2 and -ag0,0,1,2 are equivalent.\n" + " -ag0,0,1,2,g1,0,1,2 specifies the first three CPUs in groups 0 and 1.\n" + " -ag0,0,1,2,g1,0,1,2 and -ag0,0,1,2 -ag1,0,1,2 are equivalent.\n" + " -b IO size, defines the block \'b\' for sizes stated in units of blocks [default=64K]\n" + " -B[:length] bounds; specify range of target to issue IO to - base offset and length\n" + " (default: IO is issued across the entire target)\n" + " -c create file targets of the given size. Conflicts with non-file target specifications.\n" + " -C cool down time - duration of the test after measurements finished [default=0s].\n" + " -D Capture IOPs statistics in intervals of ; these are per-thread\n" + " per-target: text output provides IOPs standard deviation, XML provides the full\n" + " IOPs time series in addition. [default=1000, 1 second].\n" + " -d duration (in seconds) to run test [default=10s]\n" + " -f maximum target offset to issue IO to (non-inclusive); -Bbase -f(base+length) is the same\n" + " as -Bbase:length. For example, to test only the first sectors of a disk.\n" + " -f open file with one or more additional access hints\n" + " r : the FILE_FLAG_RANDOM_ACCESS hint\n" + " s : the FILE_FLAG_SEQUENTIAL_SCAN hint\n" + " t : the FILE_ATTRIBUTE_TEMPORARY hint\n" + " [default: none]\n" + " -F total number of threads (conflicts with -t)\n" + " -g[i] throughput per-thread per-target throttled to given value; defaults to bytes per millisecond\n" + " With the optional i qualifier the value is IOPS of the specified block size (-b).\n" + " Throughput limits cannot be specified when using completion routines (-x)\n" + " [default: no limit]\n" + " -h deprecated, see -Sh\n" + " -i number of IOs per burst; see -j [default: inactive]\n" + " -j interval in between issuing IO bursts; see -i [default: inactive]\n" + " -I Set IO priority to . Available values are: 1-very low, 2-low, 3-normal (default)\n" + " -l Use large pages for IO buffers\n" + " -L measure latency statistics\n" + " -n disable default affinity (-a)\n" + " -N specify the flush mode for memory mapped I/O\n" + " v : uses the FlushViewOfFile API\n" + " n : uses the RtlFlushNonVolatileMemory API\n" + " i : uses RtlFlushNonVolatileMemory without waiting for the flush to drain\n" + " [default: none]\n" + " -o number of outstanding I/O requests per target per thread\n" + " (1=synchronous I/O, unless more than 1 thread is specified with -F)\n" + " [default=2]\n" + " -O number of outstanding I/O requests per thread - for use with -F\n" + " (1=synchronous I/O)\n" + " -p start parallel sequential I/O operations with the same offset\n" + " (ignored if -r is specified, makes sense only with -o2 or greater)\n" + " -P enable printing a progress dot after each [default=65536]\n" + " completed I/O operations, counted separately by each thread \n" + " -r[align] random I/O aligned to [align] byte offsets within the target range (overrides -s)\n" + " [default alignment=block size (-b)]\n" + " -rd[params] specify an non-uniform distribution for random IO in the target\n" + " [default uniformly random]\n" + " distributions: pct, abs\n" + " all: IO%% and %%Target/Size are cumulative. If the sum of IO%% is less than 100%% the\n" + " remainder is applied to the remainder of the target. An IO%% of 0 indicates a gap -\n" + " no IO will be issued to that range of the target.\n" + " pct : parameter is a combination of IO%%/%%Target separated by : (colon)\n" + " Example: -rdpct90/10:0/10:5/20 specifies 90%% of IO in 10%% of the target, no IO\n" + " next 10%%, 5%% IO in the next 20%% and the remaining 5%% of IO in the last 60%%\n" + " abs : parameter is a combination of IO%%/Target Size separated by : (colon)\n" + " If the actual target size is smaller than the distribution, the relative values of IO%%\n" + " for the valid elements define the effective distribution.\n" + " Example: -rdabs90/10G:0/10G:5/20G specifies 90%% of IO in 10GiB of the target, no IO\n" + " next 10GiB, 5%% IO in the next 20GiB and the remaining 5%% of IO in the remaining\n" + " capacity of the target. If the target is only 20G, the distribution truncates at\n" + " 90/10G:0:10G and all IO is directed to the first 10G (equivalent to -f10G).\n" + " -rs percentage of requests which should be issued randomly; -r is used to specify IO alignment.\n" + " Sequential IO runs are homogeneous when a mixed r/w ratio is specified (-w) and their lengths\n" + " follow a geometric distribution based on the percentage (chance of next IO being sequential).\n" + " -R[p] output format. With the p prefix, the input profile (command line or XML) is validated and\n" + " re-output in the specified format without running load, useful for checking or building\n" + " complex profiles.\n" + " [default: text]\n" + " -s[i][align] stride size of [align] bytes, alignment & offset between operations\n" + " [default=non-interlocked, default alignment=block size (-b)]\n" + " By default threads track independent sequential IO offsets starting at base offset of the target.\n" + " With multiple threads this results in threads overlapping their IOs - see -T to divide\n" + " them into multiple separate sequential streams on the target.\n" + " With the optional i qualifier (-si) threads interlock on a shared sequential offset.\n" + " Interlocked operations may introduce overhead but make it possible to issue a single\n" + " sequential stream to a target which responds faster than one thread can drive.\n" + " (ignored if -r specified, -si conflicts with -p, -rs and -T)\n" + " -S[bhmruw] control caching behavior [default: caching is enabled, no writethrough]\n" + " non-conflicting flags may be combined in any order; ex: -Sbw, -Suw, -Swu\n" + " -S equivalent to -Su\n" + " -Sb enable caching (default, explicitly stated)\n" + " -Sh equivalent -Suw\n" + " -Sm enable memory mapped I/O\n" + " -Su disable software caching, equivalent to FILE_FLAG_NO_BUFFERING\n" + " -Sr disable local caching, with remote sw caching enabled; only valid for remote filesystems\n" + " -Sw enable writethrough (no hardware write caching), equivalent to FILE_FLAG_WRITE_THROUGH or\n" + " non-temporal writes for memory mapped I/O (-Sm)\n" + " -t number of threads per target (conflicts with -F)\n" + " -T starting separation between I/O operations performed on the same target by different threads\n" + " [default=0] (starting offset = base target offset + (thread number * )\n" + " only applies to -s sequential IO with #threads > 1, conflicts with -r and -si\n" + " -v[s] verbose mode - with s, only provide additional summary statistics\n" + " -w percentage of write requests (-w and -w0 are equivalent and result in a read-only workload).\n" + " absence of this switch indicates 100%% reads\n" + " IMPORTANT: a write test will destroy existing data without a warning\n" + " -W warm up time - duration of the test before measurements start [default=5s]\n" + " -x use completion routines instead of I/O Completion Ports\n" + " -X use an XML file to configure the workload. Profile defaults for -W/d/C (durations) and -R/v/z\n" + " (output format, verbosity and random seed) may be overriden by direct specification.\n" + " Targets can be defined in XML profiles as template paths of the form * (*1, *2, ...).\n" + " When run, specify the paths to substitute for the template paths in order on the command line.\n" + " The first specified target is *1, second is *2, and so on.\n" + " Example: diskspd -d60 -Xprof.xml first.bin second.bin (prof.xml using *1 and *2, 60s run)\n" + " -z[seed] set random seed [with no -z, seed=0; with plain -z, seed is based on system run time]\n" + "\n" + "Write buffers:\n" + " -Z zero buffers used for write tests\n" + " -Zr per IO random buffers used for write tests - this incurrs additional run-time\n" + " overhead to create random content and shouln't be compared to results run\n" + " without -Zr\n" + " -Z use a buffer filled with random data as a source for write operations.\n" + " -Z, use a buffer filled with data from as a source for write operations.\n" + "\n" + " By default, write source buffers are filled with a repeating pattern (0, 1, 2, ..., 255, 0, 1, ...)\n" + "\n" + "Synchronization:\n" + " -ys signals event before starting the actual run (no warmup)\n" + " (creates a notification event if does not exist)\n" + " -yf signals event after the actual run finishes (no cooldown)\n" + " (creates a notification event if does not exist)\n" + " -yr waits on event before starting the run (including warmup)\n" + " (creates a notification event if does not exist)\n" + " -yp stops the run when event is set; CTRL+C is bound to this event\n" + " (creates a notification event if does not exist)\n" + " -ye sets event and quits\n" + "\n" + "Event Tracing:\n" + " -e Use query perf timer (qpc), cycle count, or system timer respectively.\n" + " [default = q, query perf timer (qpc)]\n" + " -ep use paged memory for the NT Kernel Logger [default=non-paged memory]\n" + " -ePROCESS process start & end\n" + " -eTHREAD thread start & end\n" + " -eIMAGE_LOAD image load\n" + " -eDISK_IO physical disk IO\n" + " -eMEMORY_PAGE_FAULTS all page faults\n" + " -eMEMORY_HARD_FAULTS hard faults only\n" + " -eNETWORK TCP/IP, UDP/IP send & receive\n" + " -eREGISTRY registry calls\n" + "\n\n"); + + printf("Examples:\n\n"); + printf("Create 8192KB file and run read test on it for 1 second:\n\n"); + printf(" %s -c8192K -d1 testfile.dat\n", pszFilename); + printf("\n"); + printf("Set block size to 4KB, create 2 threads per file, 32 overlapped (outstanding)\n"); + printf("I/O operations per thread, disable all caching mechanisms and run block-aligned random\n"); + printf("access read test lasting 10 seconds:\n\n"); + printf(" %s -b4K -t2 -r -o32 -d10 -Sh testfile.dat\n\n", pszFilename); + printf("Create two 1GB files, set block size to 4KB, create 2 threads per file, affinitize threads\n"); + printf("to CPUs 0 and 1 (each file will have threads affinitized to both CPUs) and run read test\n"); + printf("lasting 10 seconds:\n\n"); + printf(" %s -c1G -b4K -t2 -d10 -a0,1 testfile1.dat testfile2.dat\n", pszFilename); + + printf("\n"); +} + +bool CmdLineParser::_ParseETWParameter(const char *arg, Profile *pProfile) +{ + assert(nullptr != arg); + assert(0 != *arg); + + bool fOk = true; + pProfile->SetEtwEnabled(true); + if (*(arg + 1) != '\0') + { + const char *c = arg + 1; + if (*c == 'p') + { + pProfile->SetEtwUsePagedMemory(true); + } + else if (*c == 'q') + { + pProfile->SetEtwUsePerfTimer(true); + } + else if (*c == 's') + { + pProfile->SetEtwUseSystemTimer(true); //default + } + else if (*c == 'c') + { + pProfile->SetEtwUseCyclesCounter(true); + } + else if (strcmp(c, "PROCESS") == 0) //process start & end + { + pProfile->SetEtwProcess(true); + } + else if (strcmp(c, "THREAD") == 0) //thread start & end + { + pProfile->SetEtwThread(true); + } + else if (strcmp(c, "IMAGE_LOAD") == 0) //image load + { + pProfile->SetEtwImageLoad(true); + } + else if (strcmp(c, "DISK_IO") == 0) //physical disk IO + { + pProfile->SetEtwDiskIO(true); + } + else if (strcmp(c, "MEMORY_PAGE_FAULTS") == 0) //all page faults + { + pProfile->SetEtwMemoryPageFaults(true); + } + else if (strcmp(c, "MEMORY_HARD_FAULTS") == 0) //hard faults only + { + pProfile->SetEtwMemoryHardFaults(true); + } + else if (strcmp(c, "NETWORK") == 0) //tcpip send & receive + { + pProfile->SetEtwNetwork(true); + } + else if (strcmp(c, "REGISTRY") == 0) //registry calls + { + pProfile->SetEtwRegistry(true); + } + else + { + fOk = false; + } + } + else + { + fOk = false; + } + + return fOk; +} + +bool CmdLineParser::_ParseAffinity(const char *arg, TimeSpan *pTimeSpan) +{ + bool fOk = true; + + assert(nullptr != arg); + assert('\0' != *arg); + + const char *c = arg + 1; + + // -a and -ag are functionally equivalent; group-aware affinity. + // Note that group-aware affinity is default. + + // look for the -a simple case + if (*c == '\0') + { + return true; + } + + // look for the -ag simple case + if (*c == 'g') + { + // peek ahead, done? + if (*(c + 1) == '\0') + { + return true; + } + + // leave the parser at the g; this is the start of a group number + } + + // more complex affinity -ag0,0,1,2,g1,0,1,2,... OR -a0,1,2,.. + // n counts the -a prefix, the first parsed character is string index 2 + DWORD nGroup = 0, nNum = 0, n = 2; + bool fGroup = false, fNum = false; + while (*c != '\0') + { + if ((*c >= '0') && (*c <= '9')) + { + // accumulating a number + fNum = true; + nNum = 10 * nNum + (*c - '0'); + } + else if (*c == 'g') + { + // bad: ggggg + if (fGroup) + { + fOk = false; + } + + // now parsing a group number + fGroup = true; + } + else if (*c == ',') + { + // separator; if parsing group and have a number, now have the group + if (fGroup && fNum) + { + if (nNum > MAXWORD) + { + fprintf(stderr, "ERROR: group %u is out of range\n", nNum); + fOk = false; + } + else + { + nGroup = nNum; + nNum = 0; + fGroup = false; + } + } + // at a split but don't have a parsed number, error + else if (!fNum) + { + fOk = false; + } + // have a parsed CPU number + else + { + if (nNum > MAXBYTE) + { + fprintf(stderr, "ERROR: CPU %u is out of range\n", nNum); + fOk = false; + } + else + { + pTimeSpan->AddAffinityAssignment((WORD)nGroup, (BYTE)nNum); + nNum = 0; + fNum = false; + } + } + } + else + { + fOk = false; + } + + // bail out to error pretty print on error + if (!fOk) + { + break; + } + + c++; + n++; + } + + // if parsing a group or don't have a final number, error + if (fGroup || !fNum) + { + fOk = false; + } + + if (fOk && nNum > MAXBYTE) + { + fprintf(stderr, "ERROR: CPU %u is out of range\n", nNum); + fOk = false; + } + + if (!fOk) + { + // mid-parse error, show the point at which it occured + if (*c != '\0') { + fprintf(stderr, "ERROR: syntax error parsing affinity at highlighted character\n-%s\n", arg); + while (n-- > 0) + { + fprintf(stderr, " "); + } + fprintf(stderr, "^\n"); + } + else + { + fprintf(stderr, "ERROR: incomplete affinity specification\n"); + } + } + + if (fOk) + { + // fprintf(stderr, "FINAL parsed group %d CPU %d\n", nGroup, nNum); + pTimeSpan->AddAffinityAssignment((WORD)nGroup, (BYTE)nNum); + } + + return fOk; +} + +bool CmdLineParser::_ParseFlushParameter(const char *arg, MemoryMappedIoFlushMode *FlushMode) +{ + assert(nullptr != arg); + assert(0 != *arg); + + bool fOk = true; + if (*(arg + 1) != '\0') + { + const char *c = arg + 1; + if (_stricmp(c, "v") == 0) + { + *FlushMode = MemoryMappedIoFlushMode::ViewOfFile; + } + else if (_stricmp(c, "n") == 0) + { + *FlushMode = MemoryMappedIoFlushMode::NonVolatileMemory; + } + else if (_stricmp(c, "i") == 0) + { + *FlushMode = MemoryMappedIoFlushMode::NonVolatileMemoryNoDrain; + } + else + { + fOk = false; + } + } + else + { + fOk = false; + } + return fOk; +} + +bool CmdLineParser::_ParseRandomDistribution(const char *arg, vector& vTargets) +{ + vector vOr; + DistributionType dType; + bool fOk = false; + UINT32 pctAcc = 0, pctCur; // accumulated/cur pct io + UINT64 targetAcc = 0, targetCur; // accumulated/cur target + + if (!strncmp(arg, "pct", 3)) + { + dType = DistributionType::Percent; + } + else if (strncmp(arg, "abs", 3)) + { + fprintf(stderr, "Unrecognized random distribution type\n"); + return false; + } + else + { + dType = DistributionType::Absolute; + } + + arg += 3; + + // + // Parse pairs of + // + // * pct: percentage/target percentage + // * abs: percentage/absolute range of target + // + // Ex: 90/10:5/5 => [0,90) -> [0, 10) :: [90, 95) -> [10, 15) + // a remainder of [95, 100) -> [15, 100) would be applied. + // + // Percentages are cumulative and successively define the span of + // the preceding definition. Absolute ranges are also cumulative: + // 10/1G:90/1G puts 90% of accesses in the second 1G range of the + // target. + // + // A single percentage can be 100 but is of limited value since it + // would only be valid as a single element distribution. + // + // Basic numeric validations are done here (similar to XSD for XML). + // Cross validation with other workload parameters (blocksize) and whole + // distribution validation is delayed to common code. + // + + while (true) + { + // Consume IO% integer + fOk = Util::ParseUInt(arg, pctCur, arg); + if (!fOk) + { + fprintf(stderr, "Invalid integer IO%%: must be > 0 and <= %u\n", 100 - pctAcc); + return false; + } + // hole is ok + else if (pctCur > 100) + { + fprintf(stderr, "Invalid IO%% %u: must be >= 0 and <= %u\n", pctCur, 100 - pctAcc); + return false; + } + + // Expect separator + if (*arg++ != '/') + { + fprintf(stderr, "Expected / separator after %u\n", pctCur); + return false; + } + + // Consume Target%/Absolute range integer + if (dType == DistributionType::Percent) + { + // Percent specification + fOk = Util::ParseUInt(arg, targetCur, arg); + if (!fOk) + { + fprintf(stderr, "Invalid integer Target%%: must be > 0 and <= %I64u\n", 100 - targetAcc); + return false; + } + // no hole + else if (targetCur == 0 || targetCur > 100) + { + fprintf(stderr, "Invalid Target%% %I64u: must be > 0 and <= %I64u\n", targetCur, 100 - targetAcc); + return false; + } + } + else + { + // Size specification + fOk = CmdLineParser::_GetSizeInBytes(arg, targetCur, &arg); + if (!fOk) + { + // error already emitted + return fOk; + } + + if (targetCur == 0) + { + fprintf(stderr, "Invalid zero length target range\n"); + return false; + } + } + + // Add range from [accumulator - accumulator + current) => ... + // Note that zero pctCur indicates a hole where no IO is desired - this is recorded + // for fidelity of display/profile but will never match on lookup, as intended. + vOr.emplace_back(pctAcc, pctCur, make_pair(targetAcc, targetCur)); + + // Now move accumulators for the next tuple/completion + pctAcc += pctCur; + targetAcc += targetCur; + + // Expect/consume separator for next tuple? + if (*arg == ':') + { + ++arg; + continue; + } + + // Done? + if (*arg == '\0') + { + break; + } + + fprintf(stderr, "Unexpected characters in specification '%s'\n", arg); + return false; + } + + // Apply to all targets + for (auto& t : vTargets) + { + t.SetDistributionRange(vOr, dType); + } + + return true; +} + +bool CmdLineParser::_ReadParametersFromCmdLine(const int argc, const char *argv[], Profile *pProfile, struct Synchronization *synch, bool& fXMLProfile) +{ + int nParamCnt = argc - 1; + const char** args = argv + 1; + bool fError = false; + + TimeSpan timeSpan; + + // + // Pass 1 - determine parameter set type: cmdline specification or XML, and preparse targets/blocksize + // + + ParseState isXMLSet = ParseState::Unknown; + + ParseState isXMLResultFormat = ParseState::Unknown; + ParseState isProfileOnly = ParseState::Unknown; + ParseState isVerbose = ParseState::Unknown; + ParseState isVerboseStats = ParseState::Unknown; + ParseState isRandomSeed = ParseState::Unknown; + ParseState isWarmupTime = ParseState::Unknown; + ParseState isDurationTime = ParseState::Unknown; + ParseState isCooldownTime = ParseState::Unknown; + ParseState isExperimentFlags = ParseState::Unknown; + + ULONG randomSeedValue = 0; + ULONG warmupTime = 0; + ULONG durationTime = 0; + ULONG cooldownTime = 0; + ULONG experimentFlags = 0; + const char *xmlProfile = nullptr; + + // + // Find all target specifications. Note that this assumes all non-target + // parameters are single tokens; e.g. "-Hsomevalue" and never "-H somevalue". + // Targets follow parameter specifications. + // + + vector vTargets; + for (int i = 0, inTargets = false; i < nParamCnt; ++i) + { + if (!_IsSwitchChar(args[i][0])) + { + inTargets = true; + + Target target; + target.SetPath(args[i]); + vTargets.push_back(target); + } + else if (inTargets) + { + fprintf(stderr, "ERROR: parameters (%s) must come before targets on the command line\n", args[i]); + return false; + } + } + + // + // Find composable and dependent parameters as we resolve the parameter set. + // + + for (int i = 0; i < nParamCnt; ++i) + { + if (_IsSwitchChar(args[i][0])) + { + const char *arg = &args[i][2]; + + switch(args[i][1]) + { + + + case ':': + // Experiment flags + experimentFlags = atoi(arg); + isExperimentFlags = ParseState::True; + break; + + case 'b': + + // Block size does not compose with XML profile spec + if (isXMLSet == ParseState::True) + { + fprintf(stderr, "ERROR: -b is not compatible with -X XML profile specification\n"); + return false; + } + else + { + UINT64 ullBlockSize; + if (_GetSizeInBytes(arg, ullBlockSize, nullptr) && ullBlockSize < MAXUINT32) + { + for (auto &i : vTargets) + { + i.SetBlockSizeInBytes((DWORD)ullBlockSize); + } + } + else + { + fprintf(stderr, "ERROR: invalid block size passed to -b\n"); + return false; + } + _dwBlockSize = (DWORD)ullBlockSize; + + isXMLSet = ParseState::False; + } + break; + + case 'C': + { + int c = atoi(arg); + if (c >= 0) + { + cooldownTime = c; + isCooldownTime = ParseState::True; + } + else + { + fprintf(stderr, "ERROR: invalid cooldown time (-C): '%s'\n", arg); + return false; + } + } + break; + + case 'd': + { + int c = atoi(arg); + if (c >= 0) + { + durationTime = c; + isDurationTime = ParseState::True; + } + else + { + fprintf(stderr, "ERROR: invalid measured duration time (-d): '%s'\n", arg); + return false; + } + } + break; + + case 'W': + { + int c = atoi(arg); + if (c >= 0) + { + warmupTime = c; + isWarmupTime = ParseState::True; + } + else + { + fprintf(stderr, "ERROR: invalid warmup time (-W): '%s'\n", arg); + return false; + } + } + break; + + case 'R': + + // re-output profile only (no run) + if ('p' == *arg) + { + isProfileOnly = ParseState::True; + ++arg; + } + + if ('\0' != *arg) + { + // Explicit results format + if (strcmp(arg, "xml") == 0) + { + isXMLResultFormat = ParseState::True; + } + else if (strcmp(arg, "text") != 0) + { + fprintf(stderr, "ERROR: invalid results format (-R): '%s'\n", arg); + return false; + } + else + { + isXMLResultFormat = ParseState::False; + } + } + else + { + // allow for -Rp shorthand for default profile-only format + if (isProfileOnly != ParseState::True) + { + fprintf(stderr, "ERROR: unspecified results format -R: use [p]\n"); + return false; + } + } + break; + + case 'v': + + if (*arg == '\0') + { + isVerbose = ParseState::True; + } + else if (*arg == 's' && *(arg+1) == '\0') // -vs + { + isVerboseStats = ParseState::True; + } + else + { + fprintf(stderr, "ERROR: invalid verbose mode (-v): '%s'\n", arg); + return false; + } + break; + + case 'X': + + if (isXMLSet == ParseState::Unknown) + { + isXMLSet = ParseState::True; + } + else + { + fprintf(stderr, "ERROR: multiple XML profiles specified (-X)\n"); + return false; + } + xmlProfile = arg; + break; + + case 'z': + { + char *endPtr = nullptr; + + if (*arg == '\0') + { + randomSeedValue = (ULONG) GetTickCount64(); + } + else + { + randomSeedValue = strtoul(arg, &endPtr, 10); + if (*endPtr != '\0') + { + fprintf(stderr, "ERROR: invalid random seed value '%s' specified - must be a valid 32 bit integer\n", arg); + return false; + } + } + + isRandomSeed = ParseState::True; + } + break; + + default: + // no other switches are valid in combination with -X + // if we've seen X, this means it is bad + // if not, we know it will not be X + if (isXMLSet == ParseState::True) + { + fprintf(stderr, "ERROR: invalid XML profile specification; parameter %s not compatible with -X\n", args[i]); + return false; + } + else + { + isXMLSet = ParseState::False; + } + } + } + } + + // XML profile? + if (isXMLSet == ParseState::True) + { + if (!_ReadParametersFromXmlFile(xmlProfile, pProfile, &vTargets)) + { + return false; + } + } + + // + // Apply profile common parameters - note that results format is unmodified if R not explicitly provided + // + + if (isXMLResultFormat == ParseState::True) + { + pProfile->SetResultsFormat(ResultsFormat::Xml); + } + else if (isXMLResultFormat == ParseState::False) + { + pProfile->SetResultsFormat(ResultsFormat::Text); + } + + if (isProfileOnly == ParseState::True) + { + pProfile->SetProfileOnly(true); + } + + if (isVerbose == ParseState::True) + { + pProfile->SetVerbose(true); + } + + if (isVerboseStats == ParseState::True) + { + pProfile->SetVerboseStats(true); + } + + if (isExperimentFlags == ParseState::True) + { + g_ExperimentFlags = experimentFlags; + } + + // + // Apply timespan common composable parameters + // + + if (isXMLSet == ParseState::True) + { + for (auto& ts : const_cast &>(pProfile->GetTimeSpans())) + { + if (isRandomSeed == ParseState::True) { ts.SetRandSeed(randomSeedValue); } + if (isWarmupTime == ParseState::True) { ts.SetWarmup(warmupTime); } + if (isDurationTime == ParseState::True) { ts.SetDuration(durationTime); } + if (isCooldownTime == ParseState::True) { ts.SetCooldown(cooldownTime); } + } + } + else + { + if (isRandomSeed == ParseState::True) { timeSpan.SetRandSeed(randomSeedValue); } + if (isWarmupTime == ParseState::True) { timeSpan.SetWarmup(warmupTime); } + if (isDurationTime == ParseState::True) { timeSpan.SetDuration(durationTime); } + if (isCooldownTime == ParseState::True) { timeSpan.SetCooldown(cooldownTime); } + } + + // Now done if XML profile + if (isXMLSet == ParseState::True) + { + fXMLProfile = true; + return true; + } + + // + // Parse full command line for profile + // + + // initial parse for cache/writethrough + // these are built up across the entire cmd line and applied at the end. + // this allows for conflicts to be thrown for mixed -h/-S as needed. + TargetCacheMode t = TargetCacheMode::Undefined; + WriteThroughMode w = WriteThroughMode::Undefined; + MemoryMappedIoMode m = MemoryMappedIoMode::Undefined; + MemoryMappedIoFlushMode f = MemoryMappedIoFlushMode::Undefined; + + // seen base/max target offset specification yet? + ParseState isMaxTargetOffset = ParseState::Unknown; + ParseState isBaseTargetOffset = ParseState::Unknown; + + bool bExit = false; + while (nParamCnt) + { + const char* arg = *args; + const char* const carg = arg; // save for error reporting, arg is modified during parse + + // Targets follow parameters on command line. If this is a target, we are done now. + if (!_IsSwitchChar(*arg)) + { + break; + } + + // skip switch character, provide length + ++arg; + const size_t argLen = strlen(arg); + + switch (*arg) + { + case '?': + _DisplayUsageInfo(argv[0]); + exit(0); + + case ':': // experiment flags + // handled during composable parameter evaluation + break; + + case 'a': // affinity + //-a1,2,3,4 (assign threads to cpus 1,2,3,4 (round robin)) + if (!_ParseAffinity(arg, &timeSpan)) + { + fError = true; + } + break; + + case 'b': //block size + // handled during composable parameter evaluation + break; + + case 'B': //base target offset (offset from 0) and optional length + if (*(arg + 1) != '\0') + { + UINT64 base; + UINT64 len; + const char *rest; + + if (isBaseTargetOffset == ParseState::True) + { + fprintf(stderr, "ERROR: base target offset (-Bbase[:length]) can only be specified once\n"); + fError = true; + break; + } + + if (_GetSizeInBytes(arg + 1, base, &rest)) + { + for (auto &i : vTargets) + { + i.SetBaseFileOffsetInBytes(base); + } + + isBaseTargetOffset = ParseState::True; + } + else + { + fprintf(stderr, "ERROR: invalid base target offset passed to -B\n"); + fError = true; + break; + } + + if (rest && *rest != '\0') + { + if (*rest != ':') + { + fprintf(stderr, "ERROR: unexpected characters after -Bbase; use \':\' to separate -Bbase:length\n"); + fError = true; + break; + } + + if (*(++rest) == '\0') + { + fprintf(stderr, "ERROR: -Bbase:length - no length provided\n"); + fError = true; + break; + } + + if (isMaxTargetOffset == ParseState::True) + { + fprintf(stderr, "ERROR: maximum target offset (-Bbase:length or -fsize) can only be specified once\n"); + fError = true; + break; + } + + if (!_GetSizeInBytes(rest, len, nullptr) || base + len < base) + { + fprintf(stderr, "ERROR: invalid target length passed to -B\n"); + fError = true; + break; + } + + for (auto &i : vTargets) + { + i.SetMaxFileSize(base + len); + } + + isMaxTargetOffset = ParseState::True; + } + } + else + { + fError = true; + } + break; + + case 'c': //create file of the given size + if (*(arg + 1) != '\0') + { + UINT64 cb; + if (_GetSizeInBytes(arg + 1, cb, nullptr)) + { + for (auto &i : vTargets) + { + i.SetFileSize(cb); + i.SetCreateFile(true); + } + } + else + { + fprintf(stderr, "ERROR: invalid target size passed to -c\n"); + fError = true; + } + } + else + { + fError = true; + } + break; + + case 'C': //cool down time + // handled during composable parameter evaluation + break; + + case 'd': //duration + // handled during composable parameter evaluation + break; + + case 'D': //standard deviation + { + timeSpan.SetCalculateIopsStdDev(true); + + int x = atoi(arg + 1); + if (x > 0) + { + timeSpan.SetIoBucketDurationInMilliseconds(x); + } + } + break; + + case 'e': //etw + if (!_ParseETWParameter(arg, pProfile)) + { + fError = true; + } + break; + + case 'f': + if ('\0' == *(arg + 1)) + { + fError = true; + break; + } + + // max target offset form? + if (isdigit(*(arg + 1))) + { + UINT64 cb; + + if (isMaxTargetOffset == ParseState::True) + { + fprintf(stderr, "ERROR: maximum target offset (-Bbase:length or -fsize) can only be specified once\n"); + fError = true; + break; + } + + if (!_GetSizeInBytes(arg + 1, cb, nullptr)) + { + fprintf(stderr, "ERROR: invalid max target size passed to -f\n"); + fError = true; + break; + } + + for (auto &i : vTargets) + { + i.SetMaxFileSize(cb); + } + + isMaxTargetOffset = ParseState::True; + break; + } + + // while -frs (or -fsr) are generally conflicting intentions as far as + // the OS is concerned, do not enforce + while (*(++arg) != '\0') + { + switch (*arg) + { + case 'r': + for (auto &i : vTargets) + { + i.SetRandomAccessHint(true); + } + break; + case 's': + for (auto &i : vTargets) + { + i.SetSequentialScanHint(true); + } + break; + case 't': + for (auto &i : vTargets) + { + i.SetTemporaryFileHint(true); + } + break; + default: + fError = true; + break; + } + } + break; + + case 'F': //total number of threads + { + int c = atoi(arg + 1); + if (c > 0) + { + timeSpan.SetThreadCount(c); + } + else + { + fError = true; + } + } + break; + + case 'g': //throughput in bytes per millisecond (gNNN) OR iops (gNNNi) + { + // units? + bool isBpms = false; + if (isdigit(arg[argLen - 1])) + { + isBpms = true; + } + else if (arg[argLen - 1] != 'i') + { + // not IOPS, so its bad + fError = true; + } + + if (!fError) + { + int c = atoi(arg + 1); + if (c > 0) + { + for (auto &i : vTargets) + { + if (isBpms) + { + i.SetThroughput(c); + } + else + { + i.SetThroughputIOPS(c); + } + } + } + else + { + fError = true; + } + } + } + break; + + case 'h': // compat: disable os cache and set writethrough; now equivalent to -Sh + if (t == TargetCacheMode::Undefined && + w == WriteThroughMode::Undefined) + { + t = TargetCacheMode::DisableOSCache; + w = WriteThroughMode::On; + } + else + { + fprintf(stderr, "ERROR: -h conflicts with earlier specification of cache/writethrough\n"); + fError = true; + } + break; + + case 'i': //number of IOs to issue before think time + { + int c = atoi(arg + 1); + if (c > 0) + { + for (auto &i : vTargets) + { + i.SetBurstSize(c); + i.SetUseBurstSize(true); + } + } + else + { + fError = true; + } + } + break; + + case 'j': //time to wait between bursts of IOs + { + int c = atoi(arg + 1); + if (c > 0) + { + for (auto &i : vTargets) + { + i.SetThinkTime(c); + i.SetEnableThinkTime(true); + } + } + else + { + fError = true; + } + } + break; + + case 'I': //io priority + { + int x = atoi(arg + 1); + if (x > 0 && x < 4) + { + PRIORITY_HINT hint[] = { IoPriorityHintVeryLow, IoPriorityHintLow, IoPriorityHintNormal }; + for (auto &i : vTargets) + { + i.SetIOPriorityHint(hint[x - 1]); + } + } + else + { + fError = true; + } + } + break; + + case 'l': //large pages + for (auto &i : vTargets) + { + i.SetUseLargePages(true); + } + break; + + case 'L': //measure latency + timeSpan.SetMeasureLatency(true); + break; + + case 'n': //disable affinity (by default simple affinity is turned on) + timeSpan.SetDisableAffinity(true); + break; + + case 'N': + if (!_ParseFlushParameter(arg, &f)) + { + fError = true; + } + break; + + case 'o': //request count (1==synchronous) + { + int c = atoi(arg + 1); + if (c > 0) + { + for (auto &i : vTargets) + { + i.SetRequestCount(c); + } + } + else + { + fError = true; + } + } + break; + + case 'O': //total number of IOs/thread - for use with -F + { + int c = atoi(arg + 1); + if (c > 0) + { + timeSpan.SetRequestCount(c); + } + else + { + fError = true; + } + } + break; + + case 'p': //start async IO operations with the same offset + //makes sense only for -o2 and greater + for (auto &i : vTargets) + { + i.SetUseParallelAsyncIO(true); + } + break; + + case 'P': //show progress every x IO operations + { + int c = atoi(arg + 1); + if (c < 1) + { + c = 65536; + } + pProfile->SetProgress(c); + } + break; + + case 'r': //random access + { + // mixed random/sequential pct split? + if (*(arg + 1) == 's') + { + int c = 0; + + ++arg; + if (*(arg + 1) == '\0') + { + fprintf(stderr, "ERROR: no random percentage passed to -rs\n"); + fError = true; + } + else + { + c = atoi(arg + 1); + if (c <= 0 || c > 100) + { + fprintf(stderr, "ERROR: random percentage passed to -rs should be between 1 and 100\n"); + fError = true; + } + } + if (!fError) + { + for (auto &i : vTargets) + { + // if random ratio is unset and actual alignment is already specified, + // -s was used: don't allow this for clarity of intent + if (!i.GetRandomRatio() && + i.GetBlockAlignmentInBytes(true)) + { + fprintf(stderr, "ERROR: use -r to specify IO alignment when using mixed random/sequential IO (-rs)\n"); + fError = true; + break; + } + // if random ratio was already set to something other than 100% (-r) + // then -rs was specified multiple times: catch and block this + if (i.GetRandomRatio() && + i.GetRandomRatio() != 100) + { + fprintf(stderr, "ERROR: mixed random/sequential IO (-rs) specified multiple times\n"); + fError = true; + break; + } + // Note that -rs100 is the same as -r. It will not result in the element + // in the XML profile; we will still only emit/accept 1-99 there. + // + // Saying -rs0 (sequential) would create an ambiguity between that and -r[nnn]. Rather + // than bend the intepretation of -r[nnn] for the special case of -rs0 we will error + // it out in the bounds check above. + i.SetRandomRatio(c); + } + } + } + + // random distribution + + else if (*(arg + 1) == 'd') + { + // advance past the d + arg += 2; + + fError = !_ParseRandomDistribution(arg, vTargets); + } + + // random block alignment + // if mixed random/sequential not already specified, set to 100% + else + { + + UINT64 cb = _dwBlockSize; + if (*(arg + 1) != '\0') + { + if (!_GetSizeInBytes(arg + 1, cb, nullptr) || (cb == 0)) + { + fprintf(stderr, "ERROR: invalid alignment passed to -r\n"); + fError = true; + } + } + if (!fError) + { + for (auto &i : vTargets) + { + // Do not override -rs specification + if (!i.GetRandomRatio()) + { + i.SetRandomRatio(100); + } + // Multiple -rNN? + // Note that -rs100 -r[NN] will pass since -rs does not set alignment. + // We are only validating a single -rNN specification. + else if (i.GetRandomRatio() == 100 && + i.GetBlockAlignmentInBytes(true)) + { + fprintf(stderr, "ERROR: random IO (-r) specified multiple times\n"); + fError = true; + break; + } + // -s already set the alignment? + if (i.GetBlockAlignmentInBytes(true)) + { + fprintf(stderr, "ERROR: sequential IO (-s) conflicts with random IO (-r/-rs)\n"); + fError = true; + break; + } + i.SetBlockAlignmentInBytes(cb); + } + } + } + } + break; + + case 'R': // output profile/results format engine + // handled during composable parameter evaluation + break; + + case 's': //stride size + { + int idx = 1; + + if ('i' == *(arg + idx)) + { + // do interlocked sequential mode + // ISSUE-REVIEW: this does nothing if -r is specified + // ISSUE-REVIEW: this does nothing if -p is specified + // ISSUE-REVIEW: this does nothing if we are single-threaded + for (auto &i : vTargets) + { + i.SetUseInterlockedSequential(true); + } + + idx++; + } + + for (auto &i : vTargets) + { + // conflict -s with -rs/-s + if (i.GetRandomRatio()) + { + if (i.GetRandomRatio() == 100) { + fprintf(stderr, "ERROR: sequential IO (-s) conflicts with random IO (-r/-rs)\n"); + } + else + { + fprintf(stderr, "ERROR: use -r to specify IO alignment for -rs\n"); + } + fError = true; + break; + } + + // conflict with multiple -s + if (i.GetBlockAlignmentInBytes(true)) + { + fprintf(stderr, "ERROR: sequential IO (-s) specified multiple times\n"); + fError = true; + break; + } + } + + if (*(arg + idx) != '\0') + { + UINT64 cb; + // Note that we allow -s0, as unusual as that would be. + // The counter-case of -r0 is invalid and checked for. + if (_GetSizeInBytes(arg + idx, cb, nullptr)) + { + for (auto &i : vTargets) + { + i.SetBlockAlignmentInBytes(cb); + } + } + else + { + fprintf(stderr, "ERROR: invalid stride size passed to -s\n"); + fError = true; + } + } + else + { + // explicitly pass through the block size so that we can detect + // -rs/-s intent conflicts when attempting to set -rs + for (auto &i : vTargets) + { + i.SetBlockAlignmentInBytes(i.GetBlockSizeInBytes()); + } + } + } + break; + + case 'S': //control os/hw/remote caching and writethrough + { + // parse flags - it is an error to multiply specify either property, which + // can be detected simply by checking if we move one from !undefined. + // this also handles conflict cases. + int idx; + for (idx = 1; !fError && *(arg + idx) != '\0'; idx++) + { + switch (*(arg + idx)) + { + case 'b': + if (t == TargetCacheMode::Undefined) + { + t = TargetCacheMode::Cached; + } + else + { + fprintf(stderr, "ERROR: -Sb conflicts with earlier specification of cache mode\n"); + fError = true; + } + break; + case 'h': + if (t == TargetCacheMode::Undefined && + w == WriteThroughMode::Undefined && + m == MemoryMappedIoMode::Undefined) + { + t = TargetCacheMode::DisableOSCache; + w = WriteThroughMode::On; + } + else + { + fprintf(stderr, "ERROR: -Sh conflicts with earlier specification of cache/writethrough/memory mapped\n"); + fError = true; + } + break; + case 'm': + if (m == MemoryMappedIoMode::Undefined && + t != TargetCacheMode::DisableOSCache) + { + m = MemoryMappedIoMode::On; + } + else + { + fprintf(stderr, "ERROR: -Sm conflicts with earlier specification of memory mapped IO/unbuffered IO\n"); + fError = true; + } + break; + case 'r': + if (t == TargetCacheMode::Undefined) + { + t = TargetCacheMode::DisableLocalCache; + } + else + { + fprintf(stderr, "ERROR: -Sr conflicts with earlier specification of cache mode\n"); + fError = true; + } + break; + case 'u': + if (t == TargetCacheMode::Undefined && + m == MemoryMappedIoMode::Undefined) + { + t = TargetCacheMode::DisableOSCache; + } + else + { + fprintf(stderr, "ERROR: -Su conflicts with earlier specification of cache mode/memory mapped IO\n"); + fError = true; + } + break; + case 'w': + if (w == WriteThroughMode::Undefined) + { + w = WriteThroughMode::On; + } + else + { + fprintf(stderr, "ERROR -Sw conflicts with earlier specification of write through\n"); + fError = true; + } + break; + default: + fprintf(stderr, "ERROR: unrecognized option provided to -S\n"); + fError = true; + break; + } + } + + // bare -S, parse loop did not advance + if (!fError && idx == 1) + { + if (t == TargetCacheMode::Undefined && + m == MemoryMappedIoMode::Undefined) + { + t = TargetCacheMode::DisableOSCache; + } + else + { + fprintf(stderr, "ERROR: -S conflicts with earlier specification of cache mode\n"); + fError = true; + } + } + } + break; + + case 't': //number of threads per file + { + int c = atoi(arg + 1); + if (c > 0) + { + for (auto &i : vTargets) + { + i.SetThreadsPerFile(c); + } + } + else + { + fError = true; + } + } + break; + + case 'T': //offsets between threads reading the same file + { + UINT64 cb; + if (_GetSizeInBytes(arg + 1, cb, nullptr) && (cb > 0)) + { + for (auto &i : vTargets) + { + i.SetThreadStrideInBytes(cb); + } + } + else + { + fprintf(stderr, "ERROR: invalid offset passed to -T\n"); + fError = true; + } + } + break; + + case 'v': //verbose mode + // handled during composable parameter evaluation + break; + + case 'w': //write test [default=read] + { + int c = 0; + + if (*(arg + 1) == '\0') + { + fprintf(stderr, "ERROR: no write ratio passed to -w\n"); + fError = true; + } + else + { + c = atoi(arg + 1); + if (c < 0 || c > 100) + { + fprintf(stderr, "ERROR: write ratio passed to -w must be between 0 and 100 (percent)\n"); + fError = true; + } + } + if (!fError) + { + for (auto &i : vTargets) + { + i.SetWriteRatio(c); + } + } + } + break; + + case 'W': //warm up time + // handled during composable parameter evaluation + break; + + case 'x': //completion routines + timeSpan.SetCompletionRoutines(true); + break; + + case 'y': //external synchronization + switch (*(arg + 1)) + { + + case 's': + _hEventStarted = CreateEvent(NULL, TRUE, FALSE, arg + 2); + if (NULL == _hEventStarted) + { + fprintf(stderr, "Error creating/opening start notification event: '%s'\n", arg + 2); + exit(1); // TODO: this class shouldn't terminate the process + } + break; + + case 'f': + _hEventFinished = CreateEvent(NULL, TRUE, FALSE, arg + 2); + if (NULL == _hEventFinished) + { + fprintf(stderr, "Error creating/opening finish notification event: '%s'\n", arg + 2); + exit(1); // TODO: this class shouldn't terminate the process + } + break; + + case 'r': + synch->hStartEvent = CreateEvent(NULL, TRUE, FALSE, arg + 2); + if (NULL == synch->hStartEvent) + { + fprintf(stderr, "Error creating/opening wait-for-start event: '%s'\n", arg + 2); + exit(1); // TODO: this class shouldn't terminate the process + } + break; + + case 'p': + synch->hStopEvent = CreateEvent(NULL, TRUE, FALSE, arg + 2); + if (NULL == synch->hStopEvent) + { + fprintf(stderr, "Error creating/opening force-stop event: '%s'\n", arg + 2); + exit(1); // TODO: this class shouldn't terminate the process + } + break; + + case 'e': + { + HANDLE hEvent = OpenEvent(EVENT_MODIFY_STATE, FALSE, arg + 2); + if (NULL == hEvent) + { + fprintf(stderr, "Error opening event '%s'\n", arg + 2); + exit(1); // TODO: this class shouldn't terminate the process + } + if (!SetEvent(hEvent)) + { + fprintf(stderr, "Error setting event '%s'\n", arg + 2); + exit(1); // TODO: this class shouldn't terminate the process + } + CloseHandle(hEvent); + printf("Succesfully set event: '%s'\n", arg + 2); + bExit = true; + break; + } + + default: + fError = true; + } + + case 'z': //random seed + // handled during composable parameter evaluation + break; + + case 'Z': //zero write buffers + if (*(arg + 1) == '\0') + { + for (auto &i : vTargets) + { + i.SetZeroWriteBuffers(true); + } + } + else if (*(arg + 1) == 'r' && *(arg + 2) == '\0') + { + timeSpan.SetRandomWriteData(true); + } + else + { + UINT64 cb = 0; + string sPath; + if (_GetRandomDataWriteBufferData(string(arg + 1), cb, sPath) && (cb > 0)) + { + for (auto &i : vTargets) + { + i.SetRandomDataWriteBufferSize(cb); + i.SetRandomDataWriteBufferSourcePath(sPath); + } + } + else + { + fprintf(stderr, "ERROR: invalid size passed to -Z\n"); + fError = true; + } + } + break; + + default: + fprintf(stderr, "ERROR: invalid option: '%s'\n", carg); + return false; + } + + if (fError) + { + // note: original pointer to the cmdline argument, without parse movement + fprintf(stderr, "ERROR: incorrectly provided option: '%s'\n", carg); + return false; + } + + --nParamCnt; + ++args; + } + + // + // exit if a user specified an action which was already satisfied and doesn't require running test + // + if (bExit) + { + printf("Now exiting...\n"); + exit(1); // TODO: this class shouldn't terminate the process + } + + if (vTargets.size() < 1) + { + fprintf(stderr, "ERROR: need to provide at least one filename\n"); + return false; + } + + // apply resultant cache/writethrough/memory mapped io modes to the targets + for (auto &i : vTargets) + { + if (t != TargetCacheMode::Undefined) + { + i.SetCacheMode(t); + } + if (w != WriteThroughMode::Undefined) + { + i.SetWriteThroughMode(w); + } + if (m != MemoryMappedIoMode::Undefined) + { + i.SetMemoryMappedIoMode(m); + } + if (f != MemoryMappedIoFlushMode::Undefined) + { + i.SetMemoryMappedIoFlushMode(f); + } + } + + // ... and apply targets to the timespan + for (auto &i : vTargets) + { + timeSpan.AddTarget(i); + } + pProfile->AddTimeSpan(timeSpan); + + return true; +} + +bool CmdLineParser::_ReadParametersFromXmlFile(const char *pszPath, Profile *pProfile, vector *pvSubstTargets) +{ + XmlProfileParser parser; + + return parser.ParseFile(pszPath, pProfile, pvSubstTargets, NULL); +} + +bool CmdLineParser::ParseCmdLine(const int argc, const char *argv[], Profile *pProfile, struct Synchronization *synch, SystemInformation *pSystem) +{ + assert(nullptr != argv); + assert(nullptr != pProfile); + assert(NULL != synch); + + if (argc < 2) + { + _DisplayUsageInfo(argv[0]); + return false; + } + + string sCmdLine; + for (int i = 0; i < argc - 1; i++) + { + sCmdLine += argv[i]; + sCmdLine += ' '; + } + if (argc > 0) + { + sCmdLine += argv[argc - 1]; + } + pProfile->SetCmdLine(sCmdLine); + + bool fOk = true; + bool fXMLProfile = false; + + fOk = _ReadParametersFromCmdLine(argc, argv, pProfile, synch, fXMLProfile); + + // Check additional restrictions and conditions on the parsed profile. + // Note that on the current cmdline, all targets receive the same parameters + // so their mutual consistency only needs to be checked once. Do not check + // system consistency in profile-only operation (this is only required at + // execution time). + + if (fOk) + { + fOk = pProfile->Validate(!fXMLProfile, pProfile->GetProfileOnly() ? nullptr : pSystem); + } + + return fOk; +} diff --git a/CmdRequestCreator/diskspd.rc b/CmdRequestCreator/diskspd.rc index 8a82bae..bebe04b 100644 --- a/CmdRequestCreator/diskspd.rc +++ b/CmdRequestCreator/diskspd.rc @@ -1,5 +1,5 @@ #include -#include "Common.h" +#include "Version.h" DISKSPD.XSD HTML "..\\XmlProfileParser\\diskspd.xsd" diff --git a/Common/Common.cpp b/Common/Common.cpp index e866bfc..997d580 100644 --- a/Common/Common.cpp +++ b/Common/Common.cpp @@ -34,6 +34,7 @@ TRACELOGGING_DEFINE_PROVIDER(g_hEtwProvider, (0xca13db84, 0xd0a9, 0x5145, 0xfc, 0xa4, 0x46, 0x8d, 0xa9, 0x2f, 0xdc, 0x2d)); SystemInformation g_SystemInformation; +ULONG g_ExperimentFlags; UINT64 PerfTimer::GetTime() { @@ -99,12 +100,12 @@ UINT64 PerfTimer::SecondsToPerfTime(const double seconds) Random::Random(UINT64 ulSeed) { UINT32 i; - + _ulState[0] = 0xf1ea5eed; _ulState[1] = ulSeed; _ulState[2] = ulSeed; _ulState[3] = ulSeed; - + for (i = 0; i < 20; i++) { Rand64(); } @@ -138,10 +139,10 @@ void Random::RandBuffer(BYTE *pBuffer, UINT32 ulLength, bool fPseudoRandomOkay) pBuffer += Remaining * 8; if (fPseudoRandomOkay) { - + // // Generate 5 random numbers and then mix them to produce - // 16 random (but correlated) numbers. We want to do 16 + // 16 random (but correlated) numbers. We want to do 16 // numbers at a time for optimal cache line alignment. // Only do this if the caller is okay with numbers that // aren't independent. A detailed analysis of the data @@ -150,7 +151,7 @@ void Random::RandBuffer(BYTE *pBuffer, UINT32 ulLength, bool fPseudoRandomOkay) // instance it's unlikely compression algorithms will be // able to detect this and utilize it). // - + while (Remaining > 16) { r1 = Rand64(); r2 = Rand64(); @@ -195,7 +196,7 @@ void Random::RandBuffer(BYTE *pBuffer, UINT32 ulLength, bool fPseudoRandomOkay) // // Fill in the tail of the buffer // - + while (Remaining >= 4) { r1 = Rand64(); r2 = Rand64(); @@ -241,7 +242,7 @@ string ThreadTarget::GetXml(UINT32 indent) const { char buffer[4096]; string sXml; - + AddXmlInc(sXml, "\n"); sprintf_s(buffer, _countof(buffer), "%u\n", _ulThread); @@ -262,7 +263,7 @@ string Target::GetXml(UINT32 indent) const { char buffer[4096]; string sXml; - + AddXmlInc(sXml, "\n"); AddXml(sXml, "" + _sPath + "\n"); @@ -295,7 +296,7 @@ string Target::GetXml(UINT32 indent) const AddXml(sXml, "true\n"); break; } - + // MemoryMappedIoMode::Off is implied default switch (_memoryMappedIoMode) { @@ -416,7 +417,7 @@ string Target::GetXml(UINT32 indent) const { sprintf_s(buffer, _countof(buffer), "%I64u\n", GetBlockAlignmentInBytes()); AddXml(sXml, buffer); - + AddXml(sXml, _fInterlockedSequential ? "true\n" : "false\n"); @@ -672,6 +673,13 @@ string Profile::GetXml(UINT32 indent) const sprintf_s(buffer, _countof(buffer), "%u\n", _dwProgress); AddXml(sXml, buffer); + if (g_ExperimentFlags) + { + // only output if on so that downlevel doesn't get (and fail: not in downlevel xsd) unless actually specified + sprintf_s(buffer, _countof(buffer), "%u\n", g_ExperimentFlags); + AddXml(sXml, buffer); + } + if (_resultsFormat == ResultsFormat::Text) { AddXml(sXml, "text\n"); @@ -686,6 +694,12 @@ string Profile::GetXml(UINT32 indent) const } AddXml(sXml, _fVerbose ? "true\n" : "false\n"); + if (_fVerboseStats) + { + // only output if on so that downlevel doesn't get (and fail: not in downlevel xsd) unless actually specified + AddXml(sXml, "true\n"); + } + if (_precreateFiles == PrecreateFiles::UseMaxSize) { AddXml(sXml, "UseMaxSize\n"); @@ -763,7 +777,7 @@ bool Profile::Validate(bool fSingleSpec, SystemInformation *pSystem) const } if (fOk && !pSystem->processorTopology._vProcessorGroupInformation[Affinity.wGroup].IsProcessorValid(Affinity.bProc)) { - fprintf(stderr, "ERROR: affinity assignment to group %u core %u not possible; group only has %u cores\n", + fprintf(stderr, "ERROR: affinity assignment to group %u cpu %u not possible; group has a max of %u cpus\n", Affinity.wGroup, Affinity.bProc, pSystem->processorTopology._vProcessorGroupInformation[Affinity.wGroup]._maximumProcessorCount); @@ -773,22 +787,27 @@ bool Profile::Validate(bool fSingleSpec, SystemInformation *pSystem) const if (fOk && !pSystem->processorTopology._vProcessorGroupInformation[Affinity.wGroup].IsProcessorActive(Affinity.bProc)) { - fprintf(stderr, "ERROR: affinity assignment to group %u core %u not possible; core is not active (current mask 0x%Ix)\n", + fprintf(stderr, "ERROR: affinity assignment to group %u cpu %u not possible; cpu is not active (current mask 0x%p)\n", Affinity.wGroup, Affinity.bProc, - pSystem->processorTopology._vProcessorGroupInformation[Affinity.wGroup]._activeProcessorMask); + (void *) pSystem->processorTopology._vProcessorGroupInformation[Affinity.wGroup]._activeProcessorMask); fOk = false; } } } + // ISSUE: many of the following validation errors are stated in cmdline terms, which is not helpful for XML + if (timeSpan.GetDisableAffinity() && timeSpan.GetAffinityAssignments().size() > 0) { fprintf(stderr, "ERROR: -n and -a parameters cannot be used together\n"); fOk = false; } + // ISSUE: with XML and the following the target specification validation it would be useful to say what + // target they're for + for (const auto& target : timeSpan.GetTargets()) { const bool targetHasMultipleThreads = (timeSpan.GetThreadCount() > 1) || (target.GetThreadsPerFile() > 1); @@ -881,7 +900,7 @@ bool Profile::Validate(bool fSingleSpec, SystemInformation *pSystem) const fprintf(stderr, "ERROR: random distribution ranges (-rd) do not apply to sequential-only IO patterns\n"); fOk = false; } - + if (target.GetUseParallelAsyncIO() && target.GetRequestCount() == 1) { fprintf(stderr, "WARNING: -p does not have effect unless outstanding I/O count (-o) is > 1\n"); @@ -958,7 +977,7 @@ bool Profile::Validate(bool fSingleSpec, SystemInformation *pSystem) const // Note that absolute range needs no additional validation - known nonzero/large enough for IO if (target.GetDistributionType() == DistributionType::Percent) { - if (targetAcc + r._dst.second > 100) + if (targetAcc + r._dst.second > 100) { fprintf(stderr, "ERROR: invalid random distribution Target%% %I64u: can be at most %I64u - total must be <= 100%%\n", r._dst.second, 100 - targetAcc); fOk = false; @@ -1036,10 +1055,20 @@ bool Profile::Validate(bool fSingleSpec, SystemInformation *pSystem) const } } - // in the cases where there is only a single configuration specified for each target (e.g., cmdline), - // currently there are no validations specific to individual targets (e.g., pre-existing files) - // so we can stop validation now. this allows us to only warn/error once, as opposed to repeating - // it for each target. + // Note that this error is only possible with -f or XML. The -Bbase:length form is immune. + if (target.GetMaxFileSize() && target.GetMaxFileSize() <= target.GetBaseFileOffsetInBytes()) + { + fprintf(stderr, "ERROR: maximum (-f) target offset must be greater than base (-B)\n"); + fOk = false; + } + + // If we know there is only a single target specification (the parameters which apply to targets) shared + // across the one or more targets, we can stop. In practical terms this is the command line case - for + // XML we don't know, and do need to keep going. This early exit lets us avoid repeating the same sets + // of error messages per each target we would otherwise loop over. + // + // If we ever did target property validation (say, v. its size) we'd want to divide out the validations + // into parameter-only v. parameter/property cases for similar reasons. if (fSingleSpec) { break; @@ -1113,7 +1142,7 @@ BYTE* ThreadParameters::GetReadBuffer(size_t iTarget, size_t iRequest) BYTE* ThreadParameters::GetWriteBuffer(size_t iTarget, size_t iRequest) { BYTE *pBuffer = nullptr; - + Target& target(vTargets[iTarget]); size_t cb = static_cast(target.GetRandomDataWriteBufferSize()); if (cb == 0) diff --git a/Common/Common.h b/Common/Common.h index e827953..500f6e9 100644 --- a/Common/Common.h +++ b/Common/Common.h @@ -1,2406 +1,2692 @@ -/* - -DISKSPD - -Copyright(c) Microsoft Corporation -All rights reserved. - -MIT License - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - -*/ - -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include //ntdll.dll -#include -#include "Histogram.h" -#include "IoBucketizer.h" -#include "ThroughputMeter.h" - -using namespace std; - -TRACELOGGING_DECLARE_PROVIDER(g_hEtwProvider); - -// versioning material. for simplicity in consumption, please ensure that the date string -// parses via the System.Datetime constructor as follows (in Powershell): -// -// [datetime] "string" -// -// this should result in a valid System.Datetime object, rendered like: -// -// Monday, June 16, 2014 12:00:00 AM - -#define DISKSPD_RELEASE_TAG "-dev" -#define DISKSPD_REVISION "" - -#define DISKSPD_MAJOR 2 -#define DISKSPD_MINOR 1 -#define DISKSPD_BUILD 0 -#define DISKSPD_QFE 0 - -#define DISKSPD_MAJORMINOR_VER_STR(x,y,z) #x "." #y "." #z -#define DISKSPD_MAJORMINOR_VERSION_STRING(x,y,z) DISKSPD_MAJORMINOR_VER_STR(x,y,z) -#define DISKSPD_MAJORMINOR_VERSION_STR DISKSPD_MAJORMINOR_VERSION_STRING(DISKSPD_MAJOR, DISKSPD_MINOR, DISKSPD_BUILD) - -#define DISKSPD_NUMERIC_VERSION_STRING DISKSPD_MAJORMINOR_VERSION_STR DISKSPD_REVISION DISKSPD_RELEASE_TAG -#define DISKSPD_DATE_VERSION_STRING "2021/7/1" - -#define DISKSPD_TRACE_INFO 0x00000000 -#define DISKSPD_TRACE_RESERVED 0x00000001 -#define DISKSPD_TRACE_IO 0x00000100 - -typedef void (WINAPI *PRINTF)(const char*, va_list); //function used for displaying formatted data (printf style) - -#define ROUND_DOWN(_x,_alignment) \ - ( ((_x)/(_alignment)) * (_alignment) ) - -#define ROUND_UP(_x,_alignment) \ - ROUND_DOWN((_x) + (_alignment) - 1, (_alignment)) - -#define TB (((UINT64)1)<<40) -#define GB (((UINT64)1)<<30) -#define MB (((UINT64)1)<<20) -#define KB (((UINT64)1)<<10) - -struct ETWEventCounters -{ - UINT64 ullIORead; // Read - UINT64 ullIOWrite; // Write - UINT64 ullMMTransitionFault; // Transition fault - UINT64 ullMMDemandZeroFault; // Demand Zero fault - UINT64 ullMMCopyOnWrite; // Copy on Write - UINT64 ullMMGuardPageFault; // Guard Page fault - UINT64 ullMMHardPageFault; // Hard page fault - UINT64 ullNetTcpSend; // Send - UINT64 ullNetTcpReceive; // Receive - UINT64 ullNetUdpSend; // Send - UINT64 ullNetUdpReceive; // Receive - UINT64 ullNetConnect; // Connect - UINT64 ullNetDisconnect; // Disconnect - UINT64 ullNetRetransmit; // ReTransmit - UINT64 ullNetAccept; // Accept - UINT64 ullNetReconnect; // ReConnect - UINT64 ullRegCreate; // NtCreateKey - UINT64 ullRegOpen; // NtOpenKey - UINT64 ullRegDelete; // NtDeleteKey - UINT64 ullRegQuery; // NtQueryKey - UINT64 ullRegSetValue; // NtSetValueKey - UINT64 ullRegDeleteValue; // NtDeleteValueKey - UINT64 ullRegQueryValue; // NtQueryValueKey - UINT64 ullRegEnumerateKey; // NtEnumerateKey - UINT64 ullRegEnumerateValueKey; // NtEnumerateValueKey - UINT64 ullRegQueryMultipleValue; // NtQueryMultipleValueKey - UINT64 ullRegSetInformation; // NtSetInformationKey - UINT64 ullRegFlush; // NtFlushKey - UINT64 ullThreadStart; - UINT64 ullThreadEnd; - UINT64 ullProcessStart; - UINT64 ullProcessEnd; - UINT64 ullImageLoad; -}; - -// structure containing informations about ETW session -struct ETWSessionInfo -{ - ULONG ulBufferSize; - ULONG ulMinimumBuffers; - ULONG ulMaximumBuffers; - ULONG ulFreeBuffers; - ULONG ulBuffersWritten; - ULONG ulFlushTimer; - LONG lAgeLimit; - ULONG ulNumberOfBuffers; - ULONG ulEventsLost; - ULONG ulLogBuffersLost; - ULONG ulRealTimeBuffersLost; -}; - -// structure containing parameters concerning ETW session provided by user -struct ETWMask -{ - BOOL bProcess; - BOOL bThread; - BOOL bImageLoad; - BOOL bDiskIO; - BOOL bMemoryPageFaults; - BOOL bMemoryHardFaults; - BOOL bNetwork; - BOOL bRegistry; - BOOL bUsePagedMemory; - BOOL bUsePerfTimer; - BOOL bUseSystemTimer; - BOOL bUseCyclesCounter; -}; - -namespace UnitTests -{ - class PerfTimerUnitTests; - class ProfileUnitTests; - class TargetUnitTests; - class IORequestGeneratorUnitTests; -} - -class PerfTimer -{ -public: - - static UINT64 GetTime(); - - static double PerfTimeToMicroseconds(const double); - static double PerfTimeToMilliseconds(const double); - static double PerfTimeToSeconds(const double); - static double PerfTimeToMicroseconds(const UINT64); - static double PerfTimeToMilliseconds(const UINT64); - static double PerfTimeToSeconds(const UINT64); - - static UINT64 MicrosecondsToPerfTime(const double); - static UINT64 MillisecondsToPerfTime(const double); - static UINT64 SecondsToPerfTime(const double); - -private: - - static const UINT64 TIMER_FREQ; - static UINT64 _GetPerfTimerFreq(); - - friend class UnitTests::PerfTimerUnitTests; -}; - -template -class Range -{ -public: - Range( - T1 Source, - T1 Span, - T2 Dest - ) : - _src(Source), - _span(Span), - _dst(Dest) - {} - - constexpr bool operator<(const Range& other) const - { - // - // This is used for comparison of effective distributions during result reporting (dedup). - // - // A hole with _span == 0 sorts < range with _span > 0 - // Note that a hole will never match in a find(). - // - - return _src < other._src || - (_src == other._src && - (_span < other._span || - (_span == other._span && _dst < other._dst))); - } - - static Range const * find(const vector>& v, T1 c) - { - // v must be sorted - size_t s = 0, mid, e = v.size() - 1; - - while (true) - { - mid = s + ((e - s) / 2); - if (c < v[mid]._src) { - if (s == mid) - { - return nullptr; - } - e = mid - 1; - } - else if (c > v[mid]._src + v[mid]._span - 1) - { - if (e == mid) - { - return nullptr; - } - s = mid + 1; - } - else - { - return &v[mid]; - } - } - } - - T1 _src, _span; - T2 _dst; -}; - -typedef Range> DistributionRange; - -enum class DistributionType -{ - None, - Absolute, - Percent -}; - -// -// This code implements Bob Jenkins public domain simple random number generator -// See http://burtleburtle.net/bob/rand/smallprng.html for details -// - -class Random -{ -public: - Random(UINT64 ulSeed = 0); - - inline UINT64 Rand64() - { - UINT64 e; - - e = _ulState[0] - _rotl64(_ulState[1], 7); - _ulState[0] = _ulState[1] ^ _rotl64(_ulState[2], 13); - _ulState[1] = _ulState[2] + _rotl64(_ulState[3], 37); - _ulState[2] = _ulState[3] + e; - _ulState[3] = e + _ulState[0]; - - return _ulState[3]; - } - - inline UINT32 Rand32() - { - return (UINT32)Rand64(); - } - - void RandBuffer(BYTE *pBuffer, UINT32 ulLength, bool fPseudoRandomOkay); - -private: - UINT64 _ulState[4]; -}; - -struct PercentileDescriptor -{ - double Percentile; - string Name; -}; - -class Util -{ -public: - static string DoubleToStringHelper(const double); - template static T QuotientCeiling(T dividend, T divisor) - { - return (dividend + divisor - 1) / divisor; - } - - // True if result is <= ratio. - // The ratio is on the interval [0, 100]: - // 0 will never occur (always false) - // 100 will always occur (always true) - - static bool BooleanRatio(Random *pRand, UINT32 ulRatio) - { - return ((pRand->Rand32() % 100 + 1) <= ulRatio); - } - - // - // This is close to strtoul[l], returning the next character to parse in the input string. - // This character can be used for validation (should there be any non-integer remaining), - // interpreting units that follow the integer (KMGTB), or parsing further (int[]) - // content in the string. - // - // Return value indicates whether any integers were parsed to Output. Continue is only modified - // on success, and will point to the terminator on completion. False is returned on overflow. - // - - template - static bool ParseUInt(const char* Input, T& Output, const char*& Continue) - { - T current = 0, last = 0; - const char* input = Input; - bool parsed = false; - - while (*input) - { - if (*input < '0' || *input > '9') - { - break; - } - - parsed = true; - current *= 10; - current += static_cast(*input) - static_cast('0'); - - // - // Overflow? - // - - if (current < last) - { - parsed = false; - break; - } - last = current; - - input += 1; - } - - // - // Return if string was consumed - // - // - - if (parsed) - { - Continue = input; - Output = current; - } - - return parsed; - } -}; - -// To keep track of which type of IO was issued -enum class IOOperation -{ - Unknown = 0, - ReadIO, - WriteIO -}; - -class TargetResults -{ -public: - TargetResults() : - ullFileSize(0), - ullBytesCount(0), - ullIOCount(0), - ullReadBytesCount(0), - ullReadIOCount(0), - ullWriteBytesCount(0), - ullWriteIOCount(0) - { - - } - - void Add(DWORD dwBytesTransferred, - IOOperation type, - UINT64 ullIoStartTime, - UINT64 ullSpanStartTime, - bool fMeasureLatency, - bool fCalculateIopsStdDev - ) - { - double lfDurationUsec = 0; - UINT64 ullEndTime = 0; - UINT64 ullDuration = 0; - - // assume it is worthwhile to stay off of the time query path unless needed (micro-overhead) - if (fMeasureLatency || fCalculateIopsStdDev) - { - ullEndTime = PerfTimer::GetTime(); - ullDuration = ullEndTime - ullIoStartTime; - lfDurationUsec = PerfTimer::PerfTimeToMicroseconds(ullDuration); - } - - if (fMeasureLatency) - { - if (type == IOOperation::ReadIO) - { - readLatencyHistogram.Add(static_cast(lfDurationUsec)); - } - else - { - writeLatencyHistogram.Add(static_cast(lfDurationUsec)); - } - } - - UINT64 ullRelativeCompletionTime = 0; - if (fCalculateIopsStdDev) - { - ullRelativeCompletionTime = ullEndTime - ullSpanStartTime; - - if (type == IOOperation::ReadIO) - { - readBucketizer.Add(ullRelativeCompletionTime, lfDurationUsec); - } - else - { - writeBucketizer.Add(ullRelativeCompletionTime, lfDurationUsec); - } - } - - if (type == IOOperation::ReadIO) - { - ullReadBytesCount += dwBytesTransferred; // update read bytes counter - ullReadIOCount++; // update completed read I/O operations counter - } - else - { - ullWriteBytesCount += dwBytesTransferred; // update write bytes counter - ullWriteIOCount++; // update completed write I/O operations counter - } - - ullBytesCount += dwBytesTransferred; // update bytes counter - ullIOCount++; // update completed I/O operations counter - } - - string sPath; - UINT64 ullFileSize; //size of the file - UINT64 ullBytesCount; //number of accessed bytes - UINT64 ullIOCount; //number of performed I/O operations - UINT64 ullReadBytesCount; //number of bytes read - UINT64 ullReadIOCount; //number of performed Read I/O operations - UINT64 ullWriteBytesCount; //number of bytes written - UINT64 ullWriteIOCount; //number of performed Write I/O operations - - Histogram readLatencyHistogram; - Histogram writeLatencyHistogram; - - IoBucketizer readBucketizer; - IoBucketizer writeBucketizer; - - // Effective distribution after applying to target size (if specified/non-empty) - vector vDistributionRange; -}; - -class ThreadResults -{ -public: - vector vTargetResults; -}; - -class Results -{ -public: - bool fUseETW; - struct ETWEventCounters EtwEventCounters; - struct ETWMask EtwMask; - struct ETWSessionInfo EtwSessionInfo; - vector vThreadResults; - UINT64 ullTimeCount; - vector vSystemProcessorPerfInfo; -}; - -typedef void (*CALLBACK_TEST_STARTED)(); //callback function to notify that the measured test is about to start -typedef void (*CALLBACK_TEST_FINISHED)(); //callback function to notify that the measured test has just finished - -class ProcessorGroupInformation -{ -public: - WORD _groupNumber; - BYTE _maximumProcessorCount; - BYTE _activeProcessorCount; - KAFFINITY _activeProcessorMask; - - ProcessorGroupInformation() = delete; - ProcessorGroupInformation( - BYTE MaximumProcessorCount, - BYTE ActiveProcessorCount, - WORD Group, - KAFFINITY ActiveProcessorMask) : - _maximumProcessorCount(MaximumProcessorCount), - _activeProcessorCount(ActiveProcessorCount), - _groupNumber(Group), - _activeProcessorMask(ActiveProcessorMask) - { - } - - bool IsProcessorActive(BYTE Processor) const - { - if (IsProcessorValid(Processor) && - (((KAFFINITY)1 << Processor) & _activeProcessorMask) != 0) - { - return true; - } - else - { - return false; - } - } - - bool IsProcessorValid(BYTE Processor) const - { - if (Processor < _maximumProcessorCount) - { - return true; - } - else - { - return false; - } - } -}; - -class ProcessorNumaInformation -{ -public: - DWORD _nodeNumber; - WORD _groupNumber; - KAFFINITY _processorMask; - - ProcessorNumaInformation() = delete; - ProcessorNumaInformation( - DWORD Node, - WORD Group, - KAFFINITY ProcessorMask) : - _nodeNumber(Node), - _groupNumber(Group), - _processorMask(ProcessorMask) - { - } -}; - -class ProcessorHyperThreadInformation -{ -public: - WORD _groupNumber; - KAFFINITY _processorMask; - - ProcessorHyperThreadInformation( - WORD Group, - KAFFINITY ProcessorMask) : - _groupNumber(Group), - _processorMask(ProcessorMask) - { - } -}; - -class ProcessorSocketInformation -{ -public: - vector> _vProcessorMasks; -}; - -class ProcessorTopology -{ -public: - vector _vProcessorGroupInformation; - vector _vProcessorNumaInformation; - vector _vProcessorSocketInformation; - vector _vProcessorHyperThreadInformation; - - DWORD _ulProcCount; - DWORD _ulActiveProcCount; - - ProcessorTopology() - { - BOOL fResult; - PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX pInformation; - DWORD AllocSize = 1024; - DWORD ReturnedLength = AllocSize; - pInformation = (PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX) new char[AllocSize]; - - fResult = GetLogicalProcessorInformationEx(RelationGroup, pInformation, &ReturnedLength); - if (!fResult && GetLastError() == ERROR_INSUFFICIENT_BUFFER) - { - delete [] pInformation; - AllocSize = ReturnedLength; - pInformation = (PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX) new char[AllocSize]; - fResult = GetLogicalProcessorInformationEx(RelationGroup, pInformation, &ReturnedLength); - } - - if (fResult) - { - // Group information comes back as a single (large) element, not an array. - assert(ReturnedLength == pInformation->Size); - _ulProcCount = 0; - _ulActiveProcCount = 0; - - // Fill in group topology vector so we can answer questions about active/max procs - for (WORD i = 0; i < pInformation->Group.ActiveGroupCount; i++) - { - _vProcessorGroupInformation.emplace_back( - pInformation->Group.GroupInfo[i].MaximumProcessorCount, - pInformation->Group.GroupInfo[i].ActiveProcessorCount, - i, - pInformation->Group.GroupInfo[i].ActiveProcessorMask - ); - _ulProcCount += _vProcessorGroupInformation[i]._maximumProcessorCount; - _ulActiveProcCount += _vProcessorGroupInformation[i]._activeProcessorCount; - } - } - - ReturnedLength = AllocSize; - fResult = GetLogicalProcessorInformationEx(RelationNumaNode, pInformation, &ReturnedLength); - if (!fResult && GetLastError() == ERROR_INSUFFICIENT_BUFFER) - { - delete [] pInformation; - AllocSize = ReturnedLength; - pInformation = (PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX) new char[AllocSize]; - fResult = GetLogicalProcessorInformationEx(RelationNumaNode, pInformation, &ReturnedLength); - } - - if (fResult) - { - PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX cur = pInformation; - - while (ReturnedLength != 0) - { - assert(ReturnedLength >= cur->Size); - - if (cur->Size > ReturnedLength) - { - break; - } - - _vProcessorNumaInformation.emplace_back( - cur->NumaNode.NodeNumber, - cur->NumaNode.GroupMask.Group, - cur->NumaNode.GroupMask.Mask - ); - - ReturnedLength -= cur->Size; - cur = (PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX)((PCHAR)cur + cur->Size); - } - } - - ReturnedLength = AllocSize; - fResult = GetLogicalProcessorInformationEx(RelationProcessorPackage, pInformation, &ReturnedLength); - if (!fResult && GetLastError() == ERROR_INSUFFICIENT_BUFFER) - { - delete [] pInformation; - AllocSize = ReturnedLength; - pInformation = (PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX) new char[AllocSize]; - fResult = GetLogicalProcessorInformationEx(RelationProcessorPackage, pInformation, &ReturnedLength); - } - - if (fResult) - { - PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX cur = pInformation; - - while (ReturnedLength != 0) - { - ProcessorSocketInformation socket; - - assert(ReturnedLength >= cur->Size); - - if (cur->Size > ReturnedLength) - { - break; - } - - for (WORD i = 0; i < pInformation->Processor.GroupCount; i++) - { - socket._vProcessorMasks.emplace_back(cur->Processor.GroupMask[i].Group, - cur->Processor.GroupMask[i].Mask); - } - - _vProcessorSocketInformation.push_back(socket); - - ReturnedLength -= cur->Size; - cur = (PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX)((PCHAR)cur + cur->Size); - } - } - - ReturnedLength = AllocSize; - fResult = GetLogicalProcessorInformationEx(RelationProcessorCore, pInformation, &ReturnedLength); - if (!fResult && GetLastError() == ERROR_INSUFFICIENT_BUFFER) - { - delete [] pInformation; - AllocSize = ReturnedLength; - pInformation = (PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX) new char[AllocSize]; - fResult = GetLogicalProcessorInformationEx(RelationProcessorCore, pInformation, &ReturnedLength); - } - - if (fResult) - { - PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX cur = pInformation; - - while (ReturnedLength != 0) - { - assert(ReturnedLength >= cur->Size); - - if (cur->Size > ReturnedLength) - { - break; - } - - assert(pInformation->Processor.GroupCount == 1); - - _vProcessorHyperThreadInformation.emplace_back(cur->Processor.GroupMask[0].Group, - cur->Processor.GroupMask[0].Mask); - - ReturnedLength -= cur->Size; - cur = (PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX)((PCHAR)cur + cur->Size); - } - } - - // TODO: Get the cache relationships as well??? - - delete [] pInformation; - } - - bool IsGroupValid(WORD Group) - { - if (Group < _vProcessorGroupInformation.size()) - { - return true; - } - else - { - return false; - } - } - - // Return the next active processor in the system, exclusive (Next = true) - // or inclusive (Next = false) of the input group/processor. - // Iteration is in order of absolute processor number. - // This does assume at least one core is active, but that is a given. - void GetActiveGroupProcessor(WORD& Group, BYTE& Processor, bool Next) - { - if (Next) - { - Processor++; - } - - while (!_vProcessorGroupInformation[Group].IsProcessorActive(Processor)) - { - if (!_vProcessorGroupInformation[Group].IsProcessorValid(Processor)) - { - Processor = 0; - if (!IsGroupValid(++Group)) - { - Group = 0; - } - } - else - { - Processor++; - } - } - } -}; - -// -// Helper macros for outputting indented XML. They assume a local variable "indent". -// Use the Inc form when outputting the opening tag for a multi-line section: -// Use Dec for the closing tag: -// - -// start line with indent -#define AddXml(s,str) { (s).append(indent, ' '); (s) += (str); } -// start new indented section -#define AddXmlInc(s,str) { (s).append(indent, ' '); indent += 2; (s) += (str); } -// end indented section -#define AddXmlDec(s,str) { indent -= 2; (s).append(indent, ' '); (s) += (str); } - -class SystemInformation -{ -private: - SYSTEMTIME StartTime; - -public: - string sComputerName; - ProcessorTopology processorTopology; - - SystemInformation() - { - // System Name - char buffer[64]; - DWORD cb = _countof(buffer); - BOOL fResult; - -#pragma prefast(suppress:38020, "Yes, we're aware this is an ANSI API in a UNICODE project") - fResult = GetComputerNameExA(ComputerNamePhysicalDnsHostname, buffer, &cb); - if (fResult) - { - sComputerName = buffer; - } - - // capture start time - GetSystemTime(&StartTime); - } - - // for unit test, squelch variable timestamp - void SystemInformation::ResetTime() - { - StartTime = { 0 }; - } - - string SystemInformation::GetText() const - { - char szBuffer[64]; // enough for 64bit mask (17ch) and timestamp - int nWritten; - string sText("System information:\n\n"); - - // identify computer which ran the test - sText += "\tcomputer name: "; - sText += sComputerName; - sText += "\n"; - - sText += "\tstart time: "; - if (StartTime.wYear) { - - nWritten = sprintf_s(szBuffer, _countof(szBuffer), - "%u/%02u/%02u %02u:%02u:%02u UTC", - StartTime.wYear, - StartTime.wMonth, - StartTime.wDay, - StartTime.wHour, - StartTime.wMinute, - StartTime.wSecond); - assert(nWritten && nWritten < _countof(szBuffer)); - sText += szBuffer; - } - - return sText; - } - - string SystemInformation::GetXml(UINT32 indent) const - { - char szBuffer[64]; // enough for 64bit mask (17ch) and timestamp - int nWritten; - string sXml; - - AddXmlInc(sXml, "\n"); - - // identify computer which ran the test - AddXml(sXml, ""); - sXml += sComputerName; - sXml += "\n"; - - // identify tool version which performed the test - AddXmlInc(sXml, "\n"); - AddXml(sXml,"" DISKSPD_NUMERIC_VERSION_STRING "\n"); - AddXml(sXml, "" DISKSPD_DATE_VERSION_STRING "\n"); - AddXmlDec(sXml, "\n"); - - AddXml(sXml, ""); - if (StartTime.wYear) { - - nWritten = sprintf_s(szBuffer, _countof(szBuffer), - "%u/%02u/%02u %02u:%02u:%02u UTC", - StartTime.wYear, - StartTime.wMonth, - StartTime.wDay, - StartTime.wHour, - StartTime.wMinute, - StartTime.wSecond); - assert(nWritten && nWritten < _countof(szBuffer)); - sXml += szBuffer; - } - sXml += "\n"; - - // processor topology - AddXmlInc(sXml, "\n"); - for (const auto& g : processorTopology._vProcessorGroupInformation) - { - AddXml(sXml, "\n"; - - } - for (const auto& n : processorTopology._vProcessorNumaInformation) - { - AddXml(sXml, "\n"; - } - for (const auto& s : processorTopology._vProcessorSocketInformation) - { - AddXmlInc(sXml, "\n"); - for (const auto& g : s._vProcessorMasks) - { - AddXml(sXml, "\n"; - } - AddXmlDec(sXml, "\n"); - } - for (const auto& h : processorTopology._vProcessorHyperThreadInformation) - { - AddXml(sXml, "\n"; - } - - AddXmlDec(sXml, "\n"); - AddXmlDec(sXml, "\n"); - - return sXml; - } -}; - -extern SystemInformation g_SystemInformation; - -struct Synchronization -{ - ULONG ulStructSize; //size of the structure that the caller is aware of (to easier achieve backward compatibility in a future) - HANDLE hStopEvent; //an event to be signalled if the scenario is to be stop before time ellapses - HANDLE hStartEvent; //an event for signalling start - CALLBACK_TEST_STARTED pfnCallbackTestStarted; //a function to be called if the measured test is about to start - CALLBACK_TEST_FINISHED pfnCallbackTestFinished; //a function to be called as soon as the measrued test finishes -}; - -#define STRUCT_SYNCHRONIZATION_SUPPORTS(pSynch, Field) ( \ - (NULL != (pSynch)) && \ - ((pSynch)->ulStructSize >= offsetof(struct Synchronization, Field) + sizeof((pSynch)->Field)) \ - ) - -// caching modes -// cached -> default (-Sb explicitly) -// disableoscache -> no_intermediate_buffering (-S or -Su) -// disablelocalcache -> cached, but then tear down local rdr cache (-Sr) -enum class TargetCacheMode { - Undefined = 0, - Cached, - DisableOSCache, - DisableLocalCache -}; - -// writethrough modes -// off -> default -// on -> (-Sw or implied with -Sh == -Suw/-Swu) -enum class WriteThroughMode { - Undefined = 0, - Off, - On, -}; - -// memory mapped IO modes -// off -> default -// on -> (-Sm or -Smw) -enum class MemoryMappedIoMode { - Undefined = 0, - Off, - On, -}; - -// memory mapped IO flush modes -// off / Undefined -> default -// on -> (-Sm or -Smw) -enum class MemoryMappedIoFlushMode { - Undefined = 0, - ViewOfFile, - NonVolatileMemory, - NonVolatileMemoryNoDrain, -}; - -enum class IOMode -{ - Unknown, - Random, - Sequential, - Mixed, - InterlockedSequential, - ParallelAsync -}; - -class ThreadTarget -{ -public: - - ThreadTarget() : - _ulThread(0xFFFFFFFF), - _ulWeight(0) - { - } - - void SetThread(UINT32 ulThread) { _ulThread = ulThread; } - UINT32 GetThread() const { return _ulThread; } - - void SetWeight(UINT32 ulWeight) { _ulWeight = ulWeight; } - UINT32 GetWeight() const { return _ulWeight; } - - string GetXml(UINT32 indent) const; - -private: - UINT32 _ulThread; - UINT32 _ulWeight; -}; - -// Character which leads off a template target definition; e.g. *1, *2 -#define TEMPLATE_TARGET_PREFIX ('*') - -class Target -{ -public: - - Target() : - _dwBlockSize(64 * 1024), - _dwRequestCount(2), - _ullBlockAlignment(0), - _ulWriteRatio(0), - _ulRandomRatio(0), - _ullBaseFileOffset(0), - _fParallelAsyncIO(false), - _fInterlockedSequential(false), - _cacheMode(TargetCacheMode::Cached), - _writeThroughMode(WriteThroughMode::Off), - _memoryMappedIoMode(MemoryMappedIoMode::Off), - _memoryMappedIoNvToken(nullptr), - _memoryMappedIoFlushMode(MemoryMappedIoFlushMode::Undefined), - _fZeroWriteBuffers(false), - _dwThreadsPerFile(1), - _ullThreadStride(0), - _fCreateFile(false), - _fPrecreated(false), - _ullFileSize(0), - _ullMaxFileSize(0), - _fUseBurstSize(false), - _dwBurstSize(0), - _dwThinkTime(0), - _fThinkTime(false), - _fSequentialScanHint(false), - _fRandomAccessHint(false), - _fTemporaryFileHint(false), - _fUseLargePages(false), - _mappedViewFileHandle(INVALID_HANDLE_VALUE), - _mappedView(NULL), - _ioPriorityHint(IoPriorityHintNormal), - _ulWeight(1), - _dwThroughputBytesPerMillisecond(0), - _dwThroughputIOPS(0), - _cbRandomDataWriteBuffer(0), - _sRandomDataWriteBufferSourcePath(), - _pRandomDataWriteBuffer(nullptr), - _distributionType(DistributionType::None) - { - } - - IOMode GetIOMode() const - { - if (GetRandomRatio() == 100) - { - return IOMode::Random; - } - else if (GetRandomRatio() != 0) - { - return IOMode::Mixed; - } - else if (GetUseParallelAsyncIO()) - { - return IOMode::ParallelAsync; - } - else if (GetUseInterlockedSequential()) - { - return IOMode::InterlockedSequential; - } - else - { - return IOMode::Sequential; - } - } - - void SetPath(const string& sPath) { _sPath = sPath; } - void SetPath(const char *pPath) { _sPath = pPath; } - const string& GetPath() const { return _sPath; } - - void SetBlockSizeInBytes(DWORD dwBlockSize) { _dwBlockSize = dwBlockSize; } - DWORD GetBlockSizeInBytes() const { return _dwBlockSize; } - - void SetBlockAlignmentInBytes(UINT64 ullBlockAlignment) - { - _ullBlockAlignment = ullBlockAlignment; - } - // actual is used in validation to detect unclear/mis-specified intent - // like -rs -s - UINT64 GetBlockAlignmentInBytes(bool actual = false) const - { - return _ullBlockAlignment ? _ullBlockAlignment : (actual ? 0 : _dwBlockSize); - } - - void SetWriteRatio(UINT32 writeRatio) { _ulWriteRatio = writeRatio; } - UINT32 GetWriteRatio() const { return _ulWriteRatio; } - - void SetRandomRatio(UINT32 randomRatio) { _ulRandomRatio = randomRatio; } - UINT32 GetRandomRatio() const { return _ulRandomRatio; } - - void SetBaseFileOffsetInBytes(UINT64 ullBaseFileOffset) { _ullBaseFileOffset = ullBaseFileOffset; } - UINT64 GetBaseFileOffsetInBytes() const { return _ullBaseFileOffset; } - UINT64 GetThreadBaseRelativeOffsetInBytes(UINT32 ulThreadNo) const { return ulThreadNo * _ullThreadStride; } - UINT64 GetThreadBaseFileOffsetInBytes(UINT32 ulThreadNo) const { return _ullBaseFileOffset + GetThreadBaseRelativeOffsetInBytes(ulThreadNo); } - - - void SetSequentialScanHint(bool fBool) { _fSequentialScanHint = fBool; } - bool GetSequentialScanHint() const { return _fSequentialScanHint; } - - void SetRandomAccessHint(bool fBool) { _fRandomAccessHint = fBool; } - bool GetRandomAccessHint() const { return _fRandomAccessHint; } - - void SetTemporaryFileHint(bool fBool) { _fTemporaryFileHint = fBool; } - bool GetTemporaryFileHint() const { return _fTemporaryFileHint; } - - void SetUseLargePages(bool fBool) { _fUseLargePages = fBool; } - bool GetUseLargePages() const { return _fUseLargePages; } - - void SetRequestCount(DWORD dwRequestCount) { _dwRequestCount = dwRequestCount; } - DWORD GetRequestCount() const { return _dwRequestCount; } - - void SetCacheMode(TargetCacheMode cacheMode) { _cacheMode = cacheMode; } - TargetCacheMode GetCacheMode() const { return _cacheMode; } - - void SetWriteThroughMode(WriteThroughMode writeThroughMode ) { _writeThroughMode = writeThroughMode; } - WriteThroughMode GetWriteThroughMode() const { return _writeThroughMode; } - - void SetMemoryMappedIoMode(MemoryMappedIoMode memoryMappedIoMode ) { _memoryMappedIoMode = memoryMappedIoMode; } - MemoryMappedIoMode GetMemoryMappedIoMode() const { return _memoryMappedIoMode; } - - void SetMemoryMappedIoNvToken(PVOID memoryMappedIoNvToken) { _memoryMappedIoNvToken = memoryMappedIoNvToken; } - PVOID GetMemoryMappedIoNvToken() const { return _memoryMappedIoNvToken; } - - void SetMemoryMappedIoFlushMode(MemoryMappedIoFlushMode memoryMappedIoFlushMode) { _memoryMappedIoFlushMode = memoryMappedIoFlushMode; } - MemoryMappedIoFlushMode GetMemoryMappedIoFlushMode() const { return _memoryMappedIoFlushMode; } - - void SetZeroWriteBuffers(bool fBool) { _fZeroWriteBuffers = fBool; } - bool GetZeroWriteBuffers() const { return _fZeroWriteBuffers; } - - void SetRandomDataWriteBufferSize(UINT64 cbWriteBuffer) { _cbRandomDataWriteBuffer = cbWriteBuffer; } - UINT64 GetRandomDataWriteBufferSize(void) const { return _cbRandomDataWriteBuffer; } - - void SetRandomDataWriteBufferSourcePath(string sPath) { _sRandomDataWriteBufferSourcePath = sPath; } - string GetRandomDataWriteBufferSourcePath() const { return _sRandomDataWriteBufferSourcePath; } - - void SetUseBurstSize(bool fBool) { _fUseBurstSize = fBool; } - bool GetUseBurstSize() const { return _fUseBurstSize; } - - void SetBurstSize(DWORD dwBurstSize) { _dwBurstSize = dwBurstSize; } - DWORD GetBurstSize() const { return _dwBurstSize; } - - void SetThinkTime(DWORD dwThinkTime) { _dwThinkTime = dwThinkTime; } - DWORD GetThinkTime() const { return _dwThinkTime; } - - void SetEnableThinkTime(bool fBool) { _fThinkTime = fBool; } - bool GetEnableThinkTime() const { return _fThinkTime; } - - void SetThreadsPerFile(DWORD dwThreadsPerFile) { _dwThreadsPerFile = dwThreadsPerFile; } - DWORD GetThreadsPerFile() const { return _dwThreadsPerFile; } - - void SetCreateFile(bool fBool) { _fCreateFile = fBool; } - bool GetCreateFile() const { return _fCreateFile; } - - void SetFileSize(UINT64 ullFileSize) { _ullFileSize = ullFileSize; } - UINT64 GetFileSize() const { return _ullFileSize; } // TODO: InBytes - - void SetMaxFileSize(UINT64 ullMaxFileSize) { _ullMaxFileSize = ullMaxFileSize; } - UINT64 GetMaxFileSize() const { return _ullMaxFileSize; } - - void SetUseParallelAsyncIO(bool fBool) { _fParallelAsyncIO = fBool; } - bool GetUseParallelAsyncIO() const { return _fParallelAsyncIO; } - - void SetUseInterlockedSequential(bool fBool) { _fInterlockedSequential = fBool; } - bool GetUseInterlockedSequential() const { return _fInterlockedSequential; } - - void SetThreadStrideInBytes(UINT64 ullThreadStride) { _ullThreadStride = ullThreadStride; } - UINT64 GetThreadStrideInBytes() const { return _ullThreadStride; } - - void SetMappedViewFileHandle(HANDLE FileHandle) { _mappedViewFileHandle = FileHandle; } - HANDLE GetMappedViewFileHandle() const { return _mappedViewFileHandle; } - - void SetMappedView(BYTE *MappedView) { _mappedView = MappedView; } - BYTE* GetMappedView() const { return _mappedView; } - - void SetIOPriorityHint(PRIORITY_HINT _hint) - { - assert(_hint < MaximumIoPriorityHintType); - _ioPriorityHint = _hint; - } - PRIORITY_HINT GetIOPriorityHint() const { return _ioPriorityHint; } - - void SetWeight(UINT32 ulWeight) { _ulWeight = ulWeight; } - UINT32 GetWeight() const { return _ulWeight; } - - void AddThreadTarget(const ThreadTarget &threadTarget) - { - _vThreadTargets.push_back(threadTarget); - } - vector GetThreadTargets() const { return _vThreadTargets; } - - void SetPrecreated(bool fBool) { _fPrecreated = fBool; } - bool GetPrecreated() const { return _fPrecreated; } - - // Convert units to BPMS. Nonzero value of IOPS indicates originally specified units for display/profile. - void SetThroughputIOPS(DWORD dwIOPS) - { - _dwThroughputIOPS = dwIOPS; - _dwThroughputBytesPerMillisecond = (dwIOPS * _dwBlockSize) / 1000; - } - DWORD GetThroughputIOPS() const { return _dwThroughputIOPS; } - void SetThroughput(DWORD dwThroughputBytesPerMillisecond) - { - _dwThroughputIOPS = 0; - _dwThroughputBytesPerMillisecond = dwThroughputBytesPerMillisecond; - } - DWORD GetThroughputInBytesPerMillisecond() const { return _dwThroughputBytesPerMillisecond; } - - string GetXml(UINT32 indent) const; - - bool AllocateAndFillRandomDataWriteBuffer(Random *pRand); - void FreeRandomDataWriteBuffer(); - BYTE* GetRandomDataWriteBuffer(Random *pRand); - - void SetDistributionRange(const vector& v, DistributionType t) - { - _vDistributionRange = v; _distributionType = t; - - // Now place final element if IO% is < 100. - // If this is an absolute specification, it will map to zero length here and - // conversion will occur at the time of target open to the rest of the target. - // For the percent specification we place the final element as-if directly stated, - // consuming the tail length. - // - // This done here so that the stated specification is indeed complete, and not left - // for the effective distribution. - // - // TBD this should be moved to a proper Distribution class. - - const DistributionRange& last = *_vDistributionRange.rbegin(); - - UINT32 ioCur = last._src + last._span; - if (ioCur < 100) - { - UINT64 targetCur = last._dst.first + last._dst.second; - if (t == DistributionType::Percent && targetCur < 100) - { - // tail is available - // if tail is not available, this will be caught by validation - _vDistributionRange.emplace_back(ioCur, 100 - ioCur, make_pair(targetCur, 100 - targetCur)); - } - else - { - _vDistributionRange.emplace_back(ioCur, 100 - ioCur, make_pair(targetCur, 0)); - } - } - } - auto& GetDistributionRange() const { return _vDistributionRange; } - auto GetDistributionType() const { return _distributionType; } - - DWORD GetCreateFlags(bool fAsync) - { - DWORD dwFlags = FILE_ATTRIBUTE_NORMAL; - - if (GetSequentialScanHint()) - { - dwFlags |= FILE_FLAG_SEQUENTIAL_SCAN; - } - - if (GetRandomAccessHint()) - { - dwFlags |= FILE_FLAG_RANDOM_ACCESS; - } - - if (GetTemporaryFileHint()) - { - dwFlags |= FILE_ATTRIBUTE_TEMPORARY; - } - - if (fAsync) - { - dwFlags |= FILE_FLAG_OVERLAPPED; - } - - if (GetCacheMode() == TargetCacheMode::DisableOSCache) - { - dwFlags |= FILE_FLAG_NO_BUFFERING; - } - - if (GetWriteThroughMode( ) == WriteThroughMode::On) - { - dwFlags |= FILE_FLAG_WRITE_THROUGH; - } - - return dwFlags; - } - -private: - string _sPath; - DWORD _dwBlockSize; - DWORD _dwRequestCount; // TODO: change the name to something more descriptive (OutstandingRequestCount?) - - UINT64 _ullBlockAlignment; - UINT32 _ulWriteRatio; - UINT32 _ulRandomRatio; - - UINT64 _ullBaseFileOffset; - - TargetCacheMode _cacheMode; - WriteThroughMode _writeThroughMode; - MemoryMappedIoMode _memoryMappedIoMode; - MemoryMappedIoFlushMode _memoryMappedIoFlushMode; - PVOID _memoryMappedIoNvToken; - DWORD _dwThreadsPerFile; - UINT64 _ullThreadStride; - - UINT64 _ullFileSize; - UINT64 _ullMaxFileSize; - - DWORD _dwBurstSize; // number of IOs in a burst - DWORD _dwThinkTime; // time to pause before issuing the next burst of IOs - - DWORD _dwThroughputBytesPerMillisecond; // set to 0 to disable throttling - DWORD _dwThroughputIOPS; // if IOPS are specified they are converted to BPMS but saved for fidelity to XML/output - - bool _fThinkTime:1; // variable to decide whether to think between IOs (default is false) (removed by using _dwThinkTime==0?) - bool _fUseBurstSize:1; // TODO: "use" or "enable"?; since burst size must be specified with the think time, one variable should be sufficient - bool _fZeroWriteBuffers:1; - bool _fCreateFile:1; - bool _fPrecreated:1; // used to track which files have been created before the first timespan and which have to be created later - bool _fParallelAsyncIO:1; - bool _fInterlockedSequential:1; - bool _fSequentialScanHint:1; // open file with the FILE_FLAG_SEQUENTIAL_SCAN hint - bool _fRandomAccessHint:1; // open file with the FILE_FLAG_RANDOM_ACCESS hint - bool _fTemporaryFileHint:1; // open file with the FILE_ATTRIBUTE_TEMPORARY hint - bool _fUseLargePages:1; // Use large pages for IO buffers - - UINT64 _cbRandomDataWriteBuffer; // if > 0, then the write buffer should be filled with random data - string _sRandomDataWriteBufferSourcePath; // file that should be used for filling the write buffer (if the path is not available, use a crypto provider) - BYTE *_pRandomDataWriteBuffer; // a buffer used for write data when _cbWriteBuffer > 0; it's shared by all the threads working on this target - - HANDLE _mappedViewFileHandle; - BYTE *_mappedView; - - PRIORITY_HINT _ioPriorityHint; - - UINT32 _ulWeight; - vector _vThreadTargets; - - vector _vDistributionRange; - DistributionType _distributionType; - - bool _FillRandomDataWriteBuffer(Random *pRand); - - friend class UnitTests::ProfileUnitTests; - friend class UnitTests::TargetUnitTests; -}; - -class AffinityAssignment -{ -public: - WORD wGroup; - BYTE bProc; - - AffinityAssignment() = delete; - AffinityAssignment(WORD p_wGroup, BYTE p_bProc) : - wGroup(p_wGroup), - bProc(p_bProc) - { - } -}; - -class TimeSpan -{ -public: - TimeSpan() : - _ulDuration(10), - _ulWarmUp(5), - _ulCoolDown(0), - _ulRandSeed(0), - _dwThreadCount(0), - _dwRequestCount(0), - _fRandomWriteData(false), - _fDisableAffinity(false), - _fCompletionRoutines(false), - _fMeasureLatency(false), - _fCalculateIopsStdDev(false), - _ulIoBucketDurationInMilliseconds(1000) - { - } - - void ClearAffinityAssignment() - { - _vAffinity.clear(); - } - void AddAffinityAssignment(WORD wGroup, BYTE bProc) - { - _vAffinity.emplace_back(wGroup, bProc); - } - const auto& GetAffinityAssignments() const { return _vAffinity; } - - void AddTarget(const Target& target) - { - _vTargets.push_back(Target(target)); - } - - vector GetTargets() const { return _vTargets; } - - void SetDuration(UINT32 ulDuration) { _ulDuration = ulDuration; } - UINT32 GetDuration() const { return _ulDuration; } - - void SetWarmup(UINT32 ulWarmup) { _ulWarmUp = ulWarmup; } - UINT32 GetWarmup() const { return _ulWarmUp; } - - void SetCooldown(UINT32 ulCooldown) { _ulCoolDown = ulCooldown; } - UINT32 GetCooldown() const { return _ulCoolDown; } - - void SetRandSeed(UINT32 ulRandSeed) { _ulRandSeed = ulRandSeed; } - UINT32 GetRandSeed() const { return _ulRandSeed; } - - void SetRandomWriteData(bool fRandomWriteData) { _fRandomWriteData = fRandomWriteData; } - bool GetRandomWriteData() const { return _fRandomWriteData; } - - void SetThreadCount(DWORD dwThreadCount) { _dwThreadCount = dwThreadCount; } - DWORD GetThreadCount() const { return _dwThreadCount; } - - void SetRequestCount(DWORD dwRequestCount) { _dwRequestCount = dwRequestCount; } - DWORD GetRequestCount() const { return _dwRequestCount; } - - void SetDisableAffinity(bool fDisableAffinity) { _fDisableAffinity = fDisableAffinity; } - bool GetDisableAffinity() const { return _fDisableAffinity; } - - void SetCompletionRoutines(bool fCompletionRoutines) { _fCompletionRoutines = fCompletionRoutines; } - bool GetCompletionRoutines() const { return _fCompletionRoutines; } - - void SetMeasureLatency(bool fMeasureLatency) { _fMeasureLatency = fMeasureLatency; } - bool GetMeasureLatency() const { return _fMeasureLatency; } - - void SetCalculateIopsStdDev(bool fCalculateStdDev) { _fCalculateIopsStdDev = fCalculateStdDev; } - bool GetCalculateIopsStdDev() const { return _fCalculateIopsStdDev; } - - void SetIoBucketDurationInMilliseconds(UINT32 ulIoBucketDurationInMilliseconds) { _ulIoBucketDurationInMilliseconds = ulIoBucketDurationInMilliseconds; } - UINT32 GetIoBucketDurationInMilliseconds() const { return _ulIoBucketDurationInMilliseconds; } - - string GetXml(UINT32 indent) const; - void MarkFilesAsPrecreated(const vector vFiles); - -private: - vector _vTargets; - UINT32 _ulDuration; - UINT32 _ulWarmUp; - UINT32 _ulCoolDown; - UINT32 _ulRandSeed; - DWORD _dwThreadCount; - DWORD _dwRequestCount; - bool _fRandomWriteData; - bool _fDisableAffinity; - vector _vAffinity; - bool _fCompletionRoutines; - bool _fMeasureLatency; - bool _fCalculateIopsStdDev; - UINT32 _ulIoBucketDurationInMilliseconds; - - friend class UnitTests::ProfileUnitTests; -}; - -enum class ResultsFormat -{ - Text, - Xml -}; - -enum class PrecreateFiles -{ - None, - UseMaxSize, - OnlyFilesWithConstantSizes, - OnlyFilesWithConstantOrZeroSizes -}; - -class Profile -{ -public: - Profile() : - _fProfileOnly(false), - _fVerbose(false), - _dwProgress(0), - _fEtwEnabled(false), - _fEtwProcess(false), - _fEtwThread(false), - _fEtwImageLoad(false), - _fEtwDiskIO(false), - _fEtwMemoryPageFaults(false), - _fEtwMemoryHardFaults(false), - _fEtwNetwork(false), - _fEtwRegistry(false), - _fEtwUsePagedMemory(false), - _fEtwUsePerfTimer(false), - _fEtwUseSystemTimer(false), - _fEtwUseCyclesCounter(false), - _resultsFormat(ResultsFormat::Text), - _precreateFiles(PrecreateFiles::None) - { - } - - void ClearTimeSpans() - { - _vTimeSpans.clear(); - } - - void AddTimeSpan(const TimeSpan& timeSpan) - { - _vTimeSpans.push_back(TimeSpan(timeSpan)); - } - - const vector& GetTimeSpans() const { return _vTimeSpans; } - - void SetProfileOnly(bool b) { _fProfileOnly = b; } - bool GetProfileOnly() const { return _fProfileOnly; } - - void SetVerbose(bool b) { _fVerbose = b; } - bool GetVerbose() const { return _fVerbose; } - - void SetProgress(DWORD dwProgress) { _dwProgress = dwProgress; } - DWORD GetProgress() const { return _dwProgress; } - - void SetCmdLine(string sCmdLine) { _sCmdLine = sCmdLine; } - string GetCmdLine() const { return _sCmdLine; }; - - void SetResultsFormat(ResultsFormat format) { _resultsFormat = format; } - ResultsFormat GetResultsFormat() const { return _resultsFormat; } - - void SetPrecreateFiles(PrecreateFiles c) { _precreateFiles = c; } - PrecreateFiles GetPrecreateFiles() const { return _precreateFiles; } - - //ETW - void SetEtwEnabled(bool b) { _fEtwEnabled = b; } - void SetEtwProcess(bool b) { _fEtwProcess = b; } - void SetEtwThread(bool b) { _fEtwThread = b; } - void SetEtwImageLoad(bool b) { _fEtwImageLoad = b; } - void SetEtwDiskIO(bool b) { _fEtwDiskIO = b; } - void SetEtwMemoryPageFaults(bool b) { _fEtwMemoryPageFaults = b; } - void SetEtwMemoryHardFaults(bool b) { _fEtwMemoryHardFaults = b; } - void SetEtwNetwork(bool b) { _fEtwNetwork = b; } - void SetEtwRegistry(bool b) { _fEtwRegistry = b; } - void SetEtwUsePagedMemory(bool b) { _fEtwUsePagedMemory = b; } - void SetEtwUsePerfTimer(bool b) { _fEtwUsePerfTimer = b; } - void SetEtwUseSystemTimer(bool b) { _fEtwUseSystemTimer = b; } - void SetEtwUseCyclesCounter(bool b) { _fEtwUseCyclesCounter = b; } - - bool GetEtwEnabled() const { return _fEtwEnabled; } - bool GetEtwProcess() const { return _fEtwProcess; } - bool GetEtwThread() const { return _fEtwThread; } - bool GetEtwImageLoad() const { return _fEtwImageLoad; } - bool GetEtwDiskIO() const { return _fEtwDiskIO; } - bool GetEtwMemoryPageFaults() const { return _fEtwMemoryPageFaults; } - bool GetEtwMemoryHardFaults() const { return _fEtwMemoryHardFaults; } - bool GetEtwNetwork() const { return _fEtwNetwork; } - bool GetEtwRegistry() const { return _fEtwRegistry; } - bool GetEtwUsePagedMemory() const { return _fEtwUsePagedMemory; } - bool GetEtwUsePerfTimer() const { return _fEtwUsePerfTimer; } - bool GetEtwUseSystemTimer() const { return _fEtwUseSystemTimer; } - bool GetEtwUseCyclesCounter() const { return _fEtwUseCyclesCounter; } - - string GetXml(UINT32 indent) const; - bool Validate(bool fSingleSpec, SystemInformation *pSystem = nullptr) const; - void MarkFilesAsPrecreated(const vector vFiles); - -private: - Profile(const Profile& T); - - vector_vTimeSpans; - bool _fVerbose; - bool _fProfileOnly; - DWORD _dwProgress; - string _sCmdLine; - ResultsFormat _resultsFormat; - PrecreateFiles _precreateFiles; - - //ETW - bool _fEtwEnabled; - bool _fEtwProcess; - bool _fEtwThread; - bool _fEtwImageLoad; - bool _fEtwDiskIO; - bool _fEtwMemoryPageFaults; - bool _fEtwMemoryHardFaults; - bool _fEtwNetwork; - bool _fEtwRegistry; - bool _fEtwUsePagedMemory; - bool _fEtwUsePerfTimer; - bool _fEtwUseSystemTimer; - bool _fEtwUseCyclesCounter; - - friend class UnitTests::ProfileUnitTests; -}; - -class IORequest -{ -public: - IORequest(Random *pRand) : - _ioType(IOOperation::ReadIO), - _pRand(pRand), - _pCurrentTarget(nullptr), - _ullStartTime(0), - _ulRequestIndex(0xFFFFFFFF), - _ullTotalWeight(0), - _fEqualWeights(true), - _ActivityId() - { - memset(&_overlapped, 0, sizeof(OVERLAPPED)); - } - - static IORequest *OverlappedToIORequest(OVERLAPPED *pOverlapped) - { - return CONTAINING_RECORD(pOverlapped, IORequest, _overlapped); - } - - OVERLAPPED *GetOverlapped() { return &_overlapped; } - - void AddTarget(Target *pTarget, UINT32 ulWeight) - { - _vTargets.push_back(pTarget); - _vulTargetWeights.push_back(ulWeight); - _ullTotalWeight += ulWeight; - - if (ulWeight != _vulTargetWeights[0]) { - _fEqualWeights = false; - } - } - - Target *GetCurrentTarget() { return _pCurrentTarget; } - - Target *GetNextTarget() - { - UINT64 ullWeight; - - if (_vTargets.size() == 1) { - _pCurrentTarget = _vTargets[0]; - } - else if (_fEqualWeights) { - _pCurrentTarget = _vTargets[_pRand->Rand32() % _vTargets.size()]; - } - else { - ullWeight = _pRand->Rand64() % _ullTotalWeight; - - for (size_t iTarget = 0; iTarget < _vTargets.size(); iTarget++) { - if (ullWeight < _vulTargetWeights[iTarget]) { - _pCurrentTarget = _vTargets[iTarget]; - break; - } - - ullWeight -= _vulTargetWeights[iTarget]; - } - } - - return _pCurrentTarget; - } - - void SetIoType(IOOperation ioType) { _ioType = ioType; } - IOOperation GetIoType() const { return _ioType; } - - void SetStartTime(UINT64 ullStartTime) { _ullStartTime = ullStartTime; } - UINT64 GetStartTime() const { return _ullStartTime; } - - void SetRequestIndex(UINT32 ulRequestIndex) { _ulRequestIndex = ulRequestIndex; } - UINT32 GetRequestIndex() const { return _ulRequestIndex; } - - void SetActivityId(GUID ActivityId) { _ActivityId = ActivityId; } - GUID GetActivityId() const { return _ActivityId; } - -private: - OVERLAPPED _overlapped; - vector _vTargets; - vector _vulTargetWeights; - UINT64 _ullTotalWeight; - bool _fEqualWeights; - Random *_pRand; - Target *_pCurrentTarget; - IOOperation _ioType; - UINT64 _ullStartTime; - UINT32 _ulRequestIndex; - GUID _ActivityId; -}; - -typedef struct _ACTIVITY_ID { - UINT32 Thread; - UINT32 Reserved; - UINT64 Count; -} ACTIVITY_ID; - -C_ASSERT(sizeof(ACTIVITY_ID) == sizeof(GUID)); - -// Forward declaration -class ThreadTargetState; - -class ThreadParameters -{ -public: - ThreadParameters() : - pProfile(nullptr), - pTimeSpan(nullptr), - pullSharedSequentialOffsets(nullptr), - ulRandSeed(0), - ulThreadNo(0), - ulRelativeThreadNo(0) - { - } - - const Profile *pProfile; - const TimeSpan *pTimeSpan; - - vector vTargets; - vector vTargetStates; - vector vhTargets; - - vector vulReadBufferSize; - vector vpDataBuffers; - vector vIORequest; - vector vThroughputMeters; - - // For interlocked sequential access (-si): - // Pointers to offsets shared between threads, incremented with an interlocked op - UINT64* pullSharedSequentialOffsets; - - Random *pRand; - - UINT32 ulRandSeed; - UINT32 ulThreadNo; - UINT32 ulRelativeThreadNo; - - // accounting - volatile bool *pfAccountingOn; - PUINT64 pullStartTime; - ThreadResults *pResults; - - //progress dots - DWORD dwIOCnt; - - //group affinity - WORD wGroupNum; - DWORD bProcNum; - - HANDLE hStartEvent; - - // TODO: check how it's used - HANDLE hEndEvent; //used only in case of completion routines (not for IO Completion Ports) - - bool AllocateAndFillBufferForTarget(const Target& target); - BYTE* GetReadBuffer(size_t iTarget, size_t iRequest); - BYTE* GetWriteBuffer(size_t iTarget, size_t iRequest); - DWORD GetTotalRequestCount() const; - bool InitializeMappedViewForTarget(Target& target, DWORD DesiredAccess); - - GUID NextActivityId() - { - GUID ActivityId; - ACTIVITY_ID* ActivityGuid = (ACTIVITY_ID*)&ActivityId; - - ActivityGuid->Thread = ulThreadNo; - ActivityGuid->Reserved = 0; - // The count is byte swapped so it's understandable in a trace. - ActivityGuid->Count = _byteswap_uint64(++_ullActivityCount); - - return ActivityId; - } - -private: - ThreadParameters(const ThreadParameters& T); - UINT64 _ullActivityCount; -}; - -class ThreadTargetState -{ - public: - - ThreadTargetState( - const ThreadParameters *pTp, - size_t iTarget, - UINT64 targetSize - ) : - _tp(pTp), - _target(&_tp->vTargets[iTarget]), - _targetSize(targetSize), - _mode(_target->GetIOMode()), - - _nextSeqOffset(0), - _lastIO(IOOperation::Unknown), - _sharedSeqOffset(nullptr), - _ioDistributionSpan(100) - { - // - // Now calculate the maximum base-relative file offset that IO can be issued at. - // - // Trim by max file size limit, and reduce by base file offset. - // - - if (_target->GetMaxFileSize()) - { - _relTargetSize = _targetSize > _target->GetMaxFileSize() ? _target->GetMaxFileSize() : _targetSize; - } - else - { - _relTargetSize = _targetSize; - } - - _relTargetSize -= _target->GetBaseFileOffsetInBytes(); - - // - // Align relative to the maximum offset at which aligned IO could be issued at. - // - - _relTargetSizeAligned = _relTargetSize - _target->GetBlockSizeInBytes(); - _relTargetSizeAligned -= _relTargetSizeAligned % _target->GetBlockAlignmentInBytes(); - _relTargetSizeAligned += _target->GetBlockAlignmentInBytes(); - - // Grab the shared sequential pointer if this is interlocked. - - if (_mode == IOMode::InterlockedSequential) - { - assert(_tp->pullSharedSequentialOffsets != nullptr); - _sharedSeqOffset = &_tp->pullSharedSequentialOffsets[iTarget]; - } - - // Convert and finalize the random distribution stated in the target using final bounds. - - switch (_target->GetDistributionType()) - { - case DistributionType::Percent: - { - UINT32 ioCarry = 0; - - for (auto& r : _target->GetDistributionRange()) - { - // - // The basic premise is to align the range's bounds to discover whether there are - // any aligned offsets within it. To do this we align DOWN. This moves the adjacent - // end of this range and base of the next in lockstep. - // - // There are two basic branches and three subcases in each: - // - // * aligned base - // * unaligned base - // * and within each - // * aligned end - // * unaligned end in same alignment unit - // * unaligned end in next/following alignment unit - // - // * aligned/aligned will not move b/e, there will be a positive range - // * aligned/unaligned-next will move e in step with the following b - // and there will be a positive range - // * aligned/unaligned-same will result in b=e after aligning; IO at b is - // the only possible IO - // - // Unaligned base is more interesting due to degenerate spans, spans where the - // mimimum %range is smaller than the block alignment. For instance, a 100KiB target - // with a 4K alignment has a 1%/1KB minimum and may create these cases. - // - // * unaligned/aligned aligns base (down) and there is a positive range - // * unaligned/unaligned-next aligns both down and there is a positive range - // * unaligned/unaligned-same has no aligned offset in the range; we can detect - // this by aligning e first and seeing if it is less than unaligned b. there - // are two subcases: - // * if the prior range is of zero length, we roll this range's IO% onto it - - // this combines two or more adjacent degenerate spans - // * if it was not of zero length, we roll over the IO% to the next/last range - // - // Now, in the cases where we have a positive range we may still find our aligned - // base is the same as the prior range - the prior was degenerate and the current - // is not. In this case we need to round our base up so that we do not share a base. - // We may then find that our rounded up base makes us degenerate and ... roll over. - // - // Note that this is a closed/open interval. The end offset is NOT a member of this - // range. Consider an 8KiB file divided 50:50 into two 4KB ranges. The first range is - // [0,4KB) and the second is [4KB, 8KB). The IO at offset 4KB belongs to the second - // range, not the first. - // - - // - // Skip holes. These have the effect of excluding a range of the target by way of - // zero IO will be issued to them; the resulting range is still IO 0-100%. - // - - if (!r._span) { - continue; - } - - UINT64 b, e; - - b = ((r._dst.first * _relTargetSizeAligned) / 100); - // guarantee end (don't lose it in integer math) - if (r._dst.first + r._dst.second == 100) - { - e = _relTargetSizeAligned; - } - else - { - e = b + ((r._dst.second * _relTargetSizeAligned) / 100); - } - - e = ROUND_DOWN(e, _target->GetBlockAlignmentInBytes()); - - // unaligned/unaligned-same - // carryover IO% to next/last range - if (e < b) - { - // is the prior range degenerate? - // if so, extend its IO% - // note that this cannot happen for the first range, so there - // will always be a range to look at. - if (_vDistributionRange.rbegin()->_dst.first == e) - { - _vDistributionRange.rbegin()->_span += r._span; - } - // carry over to next - else - { - ioCarry = r._span; - } - - continue; - } - - b = ROUND_DOWN(b, _target->GetBlockAlignmentInBytes()); - - // Now if b < e (a positive range) we may discover we're adjacent - // to a degenerate range. This is the case of re-aligning b up. - // Note that the degenerate range logically rounds up - this does - // not affect operation, but presents the correct appearance of a - // closed/open interval with respect to the subsequent range. - // Case: -rdpct10/1:10/1 - // - // It is possible b == e: this is a case where b was already aligned - // and we're placing a normal degenerate span. No special handling. - - if (b < e && - _vDistributionRange.size() && - _vDistributionRange.rbegin()->_dst.first == b) - { - - b += _target->GetBlockAlignmentInBytes(); - _vDistributionRange.rbegin()->_dst.second += _target->GetBlockAlignmentInBytes(); - - // Now there are two degenerate cases to manage. - - // if we're dealing with a degenerate at the tail, allow carryover - if (b == _relTargetSizeAligned) - { - ioCarry = r._span; - continue; - } - - // otherwise, if the range became degenerate in the up-alignment, it must - // combine with the prior degenerate since its logical range is included - // with it. - if (b == e) - { - _vDistributionRange.rbegin()->_span += r._span; - continue; - } - - // fall through to place re-aligned b/e (non degenerate) - } - - // prefer to roll IO% to the smaller of prior range/this range - if (ioCarry && - _vDistributionRange.rbegin()->_span < r._span) - { - _vDistributionRange.rbegin()->_span += ioCarry; - ioCarry = 0; - } - - _vDistributionRange.emplace_back( - r._src - ioCarry, - r._span + ioCarry, - make_pair(b, e - b)); - - ioCarry = 0; - } - - // Apply trailing carryover to final range, extending it. - // Guarantee target range extends to aligned size - rollover is always from - // a degenerate range we could not place directly. We need to gross up the - // actual tail so that the effective correctly spans the open/closed interval - // to target size. - // -rdpct10/96:10/3:80/1 - the last range is degenerate and needs to roll. - if (ioCarry) - { - DistributionRange& last = *_vDistributionRange.rbegin(); - - last._span += ioCarry; - last._dst.second = _relTargetSizeAligned - last._dst.first; - } - } - break; - - case DistributionType::Absolute: - { - UINT32 ioUsed = 0; - - for (auto& r : _target->GetDistributionRange()) - { - // - // The premise for absolute distributions is similar but without the complication of - // degenerate ranges. The offsets are provided and we only need to push the last to - // the end of the range if it was left open (its length is zero). They do not need to - // be aligned, similar to -T thread stride - this is the caller's dilemma. We already - // know by validation that IO can be issued in the range since any absolute distribution - // with a range < block size would have been rejected. - // - // If the range was not left open we have two cases: - // - // * the end is within the final range - // * the end is past it - // - // If the end is within the final range that will again be the caller's dilemma, we'll - // simply trim the length of that range. If it is past it, we will discard the trailing - // ranges and trim the maximum IO% so that they become a proportional specification of the - // IO. For instance, if a 10/10/80 winds up with the 80% not addressable in the file, the - // maximum IO% trims to 20 and it logically becomes a 50:50 split (10:10). - // - - UINT64 l; - - // - // Skip holes. These have the effect of excluding a range of the target by way of - // zero IO will be issued to them; the resulting range is still IO 0-100%. - // - - if (!r._span) { - continue; - } - - // beyond end? done, with whatever tail IO% not seen - if (r._dst.first >= _relTargetSize) - { - break; - } - // open end or spans end? - set to aligned remainder - else if (r._dst.second == 0 || - r._dst.first + r._dst.second > _relTargetSize) - { - // ensure tail can accept IO by blocksize - caller has stated this is aligned by - // its specification - l = _relTargetSize - r._dst.first; - - if (l < _target->GetBlockSizeInBytes()) - { - break; - } - } - else - { - l = r._dst.second; - } - - _vDistributionRange.emplace_back( - r._src, - r._span, - make_pair(r._dst.first, l)); - - ioUsed += r._span; - } - - // reduce the IO distribution to that specified by the ranges consumed. - // it is still logically 100%, simply over a range of less than 0-100. - _ioDistributionSpan = ioUsed; - } - break; - - // none - default: - break; - } - - Reset(); - } - - // - // Reset IO pointer/type state to initial conditions. - // - - VOID Reset() - { - // - // Now set the (base-relative) initial sequential offset - // * sequential: based on thread stride - // * mixed: randomized starting position - // - // Note this is repeated for ParallelAsync initialization since sequential offset is in the IO request there. - // - - switch (_mode) - { - case IOMode::Sequential: - _nextSeqOffset = _target->GetThreadBaseRelativeOffsetInBytes(_tp->ulRelativeThreadNo); - break; - - case IOMode::Mixed: - _nextSeqOffset = NextRelativeRandomOffset(); - break; - - default: - break; - } - - _lastIO = NextIOType(true); - } - - // - // Validate whether this thread can start IO given thread stride and file size. - // - - bool CanStart() - { - UINT64 startingFileOffset = _target->GetThreadBaseRelativeOffsetInBytes(_tp->ulRelativeThreadNo); - - if (startingFileOffset + _target->GetBlockSizeInBytes() > _relTargetSize) - { - return false; - } - - return true; - } - - UINT64 TargetSize() - { - return _targetSize; - } - - VOID InitializeParallelAsyncIORequest(IORequest& ioRequest) const - { - ULARGE_INTEGER initialOffset; - - // - // Bias backwards by one IO so that this functions as the last-IO-issued pointer. - // It will be incremented to the expected first offset. Note: absolute offset. - // - - initialOffset.QuadPart = _target->GetThreadBaseFileOffsetInBytes(_tp->ulRelativeThreadNo) - _target->GetBlockAlignmentInBytes(); - - ioRequest.GetOverlapped()->Offset = initialOffset.LowPart; - ioRequest.GetOverlapped()->OffsetHigh = initialOffset.HighPart; - } - - UINT64 NextRelativeSeqOffset() - { - UINT64 nextOffset; - - nextOffset = _nextSeqOffset; - - // Wrap? - - if (nextOffset + _target->GetBlockSizeInBytes() > _relTargetSize) { - nextOffset = _target->GetThreadBaseRelativeOffsetInBytes(_tp->ulRelativeThreadNo) % _target->GetBlockAlignmentInBytes(); - } - - _nextSeqOffset = nextOffset + _target->GetBlockAlignmentInBytes(); - - return nextOffset; - } - - UINT64 NextRelativeInterlockedSeqOffset() - { - UINT64 nextOffset; - - // advance shared and rewind to get offset to use - nextOffset = InterlockedAdd64((PLONG64) _sharedSeqOffset, _target->GetBlockAlignmentInBytes()); - nextOffset -= _target->GetBlockAlignmentInBytes(); - - nextOffset %= _relTargetSizeAligned; - return nextOffset; - } - - UINT64 NextRelativeParaSeqOffset(IORequest& ioRequest) - { - ULARGE_INTEGER nextOffset; - - // - // Note: parallel seq differs from the other sequential cases in that the - // pointer indicates the prior IO, not the offset to issue the current at. - // Advance it. - // - - nextOffset.LowPart = ioRequest.GetOverlapped()->Offset; - nextOffset.HighPart = ioRequest.GetOverlapped()->OffsetHigh; - nextOffset.QuadPart -= _target->GetBaseFileOffsetInBytes(); // absolute -> relative - nextOffset.QuadPart += _target->GetBlockAlignmentInBytes(); // advance past last IO (!) - - // Wrap? - - if (nextOffset.QuadPart + _target->GetBlockSizeInBytes() > _relTargetSize) { - nextOffset.QuadPart = _target->GetThreadBaseRelativeOffsetInBytes(_tp->ulRelativeThreadNo) % _target->GetBlockAlignmentInBytes(); - } - - return nextOffset.QuadPart; - } - - UINT64 NextRelativeRandomOffset() const - { - UINT64 nextOffset = _tp->pRand->Rand64(); - nextOffset -= nextOffset % _target->GetBlockAlignmentInBytes(); - - // - // With a distribution we choose by bucket. Note the bucket is already aligned. - // - - if (_vDistributionRange.size()) - { - auto r = DistributionRange::find(_vDistributionRange, _tp->pRand->Rand64() % _ioDistributionSpan); - nextOffset %= r->_dst.second; // trim to range length (already aligned) - nextOffset += r->_dst.first; // bump by range base - } - // Full width. - else - { - nextOffset %= _relTargetSizeAligned; - } - - return nextOffset; - } - - UINT64 NextRelativeMixedOffset(bool& fRandom) - { - ULARGE_INTEGER nextOffset; - - fRandom = Util::BooleanRatio(_tp->pRand, _target->GetRandomRatio()); - - if (fRandom) - { - nextOffset.QuadPart = NextRelativeRandomOffset(); - _nextSeqOffset = nextOffset.QuadPart + _target->GetBlockAlignmentInBytes(); - return nextOffset.QuadPart; - } - - return NextRelativeSeqOffset(); - } - - IOOperation NextIOType(bool newType) - { - IOOperation ioType; - - if (_target->GetWriteRatio() == 0) - { - ioType = IOOperation::ReadIO; - } - else if (_target->GetWriteRatio() == 100) - { - ioType = IOOperation::WriteIO; - } - else if (_mode == IOMode::Mixed && !newType) - { - // repeat last IO if not needing a new choice (e.g., random) - ioType = _lastIO; - } - else - { - ioType = Util::BooleanRatio(_tp->pRand, _target->GetWriteRatio()) ? IOOperation::WriteIO : IOOperation::ReadIO; - _lastIO = ioType; - } - - return ioType; - } - - void NextIORequest(IORequest &ioRequest) - { - bool fRandom = false; - ULARGE_INTEGER nextOffset = { 0 }; - - switch (_mode) - { - case IOMode::Sequential: - nextOffset.QuadPart = NextRelativeSeqOffset(); - break; - - case IOMode::InterlockedSequential: - nextOffset.QuadPart = NextRelativeInterlockedSeqOffset(); - break; - - case IOMode::ParallelAsync: - nextOffset.QuadPart = NextRelativeParaSeqOffset(ioRequest); - break; - - case IOMode::Mixed: - nextOffset.QuadPart = NextRelativeMixedOffset(fRandom); - break; - - case IOMode::Random: - nextOffset.QuadPart = NextRelativeRandomOffset(); - fRandom = true; - break; - - default: - assert(false); - } - - // - // Convert relative offset to absolute. - // - - nextOffset.QuadPart += _target->GetBaseFileOffsetInBytes(); - - // - // Move offset into the IO request and decide what IO type will be issued. - // Mixed which has chosen sequential will repeat last IO type so that seq - // runs are homogeneous. - // - - ioRequest.GetOverlapped()->Offset = nextOffset.LowPart; - ioRequest.GetOverlapped()->OffsetHigh = nextOffset.HighPart; - ioRequest.SetIoType(NextIOType(fRandom)); - } - - private: - - const ThreadParameters *_tp; - const Target *_target; - const UINT64 _targetSize; // unmodified absolute target size - const IOMode _mode; // thread's mode of IO operations to this target (Random, Sequential, etc.) - - // - // Offsets/sizes are zero-based relative to target base offset, not absolute file offset. - // Relative size is trimmed with respect to block alignment, if specified. - // - - UINT64 _relTargetSize; // relative target size for IO v. base/max - UINT64 _relTargetSizeAligned; // relative target size for zero-base aligned IO (applies to: Random, InterlockedSequential) - UINT64 _nextSeqOffset; // next IO offset to issue sequential IO at (applies to: Sequential & Mixed) - volatile UINT64 *_sharedSeqOffset; // ... for interlocked IO (applies to: InterlockedSequential) - IOOperation _lastIO; // last IO type (applies to: Mixed) - -public: - - // - // Random distribution (stated in absolute offsets of target) - // - - vector _vDistributionRange; - UINT32 _ioDistributionSpan; - - friend class UnitTests::IORequestGeneratorUnitTests; -}; - -class IResultParser -{ -public: - virtual string ParseResults(const Profile& profile, const SystemInformation& system, vector vResults) = 0; - virtual string ParseProfile(const Profile& profile) = 0; -}; - -class EtwResultParser -{ -public: - static void ParseResults(vector vResults); - -private: - static void _WriteResults(IOOperation type, const TargetResults& targetResults, size_t uThread); -}; +/* + +DISKSPD + +Copyright(c) Microsoft Corporation +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include //ntdll.dll +#include +#include "Histogram.h" +#include "IoBucketizer.h" +#include "ThroughputMeter.h" +#include "Version.h" + +using namespace std; + +TRACELOGGING_DECLARE_PROVIDER(g_hEtwProvider); + +#define DISKSPD_TRACE_INFO 0x00000000 +#define DISKSPD_TRACE_RESERVED 0x00000001 +#define DISKSPD_TRACE_IO 0x00000100 + +typedef void (WINAPI *PRINTF)(const char*, va_list); //function used for displaying formatted data (printf style) + +#define ROUND_DOWN(_x,_alignment) \ + ( ((_x)/(_alignment)) * (_alignment) ) + +#define ROUND_UP(_x,_alignment) \ + ROUND_DOWN((_x) + (_alignment) - 1, (_alignment)) + +#define TB (((UINT64)1)<<40) +#define GB (((UINT64)1)<<30) +#define MB (((UINT64)1)<<20) +#define KB (((UINT64)1)<<10) + +#define EXPERIMENT_TPUT_CALC 0x1 // precise ms sleep calculation for low rate throughput control +extern ULONG g_ExperimentFlags; + +struct ETWEventCounters +{ + UINT64 ullIORead; // Read + UINT64 ullIOWrite; // Write + UINT64 ullMMTransitionFault; // Transition fault + UINT64 ullMMDemandZeroFault; // Demand Zero fault + UINT64 ullMMCopyOnWrite; // Copy on Write + UINT64 ullMMGuardPageFault; // Guard Page fault + UINT64 ullMMHardPageFault; // Hard page fault + UINT64 ullNetTcpSend; // Send + UINT64 ullNetTcpReceive; // Receive + UINT64 ullNetUdpSend; // Send + UINT64 ullNetUdpReceive; // Receive + UINT64 ullNetConnect; // Connect + UINT64 ullNetDisconnect; // Disconnect + UINT64 ullNetRetransmit; // ReTransmit + UINT64 ullNetAccept; // Accept + UINT64 ullNetReconnect; // ReConnect + UINT64 ullRegCreate; // NtCreateKey + UINT64 ullRegOpen; // NtOpenKey + UINT64 ullRegDelete; // NtDeleteKey + UINT64 ullRegQuery; // NtQueryKey + UINT64 ullRegSetValue; // NtSetValueKey + UINT64 ullRegDeleteValue; // NtDeleteValueKey + UINT64 ullRegQueryValue; // NtQueryValueKey + UINT64 ullRegEnumerateKey; // NtEnumerateKey + UINT64 ullRegEnumerateValueKey; // NtEnumerateValueKey + UINT64 ullRegQueryMultipleValue; // NtQueryMultipleValueKey + UINT64 ullRegSetInformation; // NtSetInformationKey + UINT64 ullRegFlush; // NtFlushKey + UINT64 ullThreadStart; + UINT64 ullThreadEnd; + UINT64 ullProcessStart; + UINT64 ullProcessEnd; + UINT64 ullImageLoad; +}; + +// structure containing informations about ETW session +struct ETWSessionInfo +{ + ULONG ulBufferSize; + ULONG ulMinimumBuffers; + ULONG ulMaximumBuffers; + ULONG ulFreeBuffers; + ULONG ulBuffersWritten; + ULONG ulFlushTimer; + LONG lAgeLimit; + ULONG ulNumberOfBuffers; + ULONG ulEventsLost; + ULONG ulLogBuffersLost; + ULONG ulRealTimeBuffersLost; +}; + +// structure containing parameters concerning ETW session provided by user +struct ETWMask +{ + BOOL bProcess; + BOOL bThread; + BOOL bImageLoad; + BOOL bDiskIO; + BOOL bMemoryPageFaults; + BOOL bMemoryHardFaults; + BOOL bNetwork; + BOOL bRegistry; + BOOL bUsePagedMemory; + BOOL bUsePerfTimer; + BOOL bUseSystemTimer; + BOOL bUseCyclesCounter; +}; + +namespace UnitTests +{ + class PerfTimerUnitTests; + class ProfileUnitTests; + class TargetUnitTests; + class IORequestGeneratorUnitTests; +} + +class PerfTimer +{ +public: + + static UINT64 GetTime(); + + static double PerfTimeToMicroseconds(const double); + static double PerfTimeToMilliseconds(const double); + static double PerfTimeToSeconds(const double); + static double PerfTimeToMicroseconds(const UINT64); + static double PerfTimeToMilliseconds(const UINT64); + static double PerfTimeToSeconds(const UINT64); + + static UINT64 MicrosecondsToPerfTime(const double); + static UINT64 MillisecondsToPerfTime(const double); + static UINT64 SecondsToPerfTime(const double); + +private: + + static const UINT64 TIMER_FREQ; + static UINT64 _GetPerfTimerFreq(); + + friend class UnitTests::PerfTimerUnitTests; +}; + +template +class Range +{ +public: + Range( + T1 Source, + T1 Span, + T2 Dest + ) : + _src(Source), + _span(Span), + _dst(Dest) + {} + + constexpr bool operator<(const Range& other) const + { + // + // This is used for comparison of effective distributions during result reporting (dedup). + // + // A hole with _span == 0 sorts < range with _span > 0 + // Note that a hole will never match in a find(). + // + + return _src < other._src || + (_src == other._src && + (_span < other._span || + (_span == other._span && _dst < other._dst))); + } + + static Range const * find(const vector>& v, T1 c) + { + // v must be sorted + size_t s = 0, mid, e = v.size() - 1; + + while (true) + { + mid = s + ((e - s) / 2); + if (c < v[mid]._src) { + if (s == mid) + { + return nullptr; + } + e = mid - 1; + } + else if (c > v[mid]._src + v[mid]._span - 1) + { + if (e == mid) + { + return nullptr; + } + s = mid + 1; + } + else + { + return &v[mid]; + } + } + } + + T1 _src, _span; + T2 _dst; +}; + +typedef Range> DistributionRange; + +enum class DistributionType +{ + None, + Absolute, + Percent +}; + +// +// This code implements Bob Jenkins public domain simple random number generator +// See http://burtleburtle.net/bob/rand/smallprng.html for details +// + +class Random +{ +public: + Random(UINT64 ulSeed = 0); + + inline UINT64 Rand64() + { + UINT64 e; + + e = _ulState[0] - _rotl64(_ulState[1], 7); + _ulState[0] = _ulState[1] ^ _rotl64(_ulState[2], 13); + _ulState[1] = _ulState[2] + _rotl64(_ulState[3], 37); + _ulState[2] = _ulState[3] + e; + _ulState[3] = e + _ulState[0]; + + return _ulState[3]; + } + + inline UINT32 Rand32() + { + return (UINT32)Rand64(); + } + + void RandBuffer(BYTE *pBuffer, UINT32 ulLength, bool fPseudoRandomOkay); + +private: + UINT64 _ulState[4]; +}; + +struct PercentileDescriptor +{ + double Percentile; + string Name; +}; + +class Util +{ +public: + static string DoubleToStringHelper(const double); + template static T QuotientCeiling(T dividend, T divisor) + { + return (dividend + divisor - 1) / divisor; + } + + // True if result is <= ratio. + // The ratio is on the interval [0, 100]: + // 0 will never occur (always false) + // 100 will always occur (always true) + + static bool BooleanRatio(Random *pRand, UINT32 ulRatio) + { + return ((pRand->Rand32() % 100 + 1) <= ulRatio); + } + + // + // This is close to strtoul[l], returning the next character to parse in the input string. + // This character can be used for validation (should there be any non-integer remaining), + // interpreting units that follow the integer (KMGTB), or parsing further (int[]) + // content in the string. + // + // Return value indicates whether any integers were parsed to Output. Continue is only modified + // on success, and will point to the terminator on completion. False is returned on overflow. + // + + template + static bool ParseUInt(const char* Input, T& Output, const char*& Continue) + { + T current = 0, last = 0; + const char* input = Input; + bool parsed = false; + + while (*input) + { + if (*input < '0' || *input > '9') + { + break; + } + + parsed = true; + current *= 10; + current += static_cast(*input) - static_cast('0'); + + // + // Overflow? + // + + if (current < last) + { + parsed = false; + break; + } + last = current; + + input += 1; + } + + // + // Return if string was consumed + // + // + + if (parsed) + { + Continue = input; + Output = current; + } + + return parsed; + } +}; + +// To keep track of which type of IO was issued +enum class IOOperation +{ + Unknown = 0, + ReadIO, + WriteIO +}; + +class TargetResults +{ +public: + TargetResults() : + ullFileSize(0), + ullBytesCount(0), + ullIOCount(0), + ullReadBytesCount(0), + ullReadIOCount(0), + ullWriteBytesCount(0), + ullWriteIOCount(0) + { + + } + + void Add( + DWORD dwBytesTransferred, + IOOperation type, + UINT64 ullIoStartTime, + UINT64 ullIoEndTime, + UINT64 ullSpanStartTime, + bool fMeasureLatency, + bool fCalculateIopsStdDev + ) + { + if (type == IOOperation::ReadIO) + { + ullReadBytesCount += dwBytesTransferred; // update read bytes counter + ullReadIOCount++; // update completed read I/O operations counter + } + else + { + ullWriteBytesCount += dwBytesTransferred; // update write bytes counter + ullWriteIOCount++; // update completed write I/O operations counter + } + + ullBytesCount += dwBytesTransferred; // update bytes counter + ullIOCount++; // update completed I/O operations counter + + // end time is 0 if we're not measuring latency + assert(((fMeasureLatency || fCalculateIopsStdDev) && ullIoEndTime != 0) || + (!fMeasureLatency && !fCalculateIopsStdDev)); + + if (ullIoEndTime == 0) + { + return; + } + + UINT64 ullDuration = ullIoEndTime - ullIoStartTime;; + double lfDurationUsec = PerfTimer::PerfTimeToMicroseconds(ullDuration); + + if (fMeasureLatency) + { + if (type == IOOperation::ReadIO) + { + readLatencyHistogram.Add(static_cast(lfDurationUsec)); + } + else + { + writeLatencyHistogram.Add(static_cast(lfDurationUsec)); + } + } + + if (fCalculateIopsStdDev) + { + UINT64 ullRelativeCompletionTime = ullIoEndTime - ullSpanStartTime; + + if (type == IOOperation::ReadIO) + { + readBucketizer.Add(ullRelativeCompletionTime, lfDurationUsec); + } + else + { + writeBucketizer.Add(ullRelativeCompletionTime, lfDurationUsec); + } + } + } + + string sPath; + UINT64 ullFileSize; //size of the file + UINT64 ullBytesCount; //number of accessed bytes + UINT64 ullIOCount; //number of performed I/O operations + UINT64 ullReadBytesCount; //number of bytes read + UINT64 ullReadIOCount; //number of performed Read I/O operations + UINT64 ullWriteBytesCount; //number of bytes written + UINT64 ullWriteIOCount; //number of performed Write I/O operations + + Histogram readLatencyHistogram; + Histogram writeLatencyHistogram; + + IoBucketizer readBucketizer; + IoBucketizer writeBucketizer; + + // Effective distribution after applying to target size (if specified/non-empty) + vector vDistributionRange; +}; + +typedef struct _WAIT_STATS { + ULONGLONG Wait; + ULONGLONG ThrottleWait; + ULONGLONG ThrottleSleep; + ULONGLONG Lookaside; + ULONGLONG LookasideCompletion[8]; // 0 == none, 1 == 1, ... 7 = 7+ +} WAIT_STATS; + +class ThreadResults +{ +public: + ThreadResults() + { + WaitStats = { 0 }; + } + + WAIT_STATS WaitStats; + vector vTargetResults; +}; + +class Results +{ +public: + bool fUseETW; + struct ETWEventCounters EtwEventCounters; + struct ETWMask EtwMask; + struct ETWSessionInfo EtwSessionInfo; + vector vThreadResults; + UINT64 ullTimeCount; + vector vSystemProcessorPerfInfo; +}; + +typedef void (*CALLBACK_TEST_STARTED)(); //callback function to notify that the measured test is about to start +typedef void (*CALLBACK_TEST_FINISHED)(); //callback function to notify that the measured test has just finished + +class ProcessorGroupInformation +{ +public: + WORD _groupNumber; + BYTE _maximumProcessorCount; + BYTE _activeProcessorCount; + KAFFINITY _activeProcessorMask; + + ProcessorGroupInformation() = delete; + ProcessorGroupInformation( + WORD Group, + BYTE MaximumProcessorCount, + BYTE ActiveProcessorCount, + KAFFINITY ActiveProcessorMask) : + _groupNumber(Group), + _maximumProcessorCount(MaximumProcessorCount), + _activeProcessorCount(ActiveProcessorCount), + _activeProcessorMask(ActiveProcessorMask) + { + } + + ProcessorGroupInformation( + WORD Group, + PROCESSOR_GROUP_INFO& GroupInfo) : + _groupNumber(Group), + _maximumProcessorCount(GroupInfo.MaximumProcessorCount), + _activeProcessorCount(GroupInfo.ActiveProcessorCount), + _activeProcessorMask(GroupInfo.ActiveProcessorMask) + { + } + + // This logic is strictly unaware that sparse processor masks are not possible; + // address this later, not important. See comments around RelationGroup query. + bool IsProcessorActive(BYTE Processor) const + { + return (IsProcessorValid(Processor) && + (((KAFFINITY)1 << Processor) & _activeProcessorMask) != 0); + } + + bool IsProcessorValid(BYTE Processor) const + { + return (Processor < _maximumProcessorCount); + } +}; + +class ProcessorNumaInformation +{ +public: + DWORD _ulProcCount; + DWORD _nodeNumber; + vector> _vProcessorMasks; +}; + +class ProcessorCoreInformation +{ +public: + WORD _groupNumber; + KAFFINITY _processorMask; + BYTE _efficiencyClass; + BYTE _groupCoreNumber; + + ProcessorCoreInformation() = delete; + ProcessorCoreInformation( + WORD Group, + KAFFINITY ProcessorMask, + BYTE EfficiencyClass) : + _groupNumber(Group), + _processorMask(ProcessorMask), + _efficiencyClass(EfficiencyClass), + _groupCoreNumber(0) + { + } +}; + +class ProcessorSocketInformation +{ +public: + DWORD _ulProcCount; + DWORD _ulSocketNumber; + vector> _vProcessorMasks; +}; + +class ProcessorTopology +{ +public: + vector _vProcessorGroupInformation; + vector _vProcessorNumaInformation; + vector _vProcessorSocketInformation; + vector _vProcessorCoreInformation; + + DWORD _ulProcessorCount; // total number of (active) processors + BYTE _ubPerformanceEfficiencyClass; // highest performance class present + bool _fSMT; // any SMT cores present + + ProcessorTopology() + { + BOOL fResult; + PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX pInformation; + DWORD AllocSize = 1024; + DWORD ReturnedLength = AllocSize; + LOGICAL_PROCESSOR_RELATIONSHIP NumaRelation; + pInformation = (PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX) new char[AllocSize]; + + _ulProcessorCount = 0; + _ubPerformanceEfficiencyClass = 0; + _fSMT = false; + + //// + // Group Relations + //// + + fResult = GetLogicalProcessorInformationEx(RelationGroup, pInformation, &ReturnedLength); + if (!fResult && GetLastError() == ERROR_INSUFFICIENT_BUFFER) + { + delete [] pInformation; + AllocSize = ReturnedLength; + pInformation = (PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX) new char[AllocSize]; + fResult = GetLogicalProcessorInformationEx(RelationGroup, pInformation, &ReturnedLength); + } + + if (fResult) + { + // Group information comes back as a single (large) element, not an array. + assert(ReturnedLength == pInformation->Size); + + // + // Fill in group topology vector + // + // Note: maximum processor count has no utility other than an indication of the + // bit width of the KAFFINITY mask that might have set values. But: + // + // 1) any mask will be a contiguous run of set bits (no sparse holes); there is + // no case where a 0 bit will be present to indicate a gap/disabled processor + // 2) all system APIs (such as the cpu utilization query) are defined over active + // processors + // + // There are (new?) cases where maximum is represented as > active on large systems, + // which makes these distinctions critical... active processor count is the only + // count that matters. + // + // For the sake of documentation we do save & report out the masks as reported by the + // system, but the only ones we look at are limited to cases where we get information + // in the form of GROUP_AFFINITY, which is just group # and mask (like NUMA and package + // association). + // + + for (WORD i = 0; i < pInformation->Group.ActiveGroupCount; i++) + { + _vProcessorGroupInformation.emplace_back( + i, + pInformation->Group.GroupInfo[i] + ); + + _ulProcessorCount += _vProcessorGroupInformation[i]._activeProcessorCount; + } + } + + //// + // NUMA Relations + //// + + // + // Dynamically detect the available NUMA relations. Non-Ex returns exactly one relation and + // does not define the GroupCount field. Ex scales to return multiple groups for large systems + // with > 64 per NUMA domain and does populate GroupCount. + // + + NumaRelation = RelationNumaNodeEx; + ReturnedLength = AllocSize; + fResult = GetLogicalProcessorInformationEx(NumaRelation, pInformation, &ReturnedLength); + if (!fResult && GetLastError() == ERROR_GEN_FAILURE) + { + NumaRelation = RelationNumaNode; + fResult = GetLogicalProcessorInformationEx(NumaRelation, pInformation, &ReturnedLength); + } + if (!fResult && GetLastError() == ERROR_INSUFFICIENT_BUFFER) + { + delete [] pInformation; + AllocSize = ReturnedLength; + pInformation = (PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX) new char[AllocSize]; + fResult = GetLogicalProcessorInformationEx(NumaRelation, pInformation, &ReturnedLength); + } + + if (fResult) + { + PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX cur = pInformation; + + while (ReturnedLength > 0) + { + ProcessorNumaInformation node; + + assert(ReturnedLength >= cur->Size); + + if (cur->Size > ReturnedLength) + { + break; + } + + node._nodeNumber = cur->NumaNode.NodeNumber; + node._ulProcCount = 0; + for (WORD i = 0; i < (NumaRelation == RelationNumaNode ? 1 : cur->NumaNode.GroupCount); i++) + { + node._ulProcCount += ProcessorTopology::MaskCount(cur->NumaNode.GroupMasks[i].Mask); + node._vProcessorMasks.emplace_back(cur->NumaNode.GroupMasks[i].Group, + cur->NumaNode.GroupMasks[i].Mask); + } + + _vProcessorNumaInformation.push_back(node); + + ReturnedLength -= cur->Size; + cur = (PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX)((PCHAR)cur + cur->Size); + } + } + + //// + // Socket/Package Relations + //// + + ReturnedLength = AllocSize; + fResult = GetLogicalProcessorInformationEx(RelationProcessorPackage, pInformation, &ReturnedLength); + if (!fResult && GetLastError() == ERROR_INSUFFICIENT_BUFFER) + { + delete [] pInformation; + AllocSize = ReturnedLength; + pInformation = (PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX) new char[AllocSize]; + fResult = GetLogicalProcessorInformationEx(RelationProcessorPackage, pInformation, &ReturnedLength); + } + + if (fResult) + { + PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX cur = pInformation; + + DWORD socketNumber = 0; + while (ReturnedLength != 0) + { + ProcessorSocketInformation socket; + + assert(ReturnedLength >= cur->Size); + + if (cur->Size > ReturnedLength) + { + break; + } + + socket._ulProcCount = 0; + socket._ulSocketNumber = socketNumber; + for (WORD i = 0; i < cur->Processor.GroupCount; i++) + { + socket._ulProcCount += ProcessorTopology::MaskCount(cur->Processor.GroupMask[i].Mask); + socket._vProcessorMasks.emplace_back(cur->Processor.GroupMask[i].Group, + cur->Processor.GroupMask[i].Mask); + } + + _vProcessorSocketInformation.push_back(socket); + + ReturnedLength -= cur->Size; + cur = (PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX)((PCHAR)cur + cur->Size); + socketNumber += 1; + } + } + + //// + // Core Relations + //// + + ReturnedLength = AllocSize; + fResult = GetLogicalProcessorInformationEx(RelationProcessorCore, pInformation, &ReturnedLength); + if (!fResult && GetLastError() == ERROR_INSUFFICIENT_BUFFER) + { + delete [] pInformation; + AllocSize = ReturnedLength; + pInformation = (PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX) new char[AllocSize]; + fResult = GetLogicalProcessorInformationEx(RelationProcessorCore, pInformation, &ReturnedLength); + } + + // + // The EfficiencyClass member was added with Windows 10 + // + + BOOL fEfficiencyClass = false; + if (IsWindows10OrGreater()) + { + fEfficiencyClass = true; + } + + if (fResult) + { + PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX cur = pInformation; + BYTE curEfficiency; + + while (ReturnedLength != 0) + { + assert(ReturnedLength >= cur->Size); + + if (cur->Size > ReturnedLength) + { + break; + } + + // + // Determine the highest performance core class and presence of SMT as we sweep. + // Note that SMT is per core and can be asymmetric. + // + + if (fEfficiencyClass) + { + curEfficiency = cur->Processor.EfficiencyClass; + if (_ubPerformanceEfficiencyClass < curEfficiency) + { + _ubPerformanceEfficiencyClass = curEfficiency; + } + } + + if (cur->Processor.Flags & LTP_PC_SMT) + { + _fSMT = true; + } + + assert(pInformation->Processor.GroupCount == 1); + + _vProcessorCoreInformation.emplace_back(cur->Processor.GroupMask[0].Group, + cur->Processor.GroupMask[0].Mask, + fEfficiencyClass ? cur->Processor.EfficiencyClass : (BYTE)0); + + ReturnedLength -= cur->Size; + cur = (PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX)((PCHAR)cur + cur->Size); + } + + // Now guarantee ascending order of group number & cpu mask so that group-relative core number can be assigned + + sort(_vProcessorCoreInformation.begin(), _vProcessorCoreInformation.end(), + [](const ProcessorCoreInformation& a, const ProcessorCoreInformation& b) + { + return a._groupNumber < b._groupNumber || + (a._groupNumber == b._groupNumber && a._processorMask < b._processorMask); + }); + + // Assign group-relative core number + + BYTE coreNumber = 0; + WORD group = 0; + for (auto& core : _vProcessorCoreInformation) + { + if (core._groupNumber != group) + { + group = core._groupNumber; + coreNumber = 0; + } + core._groupCoreNumber = coreNumber++; + } + } + + // TODO: Get the cache relationships as well??? + + delete [] pInformation; + } + + bool IsGroupValid(WORD Group) + { + if (Group < _vProcessorGroupInformation.size()) + { + return true; + } + else + { + return false; + } + } + + // Return the next active processor in the system, exclusive (Next = true) + // or inclusive (Next = false) of the input group/processor. + // Iteration is in order of absolute processor number. + // This does assume at least one core is active, but that is a given. + // + // This logic is strictly unaware that sparse processor masks are not possible; + // address this later, not important. See comments around RelationGroup query. + void GetActiveGroupProcessor(WORD& Group, BYTE& Processor, bool Next) + { + if (Next) + { + Processor++; + } + + while (!_vProcessorGroupInformation[Group].IsProcessorActive(Processor)) + { + if (!_vProcessorGroupInformation[Group].IsProcessorValid(Processor)) + { + Processor = 0; + if (!IsGroupValid(++Group)) + { + Group = 0; + } + } + else + { + Processor++; + } + } + } + + // + // Efficiency of these mappings is not a first order concern. We simply use these to avoid assuming + // ordering of groups/masks of processors within topology structures. There's strictly no reason, + // for example, that socket 0 contains the first groups (0, 1, etc.) of processors, at least not + // documented or guaranteed. + // + + DWORD GetNumaOfProcessor(WORD Group, BYTE Processor) const + { + for (const auto& numa : _vProcessorNumaInformation) + { + for (const auto& mask : numa._vProcessorMasks) + { + if (mask.first == Group && (mask.second & ((KAFFINITY)1 << Processor))) + { + return numa._nodeNumber; + } + } + } + + assert(false); + return 0; + } + + DWORD GetSocketOfProcessor(WORD Group, BYTE Processor) const + { + for (const auto& socket : _vProcessorSocketInformation) + { + for (const auto& mask : socket._vProcessorMasks) + { + if (mask.first == Group && (mask.second & ((KAFFINITY)1 << Processor))) + { + return socket._ulSocketNumber; + } + } + } + + assert(false); + return 0; + } + + BYTE GetCoreOfProcessor(WORD Group, BYTE Processor, BYTE& EfficiencyClass) const + { + for (const auto& core : _vProcessorCoreInformation) + { + if (core._groupNumber == Group && (core._processorMask & ((KAFFINITY)1 << Processor))) + { + EfficiencyClass = core._efficiencyClass; + return core._groupCoreNumber; + } + } + + assert(false); + return 0; + } + + static unsigned int MaskCount(KAFFINITY Mask) + { + // + // Trivial popcount for affinity mask w/o insn dependency + // + + unsigned int count = 0; + + while (Mask) + { + Mask &= (Mask - 1); + count++; + } + + return count; + } +}; + +// +// Helper macros for outputting indented XML. They assume a local variable "indent". +// Use the Inc form when outputting the opening tag for a multi-line section: +// Use Dec for the closing tag: +// + +// start line with indent +#define AddXml(s,str) { (s).append(indent, ' '); (s) += (str); } +// start new indented section +#define AddXmlInc(s,str) { (s).append(indent, ' '); indent += 2; (s) += (str); } +// end indented section +#define AddXmlDec(s,str) { if (indent >= 2) { indent -= 2; }; (s).append(indent, ' '); (s) += (str); } + +class SystemInformation +{ +private: + SYSTEMTIME StartTime; + +public: + ProcessorTopology processorTopology; + string sComputerName; + string sActivePolicyName; + string sActivePolicyGuid; + + SystemInformation() + { + char buffer[128]; + DWORD cb = _countof(buffer); + GUID *guid = NULL; + BOOL fResult; + +#pragma prefast(suppress:38020, "Yes, we're aware this is an ANSI API in a UNICODE project") + fResult = GetComputerNameExA(ComputerNamePhysicalDnsHostname, buffer, &cb); + if (fResult) + { + sComputerName = buffer; + } + + // capture start time + GetSystemTime(&StartTime); + + if (PowerGetActiveScheme(NULL, &guid) == ERROR_SUCCESS && + PowerReadFriendlyName(NULL, guid, NULL, NULL, NULL, &cb) == ERROR_SUCCESS) + { + PUCHAR pwrBuffer; + + if (cb <= _countof(buffer)) + { + pwrBuffer = (PUCHAR) buffer; + } + else + { + pwrBuffer = new UCHAR[cb]; + } + + if (PowerReadFriendlyName(NULL, guid, NULL, NULL, pwrBuffer, &cb) == ERROR_SUCCESS) + { + // Cast wide string down to basic - all of our current output streams are basic + wstring wActivePolicyName = (PWCHAR) pwrBuffer; + std::wstring_convert> cvt; + sActivePolicyName = cvt.to_bytes(wActivePolicyName); + } + + if (pwrBuffer != (PVOID) buffer) + { + delete pwrBuffer; + } + } + + if (sActivePolicyName.empty()) + { + sActivePolicyName = ""; + } + + if (guid) + { + sprintf_s(buffer, _countof(buffer), + "%08lx-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x", + guid->Data1, guid->Data2, guid->Data3, + guid->Data4[0], guid->Data4[1], guid->Data4[2], guid->Data4[3], + guid->Data4[4], guid->Data4[5], guid->Data4[6], guid->Data4[7]); + + sActivePolicyGuid = buffer; + + LocalFree(guid); + } + } + + // for unit test, squelch variable timestamp + void SystemInformation::ResetTime() + { + StartTime = { 0 }; + } + + string SystemInformation::GetText() const + { + char szBuffer[128]; // guid (36ch), timestamp and power friendly (up to 64ch) + int nWritten; + string sText("System information:\n\n"); + + // identify computer which ran the test + sText += "\tcomputer name: "; + sText += sComputerName; + sText += "\n"; + + sText += "\tstart time: "; + if (StartTime.wYear) { + + nWritten = sprintf_s(szBuffer, _countof(szBuffer), + "%u/%02u/%02u %02u:%02u:%02u UTC", + StartTime.wYear, + StartTime.wMonth, + StartTime.wDay, + StartTime.wHour, + StartTime.wMinute, + StartTime.wSecond); + assert(nWritten && nWritten < _countof(szBuffer)); + sText += szBuffer; + } + + sText += "\n\n\tcpu count:\t\t"; + sText += to_string(processorTopology._ulProcessorCount); + sText += "\n\tcore count:\t\t"; + sText += to_string(processorTopology._vProcessorCoreInformation.size()); + sText += "\n\tgroup count:\t\t"; + sText += to_string(processorTopology._vProcessorGroupInformation.size()); + sText += "\n\tnode count:\t\t"; + sText += to_string(processorTopology._vProcessorNumaInformation.size()); + sText += "\n\tsocket count:\t\t"; + sText += to_string(processorTopology._vProcessorSocketInformation.size()); + sText += "\n\theterogeneous cores:\t"; + sText += processorTopology._ubPerformanceEfficiencyClass ? "y\n" : "n\n"; + + sText += "\n\tactive power scheme:\t"; + sText += sActivePolicyName; + + if (!sActivePolicyGuid.empty()) + { + sText += " ("; + sText += sActivePolicyGuid; + sText += ")"; + } + + sText += "\n"; + + return sText; + } + + string SystemInformation::GetXml(UINT32 indent) const + { + char szBuffer[64]; // enough for 64bit mask (17ch) and timestamp + int nWritten; + string sXml; + + AddXmlInc(sXml, "\n"); + + // identify computer which ran the test + AddXml(sXml, ""); + sXml += sComputerName; + sXml += "\n"; + + // identify tool version which performed the test + AddXmlInc(sXml, "\n"); + AddXml(sXml,"" DISKSPD_NUMERIC_VERSION_STRING "\n"); + AddXml(sXml, "" DISKSPD_DATE_VERSION_STRING "\n"); + AddXmlDec(sXml, "\n"); + + AddXml(sXml, ""); + if (StartTime.wYear) { + + nWritten = sprintf_s(szBuffer, _countof(szBuffer), + "%u/%02u/%02u %02u:%02u:%02u UTC", + StartTime.wYear, + StartTime.wMonth, + StartTime.wDay, + StartTime.wHour, + StartTime.wMinute, + StartTime.wSecond); + assert(nWritten && nWritten < _countof(szBuffer)); + sXml += szBuffer; + } + sXml += "\n"; + + AddXml(sXml, "\n"; + + // processor topology + AddXmlInc(sXml, "\n" : "false\">\n"; + + for (const auto& g : processorTopology._vProcessorGroupInformation) + { + AddXml(sXml, "\n"; + + } + for (const auto& n : processorTopology._vProcessorNumaInformation) + { + AddXmlInc(sXml, "\n"; + for (const auto& g : n._vProcessorMasks) + { + AddXml(sXml, "\n"; + } + AddXmlDec(sXml, "\n"); + } + for (const auto& s : processorTopology._vProcessorSocketInformation) + { + AddXmlInc(sXml, "\n"; + for (const auto& g : s._vProcessorMasks) + { + AddXml(sXml, "\n"; + } + AddXmlDec(sXml, "\n"); + } + for (const auto& h : processorTopology._vProcessorCoreInformation) + { + AddXml(sXml, "\n"; + } + + AddXmlDec(sXml, "\n"); + AddXmlDec(sXml, "\n"); + + return sXml; + } +}; + +extern SystemInformation g_SystemInformation; + +struct Synchronization +{ + ULONG ulStructSize; //size of the structure that the caller is aware of (to easier achieve backward compatibility in a future) + HANDLE hStopEvent; //an event to be signalled if the scenario is to be stop before time ellapses + HANDLE hStartEvent; //an event for signalling start + CALLBACK_TEST_STARTED pfnCallbackTestStarted; //a function to be called if the measured test is about to start + CALLBACK_TEST_FINISHED pfnCallbackTestFinished; //a function to be called as soon as the measrued test finishes +}; + +#define STRUCT_SYNCHRONIZATION_SUPPORTS(pSynch, Field) ( \ + (NULL != (pSynch)) && \ + ((pSynch)->ulStructSize >= offsetof(struct Synchronization, Field) + sizeof((pSynch)->Field)) \ + ) + +// caching modes +// cached -> default (-Sb explicitly) +// disableoscache -> no_intermediate_buffering (-S or -Su) +// disablelocalcache -> cached, but then tear down local rdr cache (-Sr) +enum class TargetCacheMode { + Undefined = 0, + Cached, + DisableOSCache, + DisableLocalCache +}; + +// writethrough modes +// off -> default +// on -> (-Sw or implied with -Sh == -Suw/-Swu) +enum class WriteThroughMode { + Undefined = 0, + Off, + On, +}; + +// memory mapped IO modes +// off -> default +// on -> (-Sm or -Smw) +enum class MemoryMappedIoMode { + Undefined = 0, + Off, + On, +}; + +// memory mapped IO flush modes +// off / Undefined -> default +// on -> (-Sm or -Smw) +enum class MemoryMappedIoFlushMode { + Undefined = 0, + ViewOfFile, + NonVolatileMemory, + NonVolatileMemoryNoDrain, +}; + +enum class IOMode +{ + Unknown, + Random, + Sequential, + Mixed, + InterlockedSequential, + ParallelAsync +}; + +class ThreadTarget +{ +public: + + ThreadTarget() : + _ulThread(0xFFFFFFFF), + _ulWeight(0) + { + } + + void SetThread(UINT32 ulThread) { _ulThread = ulThread; } + UINT32 GetThread() const { return _ulThread; } + + void SetWeight(UINT32 ulWeight) { _ulWeight = ulWeight; } + UINT32 GetWeight() const { return _ulWeight; } + + string GetXml(UINT32 indent) const; + +private: + UINT32 _ulThread; + UINT32 _ulWeight; +}; + +// Character which leads off a template target definition; e.g. *1, *2 +#define TEMPLATE_TARGET_PREFIX ('*') + +class Target +{ +public: + + Target() : + _dwBlockSize(64 * 1024), + _dwRequestCount(2), + _ullBlockAlignment(0), + _ulWriteRatio(0), + _ulRandomRatio(0), + _ullBaseFileOffset(0), + _fParallelAsyncIO(false), + _fInterlockedSequential(false), + _cacheMode(TargetCacheMode::Cached), + _writeThroughMode(WriteThroughMode::Off), + _memoryMappedIoMode(MemoryMappedIoMode::Off), + _memoryMappedIoNvToken(nullptr), + _memoryMappedIoFlushMode(MemoryMappedIoFlushMode::Undefined), + _fZeroWriteBuffers(false), + _dwThreadsPerFile(1), + _ullThreadStride(0), + _fCreateFile(false), + _fPrecreated(false), + _ullFileSize(0), + _ullMaxFileSize(0), + _fUseBurstSize(false), + _dwBurstSize(0), + _dwThinkTime(0), + _fThinkTime(false), + _fSequentialScanHint(false), + _fRandomAccessHint(false), + _fTemporaryFileHint(false), + _fUseLargePages(false), + _mappedViewFileHandle(INVALID_HANDLE_VALUE), + _mappedView(NULL), + _ioPriorityHint(IoPriorityHintNormal), + _ulWeight(1), + _dwThroughputBytesPerMillisecond(0), + _dwThroughputIOPS(0), + _cbRandomDataWriteBuffer(0), + _sRandomDataWriteBufferSourcePath(), + _pRandomDataWriteBuffer(nullptr), + _distributionType(DistributionType::None) + { + } + + IOMode GetIOMode() const + { + if (GetRandomRatio() == 100) + { + return IOMode::Random; + } + else if (GetRandomRatio() != 0) + { + return IOMode::Mixed; + } + else if (GetUseParallelAsyncIO()) + { + return IOMode::ParallelAsync; + } + else if (GetUseInterlockedSequential()) + { + return IOMode::InterlockedSequential; + } + else + { + return IOMode::Sequential; + } + } + + void SetPath(const string& sPath) { _sPath = sPath; } + void SetPath(const char *pPath) { _sPath = pPath; } + const string& GetPath() const { return _sPath; } + + void SetBlockSizeInBytes(DWORD dwBlockSize) { _dwBlockSize = dwBlockSize; } + DWORD GetBlockSizeInBytes() const { return _dwBlockSize; } + + void SetBlockAlignmentInBytes(UINT64 ullBlockAlignment) + { + _ullBlockAlignment = ullBlockAlignment; + } + // actual is used in validation to detect unclear/mis-specified intent + // like -rs -s + UINT64 GetBlockAlignmentInBytes(bool actual = false) const + { + return _ullBlockAlignment ? _ullBlockAlignment : (actual ? 0 : _dwBlockSize); + } + + void SetWriteRatio(UINT32 writeRatio) { _ulWriteRatio = writeRatio; } + UINT32 GetWriteRatio() const { return _ulWriteRatio; } + + void SetRandomRatio(UINT32 randomRatio) { _ulRandomRatio = randomRatio; } + UINT32 GetRandomRatio() const { return _ulRandomRatio; } + + void SetBaseFileOffsetInBytes(UINT64 ullBaseFileOffset) { _ullBaseFileOffset = ullBaseFileOffset; } + UINT64 GetBaseFileOffsetInBytes() const { return _ullBaseFileOffset; } + UINT64 GetThreadBaseRelativeOffsetInBytes(UINT32 ulThreadNo) const { return ulThreadNo * _ullThreadStride; } + UINT64 GetThreadBaseFileOffsetInBytes(UINT32 ulThreadNo) const { return _ullBaseFileOffset + GetThreadBaseRelativeOffsetInBytes(ulThreadNo); } + + + void SetSequentialScanHint(bool fBool) { _fSequentialScanHint = fBool; } + bool GetSequentialScanHint() const { return _fSequentialScanHint; } + + void SetRandomAccessHint(bool fBool) { _fRandomAccessHint = fBool; } + bool GetRandomAccessHint() const { return _fRandomAccessHint; } + + void SetTemporaryFileHint(bool fBool) { _fTemporaryFileHint = fBool; } + bool GetTemporaryFileHint() const { return _fTemporaryFileHint; } + + void SetUseLargePages(bool fBool) { _fUseLargePages = fBool; } + bool GetUseLargePages() const { return _fUseLargePages; } + + void SetRequestCount(DWORD dwRequestCount) { _dwRequestCount = dwRequestCount; } + DWORD GetRequestCount() const { return _dwRequestCount; } + + void SetCacheMode(TargetCacheMode cacheMode) { _cacheMode = cacheMode; } + TargetCacheMode GetCacheMode() const { return _cacheMode; } + + void SetWriteThroughMode(WriteThroughMode writeThroughMode ) { _writeThroughMode = writeThroughMode; } + WriteThroughMode GetWriteThroughMode() const { return _writeThroughMode; } + + void SetMemoryMappedIoMode(MemoryMappedIoMode memoryMappedIoMode ) { _memoryMappedIoMode = memoryMappedIoMode; } + MemoryMappedIoMode GetMemoryMappedIoMode() const { return _memoryMappedIoMode; } + + void SetMemoryMappedIoNvToken(PVOID memoryMappedIoNvToken) { _memoryMappedIoNvToken = memoryMappedIoNvToken; } + PVOID GetMemoryMappedIoNvToken() const { return _memoryMappedIoNvToken; } + + void SetMemoryMappedIoFlushMode(MemoryMappedIoFlushMode memoryMappedIoFlushMode) { _memoryMappedIoFlushMode = memoryMappedIoFlushMode; } + MemoryMappedIoFlushMode GetMemoryMappedIoFlushMode() const { return _memoryMappedIoFlushMode; } + + void SetZeroWriteBuffers(bool fBool) { _fZeroWriteBuffers = fBool; } + bool GetZeroWriteBuffers() const { return _fZeroWriteBuffers; } + + void SetRandomDataWriteBufferSize(UINT64 cbWriteBuffer) { _cbRandomDataWriteBuffer = cbWriteBuffer; } + UINT64 GetRandomDataWriteBufferSize(void) const { return _cbRandomDataWriteBuffer; } + + void SetRandomDataWriteBufferSourcePath(string sPath) { _sRandomDataWriteBufferSourcePath = sPath; } + string GetRandomDataWriteBufferSourcePath() const { return _sRandomDataWriteBufferSourcePath; } + + void SetUseBurstSize(bool fBool) { _fUseBurstSize = fBool; } + bool GetUseBurstSize() const { return _fUseBurstSize; } + + void SetBurstSize(DWORD dwBurstSize) { _dwBurstSize = dwBurstSize; } + DWORD GetBurstSize() const { return _dwBurstSize; } + + void SetThinkTime(DWORD dwThinkTime) { _dwThinkTime = dwThinkTime; } + DWORD GetThinkTime() const { return _dwThinkTime; } + + void SetEnableThinkTime(bool fBool) { _fThinkTime = fBool; } + bool GetEnableThinkTime() const { return _fThinkTime; } + + void SetThreadsPerFile(DWORD dwThreadsPerFile) { _dwThreadsPerFile = dwThreadsPerFile; } + DWORD GetThreadsPerFile() const { return _dwThreadsPerFile; } + + void SetCreateFile(bool fBool) { _fCreateFile = fBool; } + bool GetCreateFile() const { return _fCreateFile; } + + void SetFileSize(UINT64 ullFileSize) { _ullFileSize = ullFileSize; } + UINT64 GetFileSize() const { return _ullFileSize; } // TODO: InBytes + + void SetMaxFileSize(UINT64 ullMaxFileSize) { _ullMaxFileSize = ullMaxFileSize; } + UINT64 GetMaxFileSize() const { return _ullMaxFileSize; } + + void SetUseParallelAsyncIO(bool fBool) { _fParallelAsyncIO = fBool; } + bool GetUseParallelAsyncIO() const { return _fParallelAsyncIO; } + + void SetUseInterlockedSequential(bool fBool) { _fInterlockedSequential = fBool; } + bool GetUseInterlockedSequential() const { return _fInterlockedSequential; } + + void SetThreadStrideInBytes(UINT64 ullThreadStride) { _ullThreadStride = ullThreadStride; } + UINT64 GetThreadStrideInBytes() const { return _ullThreadStride; } + + void SetMappedViewFileHandle(HANDLE FileHandle) { _mappedViewFileHandle = FileHandle; } + HANDLE GetMappedViewFileHandle() const { return _mappedViewFileHandle; } + + void SetMappedView(BYTE *MappedView) { _mappedView = MappedView; } + BYTE* GetMappedView() const { return _mappedView; } + + void SetIOPriorityHint(PRIORITY_HINT _hint) + { + assert(_hint < MaximumIoPriorityHintType); + _ioPriorityHint = _hint; + } + PRIORITY_HINT GetIOPriorityHint() const { return _ioPriorityHint; } + + void SetWeight(UINT32 ulWeight) { _ulWeight = ulWeight; } + UINT32 GetWeight() const { return _ulWeight; } + + void AddThreadTarget(const ThreadTarget &threadTarget) + { + _vThreadTargets.push_back(threadTarget); + } + vector GetThreadTargets() const { return _vThreadTargets; } + + void SetPrecreated(bool fBool) { _fPrecreated = fBool; } + bool GetPrecreated() const { return _fPrecreated; } + + // Convert units to BPMS. Nonzero value of IOPS indicates originally specified units for display/profile. + void SetThroughputIOPS(DWORD dwIOPS) + { + _dwThroughputIOPS = dwIOPS; + _dwThroughputBytesPerMillisecond = (dwIOPS * _dwBlockSize) / 1000; + } + DWORD GetThroughputIOPS() const { return _dwThroughputIOPS; } + void SetThroughput(DWORD dwThroughputBytesPerMillisecond) + { + _dwThroughputIOPS = 0; + _dwThroughputBytesPerMillisecond = dwThroughputBytesPerMillisecond; + } + DWORD GetThroughputInBytesPerMillisecond() const { return _dwThroughputBytesPerMillisecond; } + + string GetXml(UINT32 indent) const; + + bool AllocateAndFillRandomDataWriteBuffer(Random *pRand); + void FreeRandomDataWriteBuffer(); + BYTE* GetRandomDataWriteBuffer(Random *pRand); + + void SetDistributionRange(const vector& v, DistributionType t) + { + _vDistributionRange = v; _distributionType = t; + + // Now place final element if IO% is < 100. + // If this is an absolute specification, it will map to zero length here and + // conversion will occur at the time of target open to the rest of the target. + // For the percent specification we place the final element as-if directly stated, + // consuming the tail length. + // + // This done here so that the stated specification is indeed complete, and not left + // for the effective distribution. + // + // TBD this should be moved to a proper Distribution class. + + const DistributionRange& last = *_vDistributionRange.rbegin(); + + UINT32 ioCur = last._src + last._span; + if (ioCur < 100) + { + UINT64 targetCur = last._dst.first + last._dst.second; + if (t == DistributionType::Percent && targetCur < 100) + { + // tail is available + // if tail is not available, this will be caught by validation + _vDistributionRange.emplace_back(ioCur, 100 - ioCur, make_pair(targetCur, 100 - targetCur)); + } + else + { + _vDistributionRange.emplace_back(ioCur, 100 - ioCur, make_pair(targetCur, 0)); + } + } + } + auto& GetDistributionRange() const { return _vDistributionRange; } + auto GetDistributionType() const { return _distributionType; } + + DWORD GetCreateFlags(bool fAsync) + { + DWORD dwFlags = FILE_ATTRIBUTE_NORMAL; + + if (GetSequentialScanHint()) + { + dwFlags |= FILE_FLAG_SEQUENTIAL_SCAN; + } + + if (GetRandomAccessHint()) + { + dwFlags |= FILE_FLAG_RANDOM_ACCESS; + } + + if (GetTemporaryFileHint()) + { + dwFlags |= FILE_ATTRIBUTE_TEMPORARY; + } + + if (fAsync) + { + dwFlags |= FILE_FLAG_OVERLAPPED; + } + + if (GetCacheMode() == TargetCacheMode::DisableOSCache) + { + dwFlags |= FILE_FLAG_NO_BUFFERING; + } + + if (GetWriteThroughMode( ) == WriteThroughMode::On) + { + dwFlags |= FILE_FLAG_WRITE_THROUGH; + } + + return dwFlags; + } + +private: + string _sPath; + DWORD _dwBlockSize; + DWORD _dwRequestCount; // TODO: change the name to something more descriptive (OutstandingRequestCount?) + + UINT64 _ullBlockAlignment; + UINT32 _ulWriteRatio; + UINT32 _ulRandomRatio; + + UINT64 _ullBaseFileOffset; + + TargetCacheMode _cacheMode; + WriteThroughMode _writeThroughMode; + MemoryMappedIoMode _memoryMappedIoMode; + MemoryMappedIoFlushMode _memoryMappedIoFlushMode; + PVOID _memoryMappedIoNvToken; + DWORD _dwThreadsPerFile; + UINT64 _ullThreadStride; + + UINT64 _ullFileSize; + UINT64 _ullMaxFileSize; + + DWORD _dwBurstSize; // number of IOs in a burst + DWORD _dwThinkTime; // time to pause before issuing the next burst of IOs + + DWORD _dwThroughputBytesPerMillisecond; // set to 0 to disable throttling + DWORD _dwThroughputIOPS; // if IOPS are specified they are converted to BPMS but saved for fidelity to XML/output + + bool _fThinkTime:1; // variable to decide whether to think between IOs (default is false) (removed by using _dwThinkTime==0?) + bool _fUseBurstSize:1; // TODO: "use" or "enable"?; since burst size must be specified with the think time, one variable should be sufficient + bool _fZeroWriteBuffers:1; + bool _fCreateFile:1; + bool _fPrecreated:1; // used to track which files have been created before the first timespan and which have to be created later + bool _fParallelAsyncIO:1; + bool _fInterlockedSequential:1; + bool _fSequentialScanHint:1; // open file with the FILE_FLAG_SEQUENTIAL_SCAN hint + bool _fRandomAccessHint:1; // open file with the FILE_FLAG_RANDOM_ACCESS hint + bool _fTemporaryFileHint:1; // open file with the FILE_ATTRIBUTE_TEMPORARY hint + bool _fUseLargePages:1; // Use large pages for IO buffers + + UINT64 _cbRandomDataWriteBuffer; // if > 0, then the write buffer should be filled with random data + string _sRandomDataWriteBufferSourcePath; // file that should be used for filling the write buffer (if the path is not available, use a crypto provider) + BYTE *_pRandomDataWriteBuffer; // a buffer used for write data when _cbWriteBuffer > 0; it's shared by all the threads working on this target + + HANDLE _mappedViewFileHandle; + BYTE *_mappedView; + + PRIORITY_HINT _ioPriorityHint; + + UINT32 _ulWeight; + vector _vThreadTargets; + + vector _vDistributionRange; + DistributionType _distributionType; + + bool _FillRandomDataWriteBuffer(Random *pRand); + + friend class UnitTests::ProfileUnitTests; + friend class UnitTests::TargetUnitTests; +}; + +class AffinityAssignment +{ +public: + WORD wGroup; + BYTE bProc; + + AffinityAssignment() = delete; + AffinityAssignment(WORD p_wGroup, BYTE p_bProc) : + wGroup(p_wGroup), + bProc(p_bProc) + { + } +}; + +class TimeSpan +{ +public: + TimeSpan() : + _ulDuration(10), + _ulWarmUp(5), + _ulCoolDown(0), + _ulRandSeed(0), + _dwThreadCount(0), + _dwRequestCount(0), + _fRandomWriteData(false), + _fDisableAffinity(false), + _fCompletionRoutines(false), + _fMeasureLatency(false), + _fCalculateIopsStdDev(false), + _ulIoBucketDurationInMilliseconds(1000) + { + } + + void ClearAffinityAssignment() + { + _vAffinity.clear(); + } + void AddAffinityAssignment(WORD wGroup, BYTE bProc) + { + _vAffinity.emplace_back(wGroup, bProc); + } + const auto& GetAffinityAssignments() const { return _vAffinity; } + + void AddTarget(const Target& target) + { + _vTargets.push_back(Target(target)); + } + + vector GetTargets() const { return _vTargets; } + + void SetDuration(UINT32 ulDuration) { _ulDuration = ulDuration; } + UINT32 GetDuration() const { return _ulDuration; } + + void SetWarmup(UINT32 ulWarmup) { _ulWarmUp = ulWarmup; } + UINT32 GetWarmup() const { return _ulWarmUp; } + + void SetCooldown(UINT32 ulCooldown) { _ulCoolDown = ulCooldown; } + UINT32 GetCooldown() const { return _ulCoolDown; } + + void SetRandSeed(UINT32 ulRandSeed) { _ulRandSeed = ulRandSeed; } + UINT32 GetRandSeed() const { return _ulRandSeed; } + + void SetRandomWriteData(bool fRandomWriteData) { _fRandomWriteData = fRandomWriteData; } + bool GetRandomWriteData() const { return _fRandomWriteData; } + + void SetThreadCount(DWORD dwThreadCount) { _dwThreadCount = dwThreadCount; } + DWORD GetThreadCount() const { return _dwThreadCount; } + + void SetRequestCount(DWORD dwRequestCount) { _dwRequestCount = dwRequestCount; } + DWORD GetRequestCount() const { return _dwRequestCount; } + + void SetDisableAffinity(bool fDisableAffinity) { _fDisableAffinity = fDisableAffinity; } + bool GetDisableAffinity() const { return _fDisableAffinity; } + + void SetCompletionRoutines(bool fCompletionRoutines) { _fCompletionRoutines = fCompletionRoutines; } + bool GetCompletionRoutines() const { return _fCompletionRoutines; } + + void SetMeasureLatency(bool fMeasureLatency) { _fMeasureLatency = fMeasureLatency; } + bool GetMeasureLatency() const { return _fMeasureLatency; } + + void SetCalculateIopsStdDev(bool fCalculateStdDev) { _fCalculateIopsStdDev = fCalculateStdDev; } + bool GetCalculateIopsStdDev() const { return _fCalculateIopsStdDev; } + + void SetIoBucketDurationInMilliseconds(UINT32 ulIoBucketDurationInMilliseconds) { _ulIoBucketDurationInMilliseconds = ulIoBucketDurationInMilliseconds; } + UINT32 GetIoBucketDurationInMilliseconds() const { return _ulIoBucketDurationInMilliseconds; } + + string GetXml(UINT32 indent) const; + void MarkFilesAsPrecreated(const vector vFiles); + +private: + vector _vTargets; + UINT32 _ulDuration; + UINT32 _ulWarmUp; + UINT32 _ulCoolDown; + UINT32 _ulRandSeed; + DWORD _dwThreadCount; + DWORD _dwRequestCount; + bool _fRandomWriteData; + bool _fDisableAffinity; + vector _vAffinity; + bool _fCompletionRoutines; + bool _fMeasureLatency; + bool _fCalculateIopsStdDev; + UINT32 _ulIoBucketDurationInMilliseconds; + + friend class UnitTests::ProfileUnitTests; +}; + +enum class ResultsFormat +{ + Text, + Xml +}; + +enum class PrecreateFiles +{ + None, + UseMaxSize, + OnlyFilesWithConstantSizes, + OnlyFilesWithConstantOrZeroSizes +}; + +class Profile +{ +public: + Profile() : + _fProfileOnly(false), + _fVerbose(false), + _fVerboseStats(false), + _dwProgress(0), + _fEtwEnabled(false), + _fEtwProcess(false), + _fEtwThread(false), + _fEtwImageLoad(false), + _fEtwDiskIO(false), + _fEtwMemoryPageFaults(false), + _fEtwMemoryHardFaults(false), + _fEtwNetwork(false), + _fEtwRegistry(false), + _fEtwUsePagedMemory(false), + _fEtwUsePerfTimer(false), + _fEtwUseSystemTimer(false), + _fEtwUseCyclesCounter(false), + _resultsFormat(ResultsFormat::Text), + _precreateFiles(PrecreateFiles::None) + { + } + + void ClearTimeSpans() + { + _vTimeSpans.clear(); + } + + void AddTimeSpan(const TimeSpan& timeSpan) + { + _vTimeSpans.push_back(TimeSpan(timeSpan)); + } + + const vector& GetTimeSpans() const { return _vTimeSpans; } + + void SetProfileOnly(bool b) { _fProfileOnly = b; } + bool GetProfileOnly() const { return _fProfileOnly; } + + void SetVerbose(bool b) { _fVerbose = b; } + bool GetVerbose() const { return _fVerbose; } + + void SetVerboseStats(bool b) { _fVerboseStats = b; } + bool GetVerboseStats() const { return _fVerboseStats; } + + void SetProgress(DWORD dwProgress) { _dwProgress = dwProgress; } + DWORD GetProgress() const { return _dwProgress; } + + void SetCmdLine(string sCmdLine) { _sCmdLine = sCmdLine; } + string GetCmdLine() const { return _sCmdLine; }; + + void SetResultsFormat(ResultsFormat format) { _resultsFormat = format; } + ResultsFormat GetResultsFormat() const { return _resultsFormat; } + + void SetPrecreateFiles(PrecreateFiles c) { _precreateFiles = c; } + PrecreateFiles GetPrecreateFiles() const { return _precreateFiles; } + + //ETW + void SetEtwEnabled(bool b) { _fEtwEnabled = b; } + void SetEtwProcess(bool b) { _fEtwProcess = b; } + void SetEtwThread(bool b) { _fEtwThread = b; } + void SetEtwImageLoad(bool b) { _fEtwImageLoad = b; } + void SetEtwDiskIO(bool b) { _fEtwDiskIO = b; } + void SetEtwMemoryPageFaults(bool b) { _fEtwMemoryPageFaults = b; } + void SetEtwMemoryHardFaults(bool b) { _fEtwMemoryHardFaults = b; } + void SetEtwNetwork(bool b) { _fEtwNetwork = b; } + void SetEtwRegistry(bool b) { _fEtwRegistry = b; } + void SetEtwUsePagedMemory(bool b) { _fEtwUsePagedMemory = b; } + void SetEtwUsePerfTimer(bool b) { _fEtwUsePerfTimer = b; } + void SetEtwUseSystemTimer(bool b) { _fEtwUseSystemTimer = b; } + void SetEtwUseCyclesCounter(bool b) { _fEtwUseCyclesCounter = b; } + + bool GetEtwEnabled() const { return _fEtwEnabled; } + bool GetEtwProcess() const { return _fEtwProcess; } + bool GetEtwThread() const { return _fEtwThread; } + bool GetEtwImageLoad() const { return _fEtwImageLoad; } + bool GetEtwDiskIO() const { return _fEtwDiskIO; } + bool GetEtwMemoryPageFaults() const { return _fEtwMemoryPageFaults; } + bool GetEtwMemoryHardFaults() const { return _fEtwMemoryHardFaults; } + bool GetEtwNetwork() const { return _fEtwNetwork; } + bool GetEtwRegistry() const { return _fEtwRegistry; } + bool GetEtwUsePagedMemory() const { return _fEtwUsePagedMemory; } + bool GetEtwUsePerfTimer() const { return _fEtwUsePerfTimer; } + bool GetEtwUseSystemTimer() const { return _fEtwUseSystemTimer; } + bool GetEtwUseCyclesCounter() const { return _fEtwUseCyclesCounter; } + + string GetXml(UINT32 indent) const; + bool Validate(bool fSingleSpec, SystemInformation *pSystem = nullptr) const; + void MarkFilesAsPrecreated(const vector vFiles); + +private: + Profile(const Profile& T); + + vector_vTimeSpans; + bool _fVerbose; + bool _fVerboseStats; + bool _fProfileOnly; + DWORD _dwProgress; + string _sCmdLine; + ResultsFormat _resultsFormat; + PrecreateFiles _precreateFiles; + + //ETW + bool _fEtwEnabled; + bool _fEtwProcess; + bool _fEtwThread; + bool _fEtwImageLoad; + bool _fEtwDiskIO; + bool _fEtwMemoryPageFaults; + bool _fEtwMemoryHardFaults; + bool _fEtwNetwork; + bool _fEtwRegistry; + bool _fEtwUsePagedMemory; + bool _fEtwUsePerfTimer; + bool _fEtwUseSystemTimer; + bool _fEtwUseCyclesCounter; + + friend class UnitTests::ProfileUnitTests; +}; + +class IORequest +{ +public: + IORequest(Random *pRand) : + _ioType(IOOperation::ReadIO), + _pRand(pRand), + _iCurrentTarget(0), + _ullStartTime(0), + _ulRequestIndex(0xFFFFFFFF), + _ullTotalWeight(0), + _fEqualWeights(true), + _ActivityId() + { + memset(&_overlapped, 0, sizeof(OVERLAPPED)); + } + + static IORequest *OverlappedToIORequest(OVERLAPPED *pOverlapped) + { + return CONTAINING_RECORD(pOverlapped, IORequest, _overlapped); + } + + OVERLAPPED *GetOverlapped() { return &_overlapped; } + + void AddTarget(Target *pTarget, UINT32 ulWeight) + { + _vTargets.push_back(pTarget); + _vulTargetWeights.push_back(ulWeight); + _ullTotalWeight += ulWeight; + + if (ulWeight != _vulTargetWeights[0]) { + _fEqualWeights = false; + } + } + + Target *GetCurrentTarget() { return _vTargets[_iCurrentTarget]; } + size_t GetCurrentTargetIndex() { return _iCurrentTarget; } + + Target *GetNextTarget() + { + UINT64 ullWeight; + + if (_vTargets.size() == 1) { + _iCurrentTarget = 0; + } + else if (_fEqualWeights) { + _iCurrentTarget = _pRand->Rand32() % _vTargets.size(); + } + else { + ullWeight = _pRand->Rand64() % _ullTotalWeight; + + for (size_t iTarget = 0; iTarget < _vTargets.size(); iTarget++) { + if (ullWeight < _vulTargetWeights[iTarget]) { + _iCurrentTarget = iTarget; + break; + } + + ullWeight -= _vulTargetWeights[iTarget]; + } + } + + return GetCurrentTarget(); + } + + void SetIoType(IOOperation ioType) { _ioType = ioType; } + IOOperation GetIoType() const { return _ioType; } + + void SetStartTime(UINT64 ullStartTime) { _ullStartTime = ullStartTime; } + UINT64 GetStartTime() const { return _ullStartTime; } + + void SetRequestIndex(UINT32 ulRequestIndex) { _ulRequestIndex = ulRequestIndex; } + UINT32 GetRequestIndex() const { return _ulRequestIndex; } + + void SetActivityId(GUID ActivityId) { _ActivityId = ActivityId; } + GUID GetActivityId() const { return _ActivityId; } + +private: + OVERLAPPED _overlapped; + vector _vTargets; + vector _vulTargetWeights; + UINT64 _ullTotalWeight; + bool _fEqualWeights; + Random *_pRand; + size_t _iCurrentTarget; + IOOperation _ioType; + UINT64 _ullStartTime; + UINT32 _ulRequestIndex; + GUID _ActivityId; +}; + +typedef struct _ACTIVITY_ID { + UINT32 Thread; + UINT32 Reserved; + UINT64 Count; +} ACTIVITY_ID; + +C_ASSERT(sizeof(ACTIVITY_ID) == sizeof(GUID)); + +// Forward declaration +class ThreadTargetState; + +class ThreadParameters +{ +public: + ThreadParameters() : + pProfile(nullptr), + pTimeSpan(nullptr), + pullSharedSequentialOffsets(nullptr), + ulRandSeed(0), + ulThreadNo(0), + ulRelativeThreadNo(0) + { + } + + const Profile *pProfile; + const TimeSpan *pTimeSpan; + + vector vTargets; + vector vTargetStates; + vector vhTargets; + + vector vulReadBufferSize; + vector vpDataBuffers; + vector vIORequest; + vector vThroughputMeters; + + // For interlocked sequential access (-si): + // Pointers to offsets shared between threads, incremented with an interlocked op + UINT64* pullSharedSequentialOffsets; + + Random *pRand; + + UINT32 ulRandSeed; + UINT32 ulThreadNo; + UINT32 ulRelativeThreadNo; + + // accounting + volatile bool *pfAccountingOn; + PUINT64 pullStartTime; + ThreadResults *pResults; + + //progress dots + DWORD dwIOCnt; + + //group affinity + WORD wGroupNum; + DWORD bProcNum; + + HANDLE hStartEvent; + + // TODO: check how it's used + HANDLE hEndEvent; //used only in case of completion routines (not for IO Completion Ports) + + bool AllocateAndFillBufferForTarget(const Target& target); + BYTE* GetReadBuffer(size_t iTarget, size_t iRequest); + BYTE* GetWriteBuffer(size_t iTarget, size_t iRequest); + DWORD GetTotalRequestCount() const; + bool InitializeMappedViewForTarget(Target& target, DWORD DesiredAccess); + + GUID NextActivityId() + { + GUID ActivityId; + ACTIVITY_ID* ActivityGuid = (ACTIVITY_ID*)&ActivityId; + + ActivityGuid->Thread = ulThreadNo; + ActivityGuid->Reserved = 0; + // The count is byte swapped so it's understandable in a trace. + ActivityGuid->Count = _byteswap_uint64(++_ullActivityCount); + + return ActivityId; + } + +private: + ThreadParameters(const ThreadParameters& T); + UINT64 _ullActivityCount; +}; + +class ThreadTargetState +{ + public: + + ThreadTargetState( + const ThreadParameters *pTp, + size_t iTarget, + UINT64 targetSize + ) : + _tp(pTp), + _target(&_tp->vTargets[iTarget]), + _targetSize(targetSize), + _mode(_target->GetIOMode()), + + _nextSeqOffset(0), + _lastIO(IOOperation::Unknown), + _sharedSeqOffset(nullptr), + _ioDistributionSpan(100) + { + // + // Now calculate the maximum base-relative file offset that IO can be issued at. + // + // Trim by max file size limit, and reduce by base file offset. + // + + if (_target->GetMaxFileSize()) + { + _relTargetSize = _targetSize > _target->GetMaxFileSize() ? _target->GetMaxFileSize() : _targetSize; + } + else + { + _relTargetSize = _targetSize; + } + + _relTargetSize -= _target->GetBaseFileOffsetInBytes(); + + // + // Align relative to the maximum offset at which aligned IO could be issued at. + // + + _relTargetSizeAligned = _relTargetSize - _target->GetBlockSizeInBytes(); + _relTargetSizeAligned -= _relTargetSizeAligned % _target->GetBlockAlignmentInBytes(); + _relTargetSizeAligned += _target->GetBlockAlignmentInBytes(); + + // Grab the shared sequential pointer if this is interlocked. + + if (_mode == IOMode::InterlockedSequential) + { + assert(_tp->pullSharedSequentialOffsets != nullptr); + _sharedSeqOffset = &_tp->pullSharedSequentialOffsets[iTarget]; + } + + // Convert and finalize the random distribution stated in the target using final bounds. + + switch (_target->GetDistributionType()) + { + case DistributionType::Percent: + { + UINT32 ioCarry = 0; + + for (auto& r : _target->GetDistributionRange()) + { + // + // The basic premise is to align the range's bounds to discover whether there are + // any aligned offsets within it. To do this we align DOWN. This moves the adjacent + // end of this range and base of the next in lockstep. + // + // There are two basic branches and three subcases in each: + // + // * aligned base + // * unaligned base + // * and within each + // * aligned end + // * unaligned end in same alignment unit + // * unaligned end in next/following alignment unit + // + // * aligned/aligned will not move b/e, there will be a positive range + // * aligned/unaligned-next will move e in step with the following b + // and there will be a positive range + // * aligned/unaligned-same will result in b=e after aligning; IO at b is + // the only possible IO + // + // Unaligned base is more interesting due to degenerate spans, spans where the + // mimimum %range is smaller than the block alignment. For instance, a 100KiB target + // with a 4K alignment has a 1%/1KB minimum and may create these cases. + // + // * unaligned/aligned aligns base (down) and there is a positive range + // * unaligned/unaligned-next aligns both down and there is a positive range + // * unaligned/unaligned-same has no aligned offset in the range; we can detect + // this by aligning e first and seeing if it is less than unaligned b. there + // are two subcases: + // * if the prior range is of zero length, we roll this range's IO% onto it - + // this combines two or more adjacent degenerate spans + // * if it was not of zero length, we roll over the IO% to the next/last range + // + // Now, in the cases where we have a positive range we may still find our aligned + // base is the same as the prior range - the prior was degenerate and the current + // is not. In this case we need to round our base up so that we do not share a base. + // We may then find that our rounded up base makes us degenerate and ... roll over. + // + // Note that this is a closed/open interval. The end offset is NOT a member of this + // range. Consider an 8KiB file divided 50:50 into two 4KB ranges. The first range is + // [0,4KB) and the second is [4KB, 8KB). The IO at offset 4KB belongs to the second + // range, not the first. + // + + // + // Skip holes. These have the effect of excluding a range of the target by way of + // zero IO will be issued to them; the resulting range is still IO 0-100%. + // + + if (!r._span) { + continue; + } + + UINT64 b, e; + + b = ((r._dst.first * _relTargetSizeAligned) / 100); + // guarantee end (don't lose it in integer math) + if (r._dst.first + r._dst.second == 100) + { + e = _relTargetSizeAligned; + } + else + { + e = b + ((r._dst.second * _relTargetSizeAligned) / 100); + } + + e = ROUND_DOWN(e, _target->GetBlockAlignmentInBytes()); + + // unaligned/unaligned-same + // carryover IO% to next/last range + if (e < b) + { + // is the prior range degenerate? + // if so, extend its IO% + // note that this cannot happen for the first range, so there + // will always be a range to look at. + if (_vDistributionRange.rbegin()->_dst.first == e) + { + _vDistributionRange.rbegin()->_span += r._span; + } + // carry over to next + else + { + ioCarry = r._span; + } + + continue; + } + + b = ROUND_DOWN(b, _target->GetBlockAlignmentInBytes()); + + // Now if b < e (a positive range) we may discover we're adjacent + // to a degenerate range. This is the case of re-aligning b up. + // Note that the degenerate range logically rounds up - this does + // not affect operation, but presents the correct appearance of a + // closed/open interval with respect to the subsequent range. + // Case: -rdpct10/1:10/1 + // + // It is possible b == e: this is a case where b was already aligned + // and we're placing a normal degenerate span. No special handling. + + if (b < e && + _vDistributionRange.size() && + _vDistributionRange.rbegin()->_dst.first == b) + { + + b += _target->GetBlockAlignmentInBytes(); + _vDistributionRange.rbegin()->_dst.second += _target->GetBlockAlignmentInBytes(); + + // Now there are two degenerate cases to manage. + + // if we're dealing with a degenerate at the tail, allow carryover + if (b == _relTargetSizeAligned) + { + ioCarry = r._span; + continue; + } + + // otherwise, if the range became degenerate in the up-alignment, it must + // combine with the prior degenerate since its logical range is included + // with it. + if (b == e) + { + _vDistributionRange.rbegin()->_span += r._span; + continue; + } + + // fall through to place re-aligned b/e (non degenerate) + } + + // prefer to roll IO% to the smaller of prior range/this range + if (ioCarry && + _vDistributionRange.rbegin()->_span < r._span) + { + _vDistributionRange.rbegin()->_span += ioCarry; + ioCarry = 0; + } + + _vDistributionRange.emplace_back( + r._src - ioCarry, + r._span + ioCarry, + make_pair(b, e - b)); + + ioCarry = 0; + } + + // Apply trailing carryover to final range, extending it. + // Guarantee target range extends to aligned size - rollover is always from + // a degenerate range we could not place directly. We need to gross up the + // actual tail so that the effective correctly spans the open/closed interval + // to target size. + // -rdpct10/96:10/3:80/1 - the last range is degenerate and needs to roll. + if (ioCarry) + { + DistributionRange& last = *_vDistributionRange.rbegin(); + + last._span += ioCarry; + last._dst.second = _relTargetSizeAligned - last._dst.first; + } + } + break; + + case DistributionType::Absolute: + { + UINT32 ioUsed = 0; + + for (auto& r : _target->GetDistributionRange()) + { + // + // The premise for absolute distributions is similar but without the complication of + // degenerate ranges. The offsets are provided and we only need to push the last to + // the end of the range if it was left open (its length is zero). They do not need to + // be aligned, similar to -T thread stride - this is the caller's dilemma. We already + // know by validation that IO can be issued in the range since any absolute distribution + // with a range < block size would have been rejected. + // + // If the range was not left open we have two cases: + // + // * the end is within the final range + // * the end is past it + // + // If the end is within the final range that will again be the caller's dilemma, we'll + // simply trim the length of that range. If it is past it, we will discard the trailing + // ranges and trim the maximum IO% so that they become a proportional specification of the + // IO. For instance, if a 10/10/80 winds up with the 80% not addressable in the file, the + // maximum IO% trims to 20 and it logically becomes a 50:50 split (10:10). + // + + UINT64 l; + + // + // Skip holes. These have the effect of excluding a range of the target by way of + // zero IO will be issued to them; the resulting range is still IO 0-100%. + // + + if (!r._span) { + continue; + } + + // beyond end? done, with whatever tail IO% not seen + if (r._dst.first >= _relTargetSize) + { + break; + } + // open end or spans end? - set to aligned remainder + else if (r._dst.second == 0 || + r._dst.first + r._dst.second > _relTargetSize) + { + // ensure tail can accept IO by blocksize - caller has stated this is aligned by + // its specification + l = _relTargetSize - r._dst.first; + + if (l < _target->GetBlockSizeInBytes()) + { + break; + } + } + else + { + l = r._dst.second; + } + + _vDistributionRange.emplace_back( + r._src, + r._span, + make_pair(r._dst.first, l)); + + ioUsed += r._span; + } + + // reduce the IO distribution to that specified by the ranges consumed. + // it is still logically 100%, simply over a range of less than 0-100. + _ioDistributionSpan = ioUsed; + } + break; + + // none + default: + break; + } + + Reset(); + } + + // + // Reset IO pointer/type state to initial conditions. + // + + VOID Reset() + { + // + // Now set the (base-relative) initial sequential offset + // * sequential: based on thread stride + // * mixed: randomized starting position + // + // Note this is repeated for ParallelAsync initialization since sequential offset is in the IO request there. + // + + switch (_mode) + { + case IOMode::Sequential: + _nextSeqOffset = _target->GetThreadBaseRelativeOffsetInBytes(_tp->ulRelativeThreadNo); + break; + + case IOMode::Mixed: + _nextSeqOffset = NextRelativeRandomOffset(); + break; + + default: + break; + } + + _lastIO = NextIOType(true); + } + + // + // Validate whether this thread can start IO given thread stride and file size. + // + + bool CanStart() + { + UINT64 startingFileOffset = _target->GetThreadBaseRelativeOffsetInBytes(_tp->ulRelativeThreadNo); + + if (startingFileOffset + _target->GetBlockSizeInBytes() > _relTargetSize) + { + return false; + } + + return true; + } + + UINT64 TargetSize() + { + return _targetSize; + } + + VOID InitializeParallelAsyncIORequest(IORequest& ioRequest) const + { + ULARGE_INTEGER initialOffset; + + // + // Bias backwards by one IO so that this functions as the last-IO-issued pointer. + // It will be incremented to the expected first offset. Note: absolute offset. + // + + initialOffset.QuadPart = _target->GetThreadBaseFileOffsetInBytes(_tp->ulRelativeThreadNo) - _target->GetBlockAlignmentInBytes(); + + ioRequest.GetOverlapped()->Offset = initialOffset.LowPart; + ioRequest.GetOverlapped()->OffsetHigh = initialOffset.HighPart; + } + + UINT64 NextRelativeSeqOffset() + { + UINT64 nextOffset; + + nextOffset = _nextSeqOffset; + + // Wrap? + + if (nextOffset + _target->GetBlockSizeInBytes() > _relTargetSize) { + nextOffset = _target->GetThreadBaseRelativeOffsetInBytes(_tp->ulRelativeThreadNo) % _target->GetBlockAlignmentInBytes(); + } + + _nextSeqOffset = nextOffset + _target->GetBlockAlignmentInBytes(); + + return nextOffset; + } + + UINT64 NextRelativeInterlockedSeqOffset() + { + UINT64 nextOffset; + + // advance shared and rewind to get offset to use + nextOffset = InterlockedAdd64((PLONG64) _sharedSeqOffset, _target->GetBlockAlignmentInBytes()); + nextOffset -= _target->GetBlockAlignmentInBytes(); + + nextOffset %= _relTargetSizeAligned; + return nextOffset; + } + + UINT64 NextRelativeParaSeqOffset(IORequest& ioRequest) + { + ULARGE_INTEGER nextOffset; + + // + // Note: parallel seq differs from the other sequential cases in that the + // pointer indicates the prior IO, not the offset to issue the current at. + // Advance it. + // + + nextOffset.LowPart = ioRequest.GetOverlapped()->Offset; + nextOffset.HighPart = ioRequest.GetOverlapped()->OffsetHigh; + nextOffset.QuadPart -= _target->GetBaseFileOffsetInBytes(); // absolute -> relative + nextOffset.QuadPart += _target->GetBlockAlignmentInBytes(); // advance past last IO (!) + + // Wrap? + + if (nextOffset.QuadPart + _target->GetBlockSizeInBytes() > _relTargetSize) { + nextOffset.QuadPart = _target->GetThreadBaseRelativeOffsetInBytes(_tp->ulRelativeThreadNo) % _target->GetBlockAlignmentInBytes(); + } + + return nextOffset.QuadPart; + } + + UINT64 NextRelativeRandomOffset() const + { + UINT64 nextOffset = _tp->pRand->Rand64(); + nextOffset -= nextOffset % _target->GetBlockAlignmentInBytes(); + + // + // With a distribution we choose by bucket. Note the bucket is already aligned. + // + + if (_vDistributionRange.size()) + { + auto r = DistributionRange::find(_vDistributionRange, _tp->pRand->Rand64() % _ioDistributionSpan); + nextOffset %= r->_dst.second; // trim to range length (already aligned) + nextOffset += r->_dst.first; // bump by range base + } + // Full width. + else + { + nextOffset %= _relTargetSizeAligned; + } + + return nextOffset; + } + + UINT64 NextRelativeMixedOffset(bool& fRandom) + { + ULARGE_INTEGER nextOffset; + + fRandom = Util::BooleanRatio(_tp->pRand, _target->GetRandomRatio()); + + if (fRandom) + { + nextOffset.QuadPart = NextRelativeRandomOffset(); + _nextSeqOffset = nextOffset.QuadPart + _target->GetBlockAlignmentInBytes(); + return nextOffset.QuadPart; + } + + return NextRelativeSeqOffset(); + } + + IOOperation NextIOType(bool newType) + { + IOOperation ioType; + + if (_target->GetWriteRatio() == 0) + { + ioType = IOOperation::ReadIO; + } + else if (_target->GetWriteRatio() == 100) + { + ioType = IOOperation::WriteIO; + } + else if (_mode == IOMode::Mixed && !newType) + { + // repeat last IO if not needing a new choice (e.g., random) + ioType = _lastIO; + } + else + { + ioType = Util::BooleanRatio(_tp->pRand, _target->GetWriteRatio()) ? IOOperation::WriteIO : IOOperation::ReadIO; + _lastIO = ioType; + } + + return ioType; + } + + void NextIORequest(IORequest &ioRequest) + { + bool fRandom = false; + ULARGE_INTEGER nextOffset = { 0 }; + + switch (_mode) + { + case IOMode::Sequential: + nextOffset.QuadPart = NextRelativeSeqOffset(); + break; + + case IOMode::InterlockedSequential: + nextOffset.QuadPart = NextRelativeInterlockedSeqOffset(); + break; + + case IOMode::ParallelAsync: + nextOffset.QuadPart = NextRelativeParaSeqOffset(ioRequest); + break; + + case IOMode::Mixed: + nextOffset.QuadPart = NextRelativeMixedOffset(fRandom); + break; + + case IOMode::Random: + nextOffset.QuadPart = NextRelativeRandomOffset(); + fRandom = true; + break; + + default: + assert(false); + } + + // + // Convert relative offset to absolute. + // + + nextOffset.QuadPart += _target->GetBaseFileOffsetInBytes(); + + // + // Move offset into the IO request and decide what IO type will be issued. + // Mixed which has chosen sequential will repeat last IO type so that seq + // runs are homogeneous. + // + + ioRequest.GetOverlapped()->Offset = nextOffset.LowPart; + ioRequest.GetOverlapped()->OffsetHigh = nextOffset.HighPart; + ioRequest.SetIoType(NextIOType(fRandom)); + } + + private: + + const ThreadParameters *_tp; + const Target *_target; + const UINT64 _targetSize; // unmodified absolute target size + const IOMode _mode; // thread's mode of IO operations to this target (Random, Sequential, etc.) + + // + // Offsets/sizes are zero-based relative to target base offset, not absolute file offset. + // Relative size is trimmed with respect to block alignment, if specified. + // + + UINT64 _relTargetSize; // relative target size for IO v. base/max + UINT64 _relTargetSizeAligned; // relative target size for zero-base aligned IO (applies to: Random, InterlockedSequential) + UINT64 _nextSeqOffset; // next IO offset to issue sequential IO at (applies to: Sequential & Mixed) + volatile UINT64 *_sharedSeqOffset; // ... for interlocked IO (applies to: InterlockedSequential) + IOOperation _lastIO; // last IO type (applies to: Mixed) + +public: + + // + // Random distribution (stated in absolute offsets of target) + // + + vector _vDistributionRange; + UINT32 _ioDistributionSpan; + + friend class UnitTests::IORequestGeneratorUnitTests; +}; + +class IResultParser +{ +public: + virtual string ParseResults(const Profile& profile, const SystemInformation& system, vector vResults) = 0; + virtual string ParseProfile(const Profile& profile) = 0; +}; + +class EtwResultParser +{ +public: + static void ParseResults(vector vResults); + +private: + static void _WriteResults(IOOperation type, const TargetResults& targetResults, size_t uThread); +}; diff --git a/Common/Histogram.h b/Common/Histogram.h index 8d15ad9..92ed6d1 100644 --- a/Common/Histogram.h +++ b/Common/Histogram.h @@ -1,275 +1,330 @@ -/* - -DISKSPD - -Copyright(c) Microsoft Corporation -All rights reserved. - -MIT License - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - -*/ - -#pragma once - -#include -#include -#include -#include -#include -#include - -#pragma push_macro("min") -#pragma push_macro("max") -#undef min -#undef max - -template -class Histogram -{ - private: - - unsigned _samples; - -#define USE_HASH_TABLE -#ifdef USE_HASH_TABLE - std::unordered_map _data; - - std::map _GetSortedData() const - { - return std::map(_data.begin(), _data.end()); - } -#else - std::map _data; - - std::map _GetSortedData() const - { - return _data; - } -#endif - public: - - Histogram() - : _samples(0) - {} - - void Clear() - { - _data.clear(); - _samples = 0; - } - - void Add(T v) - { - _data[ v ]++; - _samples++; - } - - void Merge(const Histogram &other) - { - for (auto i : other._data) - { - _data[ i.first ] += i.second; - } - - _samples += other._samples; - } - - T GetMin() const - { - T min(std::numeric_limits::max()); - - for (auto i : _data) - { - if (i.first < min) - { - min = i.first; - } - } - - return min; - } - - T GetMax() const - { - T max(std::numeric_limits::min()); - - for (auto i : _data) - { - if (i.first > max) - { - max = i.first; - } - } - - return max; - } - - unsigned GetSampleSize() const - { - return _samples; - } - - T GetPercentile(double p) const - { - // ISSUE-REVIEW - // What do the 0th and 100th percentile really mean? - if ((p < 0) || (p > 1)) - { - throw std::invalid_argument("Percentile must be >= 0 and <= 1"); - } - - const double target = GetSampleSize() * p; - - unsigned cur = 0; - for (auto i : _GetSortedData()) - { - cur += i.second; - if (cur >= target) - { - return i.first; - } - } - - // We can get here if no IOs are issued, simply return 0 since - // we don't want to throw an exception and crash - return 0; - } - - T GetPercentile(int p) const - { - return GetPercentile(static_cast(p)/100); - } - - T GetMedian() const - { - return GetPercentile(0.5); - } - - double GetStdDev() const { return GetStandardDeviation(); } - double GetAvg() const { return GetMean(); } - - double GetMean() const - { - double sum(0); - unsigned samples = GetSampleSize(); - - for (auto i : _data) - { - double bucket_val = - static_cast(i.first) * i.second / samples; - - if (sum + bucket_val < 0) - { - throw std::overflow_error("while trying to accumulate sum"); - } - - sum += bucket_val; - } - - return sum; - } - - double GetStandardDeviation() const - { - double mean(GetMean()); - double ssd(0); - - for (auto i : _data) - { - double dev = static_cast(i.first) - mean; - double sqdev = dev*dev; - ssd += i.second * sqdev; - } - - return sqrt(ssd / GetSampleSize()); - } - - std::string GetHistogramCsv(const unsigned bins) const - { - return GetHistogramCsv(bins, GetMin(), GetMax()); - } - - std::string GetHistogramCsv(const unsigned bins, const T LOW, const T HIGH) const - { - // ISSUE-REVIEW - // Currently bins are defined as strictly less-than - // their upper limit, with the exception of the last - // bin. Otherwise where would I put the max value? - const double binSize = static_cast((HIGH - LOW) / bins); - double limit = static_cast(LOW); - - std::ostringstream os; - os.precision(std::numeric_limits::digits10); - - std::map sortedData = _GetSortedData(); - - auto pos = sortedData.begin(); - - unsigned cumulative = 0; - - for (unsigned bin = 1; bin <= bins; ++bin) - { - unsigned count = 0; - limit += binSize; - - while (pos != sortedData.end() && - (pos->first < limit || bin == bins)) - { - count += pos->second; - ++pos; - } - - cumulative += count; - - os << limit << "," << count << "," << cumulative << std::endl; - } - - return os.str(); - } - - std::string GetRawCsv() const - { - std::ostringstream os; - os.precision(std::numeric_limits::digits10); - - for (auto i : _GetSortedData()) - { - os << i.first << "," << i.second << std::endl; - } - - return os.str(); - } - - std::string GetRaw() const - { - std::ostringstream os; - - for (auto i : _GetSortedData()) - { - os << i.second << " " << i.first << std::endl; - } - - return os.str(); - } -}; - -#pragma pop_macro("min") -#pragma pop_macro("max") +/* + +DISKSPD + +Copyright(c) Microsoft Corporation +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +#pragma once + +#include +#include +#include +#include +#include +#include + +// Plain min/max macros from common headers will interfere with std::numeric_limits + +#pragma push_macro("min") +#pragma push_macro("max") +#undef min +#undef max + +template +class Histogram +{ +private: + + mutable std::unordered_map _data; + mutable unsigned _samples; + + mutable std::map _sdata; + mutable unsigned _ssamples; + + // Save most recent percentile/iterator/nth-distance query. If the next is strictly >= it allows + // an efficient forward iteration through an ascending set of queries. + + mutable double _lastptile; + mutable unsigned _lastptilen; + mutable decltype(_sdata.cbegin()) _lastptilepos; + + // A histogram starts writable/unsealed and automatically seals after the first read operation. + // Subsequent writes which add data restart from empty. + + void _SealData() const + { + if (!_data.empty()) + { + _sdata.clear(); + + _sdata = std::map(_data.cbegin(), _data.cend()); + _ssamples = _samples; + + // invalid ptile > 1; first ptile query will initialize + _lastptile = 1.1; + + _data.clear(); + _samples = 0; + } + } + +public: + + Histogram() + : _samples(0), + _ssamples(0) + {} + + void Clear() + { + _data.clear(); + _samples = 0; + + _sdata.clear(); + _ssamples = 0; + + } + + void Add(T v) + { + _data[v]++; + _samples++; + } + + void Merge(const Histogram &other) + { + for (auto i : other._data) + { + _data[ i.first ] += i.second; + } + + _samples += other._samples; + } + + T GetMin() const + { + _SealData(); + + // Default low if empty + if (!_ssamples) + { + return std::numeric_limits::min(); + } + + return _sdata.cbegin()->first; + } + + T GetMax() const + { + _SealData(); + + // Default low if empty + if (!_ssamples) + { + return std::numeric_limits::min(); + } + + return _sdata.crbegin()->first; + } + + unsigned GetSampleBuckets() const + { + return (unsigned) (_samples ? _data.size() : _sdata.size()); + } + + unsigned GetSampleSize() const + { + return _samples ? _samples : _ssamples; + } + + T GetPercentile(double p) const + { + if ((p < 0.0) || (p > 1.0)) + { + throw std::invalid_argument("Percentile must be >= 0 and <= 1"); + } + + _SealData(); + + // Default low if empty + if (!_ssamples) + { + return std::numeric_limits::min(); + } + + const double target = p * _ssamples; + + // Default to beginning; n is the number of samples iterated over so far + unsigned n = 0; + auto pos = _sdata.cbegin(); + + // Resume from last? + if (p >= _lastptile) + { + n = _lastptilen; + pos = _lastptilepos; + } + + while (pos != _sdata.cend()) + { + if (n + pos->second >= target) + { + // Save position. Note the pre-incremented distance through the histogram + // must be saved in case next is still in the same bucket. + _lastptile = p; + _lastptilen = n; + _lastptilepos = pos; + + return pos->first; + } + + n += pos->second; + ++pos; + } + + throw std::overflow_error("overran end trying to find percentile"); + } + + T GetPercentile(int p) const + { + return GetPercentile(static_cast(p) / 100); + } + + T GetMedian() const + { + return GetPercentile(0.5); + } + + double GetStdDev() const { return GetStandardDeviation(); } + double GetAvg() const { return GetMean(); } + + double GetMean() const + { + _SealData(); + + // Default low if empty + if (!_ssamples) + { + return std::numeric_limits::min(); + } + + double sum(0); + + for (const auto i : _sdata) + { + double bucket_val = + static_cast(i.first) * i.second / _ssamples; + + if (sum + bucket_val < 0) + { + throw std::overflow_error("while trying to accumulate sum"); + } + + sum += bucket_val; + } + + return sum; + } + + double GetStandardDeviation() const + { + double mean(GetMean()); + double ssd(0); + + for (const auto i : _sdata) + { + double dev = static_cast(i.first) - mean; + double sqdev = dev * dev; + ssd += i.second * sqdev; + } + + return sqrt(ssd / _ssamples); + } + + std::string GetHistogramCsv(const unsigned bins) const + { + return GetHistogramCsv(bins, GetMin(), GetMax()); + } + + std::string GetHistogramCsv(const unsigned bins, const T LOW, const T HIGH) const + { + _SealData(); + + // ISSUE-REVIEW + // Currently bins are defined as strictly less-than + // their upper limit, with the exception of the last + // bin. Otherwise where would I put the max value? + const double binSize = static_cast((HIGH - LOW) / bins); + double limit = static_cast(LOW); + + std::ostringstream os; + os.precision(std::numeric_limits::digits10); + + auto pos = _sdata.cbegin(); + unsigned cumulative = 0; + + for (unsigned bin = 1; bin <= bins; ++bin) + { + unsigned count = 0; + limit += binSize; + + while (pos != _sdata.end() && + (pos->first < limit || bin == bins)) + { + count += pos->second; + ++pos; + } + + cumulative += count; + + os << limit << "," << count << "," << cumulative << std::endl; + } + + return os.str(); + } + + std::string GetRawCsv() const + { + _SealData(); + + std::ostringstream os; + os.precision(std::numeric_limits::digits10); + + for (const auto i : _sdata) + { + os << i.first << "," << i.second << std::endl; + } + + return os.str(); + } + + std::string GetRaw() const + { + _SealData(); + + std::ostringstream os; + + for (const auto i : _sdata) + { + os << i.second << " " << i.first << std::endl; + } + + return os.str(); + } +}; + +#pragma pop_macro("min") +#pragma pop_macro("max") diff --git a/Common/IORequestGenerator.h b/Common/IORequestGenerator.h index 7d02bb1..a72ce75 100644 --- a/Common/IORequestGenerator.h +++ b/Common/IORequestGenerator.h @@ -66,7 +66,7 @@ class IORequestGenerator bool _CreateFile(UINT64 ullFileSize, const char *pszFilename, bool fZeroBuffers, bool fVerbose) const; bool _GetActiveGroupsAndProcs() const; struct ETWSessionInfo _GetResultETWSession(const EVENT_TRACE_PROPERTIES *pTraceProperties) const; - bool _GetSystemPerfInfo(SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION *pInfo, UINT32 uCpuCount) const; + bool _GetSystemPerfInfo(vector& vSPPI, bool fVerbose) const; void _InitializeGlobalParameters(); bool _LoadDLLs(); bool _StopETW(bool fUseETW, TRACEHANDLE hTraceSession) const; diff --git a/Common/ResultParser.h b/Common/ResultParser.h index 7858cbd..bf901fe 100644 --- a/Common/ResultParser.h +++ b/Common/ResultParser.h @@ -61,6 +61,7 @@ class ResultParser : public IResultParser void _PrintTarget(const Target &target, bool fUseThreadsPerFile, bool fUseRequestsPerFile, bool fCompletionRoutines); void _PrintDistribution(DistributionType dT, const vector& v, char* spc); void _PrintEffectiveDistributions(const Results& results); + void _PrintWaitStats(const Results& result); string _sResult; diff --git a/Common/Version.h b/Common/Version.h new file mode 100644 index 0000000..a04a6e9 --- /dev/null +++ b/Common/Version.h @@ -0,0 +1,52 @@ +/* + +DISKSPD + +Copyright(c) Microsoft Corporation +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +// versioning material. for simplicity in consumption, please ensure that the date string +// parses via the System.Datetime constructor as follows (in Powershell): +// +// [datetime] "string" +// +// this should result in a valid System.Datetime object, rendered like: +// +// Monday, June 16, 2014 12:00:00 AM + +#define DISKSPD_RELEASE_TAG "" +#define DISKSPD_REVISION "" + +#define DISKSPD_MAJOR 2 +#define DISKSPD_MINOR 2 +#define DISKSPD_BUILD 0 +#define DISKSPD_QFE 0 + +#define DISKSPD_MAJORMINOR_VER_STR(x,y,z) #x "." #y "." #z +#define DISKSPD_MAJORMINOR_VERSION_STRING(x,y,z) DISKSPD_MAJORMINOR_VER_STR(x,y,z) +#define DISKSPD_MAJORMINOR_VERSION_STR DISKSPD_MAJORMINOR_VERSION_STRING(DISKSPD_MAJOR, DISKSPD_MINOR, DISKSPD_BUILD) + +#define DISKSPD_NUMERIC_VERSION_STRING DISKSPD_MAJORMINOR_VERSION_STR DISKSPD_REVISION DISKSPD_RELEASE_TAG +#define DISKSPD_DATE_VERSION_STRING "2024/6/3" diff --git a/Common/XmlResultParser.h b/Common/XmlResultParser.h index 79e37b1..c87cd45 100644 --- a/Common/XmlResultParser.h +++ b/Common/XmlResultParser.h @@ -1,56 +1,58 @@ -/* - -DISKSPD - -Copyright(c) Microsoft Corporation -All rights reserved. - -MIT License - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - -*/ - -#pragma once -#include "Common.h" - -class XmlResultParser: public IResultParser -{ -public: - string ParseResults(const Profile& profile, const SystemInformation& system, vector vResults); - string ParseProfile(const Profile& profile); - -private: - void _PrintCpuUtilization(const Results& results, const SystemInformation& system); - void _PrintETW(struct ETWMask ETWMask, struct ETWEventCounters EtwEventCounters); - void _PrintETWSessionInfo(struct ETWSessionInfo sessionInfo); - void _PrintLatencyPercentiles(const Results& results); - void _PrintTargetResults(const TargetResults& results); - void _PrintTargetLatency(const TargetResults& results); - void _PrintTargetIops(const IoBucketizer& readBucketizer, const IoBucketizer& writeBucketizer, UINT32 bucketTimeInMs); - void _PrintOverallIops(const Results& results, UINT32 bucketTimeInMs); - void _PrintIops(const IoBucketizer& readBucketizer, const IoBucketizer& writeBucketizer, UINT32 bucketTimeInMs); - - void _Print(const char *format, ...); - void _PrintInc(const char *format, ...); - void _PrintDec(const char *format, ...); - - string _sResult; - UINT32 _indent = 0; +/* + +DISKSPD + +Copyright(c) Microsoft Corporation +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +#pragma once +#include "Common.h" + +class XmlResultParser: public IResultParser +{ +public: + string ParseResults(const Profile& profile, const SystemInformation& system, vector vResults); + string ParseProfile(const Profile& profile); + +private: + void _PrintCpuUtilization(const Results& results, const SystemInformation& system); + void _PrintETW(struct ETWMask ETWMask, struct ETWEventCounters EtwEventCounters); + void _PrintETWSessionInfo(struct ETWSessionInfo sessionInfo); + void _PrintLatencyPercentiles(const Results& results); + void _PrintTargetResults(const TargetResults& results); + void _PrintTargetLatency(const TargetResults& results); + void _PrintTargetIops(const IoBucketizer& readBucketizer, const IoBucketizer& writeBucketizer, UINT32 bucketTimeInMs); + void _PrintOverallIops(const Results& results, UINT32 bucketTimeInMs); + void _PrintIops(const IoBucketizer& readBucketizer, const IoBucketizer& writeBucketizer, UINT32 bucketTimeInMs); + void _PrintWaitStats(const ThreadResults& threadResult); + + void _PrintV(const char *format, va_list listArg); + void _Print(const char *format, ...); + void _PrintInc(const char *format, ...); + void _PrintDec(const char *format, ...); + + string _sResult; + UINT32 _indent = 0; }; \ No newline at end of file diff --git a/IORequestGenerator/IORequestGenerator.cpp b/IORequestGenerator/IORequestGenerator.cpp index 120aaa1..1f09d48 100644 --- a/IORequestGenerator/IORequestGenerator.cpp +++ b/IORequestGenerator/IORequestGenerator.cpp @@ -1,2695 +1,2860 @@ -/* - -DISKSPD - -Copyright(c) Microsoft Corporation -All rights reserved. - -MIT License - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - -*/ - -//FUTURE EXTENSION: make it compile with /W4 - -// Windows 7 -#ifndef _WIN32_WINNT -#define _WIN32_WINNT 0x0601 -#endif - -#include "common.h" -#include "IORequestGenerator.h" - -#include -#include -#include //DISK_GEOMETRY -#include -#include - -#include //WNODE_HEADER - -#include "etw.h" -#include -#include "ThroughputMeter.h" -#include "OverlappedQueue.h" - -// Flags for RtlFlushNonVolatileMemory -#ifndef FLUSH_NV_MEMORY_IN_FLAG_NO_DRAIN -#define FLUSH_NV_MEMORY_IN_FLAG_NO_DRAIN (0x00000001) -#endif - -/*****************************************************************************/ -// gets size of a dynamic volume, return zero on failure -// -UINT64 GetDynamicPartitionSize(HANDLE hFile) -{ - assert(NULL != hFile && INVALID_HANDLE_VALUE != hFile); - - UINT64 size = 0; - VOLUME_DISK_EXTENTS diskExt = {0}; - PVOLUME_DISK_EXTENTS pDiskExt = &diskExt; - DWORD bytesReturned; - - DWORD status = ERROR_SUCCESS; - BOOL rslt; - - OVERLAPPED ovlp = {0}; - ovlp.hEvent = CreateEvent(NULL, FALSE, FALSE, NULL); - if (ovlp.hEvent == nullptr) - { - PrintError("ERROR: Failed to create event (error code: %u)\n", GetLastError()); - return 0; - } - - rslt = DeviceIoControl(hFile, - IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS, - NULL, - 0, - pDiskExt, - sizeof(VOLUME_DISK_EXTENTS), - &bytesReturned, - &ovlp); - if (!rslt) { - status = GetLastError(); - if (status == ERROR_MORE_DATA) { - status = ERROR_SUCCESS; - - bytesReturned = sizeof(VOLUME_DISK_EXTENTS) + ((pDiskExt->NumberOfDiskExtents - 1) * sizeof(DISK_EXTENT)); - pDiskExt = (PVOLUME_DISK_EXTENTS)LocalAlloc(LPTR, bytesReturned); - - if (pDiskExt) - { - rslt = DeviceIoControl(hFile, - IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS, - NULL, - 0, - pDiskExt, - bytesReturned, - &bytesReturned, - &ovlp); - if (!rslt) - { - status = GetLastError(); - if (status == ERROR_IO_PENDING) - { - if (WAIT_OBJECT_0 != WaitForSingleObject(ovlp.hEvent, INFINITE)) - { - status = GetLastError(); - PrintError("ERROR: Failed while waiting for event to be signaled (error code: %u)\n", status); - } - else - { - status = ERROR_SUCCESS; - assert(pDiskExt->NumberOfDiskExtents <= 1); - } - } - else - { - PrintError("ERROR: Could not obtain dynamic volume extents (error code: %u)\n", status); - } - } - } - else - { - status = GetLastError(); - PrintError("ERROR: Could not allocate memory (error code: %u)\n", status); - } - } - else if (status == ERROR_IO_PENDING) - { - if (WAIT_OBJECT_0 != WaitForSingleObject(ovlp.hEvent, INFINITE)) - { - status = GetLastError(); - PrintError("ERROR: Failed while waiting for event to be signaled (error code: %u)\n", status); - } - else - { - status = ERROR_SUCCESS; - assert(pDiskExt->NumberOfDiskExtents <= 1); - } - } - else - { - PrintError("ERROR: Could not obtain dynamic volume extents (error code: %u)\n", status); - } - } - else - { - assert(pDiskExt->NumberOfDiskExtents <= 1); - } - - if (status == ERROR_SUCCESS) - { - for (DWORD n = 0; n < pDiskExt->NumberOfDiskExtents; n++) { - size += pDiskExt->Extents[n].ExtentLength.QuadPart; - } - } - - if (pDiskExt && (pDiskExt != &diskExt)) { - LocalFree(pDiskExt); - } - CloseHandle(ovlp.hEvent); - - return size; -} - -/*****************************************************************************/ -// gets partition size, return zero on failure -// -UINT64 GetPartitionSize(HANDLE hFile) -{ - assert(NULL != hFile && INVALID_HANDLE_VALUE != hFile); - - PARTITION_INFORMATION_EX pinf; - OVERLAPPED ovlp = {}; - - ovlp.hEvent = CreateEvent(NULL, FALSE, FALSE, NULL); - if (ovlp.hEvent == nullptr) - { - PrintError("ERROR: Failed to create event (error code: %u)\n", GetLastError()); - return 0; - } - - DWORD rbcnt = 0; - DWORD status = ERROR_SUCCESS; - UINT64 size = 0; - - if (!DeviceIoControl(hFile, - IOCTL_DISK_GET_PARTITION_INFO_EX, - NULL, - 0, - &pinf, - sizeof(pinf), - &rbcnt, - &ovlp) - ) - { - status = GetLastError(); - if (status == ERROR_IO_PENDING) - { - if (WAIT_OBJECT_0 != WaitForSingleObject(ovlp.hEvent, INFINITE)) - { - PrintError("ERROR: Failed while waiting for event to be signaled (error code: %u)\n", GetLastError()); - } - else - { - size = pinf.PartitionLength.QuadPart; - } - } - else - { - size = GetDynamicPartitionSize(hFile); - } - } - else - { - size = pinf.PartitionLength.QuadPart; - } - - CloseHandle(ovlp.hEvent); - - return size; -} - -/*****************************************************************************/ -// gets physical drive size, return zero on failure -// -UINT64 GetPhysicalDriveSize(HANDLE hFile) -{ - assert(NULL != hFile && INVALID_HANDLE_VALUE != hFile); - - DISK_GEOMETRY_EX geom; - OVERLAPPED ovlp = {}; - - ovlp.hEvent = CreateEvent(NULL, FALSE, FALSE, NULL); - if (ovlp.hEvent == nullptr) - { - PrintError("ERROR: Failed to create event (error code: %u)\n", GetLastError()); - return 0; - } - - DWORD rbcnt = 0; - DWORD status = ERROR_SUCCESS; - BOOL rslt; - - rslt = DeviceIoControl(hFile, - IOCTL_DISK_GET_DRIVE_GEOMETRY_EX, - NULL, - 0, - &geom, - sizeof(geom), - &rbcnt, - &ovlp); - - if (!rslt) - { - status = GetLastError(); - if (status == ERROR_IO_PENDING) - { - if (WAIT_OBJECT_0 != WaitForSingleObject(ovlp.hEvent, INFINITE)) - { - PrintError("ERROR: Failed while waiting for event to be signaled (error code: %u)\n", GetLastError()); - } - else - { - rslt = TRUE; - } - } - else - { - PrintError("ERROR: Could not obtain drive geometry (error code: %u)\n", status); - } - } - - CloseHandle(ovlp.hEvent); - - if (!rslt) - { - return 0; - } - - return (UINT64)geom.DiskSize.QuadPart; -} - -/*****************************************************************************/ -// activates specified privilege in process token -// -bool SetPrivilege(LPCSTR pszPrivilege, LPCSTR pszErrorPrefix = "ERROR:") -{ - TOKEN_PRIVILEGES TokenPriv; - HANDLE hToken = INVALID_HANDLE_VALUE; - DWORD dwError; - bool fOk = true; - - if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken)) - { - PrintError("%s Error opening process token (error code: %u)\n", pszErrorPrefix, GetLastError()); - fOk = false; - goto cleanup; - } - - TokenPriv.PrivilegeCount = 1; - TokenPriv.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; - if (!LookupPrivilegeValue(nullptr, pszPrivilege, &TokenPriv.Privileges[0].Luid)) - { - PrintError("%s Error looking up privilege value %s (error code: %u)\n", pszErrorPrefix, pszPrivilege, GetLastError()); - fOk = false; - goto cleanup; - } - - if (!AdjustTokenPrivileges(hToken, FALSE, &TokenPriv, 0, nullptr, nullptr)) - { - PrintError("%s Error adjusting token privileges for %s (error code: %u)\n", pszErrorPrefix, pszPrivilege, GetLastError()); - fOk = false; - goto cleanup; - } - - if (ERROR_SUCCESS != (dwError = GetLastError())) - { - PrintError("%s Error adjusting token privileges for %s (error code: %u)\n", pszErrorPrefix, pszPrivilege, dwError); - fOk = false; - goto cleanup; - } - -cleanup: - if (hToken != INVALID_HANDLE_VALUE) - { - CloseHandle(hToken); - } - - return fOk; -} - -BOOL -DisableLocalCache( - HANDLE h -) -/*++ -Routine Description: - - Disables local caching of I/O to a file by SMB. All reads/writes will flow to the server. - -Arguments: - - h - Handle to the file - -Return Value: - - Returns ERROR_SUCCESS (0) on success, nonzero error code on failure. - ---*/ -{ - DWORD BytesReturned = 0; - OVERLAPPED Overlapped = { 0 }; - DWORD Status = ERROR_SUCCESS; - BOOL Success = false; - - Overlapped.hEvent = CreateEvent(nullptr, true, false, nullptr); - if (!Overlapped.hEvent) - { - return GetLastError(); - } - -#ifndef FSCTL_DISABLE_LOCAL_BUFFERING -#define FSCTL_DISABLE_LOCAL_BUFFERING CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 174, METHOD_BUFFERED, FILE_ANY_ACCESS) -#endif - - Success = DeviceIoControl(h, - FSCTL_DISABLE_LOCAL_BUFFERING, - nullptr, - 0, - nullptr, - 0, - nullptr, - &Overlapped); - - if (!Success) { - Status = GetLastError(); - } - - if (!Success && Status == ERROR_IO_PENDING) - { - if (!GetOverlappedResult(h, &Overlapped, &BytesReturned, true)) - { - Status = GetLastError(); - } - else - { - Status = (DWORD) Overlapped.Internal; - } - } - - if (Overlapped.hEvent) - { - CloseHandle(Overlapped.hEvent); - } - - return Status; -} - -/*****************************************************************************/ -// structures and global variables -// -struct ETWEventCounters g_EtwEventCounters; - -__declspec(align(4)) static LONG volatile g_lRunningThreadsCount = 0; //must be aligned on a 32-bit boundary, otherwise InterlockedIncrement - //and InterlockedDecrement will fail on 64-bit systems - -static BOOL volatile g_bRun; //used for letting threads know that they should stop working - -typedef NTSTATUS (__stdcall *NtQuerySysInfo)(SYSTEM_INFORMATION_CLASS, PVOID, ULONG, PULONG); -static NtQuerySysInfo g_pfnNtQuerySysInfo; - -typedef VOID (__stdcall *RtlCopyMemNonTemporal)(VOID UNALIGNED *, VOID UNALIGNED *, SIZE_T); -static RtlCopyMemNonTemporal g_pfnRtlCopyMemoryNonTemporal; - -typedef NTSTATUS (__stdcall *RtlFlushNvMemory)(PVOID, PVOID, SIZE_T, ULONG); -static RtlFlushNvMemory g_pfnRtlFlushNonVolatileMemory; - -typedef NTSTATUS(__stdcall *RtlGetNvToken)(PVOID, SIZE_T, PVOID *); -static RtlGetNvToken g_pfnRtlGetNonVolatileToken; - -typedef NTSTATUS(__stdcall *RtlFreeNvToken)(PVOID); -static RtlFreeNvToken g_pfnRtlFreeNonVolatileToken; - -static BOOL volatile g_bThreadError = FALSE; //true means that an error has occured in one of the threads -BOOL volatile g_bTracing = TRUE; //true means that ETW is turned on - -// TODO: is this still needed? -__declspec(align(4)) static LONG volatile g_lGeneratorRunning = 0; //used to detect if GenerateRequests is already running - -static BOOL volatile g_bError = FALSE; //true means there was fatal error during intialization and threads shouldn't perform their work - -VOID SetProcGroupMask(WORD wGroupNum, DWORD dwProcNum, PGROUP_AFFINITY pGroupAffinity) -{ - //must zero this structure first, otherwise it fails to set affinity - memset(pGroupAffinity, 0, sizeof(GROUP_AFFINITY)); - - pGroupAffinity->Group = wGroupNum; - pGroupAffinity->Mask = (KAFFINITY)1<& vhFiles) const -{ - for (size_t x = 0; x < vhFiles.size(); ++x) - { - if ((INVALID_HANDLE_VALUE != vhFiles[x]) && (nullptr != vhFiles[x])) - { - if (!CloseHandle(vhFiles[x])) - { - PrintError("Warning: unable to close file handle (error code: %u)\n", GetLastError()); - } - vhFiles[x] = nullptr; - } - } -} - -/*****************************************************************************/ -// wrapper for stderr -void PrintError(const char *format, ...) -{ - assert(NULL != format); - - va_list listArg; - va_start(listArg, format); - vfprintf(stderr, format, listArg); - va_end(listArg); -} - -/*****************************************************************************/ -// prints the string only if verbose mode is set to true -// -static void PrintVerbose(bool fVerbose, const char *format, ...) -{ - assert(NULL != format); - - if(fVerbose ) - { - va_list argList; - va_start(argList, format); - vprintf(format, argList); - va_end(argList); - } -} - -/*****************************************************************************/ -// thread for gathering ETW data (etw functions are defined in etw.cpp) -// -DWORD WINAPI etwThreadFunc(LPVOID cookie) -{ - UNREFERENCED_PARAMETER(cookie); - - g_bTracing = TRUE; - BOOL result = TraceEvents(); - g_bTracing = FALSE; - - return result ? 0 : 1; -} - -/*****************************************************************************/ -bool IORequestGenerator::_LoadDLLs() -{ - _hNTDLL = LoadLibraryExW(L"ntdll.dll", nullptr, 0); - if( nullptr == _hNTDLL ) - { - return false; - } - - g_pfnNtQuerySysInfo = (NtQuerySysInfo)GetProcAddress(_hNTDLL, "NtQuerySystemInformation"); - if( nullptr == g_pfnNtQuerySysInfo ) - { - return false; - } - - g_pfnRtlCopyMemoryNonTemporal = (RtlCopyMemNonTemporal)GetProcAddress(_hNTDLL, "RtlCopyMemoryNonTemporal"); - g_pfnRtlFlushNonVolatileMemory = (RtlFlushNvMemory)GetProcAddress(_hNTDLL, "RtlFlushNonVolatileMemory"); - g_pfnRtlGetNonVolatileToken = (RtlGetNvToken)GetProcAddress(_hNTDLL, "RtlGetNonVolatileToken"); - g_pfnRtlFreeNonVolatileToken = (RtlFreeNvToken)GetProcAddress(_hNTDLL, "RtlFreeNonVolatileToken"); - - return true; -} - -/*****************************************************************************/ -bool IORequestGenerator::_GetSystemPerfInfo(SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION *pInfo, UINT32 uCpuCount) const -{ - NTSTATUS Status = NO_ERROR; - UINT32 uCpuCtr; - WORD wActiveGroupCtr; - BYTE bActiveProc; - HANDLE hThread = GetCurrentThread(); - GROUP_AFFINITY GroupAffinity; - PROCESSOR_NUMBER procNumber; - bool fOk = true; - - assert(NULL != pInfo); - assert(uCpuCount > 0); - - for (uCpuCtr=0,wActiveGroupCtr=0; wActiveGroupCtr < g_SystemInformation.processorTopology._vProcessorGroupInformation.size(); wActiveGroupCtr++) - { - ProcessorGroupInformation *pGroup = &g_SystemInformation.processorTopology._vProcessorGroupInformation[wActiveGroupCtr]; - - if (pGroup->_activeProcessorCount != 0) { - - // - // Affinitize to the group we're querying counters from - // - - GetCurrentProcessorNumberEx(&procNumber); - - if (procNumber.Group != wActiveGroupCtr) - { - for (bActiveProc = 0; bActiveProc < pGroup->_maximumProcessorCount; bActiveProc++) - { - if (pGroup->IsProcessorActive(bActiveProc)) - { - SetProcGroupMask(wActiveGroupCtr, bActiveProc, &GroupAffinity); - break; - } - } - - if (bActiveProc == pGroup->_maximumProcessorCount || - SetThreadGroupAffinity(hThread, &GroupAffinity, nullptr) == FALSE) - { - fOk = false; - break; - } - } - - Status = g_pfnNtQuerySysInfo(SystemProcessorPerformanceInformation, - (PVOID)(pInfo + uCpuCtr), - (sizeof(*pInfo) * uCpuCount) - (uCpuCtr * sizeof(SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION)), - NULL); - - if (!NT_SUCCESS(Status)) - { - fOk = false; - break; - } - } - - uCpuCtr += pGroup->_maximumProcessorCount; - } - - return fOk; -} - -VOID CALLBACK fileIOCompletionRoutine(DWORD dwErrorCode, DWORD dwBytesTransferred, LPOVERLAPPED pOverlapped); - -static bool issueNextIO(ThreadParameters *p, IORequest *pIORequest, DWORD *pdwBytesTransferred, bool useCompletionRoutines) -{ - OVERLAPPED *pOverlapped = pIORequest->GetOverlapped(); - Target *pTarget = pIORequest->GetCurrentTarget(); - size_t iTarget = pTarget - &p->vTargets[0]; - UINT32 iRequest = pIORequest->GetRequestIndex(); - LARGE_INTEGER li; - BOOL rslt = true; - - // - // Compute next IO - // - - p->vTargetStates[iTarget].NextIORequest(*pIORequest); - - li.LowPart = pIORequest->GetOverlapped()->Offset; - li.HighPart = pIORequest->GetOverlapped()->OffsetHigh; - - if (TraceLoggingProviderEnabled(g_hEtwProvider, - TRACE_LEVEL_VERBOSE, - DISKSPD_TRACE_IO)) - { - GUID ActivityId = p->NextActivityId(); - pIORequest->SetActivityId(ActivityId); - - TraceLoggingWriteActivity(g_hEtwProvider, - "DiskSpd IO", - &ActivityId, - NULL, - TraceLoggingKeyword(DISKSPD_TRACE_IO), - TraceLoggingOpcode(EVENT_TRACE_TYPE_START), - TraceLoggingLevel(TRACE_LEVEL_VERBOSE), - TraceLoggingUInt32(p->ulThreadNo, "Thread"), - TraceLoggingString(pIORequest->GetIoType() == IOOperation::ReadIO ? "Read" : "Write", "IO Type"), - TraceLoggingUInt64(iTarget, "Target"), - TraceLoggingInt32(pTarget->GetBlockSizeInBytes(), "Block Size"), - TraceLoggingInt64(li.QuadPart, "Offset")); - } - -#if 0 - PrintError("t[%u:%u] issuing %u %s @ %I64u)\n", p->ulThreadNo, iTarget, - pTarget->GetBlockSizeInBytes(), - (pIORequest->GetIoType() == IOOperation::ReadIO ? "read" : "write"), - li.QuadPart); -#endif - - if (p->pTimeSpan->GetMeasureLatency()) - { - pIORequest->SetStartTime(PerfTimer::GetTime()); - } - - if (pIORequest->GetIoType() == IOOperation::ReadIO) - { - if (pTarget->GetMemoryMappedIoMode() == MemoryMappedIoMode::On) - { - if (pTarget->GetWriteThroughMode() == WriteThroughMode::On ) - { - g_pfnRtlCopyMemoryNonTemporal(p->GetReadBuffer(iTarget, iRequest), pTarget->GetMappedView() + li.QuadPart, pTarget->GetBlockSizeInBytes()); - } - else - { - memcpy(p->GetReadBuffer(iTarget, iRequest), pTarget->GetMappedView() + li.QuadPart, pTarget->GetBlockSizeInBytes()); - } - *pdwBytesTransferred = pTarget->GetBlockSizeInBytes(); - } - else - { - if (useCompletionRoutines) - { - rslt = ReadFileEx(p->vhTargets[iTarget], p->GetReadBuffer(iTarget, iRequest), pTarget->GetBlockSizeInBytes(), pOverlapped, fileIOCompletionRoutine); - } - else - { - rslt = ReadFile(p->vhTargets[iTarget], p->GetReadBuffer(iTarget, iRequest), pTarget->GetBlockSizeInBytes(), pdwBytesTransferred, pOverlapped); - } - } - } - else - { - if (pTarget->GetMemoryMappedIoMode() == MemoryMappedIoMode::On) - { - if (pTarget->GetWriteThroughMode() == WriteThroughMode::On) - { - g_pfnRtlCopyMemoryNonTemporal(pTarget->GetMappedView() + li.QuadPart, p->GetWriteBuffer(iTarget, iRequest), pTarget->GetBlockSizeInBytes()); - } - else - { - memcpy(pTarget->GetMappedView() + li.QuadPart, p->GetWriteBuffer(iTarget, iRequest), pTarget->GetBlockSizeInBytes()); - - switch (pTarget->GetMemoryMappedIoFlushMode()) - { - case MemoryMappedIoFlushMode::ViewOfFile: - FlushViewOfFile(pTarget->GetMappedView() + li.QuadPart, pTarget->GetBlockSizeInBytes()); - break; - case MemoryMappedIoFlushMode::NonVolatileMemory: - g_pfnRtlFlushNonVolatileMemory(pTarget->GetMemoryMappedIoNvToken(), pTarget->GetMappedView() + li.QuadPart, pTarget->GetBlockSizeInBytes(), 0); - break; - case MemoryMappedIoFlushMode::NonVolatileMemoryNoDrain: - g_pfnRtlFlushNonVolatileMemory(pTarget->GetMemoryMappedIoNvToken(), pTarget->GetMappedView() + li.QuadPart, pTarget->GetBlockSizeInBytes(), FLUSH_NV_MEMORY_IN_FLAG_NO_DRAIN); - break; - } - } - *pdwBytesTransferred = pTarget->GetBlockSizeInBytes(); - } - else - { - if (useCompletionRoutines) - { - rslt = WriteFileEx(p->vhTargets[iTarget], p->GetWriteBuffer(iTarget, iRequest), pTarget->GetBlockSizeInBytes(), pOverlapped, fileIOCompletionRoutine); - } - else - { - rslt = WriteFile(p->vhTargets[iTarget], p->GetWriteBuffer(iTarget, iRequest), pTarget->GetBlockSizeInBytes(), pdwBytesTransferred, pOverlapped); - } - } - } - - if (p->vThroughputMeters.size() != 0 && p->vThroughputMeters[iTarget].IsRunning()) - { - p->vThroughputMeters[iTarget].Adjust(pTarget->GetBlockSizeInBytes()); - } - - return (rslt) ? true : false; -} - -static void completeIO(ThreadParameters *p, IORequest *pIORequest, DWORD dwBytesTransferred) -{ - Target *pTarget = pIORequest->GetCurrentTarget(); - size_t iTarget = pTarget - &p->vTargets[0]; - - if (TraceLoggingProviderEnabled(g_hEtwProvider, - TRACE_LEVEL_VERBOSE, - DISKSPD_TRACE_IO)) - { - GUID ActivityId = pIORequest->GetActivityId(); - - TraceLoggingWriteActivity(g_hEtwProvider, - "DiskSpd IO", - &ActivityId, - NULL, - TraceLoggingKeyword(DISKSPD_TRACE_IO), - TraceLoggingOpcode(EVENT_TRACE_TYPE_STOP), - TraceLoggingLevel(TRACE_LEVEL_VERBOSE)); - } - - //check if I/O transferred all of the requested bytes - if (dwBytesTransferred != pTarget->GetBlockSizeInBytes()) - { - PrintError("Warning: thread %u transferred %u bytes instead of %u bytes\n", - p->ulThreadNo, - dwBytesTransferred, - pTarget->GetBlockSizeInBytes()); - } - - if (*p->pfAccountingOn) - { - p->pResults->vTargetResults[iTarget].Add(dwBytesTransferred, - pIORequest->GetIoType(), - pIORequest->GetStartTime(), - *(p->pullStartTime), - p->pTimeSpan->GetMeasureLatency(), - p->pTimeSpan->GetCalculateIopsStdDev()); - } - - // check if we should print a progress dot - if (p->pProfile->GetProgress() != 0) - { - DWORD dwIOCnt = ++p->dwIOCnt; - if (dwIOCnt % p->pProfile->GetProgress() == 0) - { - printf("."); - } - } -} - -/*****************************************************************************/ -// function called from worker thread -// performs synch I/O -// -static bool doWorkUsingSynchronousIO(ThreadParameters *p) -{ - bool fOk = true; - BOOL rslt = FALSE; - DWORD dwBytesTransferred; - size_t cIORequests = p->vIORequest.size(); - - while(g_bRun && !g_bThreadError) - { - DWORD dwMinSleepTime = ~((DWORD)0); - for (size_t i = 0; i < cIORequests; i++) - { - IORequest *pIORequest = &p->vIORequest[i]; - Target *pTarget = pIORequest->GetNextTarget(); - - if (p->vThroughputMeters.size() != 0) - { - size_t iTarget = pTarget - &p->vTargets[0]; - ThroughputMeter *pThroughputMeter = &p->vThroughputMeters[iTarget]; - - DWORD dwSleepTime = pThroughputMeter->GetSleepTime(); - dwMinSleepTime = min(dwMinSleepTime, dwSleepTime); - if (pThroughputMeter->IsRunning() && dwSleepTime > 0) - { - continue; - } - } - - rslt = issueNextIO(p, pIORequest, &dwBytesTransferred, false); - - if (!rslt) - { - PrintError("t[%u] error during %s error code: %u)\n", (UINT32)i, (pIORequest->GetIoType() == IOOperation::ReadIO ? "read" : "write"), GetLastError()); - fOk = false; - goto cleanup; - } - - completeIO(p, pIORequest, dwBytesTransferred); - } - - // if no IOs were issued, wait for the next scheduling time - if (dwMinSleepTime != ~((DWORD)0) && dwMinSleepTime != 0) - { - Sleep(dwMinSleepTime); - } - - assert(!g_bError); // at this point we shouldn't be seeing initialization error - } - -cleanup: - return fOk; -} - -/*****************************************************************************/ -// function called from worker thread -// performs asynch I/O using IO Completion Ports -// -static bool doWorkUsingIOCompletionPorts(ThreadParameters *p, HANDLE hCompletionPort) -{ - assert(nullptr!= p); - assert(nullptr != hCompletionPort); - - bool fOk = true; - BOOL rslt = FALSE; - OVERLAPPED * pCompletedOvrp; - ULONG_PTR ulCompletionKey; - DWORD dwBytesTransferred; - OverlappedQueue overlappedQueue; - size_t cIORequests = p->vIORequest.size(); - - //start IO operations - for (size_t i = 0; i < cIORequests; i++) - { - overlappedQueue.Add(p->vIORequest[i].GetOverlapped()); - } - - // - // perform work - // - while(g_bRun && !g_bThreadError) - { - DWORD dwMinSleepTime = ~((DWORD)0); - for (size_t i = 0; i < overlappedQueue.GetCount(); i++) - { - OVERLAPPED *pReadyOverlapped = overlappedQueue.Remove(); - IORequest *pIORequest = IORequest::OverlappedToIORequest(pReadyOverlapped); - Target *pTarget = pIORequest->GetNextTarget(); - - if (p->vThroughputMeters.size() != 0) - { - size_t iTarget = pTarget - &p->vTargets[0]; - ThroughputMeter *pThroughputMeter = &p->vThroughputMeters[iTarget]; - - DWORD dwSleepTime = pThroughputMeter->GetSleepTime(); - if (pThroughputMeter->IsRunning() && dwSleepTime > 0) - { - dwMinSleepTime = min(dwMinSleepTime, dwSleepTime); - overlappedQueue.Add(pReadyOverlapped); - continue; - } - } - - rslt = issueNextIO(p, pIORequest, &dwBytesTransferred, false); - - if (!rslt && GetLastError() != ERROR_IO_PENDING) - { - UINT32 iIORequest = (UINT32)(pIORequest - &p->vIORequest[0]); - PrintError("t[%u] error during %s error code: %u)\n", iIORequest, (pIORequest->GetIoType()== IOOperation::ReadIO ? "read" : "write"), GetLastError()); - fOk = false; - goto cleanup; - } - - if (rslt && pTarget->GetMemoryMappedIoMode() == MemoryMappedIoMode::On) - { - completeIO(p, pIORequest, dwBytesTransferred); - overlappedQueue.Add(pReadyOverlapped); - } - } - - // if no IOs are in flight, wait for the next scheduling time - if ((overlappedQueue.GetCount() == p->vIORequest.size()) && dwMinSleepTime != ~((DWORD)0)) - { - Sleep(dwMinSleepTime); - } - - // wait till one of the IO operations finishes - if (GetQueuedCompletionStatus(hCompletionPort, &dwBytesTransferred, &ulCompletionKey, &pCompletedOvrp, 1) != 0) - { - //find which I/O operation it was (so we know to which buffer should we use) - IORequest *pIORequest = IORequest::OverlappedToIORequest(pCompletedOvrp); - completeIO(p, pIORequest, dwBytesTransferred); - overlappedQueue.Add(pCompletedOvrp); - } - else - { - DWORD err = GetLastError(); - if (err != WAIT_TIMEOUT) - { - PrintError("error during overlapped IO operation (error code: %u)\n", err); - fOk = false; - goto cleanup; - } - } - } // end work loop - -cleanup: - return fOk; -} - -/*****************************************************************************/ -// I/O completion routine. used by ReadFileEx and WriteFileEx -// - -VOID CALLBACK fileIOCompletionRoutine(DWORD dwErrorCode, DWORD dwBytesTransferred, LPOVERLAPPED pOverlapped) -{ - assert(NULL != pOverlapped); - - BOOL rslt = FALSE; - - ThreadParameters *p = (ThreadParameters *)pOverlapped->hEvent; - - assert(NULL != p); - - //check error code - if (0 != dwErrorCode) - { - PrintError("Thread %u failed executing an I/O operation (error code: %u)\n", p->ulThreadNo, dwErrorCode); - goto cleanup; - } - - IORequest *pIORequest = IORequest::OverlappedToIORequest(pOverlapped); - - completeIO(p, pIORequest, dwBytesTransferred); - - // start a new IO operation - if (g_bRun && !g_bThreadError) - { - Target *pTarget = pIORequest->GetNextTarget(); - size_t iTarget = pTarget - &p->vTargets[0]; - - rslt = issueNextIO(p, pIORequest, NULL, true); - - if (!rslt) - { - PrintError("t[%u:%u] error during %s error code: %u)\n", p->ulThreadNo, iTarget, (pIORequest->GetIoType() == IOOperation::ReadIO ? "read" : "write"), GetLastError()); - goto cleanup; - } - } - -cleanup: - return; -} - -/*****************************************************************************/ -// function called from worker thread -// performs asynch I/O using IO Completion Routines (ReadFileEx, WriteFileEx) -// -static bool doWorkUsingCompletionRoutines(ThreadParameters *p) -{ - assert(NULL != p); - bool fOk = true; - BOOL rslt = FALSE; - - //start IO operations - UINT32 cIORequests = (UINT32)p->vIORequest.size(); - - for (size_t iIORequest = 0; iIORequest < cIORequests; iIORequest++) { - IORequest *pIORequest = &p->vIORequest[iIORequest]; - Target *pTarget = pIORequest->GetNextTarget(); - size_t iTarget = pTarget - &p->vTargets[0]; - - rslt = issueNextIO(p, pIORequest, NULL, true); - - if (!rslt) - { - PrintError("t[%u:%u] error during %s error code: %u)\n", p->ulThreadNo, iTarget, (pIORequest->GetIoType() == IOOperation::ReadIO ? "read" : "write"), GetLastError()); - fOk = false; - goto cleanup; - } - } - - DWORD dwWaitResult = 0; - while( g_bRun && !g_bThreadError ) - { - dwWaitResult = WaitForSingleObjectEx(p->hEndEvent, INFINITE, TRUE); - - assert(WAIT_IO_COMPLETION == dwWaitResult || (WAIT_OBJECT_0 == dwWaitResult && (!g_bRun || g_bThreadError))); - - //check WaitForSingleObjectEx status - if( WAIT_IO_COMPLETION != dwWaitResult && WAIT_OBJECT_0 != dwWaitResult ) - { - PrintError("Error in thread %u during WaitForSingleObjectEx (in completion routines)\n", p->ulThreadNo); - fOk = false; - goto cleanup; - } - } -cleanup: - return fOk; -} - -struct UniqueTarget { - string path; - TargetCacheMode caching; - PRIORITY_HINT priority; - DWORD dwDesiredAccess; - DWORD dwFlags; - - bool operator < (const struct UniqueTarget &ut) const { - if (path < ut.path) { - return true; - } - else if (ut.path < path) { - return false; - } - - if (caching < ut.caching) { - return true; - } - else if (ut.caching < caching) { - return false; - } - - if (priority < ut.priority) { - return true; - } - else if (ut.priority < priority) { - return false; - } - - if (dwDesiredAccess < ut.dwDesiredAccess) { - return true; - } - else if (ut.dwDesiredAccess < dwDesiredAccess) { - return false; - } - - if (dwFlags < ut.dwFlags) { - return true; - } - - return false; - } -}; - -/*****************************************************************************/ -// worker thread function -// -DWORD WINAPI threadFunc(LPVOID cookie) -{ - bool fOk = true; - bool fAnyMappedIo = false; - bool fAllMappedIo = true; - ThreadParameters *p = reinterpret_cast(cookie); - HANDLE hCompletionPort = nullptr; - - // - // A single file can be specified in multiple targets, so only open one - // handle for each unique file. - // - - vector vhUniqueHandles; - map mHandleMap; - - bool fCalculateIopsStdDev = p->pTimeSpan->GetCalculateIopsStdDev(); - UINT64 ioBucketDuration = 0; - UINT32 expectedNumberOfBuckets = 0; - if(fCalculateIopsStdDev) - { - UINT32 ioBucketDurationInMilliseconds = p->pTimeSpan->GetIoBucketDurationInMilliseconds(); - ioBucketDuration = PerfTimer::MillisecondsToPerfTime(ioBucketDurationInMilliseconds); - expectedNumberOfBuckets = Util::QuotientCeiling(p->pTimeSpan->GetDuration() * 1000, ioBucketDurationInMilliseconds); - } - - // apply affinity. The specific assignment is provided in the thread profile up front. - if (!p->pTimeSpan->GetDisableAffinity()) - { - GROUP_AFFINITY GroupAffinity; - - PrintVerbose(p->pProfile->GetVerbose(), "affinitizing thread %u to Group %u / CPU %u\n", p->ulThreadNo, p->wGroupNum, p->bProcNum); - SetProcGroupMask(p->wGroupNum, p->bProcNum, &GroupAffinity); - - HANDLE hThread = GetCurrentThread(); - if (SetThreadGroupAffinity(hThread, &GroupAffinity, nullptr) == FALSE) - { - PrintError("Error setting affinity mask in thread %u\n", p->ulThreadNo); - fOk = false; - goto cleanup; - } - } - - // adjust thread token if large pages are needed - for (auto pTarget = p->vTargets.begin(); pTarget != p->vTargets.end(); pTarget++) - { - if (pTarget->GetUseLargePages()) - { - if (!SetPrivilege(SE_LOCK_MEMORY_NAME)) - { - fOk = false; - goto cleanup; - } - break; - } - } - - UINT32 cIORequests = p->GetTotalRequestCount(); - - size_t iTarget = 0; - for (auto pTarget = p->vTargets.begin(); pTarget != p->vTargets.end(); pTarget++) - { - bool fPhysical = false; - bool fPartition = false; - - string sPath(pTarget->GetPath()); - const char *filename = sPath.c_str(); - - const char *fname = nullptr; //filename (can point to physFN) - char physFN[32]; //disk/partition name - - if (NULL == filename || NULL == *(filename)) - { - PrintError("FATAL ERROR: invalid filename\n"); - fOk = false; - goto cleanup; - } - - //check if it is a physical drive - if ('#' == *filename && NULL != *(filename + 1)) - { - if (pTarget->GetMemoryMappedIoMode() == MemoryMappedIoMode::On) - { - PrintError("Memory mapped I/O is not supported on physical drives\n"); - fOk = false; - goto cleanup; - } - UINT32 nDriveNo = (UINT32)atoi(filename + 1); - fPhysical = true; - sprintf_s(physFN, 32, "\\\\.\\PhysicalDrive%u", nDriveNo); - fname = physFN; - } - - //check if it is a partition - if (!fPhysical && NULL != *(filename + 1) && NULL == *(filename + 2) && isalpha((unsigned char)filename[0]) && ':' == filename[1]) - { - if (pTarget->GetMemoryMappedIoMode() == MemoryMappedIoMode::On) - { - PrintError("Memory mapped I/O is not supported on partitions\n"); - fOk = false; - goto cleanup; - } - fPartition = true; - - sprintf_s(physFN, 32, "\\\\.\\%c:", filename[0]); - fname = physFN; - } - - //check if it is a regular file - if (!fPhysical && !fPartition) - { - fname = sPath.c_str(); - } - - // get/set file flags - DWORD dwFlags = pTarget->GetCreateFlags(cIORequests > 1); - DWORD dwDesiredAccess = 0; - if (pTarget->GetWriteRatio() == 0) - { - dwDesiredAccess = GENERIC_READ; - } - else if (pTarget->GetWriteRatio() == 100) - { - dwDesiredAccess = GENERIC_WRITE; - } - else - { - dwDesiredAccess = GENERIC_READ | GENERIC_WRITE; - } - - if (pTarget->GetMemoryMappedIoMode() == MemoryMappedIoMode::On) - { - dwDesiredAccess = GENERIC_READ | GENERIC_WRITE; - fAnyMappedIo = true; - } - else - { - fAllMappedIo = false; - } - - HANDLE hFile; - UniqueTarget ut; - ut.path = sPath; - ut.priority = pTarget->GetIOPriorityHint(); - ut.caching = pTarget->GetCacheMode(); - ut.dwDesiredAccess = dwDesiredAccess; - ut.dwFlags = dwFlags; - - if (mHandleMap.find(ut) == mHandleMap.end()) { - hFile = CreateFile(fname, - dwDesiredAccess, - FILE_SHARE_READ | FILE_SHARE_WRITE, - nullptr, //security - OPEN_EXISTING, - dwFlags, //flags - nullptr); //template file - if (INVALID_HANDLE_VALUE == hFile) - { - // TODO: error out - PrintError("Error opening file: %s [%u]\n", sPath.c_str(), GetLastError()); - fOk = false; - goto cleanup; - } - - if (pTarget->GetCacheMode() == TargetCacheMode::DisableLocalCache) - { - DWORD Status = DisableLocalCache(hFile); - if (Status != ERROR_SUCCESS) - { - PrintError("Failed to disable local caching (error %u). NOTE: only supported on remote filesystems with Windows 8 or newer.\n", Status); - fOk = false; - goto cleanup; - } - } - - //set IO priority - if (pTarget->GetIOPriorityHint() != IoPriorityHintNormal) - { - _declspec(align(8)) FILE_IO_PRIORITY_HINT_INFO hintInfo; - hintInfo.PriorityHint = pTarget->GetIOPriorityHint(); - if (!SetFileInformationByHandle(hFile, FileIoPriorityHintInfo, &hintInfo, sizeof(hintInfo))) - { - PrintError("Error setting IO priority for file: %s [%u]\n", sPath.c_str(), GetLastError()); - fOk = false; - goto cleanup; - } - } - - mHandleMap[ut] = (UINT32)vhUniqueHandles.size(); - vhUniqueHandles.push_back(hFile); - } - else { - hFile = vhUniqueHandles[mHandleMap[ut]]; - } - - p->vhTargets.push_back(hFile); - - // obtain file/disk/partition size - { - UINT64 fsize = 0; //file size - - //check if it is a disk - if (fPhysical) - { - fsize = GetPhysicalDriveSize(hFile); - } - // check if it is a partition - else if (fPartition) - { - fsize = GetPartitionSize(hFile); - } - // it has to be a regular file - else - { - ULARGE_INTEGER ulsize; - - ulsize.LowPart = GetFileSize(hFile, &ulsize.HighPart); - if (INVALID_FILE_SIZE == ulsize.LowPart && GetLastError() != NO_ERROR) - { - PrintError("Error getting file size\n"); - fOk = false; - goto cleanup; - } - else - { - fsize = ulsize.QuadPart; - } - } - - // check if file size is valid (if it's == 0, it won't be useful) - if (0 == fsize) - { - // TODO: error out - PrintError("ERROR: target size could not be determined\n"); - fOk = false; - goto cleanup; - } - - if (fsize < pTarget->GetMaxFileSize()) - { - PrintError("WARNING: file size %I64u is less than MaxFileSize %I64u\n", fsize, pTarget->GetMaxFileSize()); - } - - // - // Build target state. - // - - p->vTargetStates.emplace_back( - p, - iTarget, - fsize); - - // - // Ensure this thread can start given stride/size of target. - // - - if (!p->vTargetStates[iTarget].CanStart()) - { - PrintError("The file is too small. File: '%s' relative thread %u: file size: %I64u, base offset: %I64u, thread stride: %I64u, block size: %u\n", - pTarget->GetPath().c_str(), - p->ulRelativeThreadNo, - fsize, - pTarget->GetBaseFileOffsetInBytes(), - pTarget->GetThreadStrideInBytes(), - pTarget->GetBlockSizeInBytes()); - fOk = false; - goto cleanup; - } - - PrintVerbose(p->pProfile->GetVerbose(), "thread %u starting: file '%s' relative thread %u", - p->ulThreadNo, - pTarget->GetPath().c_str(), - p->ulRelativeThreadNo); - - if (pTarget->GetRandomRatio() > 0) - { - PrintVerbose(p->pProfile->GetVerbose(), ", %u% random pattern\n", - pTarget->GetRandomRatio()); - } - else - { - PrintVerbose(p->pProfile->GetVerbose(), ", %ssequential file offset\n", pTarget->GetUseInterlockedSequential() ? "interlocked ":""); - } - } - - // allocate memory for a data buffer - if (!p->AllocateAndFillBufferForTarget(*pTarget)) - { - PrintError("ERROR: Could not allocate a buffer for target '%s'. Error code: 0x%x\n", pTarget->GetPath().c_str(), GetLastError()); - fOk = false; - goto cleanup; - } - - // initialize memory mapped views of files - if (pTarget->GetMemoryMappedIoMode() == MemoryMappedIoMode::On) - { - NTSTATUS status; - PVOID nvToken; - - pTarget->SetMappedViewFileHandle(hFile); - if (!p->InitializeMappedViewForTarget(*pTarget, dwDesiredAccess)) - { - PrintError("ERROR: Could not map view for target '%s'. Error code: 0x%x\n", pTarget->GetPath().c_str(), GetLastError()); - fOk = false; - goto cleanup; - } - - if (pTarget->GetWriteThroughMode() == WriteThroughMode::On && nullptr == g_pfnRtlCopyMemoryNonTemporal) - { - PrintError("ERROR: Windows runtime environment does not support the non-temporal memory copy API for target '%s'.\n", pTarget->GetPath().c_str()); - fOk = false; - goto cleanup; - } - - if ((pTarget->GetMemoryMappedIoFlushMode() == MemoryMappedIoFlushMode::NonVolatileMemory) || (pTarget->GetMemoryMappedIoFlushMode() == MemoryMappedIoFlushMode::NonVolatileMemoryNoDrain)) - { - // RtlGetNonVolatileToken() works only on DAX enabled PMEM devices. - if (g_pfnRtlGetNonVolatileToken != nullptr && g_pfnRtlFreeNonVolatileToken != nullptr) - { - status = g_pfnRtlGetNonVolatileToken(pTarget->GetMappedView(), (SIZE_T) pTarget->GetFileSize(), &nvToken); - if (!NT_SUCCESS(status)) - { - PrintError("ERROR: Could not get non-volatile token for target '%s'. Error code: 0x%x\n", pTarget->GetPath().c_str(), GetLastError()); - fOk = false; - goto cleanup; - } - pTarget->SetMemoryMappedIoNvToken(nvToken); - } - else - { - PrintError("ERROR: Windows runtime environment does not support the non-volatile memory flushing APIs for target '%s'.\n", pTarget->GetPath().c_str()); - fOk = false; - goto cleanup; - } - } - } - - iTarget++; - } - - // TODO: copy parameters for better memory locality? - // TODO: tell the main thread we're ready - - PrintVerbose(p->pProfile->GetVerbose(), "thread %u started (random seed: %u)\n", p->ulThreadNo, p->ulRandSeed); - - p->pResults->vTargetResults.clear(); - p->pResults->vTargetResults.resize(p->vTargets.size()); - - for (size_t i = 0; i < p->vTargets.size(); i++) - { - p->pResults->vTargetResults[i].sPath = p->vTargets[i].GetPath(); - p->pResults->vTargetResults[i].ullFileSize = p->vTargetStates[i].TargetSize(); - - if(fCalculateIopsStdDev) - { - p->pResults->vTargetResults[i].readBucketizer.Initialize(ioBucketDuration, expectedNumberOfBuckets); - p->pResults->vTargetResults[i].writeBucketizer.Initialize(ioBucketDuration, expectedNumberOfBuckets); - } - - // - // Copy effective distribution range to results for reporting (may be empty) - // - - p->pResults->vTargetResults[i].vDistributionRange = p->vTargetStates[i]._vDistributionRange; - } - - // - // fill the IORequest structures - // - - p->vIORequest.clear(); - - if (p->pTimeSpan->GetThreadCount() != 0 && - p->pTimeSpan->GetRequestCount() != 0) - { - p->vIORequest.resize(cIORequests, IORequest(p->pRand)); - - for (UINT32 iIORequest = 0; iIORequest < cIORequests; iIORequest++) - { - p->vIORequest[iIORequest].SetRequestIndex(iIORequest); - - for (unsigned int iFile = 0; iFile < p->vTargets.size(); iFile++) - { - Target *pTarget = &p->vTargets[iFile]; - const vector vThreadTargets = pTarget->GetThreadTargets(); - UINT32 ulWeight = pTarget->GetWeight(); - - for (UINT32 iThreadTarget = 0; iThreadTarget < vThreadTargets.size(); iThreadTarget++) - { - if (vThreadTargets[iThreadTarget].GetThread() == p->ulRelativeThreadNo) - { - if (vThreadTargets[iThreadTarget].GetWeight() != 0) - { - ulWeight = vThreadTargets[iThreadTarget].GetWeight(); - } - break; - } - } - - // - // Parallel async is not supported with -O for exactly this reason, - // and is validated in the profile before reaching here. Document this - // with the assert in comparison to the code in the non-O case below. - // Parallel depends on the IORequest being for a single file only (the - // seq offset is in the IORequest itself). - // - - assert(pTarget->GetUseParallelAsyncIO() == false); - - p->vIORequest[iIORequest].AddTarget(pTarget, ulWeight); - } - } - } - else - { - for (unsigned int iFile = 0; iFile < p->vTargets.size(); iFile++) - { - Target *pTarget = &p->vTargets[iFile]; - - for (DWORD iRequest = 0; iRequest < pTarget->GetRequestCount(); ++iRequest) - { - IORequest ioRequest(p->pRand); - ioRequest.AddTarget(pTarget, 1); - ioRequest.SetRequestIndex(iRequest); - if (pTarget->GetUseParallelAsyncIO()) - { - p->vTargetStates[iFile].InitializeParallelAsyncIORequest(ioRequest); - } - - p->vIORequest.push_back(ioRequest); - } - } - } - - // - // fill the throughput meter structures - // - size_t cTargets = p->vTargets.size(); - bool fUseThrougputMeter = false; - for (size_t i = 0; i < cTargets; i++) - { - ThroughputMeter throughputMeter; - Target *pTarget = &p->vTargets[i]; - DWORD dwBurstSize = pTarget->GetBurstSize(); - if (p->pTimeSpan->GetThreadCount() > 0) - { - if (pTarget->GetThreadTargets().size() == 0) - { - dwBurstSize /= p->pTimeSpan->GetThreadCount(); - } - else - { - dwBurstSize /= (DWORD)pTarget->GetThreadTargets().size(); - } - } - else - { - dwBurstSize /= pTarget->GetThreadsPerFile(); - } - - if (pTarget->GetThroughputInBytesPerMillisecond() > 0 || pTarget->GetThinkTime() > 0) - { - fUseThrougputMeter = true; - throughputMeter.Start(pTarget->GetThroughputInBytesPerMillisecond(), pTarget->GetBlockSizeInBytes(), pTarget->GetThinkTime(), dwBurstSize); - } - - p->vThroughputMeters.push_back(throughputMeter); - } - - if (!fUseThrougputMeter) - { - p->vThroughputMeters.clear(); - } - - //FUTURE EXTENSION: enable asynchronous I/O even if only 1 outstanding I/O per file (requires another parameter) - if (cIORequests == 1 || fAllMappedIo) - { - //synchronous IO - no setup needed - } - else if (p->pTimeSpan->GetCompletionRoutines() && !fAnyMappedIo) - { - //in case of completion routines hEvent field is not used, - //so we can use it to pass a pointer to the thread parameters - for (UINT32 iIORequest = 0; iIORequest < cIORequests; iIORequest++) { - OVERLAPPED *pOverlapped; - - pOverlapped = p->vIORequest[iIORequest].GetOverlapped(); - pOverlapped->hEvent = (HANDLE)p; - } - } - else - { - // - // create IO completion port if not doing completion routines or synchronous IO - // - for (unsigned int i = 0; i < vhUniqueHandles.size(); i++) - { - hCompletionPort = CreateIoCompletionPort(vhUniqueHandles[i], hCompletionPort, 0, 1); - if (nullptr == hCompletionPort) - { - PrintError("unable to create IO completion port (error code: %u)\n", GetLastError()); - fOk = false; - goto cleanup; - } - } - } - - // - // wait for a signal to start - // - PrintVerbose(p->pProfile->GetVerbose(), "thread %u: waiting for a signal to start\n", p->ulThreadNo); - if( WAIT_FAILED == WaitForSingleObject(p->hStartEvent, INFINITE) ) - { - PrintError("Waiting for a signal to start failed (error code: %u)\n", GetLastError()); - fOk = false; - goto cleanup; - } - PrintVerbose(p->pProfile->GetVerbose(), "thread %u: received signal to start\n", p->ulThreadNo); - - //check if everything is ok - if (g_bError) - { - fOk = false; - goto cleanup; - } - - //error handling and memory freeing is done in doWorkUsingIOCompletionPorts and doWorkUsingCompletionRoutines - if (cIORequests == 1 || fAllMappedIo) - { - // use synchronous IO (it will also clse the event) - if (!doWorkUsingSynchronousIO(p)) - { - fOk = false; - goto cleanup; - } - } - else if (!p->pTimeSpan->GetCompletionRoutines() || fAnyMappedIo) - { - // use IO Completion Ports (it will also close the I/O completion port) - if (!doWorkUsingIOCompletionPorts(p, hCompletionPort)) - { - fOk = false; - goto cleanup; - } - } - else - { - //use completion routines - if (!doWorkUsingCompletionRoutines(p)) - { - fOk = false; - goto cleanup; - } - } - - assert(!g_bError); // at this point we shouldn't be seeing initialization error - - // save results - -cleanup: - if (!fOk) - { - g_bThreadError = TRUE; - } - - // free memory allocated with VirtualAlloc - for (auto i = p->vpDataBuffers.begin(); i != p->vpDataBuffers.end(); i++) - { - if (nullptr != *i) - { -#pragma prefast(suppress:6001, "Prefast does not understand this vector will only contain validly allocated buffer pointers") - VirtualFree(*i, 0, MEM_RELEASE); - } - } - - // free NV tokens - for (auto i = p->vTargets.begin(); i != p->vTargets.end(); i++) - { - if (i->GetMemoryMappedIoNvToken() != nullptr && g_pfnRtlFreeNonVolatileToken != nullptr) - { - g_pfnRtlFreeNonVolatileToken(i->GetMemoryMappedIoNvToken()); - i->SetMemoryMappedIoNvToken(nullptr); - } - } - - // close files - for (auto i = vhUniqueHandles.begin(); i != vhUniqueHandles.end(); i++) - { - CloseHandle(*i); - } - - // close completion ports - if (hCompletionPort != nullptr) - { - CloseHandle(hCompletionPort); - } - - delete p->pRand; - delete p; - - // notify master thread that we've finished - InterlockedDecrement(&g_lRunningThreadsCount); - - return fOk ? 1 : 0; -} - -/*****************************************************************************/ -struct ETWSessionInfo IORequestGenerator::_GetResultETWSession(const EVENT_TRACE_PROPERTIES *pTraceProperties) const -{ - struct ETWSessionInfo session = {}; - if (nullptr != pTraceProperties) - { - session.lAgeLimit = pTraceProperties->AgeLimit; - session.ulBufferSize = pTraceProperties->BufferSize; - session.ulBuffersWritten = pTraceProperties->BuffersWritten; - session.ulEventsLost = pTraceProperties->EventsLost; - session.ulFlushTimer = pTraceProperties->FlushTimer; - session.ulFreeBuffers = pTraceProperties->FreeBuffers; - session.ulLogBuffersLost = pTraceProperties->LogBuffersLost; - session.ulMaximumBuffers = pTraceProperties->MaximumBuffers; - session.ulMinimumBuffers = pTraceProperties->MinimumBuffers; - session.ulNumberOfBuffers = pTraceProperties->NumberOfBuffers; - session.ulRealTimeBuffersLost = pTraceProperties->RealTimeBuffersLost; - } - return session; -} - -DWORD IORequestGenerator::_CreateDirectoryPath(const char *pszPath) const -{ - char *c = nullptr; //variable used to browse the path - char dirPath[MAX_PATH]; //copy of the path (it will be altered) - - //only support absolute paths that specify the drive letter - if (pszPath[0] == '\0' || pszPath[1] != ':') - { - return ERROR_NOT_SUPPORTED; - } - - if (strcpy_s(dirPath, _countof(dirPath), pszPath) != 0) - { - return ERROR_BUFFER_OVERFLOW; - } - - c = dirPath; - while('\0' != *c) - { - if ('\\' == *c) - { - //skip the first one as it will be the drive name - if (c-dirPath >= 3) - { - *c = '\0'; - //create directory if it doesn't exist - if (GetFileAttributes(dirPath) == INVALID_FILE_ATTRIBUTES) - { - if (CreateDirectory(dirPath, NULL) == FALSE) - { - return GetLastError(); - } - } - *c = L'\\'; - } - } - - c++; - } - - return ERROR_SUCCESS; -} - -/*****************************************************************************/ -// create a file of the given size -// -bool IORequestGenerator::_CreateFile(UINT64 ullFileSize, const char *pszFilename, bool fZeroBuffers, bool fVerbose) const -{ - bool fSlowWrites = false; - PrintVerbose(fVerbose, "Creating file '%s' of size %I64u.\n", pszFilename, ullFileSize); - - //enable SE_MANAGE_VOLUME_NAME privilege, required to set valid size of a file - if (!SetPrivilege(SE_MANAGE_VOLUME_NAME, "WARNING:")) - { - PrintError("WARNING: Could not set privileges for setting valid file size; will use a slower method of preparing the file\n", GetLastError()); - fSlowWrites = true; - } - - // there are various forms of paths we do not support creating subdir hierarchies - // for - relative and unc paths specifically. this is fine, and not neccesary to - // warn about. we can add support in the future. - DWORD dwError = _CreateDirectoryPath(pszFilename); - if (dwError != ERROR_SUCCESS && dwError != ERROR_NOT_SUPPORTED) - { - PrintError("WARNING: Could not create intermediate directory (error code: %u)\n", dwError); - } - - // create handle to the file - HANDLE hFile = CreateFile(pszFilename, - GENERIC_READ | GENERIC_WRITE, - FILE_SHARE_READ | FILE_SHARE_WRITE, - nullptr, - CREATE_ALWAYS, - FILE_ATTRIBUTE_NORMAL, - nullptr); - if (INVALID_HANDLE_VALUE == hFile) - { - PrintError("Could not create the file (error code: %u)\n", GetLastError()); - return false; - } - - if (ullFileSize > 0) - { - LARGE_INTEGER li; - li.QuadPart = ullFileSize; - - LARGE_INTEGER liNewFilePointer; - - if (!SetFilePointerEx(hFile, li, &liNewFilePointer, FILE_BEGIN)) - { - PrintError("Could not set file pointer during file creation when extending file (error code: %u)\n", GetLastError()); - CloseHandle(hFile); - return false; - } - if (liNewFilePointer.QuadPart != li.QuadPart) - { - PrintError("File pointer improperly moved during file creation when extending file\n"); - CloseHandle(hFile); - return false; - } - - //extends file (warning! this is a kind of "reservation" of space; valid size of the file is still 0!) - if (!SetEndOfFile(hFile)) - { - PrintError("Error setting end of file (error code: %u)\n", GetLastError()); - CloseHandle(hFile); - return false; - } - //try setting valid size of the file (privileges for that are enabled before CreateFile) - if (!fSlowWrites && !SetFileValidData(hFile, ullFileSize)) - { - PrintError("WARNING: Could not set valid file size (error code: %u); trying a slower method of filling the file" - " (this does not affect performance, just makes the test preparation longer)\n", - GetLastError()); - fSlowWrites = true; - } - - //if setting valid size couldn't be performed, fill in the file by simply writing to it (slower) - if (fSlowWrites) - { - li.QuadPart = 0; - if (!SetFilePointerEx(hFile, li, &liNewFilePointer, FILE_BEGIN)) - { - PrintError("Could not set file pointer during file creation (error code: %u)\n", GetLastError()); - CloseHandle(hFile); - return false; - } - if (liNewFilePointer.QuadPart != li.QuadPart) - { - PrintError("File pointer improperly moved during file creation\n"); - CloseHandle(hFile); - return false; - } - - UINT32 ulBufSize; - UINT64 ullRemainSize; - - ulBufSize = 1024*1024; - if (ullFileSize < (UINT64)ulBufSize) - { - ulBufSize = (UINT32)ullFileSize; - } - - vector vBuf(ulBufSize); - for (UINT32 i=0; i 0) - { - DWORD dwBytesWritten; - if ((UINT64)ulBufSize > ullRemainSize) - { - ulBufSize = (UINT32)ullRemainSize; - } - - if (!WriteFile(hFile, &vBuf[0], ulBufSize, &dwBytesWritten, NULL)) - { - PrintError("Error while writng during file creation (error code: %u)\n", GetLastError()); - CloseHandle(hFile); - return false; - } - - if (dwBytesWritten != ulBufSize) - { - PrintError("Improperly written data during file creation\n"); - CloseHandle(hFile); - return false; - } - - ullRemainSize -= ulBufSize; - } - } - } - - //if compiled with debug support, check file size -#ifndef NDEBUG - LARGE_INTEGER li; - if( GetFileSizeEx(hFile, &li) ) - { - assert(li.QuadPart == (LONGLONG)ullFileSize); - } -#endif - - CloseHandle(hFile); - - return true; -} - -/*****************************************************************************/ -void IORequestGenerator::_TerminateWorkerThreads(vector& vhThreads) const -{ - for (UINT32 x = 0; x < vhThreads.size(); ++x) - { - assert(NULL != vhThreads[x]); -#pragma warning( push ) -#pragma warning( disable : 6258 ) - if (!TerminateThread(vhThreads[x], 0)) - { - PrintError("Warning: unable to terminate worker thread %u\n", x); - } -#pragma warning( pop ) - } -} -/*****************************************************************************/ -void IORequestGenerator::_AbortWorkerThreads(HANDLE hStartEvent, vector& vhThreads) const -{ - assert(NULL != hStartEvent); - - if (NULL == hStartEvent) - { - return; - } - - g_bError = TRUE; - if (!SetEvent(hStartEvent)) - { - PrintError("Error signaling start event\n"); - _TerminateWorkerThreads(vhThreads); - } - else - { - //FUTURE EXTENSION: maximal timeout may be added here (and below) - while (g_lRunningThreadsCount > 0) - { - Sleep(100); - } - } -} - -/*****************************************************************************/ -bool IORequestGenerator::_StopETW(bool fUseETW, TRACEHANDLE hTraceSession) const -{ - bool fOk = true; - if (fUseETW) - { - PEVENT_TRACE_PROPERTIES pETWSession = StopETWSession(hTraceSession); - if (nullptr == pETWSession) - { - PrintError("Error stopping ETW session\n"); - fOk = false; - } - else - { - free(pETWSession); - } - } - return fOk; -} - -/*****************************************************************************/ -// initializes all global parameters -// -void IORequestGenerator::_InitializeGlobalParameters() -{ - g_lRunningThreadsCount = 0; //number of currently running worker threads - g_bRun = TRUE; //used for letting threads know that they should stop working - - g_bThreadError = FALSE; //true means that an error has occured in one of the threads - g_bTracing = FALSE; //true means that ETW is turned on - - _hNTDLL = nullptr; //handle to ntdll.dll - g_bError = FALSE; //true means there was fatal error during intialization and threads shouldn't perform their work -} - -bool IORequestGenerator::_PrecreateFiles(Profile& profile) const -{ - bool fOk = true; - - if (profile.GetPrecreateFiles() != PrecreateFiles::None) - { - vector vFilesToCreate = _GetFilesToPrecreate(profile); - vector vCreatedFiles; - for (auto file : vFilesToCreate) - { - fOk = _CreateFile(file.ullFileSize, file.sPath.c_str(), file.fZeroWriteBuffers, profile.GetVerbose()); - if (!fOk) - { - break; - } - vCreatedFiles.push_back(file.sPath); - } - - if (fOk) - { - profile.MarkFilesAsPrecreated(vCreatedFiles); - } - } - - return fOk; -} - -bool IORequestGenerator::GenerateRequests(Profile& profile, IResultParser& resultParser, struct Synchronization *pSynch) -{ - bool fOk = _PrecreateFiles(profile); - if (fOk) - { - const vector& vTimeSpans = profile.GetTimeSpans(); - vector vResults(vTimeSpans.size()); - for (size_t i = 0; fOk && (i < vTimeSpans.size()); i++) - { - PrintVerbose(profile.GetVerbose(), "Generating requests for timespan %u.\n", i + 1); - fOk = _GenerateRequestsForTimeSpan(profile, vTimeSpans[i], vResults[i], pSynch); - } - - // TODO: show results only for timespans that succeeded - SystemInformation system; - EtwResultParser::ParseResults(vResults); - string sResults = resultParser.ParseResults(profile, system, vResults); - printf("%s", sResults.c_str()); - fflush(stdout); - } - - return fOk; -} - -bool IORequestGenerator::_GenerateRequestsForTimeSpan(const Profile& profile, const TimeSpan& timeSpan, Results& results, struct Synchronization *pSynch) -{ - //FUTURE EXTENSION: add new I/O capabilities presented in Longhorn - //FUTURE EXTENSION: add a check if the folder is compressed (cache is always enabled in case of compressed folders) - - //check if I/O request generator is already running - LONG lGenState = InterlockedExchange(&g_lGeneratorRunning, 1); - if (1 == lGenState) - { - PrintError("FATAL ERROR: I/O Request Generator already running\n"); - return false; - } - - //initialize all global parameters (in case of second run, after the first one is finished) - _InitializeGlobalParameters(); - - HANDLE hStartEvent = nullptr; // start event (used to inform the worker threads that they should start the work) - HANDLE hEndEvent = nullptr; // end event (used only in case of completin routines (not for IO Completion Ports)) - - memset(&g_EtwEventCounters, 0, sizeof(struct ETWEventCounters)); // reset all etw event counters - - bool fUseETW = profile.GetEtwEnabled(); //true if user wants ETW - - // - // load dlls - // - assert(nullptr == _hNTDLL); - if (!_LoadDLLs()) - { - PrintError("Error loading NtQuerySystemInformation\n"); - return false; - } - - //FUTURE EXTENSION: check for conflicts in alignment (when cache is turned off only sector aligned I/O are permitted) - //FUTURE EXTENSION: check if file sizes are enough to have at least first requests not wrapping around - - Random r; - vector vTargets = timeSpan.GetTargets(); - // allocate memory for random data write buffers - for (auto i = vTargets.begin(); i != vTargets.end(); i++) - { - if ((i->GetRandomDataWriteBufferSize() > 0) && !i->AllocateAndFillRandomDataWriteBuffer(&r)) - { - return false; - } - } - - // check if user wanted to create a file - for (auto i = vTargets.begin(); i != vTargets.end(); i++) - { - if ((i->GetFileSize() > 0) && (i->GetPrecreated() == false)) - { - string str = i->GetPath(); - if (str.empty()) - { - PrintError("You have to provide a filename\n"); - return false; - } - - //skip physical drives and partitions - if ('#' == str[0] || (':' == str[1] && '\0' == str[2])) - { - continue; - } - - //create only regular files - if (!_CreateFile(i->GetFileSize(), str.c_str(), i->GetZeroWriteBuffers(), profile.GetVerbose())) - { - return false; - } - } - } - - // get thread count - UINT32 cThreads = timeSpan.GetThreadCount(); - if (cThreads < 1) - { - for (auto i = vTargets.begin(); i != vTargets.end(); i++) - { - cThreads += i->GetThreadsPerFile(); - } - } - - // allocate memory for thread handles - vector vhThreads(cThreads); - - // - // allocate memory for performance counters - // - vector vPerfInit(g_SystemInformation.processorTopology._ulProcCount); - vector vPerfDone(g_SystemInformation.processorTopology._ulProcCount); - vector vPerfDiff(g_SystemInformation.processorTopology._ulProcCount); - - // - //create start event - // - - hStartEvent = CreateEvent(NULL, TRUE, FALSE, ""); - if (NULL == hStartEvent) - { - PrintError("Error creating the start event\n"); - return false; - } - - // - // create end event - // - if (timeSpan.GetCompletionRoutines()) - { - hEndEvent = CreateEvent(NULL, TRUE, FALSE, ""); - if (NULL == hEndEvent) - { - PrintError("Error creating the end event\n"); - return false; - } - } - - // - // set to high priority to ensure the controller thread gets to run immediately - // when signalled. - // - - SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST); - - // - // create the threads - // - - g_bRun = TRUE; - - // gather affinity information, and move to the first active processor - const auto& vAffinity = timeSpan.GetAffinityAssignments(); - WORD wGroupCtr = 0; - BYTE bProcCtr = 0; - g_SystemInformation.processorTopology.GetActiveGroupProcessor(wGroupCtr, bProcCtr, false); - - volatile bool fAccountingOn = false; - UINT64 ullStartTime; //start time - UINT64 ullTimeDiff; //elapsed test time (in units returned by QueryPerformanceCounter) - vector vullSharedSequentialOffsets(vTargets.size(), 0); - - results.vThreadResults.clear(); - results.vThreadResults.resize(cThreads); - for (UINT32 iThread = 0; iThread < cThreads; ++iThread) - { - PrintVerbose(profile.GetVerbose(), "creating thread %u\n", iThread); - ThreadParameters *cookie = new ThreadParameters(); // threadFunc is going to free the memory - if (nullptr == cookie) - { - PrintError("FATAL ERROR: could not allocate memory\n"); - _AbortWorkerThreads(hStartEvent, vhThreads); - return false; - } - - // each thread has a different random seed - Random *pRand = new Random(timeSpan.GetRandSeed() + iThread); - if (nullptr == pRand) - { - PrintError("FATAL ERROR: could not allocate memory\n"); - _AbortWorkerThreads(hStartEvent, vhThreads); - delete cookie; - return false; - } - - UINT32 ulRelativeThreadNo = 0; - - if (timeSpan.GetThreadCount() > 0) - { - // fixed thread mode: threads operate on specified files - // and receive the entire seq index array. - // relative thread number is the same as thread number. - cookie->pullSharedSequentialOffsets = &vullSharedSequentialOffsets[0]; - ulRelativeThreadNo = iThread; - for (auto i = vTargets.begin(); - i != vTargets.end(); - i++) - { - const vector vThreadTargets = i->GetThreadTargets(); - - // no thread targets specified - add to all threads - if (vThreadTargets.size() == 0) - { - cookie->vTargets.push_back(*i); - } - else - { - // check if the target should be added to the current thread - for (UINT32 iThreadTarget = 0; iThreadTarget < vThreadTargets.size(); iThreadTarget++) - { - if (vThreadTargets[iThreadTarget].GetThread() == iThread) - { - // confirm copy constructor? - cookie->vTargets.push_back(*i); - break; - } - } - } - } - } - else - { - size_t cAssignedThreads = 0; - size_t cBaseThread = 0; - auto psi = vullSharedSequentialOffsets.begin(); - for (auto i = vTargets.begin(); - i != vTargets.end(); - i++, psi++) - { - // per-file thread mode: groups of threads operate on individual files - // and receive the specific seq index for their file (note: singular). - // loop up through the targets to assign thread n to the appropriate file. - // relative thread number is file-relative, so keep track of the base - // thread number for the file and calculate relative to that. - // - // ex: two files, two threads per file - // t0: rt0 for f0 (cAssigned = 2, cBase = 0) - // t1: rt1 for f0 (cAssigned = 2, cBase = 0) - // t2: rt0 for f1 (cAssigned = 4, cBase = 2) - // t3: rt1 for f1 (cAssigned = 4, cBase = 2) - - cAssignedThreads += i->GetThreadsPerFile(); - if (iThread < cAssignedThreads) - { - // confirm copy constructor? - cookie->vTargets.push_back(*i); - cookie->pullSharedSequentialOffsets = &(*psi); - ulRelativeThreadNo = (iThread - cBaseThread) % i->GetThreadsPerFile(); - - PrintVerbose(profile.GetVerbose(), "thread %u is relative thread %u for %s\n", iThread, ulRelativeThreadNo, i->GetPath().c_str()); - break; - } - cBaseThread += i->GetThreadsPerFile(); - } - } - - cookie->pProfile = &profile; - cookie->pTimeSpan = &timeSpan; - cookie->hStartEvent = hStartEvent; - cookie->hEndEvent = hEndEvent; - cookie->ulThreadNo = iThread; - cookie->ulRelativeThreadNo = ulRelativeThreadNo; - cookie->pfAccountingOn = &fAccountingOn; - cookie->pullStartTime = &ullStartTime; - cookie->ulRandSeed = timeSpan.GetRandSeed() + iThread; // each thread has a different random seed - cookie->pRand = pRand; - - //Set thread group and proc affinity - - // Default: Round robin cores in order of groups, starting at group 0. - // Fill each group before moving to next. - if (vAffinity.size() == 0) - { - cookie->wGroupNum = wGroupCtr; - cookie->bProcNum = bProcCtr; - - // advance to next active - g_SystemInformation.processorTopology.GetActiveGroupProcessor(wGroupCtr, bProcCtr, true); - } - // Assigned affinity. Round robin through the assignment list. - else - { - ULONG i = iThread % vAffinity.size(); - - cookie->wGroupNum = vAffinity[i].wGroup; - cookie->bProcNum = vAffinity[i].bProc; - } - - //create thread - cookie->pResults = &results.vThreadResults[iThread]; - - InterlockedIncrement(&g_lRunningThreadsCount); - DWORD dwThreadId; - HANDLE hThread = CreateThread(NULL, 64 * 1024, threadFunc, cookie, 0, &dwThreadId); - if (NULL == hThread) - { - //in case of error terminate running worker threads - PrintError("ERROR: unable to create thread (error code: %u)\n", GetLastError()); - InterlockedDecrement(&g_lRunningThreadsCount); - _AbortWorkerThreads(hStartEvent, vhThreads); - delete pRand; - delete cookie; - return false; - } - - //store handle to the thread - vhThreads[iThread] = hThread; - } - - if (STRUCT_SYNCHRONIZATION_SUPPORTS(pSynch, hStartEvent) && (NULL != pSynch->hStartEvent)) - { - if (WAIT_OBJECT_0 != WaitForSingleObject(pSynch->hStartEvent, INFINITE)) - { - PrintError("Error during WaitForSingleObject\n"); - _AbortWorkerThreads(hStartEvent, vhThreads); - return false; - } - } - - // - // get cycle count (it will be used to calculate actual work time) - // - DWORD dwWaitStatus = 0; - - //bAccountingOn = FALSE; // clear the accouning flag so that threads didn't count what they do while in the warmup phase - - BOOL bSynchStop = STRUCT_SYNCHRONIZATION_SUPPORTS(pSynch, hStopEvent) && (NULL != pSynch->hStopEvent); - BOOL bBreak = FALSE; - PEVENT_TRACE_PROPERTIES pETWSession = NULL; - - PrintVerbose(profile.GetVerbose(), "starting warm up...\n"); - // - // send start signal - // - if (!SetEvent(hStartEvent)) - { - PrintError("Error signaling start event\n"); - // stopETW(bUseETW, hTraceSession); - _TerminateWorkerThreads(vhThreads); //FUTURE EXTENSION: timeout for worker threads - return false; - } - - // - // wait specified amount of time in each phase (warm up, test, cool down) - // - if (timeSpan.GetWarmup() > 0) - { - TraceLoggingActivity WarmActivity; - TraceLoggingWriteStart(WarmActivity, "Warm Up"); - - if (bSynchStop) - { - assert(NULL != pSynch->hStopEvent); - dwWaitStatus = WaitForSingleObject(pSynch->hStopEvent, 1000 * timeSpan.GetWarmup()); - if (WAIT_OBJECT_0 != dwWaitStatus && WAIT_TIMEOUT != dwWaitStatus) - { - PrintError("Error during WaitForSingleObject\n"); - _TerminateWorkerThreads(vhThreads); - return false; - } - bBreak = (WAIT_TIMEOUT != dwWaitStatus); - } - else - { - Sleep(1000 * timeSpan.GetWarmup()); - } - - TraceLoggingWriteStop(WarmActivity, "Warm Up"); - } - - if (!bBreak) // proceed only if user didn't break the test - { - //FUTURE EXTENSION: starting ETW session shouldn't be done brutally here, should be done before warmup and here just a fast signal to start logging (see also stopping ETW session) - //FUTURE EXTENSION: put an ETW mark here, for easier parsing by external tools - - // - // start etw session - // - TRACEHANDLE hTraceSession = NULL; - if (fUseETW) - { - PrintVerbose(profile.GetVerbose(), "starting trace session\n"); - hTraceSession = StartETWSession(profile); - if (NULL == hTraceSession) - { - PrintError("Could not start ETW session\n"); - _TerminateWorkerThreads(vhThreads); - return false; - } - - if (NULL == CreateThread(NULL, 64 * 1024, etwThreadFunc, NULL, 0, NULL)) - { - PrintError("Warning: unable to create thread for ETW session\n"); - _TerminateWorkerThreads(vhThreads); - return false; - } - PrintVerbose(profile.GetVerbose(), "tracing events\n"); - } - - PrintVerbose(profile.GetVerbose(), "starting measurements...\n"); - - // - // notify the front-end that the test is about to start; - // do it before starting timing in order not to perturb measurements - // - if (STRUCT_SYNCHRONIZATION_SUPPORTS(pSynch, pfnCallbackTestStarted) && (NULL != pSynch->pfnCallbackTestStarted)) - { - pSynch->pfnCallbackTestStarted(); - } - - // - // read performance counters - // - if (_GetSystemPerfInfo(&vPerfInit[0], g_SystemInformation.processorTopology._ulProcCount) == FALSE) - { - PrintError("Error reading performance counters\n"); - _StopETW(fUseETW, hTraceSession); - _TerminateWorkerThreads(vhThreads); - return false; - } - - TraceLoggingActivity RunActivity; - TraceLoggingWriteStart(RunActivity, "Run Time"); - - //get cycle count (it will be used to calculate actual work time) - ullStartTime = PerfTimer::GetTime(); - -#pragma warning( push ) -#pragma warning( disable : 28931 ) - fAccountingOn = true; -#pragma warning( pop ) - - assert(timeSpan.GetDuration() > 0); - if (bSynchStop) - { - assert(NULL != pSynch->hStopEvent); - dwWaitStatus = WaitForSingleObject(pSynch->hStopEvent, 1000 * timeSpan.GetDuration()); - if (WAIT_OBJECT_0 != dwWaitStatus && WAIT_TIMEOUT != dwWaitStatus) - { - PrintError("Error during WaitForSingleObject\n"); - _StopETW(fUseETW, hTraceSession); - _TerminateWorkerThreads(vhThreads); //FUTURE EXTENSION: worker threads should have a chance to free allocated memory (see also other places calling terminateWorkerThreads()) - return FALSE; - } - bBreak = (WAIT_TIMEOUT != dwWaitStatus); - } - else - { - Sleep(1000 * timeSpan.GetDuration()); - } - - fAccountingOn = false; - - //get cycle count and perf counters - ullTimeDiff = PerfTimer::GetTime() - ullStartTime; - - TraceLoggingWriteStop(RunActivity, "Run Time"); - - if (_GetSystemPerfInfo(&vPerfDone[0], g_SystemInformation.processorTopology._ulProcCount) == FALSE) - { - PrintError("Error getting performance counters\n"); - _StopETW(fUseETW, hTraceSession); - _TerminateWorkerThreads(vhThreads); - return false; - } - - // - // notify the front-end that the test has just finished; - // do it after stopping timing in order not to perturb measurements - // - if (STRUCT_SYNCHRONIZATION_SUPPORTS(pSynch, pfnCallbackTestFinished) && (NULL != pSynch->pfnCallbackTestFinished)) - { - pSynch->pfnCallbackTestFinished(); - } - - // - // stop etw session - // - if (fUseETW) - { - PrintVerbose(profile.GetVerbose(), "stopping ETW session\n"); - pETWSession = StopETWSession(hTraceSession); - if (NULL == pETWSession) - { - PrintError("Error stopping ETW session\n"); - return false; - } - } - } - else - { - ullTimeDiff = 0; // mark that no test was run - } - - PrintVerbose(profile.GetVerbose(), "starting cool down...\n"); - if ((timeSpan.GetCooldown() > 0) && !bBreak) - { - TraceLoggingActivity CoolActivity; - TraceLoggingWriteStart(CoolActivity, "Cool Down"); - - if (bSynchStop) - { - assert(NULL != pSynch->hStopEvent); - dwWaitStatus = WaitForSingleObject(pSynch->hStopEvent, 1000 * timeSpan.GetCooldown()); - if (WAIT_OBJECT_0 != dwWaitStatus && WAIT_TIMEOUT != dwWaitStatus) - { - PrintError("Error during WaitForSingleObject\n"); - // stopETW(bUseETW, hTraceSession); - _TerminateWorkerThreads(vhThreads); - return false; - } - } - else - { - Sleep(1000 * timeSpan.GetCooldown()); - } - - TraceLoggingWriteStop(CoolActivity, "Cool Down"); - } - PrintVerbose(profile.GetVerbose(), "finished test...\n"); - - // - // signal the threads to finish - // - g_bRun = FALSE; - if (timeSpan.GetCompletionRoutines()) - { - if (!SetEvent(hEndEvent)) - { - PrintError("Error signaling end event\n"); - // stopETW(bUseETW, hTraceSession); - return false; - } - } - - // - // wait till all of the threads finish - // -#pragma warning( push ) -#pragma warning( disable : 28112 ) - while (g_lRunningThreadsCount > 0) - { - Sleep(10); //FUTURE EXTENSION: a timeout should be implemented - } -#pragma warning( pop ) - - - //check if there has been an error during threads execution - if (g_bThreadError) - { - PrintError("There has been an error during threads execution\n"); - return false; - } - - // - // close events' handles - // - CloseHandle(hStartEvent); - hStartEvent = NULL; - - if (NULL != hEndEvent) - { - CloseHandle(hEndEvent); - hEndEvent = NULL; - } - //FUTURE EXTENSION: hStartEvent and hEndEvent should be closed in case of error too - - // - // compute time spent by each cpu - // - for (unsigned int p = 0; p < g_SystemInformation.processorTopology._ulProcCount; ++p) - { - assert(vPerfDone[p].IdleTime.QuadPart >= vPerfInit[p].IdleTime.QuadPart); - assert(vPerfDone[p].KernelTime.QuadPart >= vPerfInit[p].KernelTime.QuadPart); - assert(vPerfDone[p].UserTime.QuadPart >= vPerfInit[p].UserTime.QuadPart); - assert(vPerfDone[p].Reserved1[0].QuadPart >= vPerfInit[p].Reserved1[0].QuadPart); - assert(vPerfDone[p].Reserved1[1].QuadPart >= vPerfInit[p].Reserved1[1].QuadPart); - assert(vPerfDone[p].Reserved2 >= vPerfInit[p].Reserved2); - - vPerfDiff[p].IdleTime.QuadPart = vPerfDone[p].IdleTime.QuadPart - vPerfInit[p].IdleTime.QuadPart; - vPerfDiff[p].KernelTime.QuadPart = vPerfDone[p].KernelTime.QuadPart - vPerfInit[p].KernelTime.QuadPart; - vPerfDiff[p].UserTime.QuadPart = vPerfDone[p].UserTime.QuadPart - vPerfInit[p].UserTime.QuadPart; - vPerfDiff[p].Reserved1[0].QuadPart = vPerfDone[p].Reserved1[0].QuadPart - vPerfInit[p].Reserved1[0].QuadPart; - vPerfDiff[p].Reserved1[1].QuadPart = vPerfDone[p].Reserved1[1].QuadPart - vPerfInit[p].Reserved1[1].QuadPart; - vPerfDiff[p].Reserved2 = vPerfDone[p].Reserved2 - vPerfInit[p].Reserved2; - } - - // - // process results and pass them to the result parser - // - - // get processors perf. info - results.vSystemProcessorPerfInfo = vPerfDiff; - results.ullTimeCount = ullTimeDiff; - - // - // create structure containing etw results and properties - // - results.fUseETW = fUseETW; - if (fUseETW) - { - results.EtwEventCounters = g_EtwEventCounters; - results.EtwSessionInfo = _GetResultETWSession(pETWSession); - - // TODO: refactor to a separate function - results.EtwMask.bProcess = profile.GetEtwProcess(); - results.EtwMask.bThread = profile.GetEtwThread(); - results.EtwMask.bImageLoad = profile.GetEtwImageLoad(); - results.EtwMask.bDiskIO = profile.GetEtwDiskIO(); - results.EtwMask.bMemoryPageFaults = profile.GetEtwMemoryPageFaults(); - results.EtwMask.bMemoryHardFaults = profile.GetEtwMemoryHardFaults(); - results.EtwMask.bNetwork = profile.GetEtwNetwork(); - results.EtwMask.bRegistry = profile.GetEtwRegistry(); - results.EtwMask.bUsePagedMemory = profile.GetEtwUsePagedMemory(); - results.EtwMask.bUsePerfTimer = profile.GetEtwUsePerfTimer(); - results.EtwMask.bUseSystemTimer = profile.GetEtwUseSystemTimer(); - results.EtwMask.bUseCyclesCounter = profile.GetEtwUseCyclesCounter(); - - free(pETWSession); - } - - // free memory used by random data write buffers - for (auto i = vTargets.begin(); i != vTargets.end(); i++) - { - i->FreeRandomDataWriteBuffer(); - } - - // TODO: this won't catch error cases, which exit early - InterlockedExchange(&g_lGeneratorRunning, 0); - return true; -} - -vector IORequestGenerator::_GetFilesToPrecreate(const Profile& profile) const -{ - vector vFilesToCreate; - const vector& vTimeSpans = profile.GetTimeSpans(); - map> filesMap; - for (const auto& timeSpan : vTimeSpans) - { - vector vTargets(timeSpan.GetTargets()); - for (const auto& target : vTargets) - { - struct CreateFileParameters createFileParameters; - createFileParameters.sPath = target.GetPath(); - createFileParameters.ullFileSize = target.GetFileSize(); - createFileParameters.fZeroWriteBuffers = target.GetZeroWriteBuffers(); - - filesMap[createFileParameters.sPath].push_back(createFileParameters); - } - } - - PrecreateFiles filter = profile.GetPrecreateFiles(); - for (auto fileMapEntry : filesMap) - { - if (fileMapEntry.second.size() > 0) - { - UINT64 ullLastNonZeroSize = fileMapEntry.second[0].ullFileSize; - UINT64 ullMaxSize = fileMapEntry.second[0].ullFileSize; - bool fLastZeroWriteBuffers = fileMapEntry.second[0].fZeroWriteBuffers; - bool fHasZeroSizes = false; - bool fConstantSize = true; - bool fConstantZeroWriteBuffers = true; - for (auto file : fileMapEntry.second) - { - ullMaxSize = max(ullMaxSize, file.ullFileSize); - if (ullLastNonZeroSize == 0) - { - ullLastNonZeroSize = file.ullFileSize; - } - if (file.ullFileSize == 0) - { - fHasZeroSizes = true; - } - if ((file.ullFileSize != 0) && (file.ullFileSize != ullLastNonZeroSize)) - { - fConstantSize = false; - } - if (file.fZeroWriteBuffers != fLastZeroWriteBuffers) - { - fConstantZeroWriteBuffers = false; - } - if (file.ullFileSize != 0) - { - ullLastNonZeroSize = file.ullFileSize; - } - fLastZeroWriteBuffers = file.fZeroWriteBuffers; - } - - if (fConstantZeroWriteBuffers && ullMaxSize > 0) - { - struct CreateFileParameters file = fileMapEntry.second[0]; - file.ullFileSize = ullMaxSize; - if (filter == PrecreateFiles::UseMaxSize) - { - vFilesToCreate.push_back(file); - } - else if ((filter == PrecreateFiles::OnlyFilesWithConstantSizes) && fConstantSize && !fHasZeroSizes) - { - vFilesToCreate.push_back(file); - } - else if ((filter == PrecreateFiles::OnlyFilesWithConstantOrZeroSizes) && fConstantSize) - { - vFilesToCreate.push_back(file); - } - } - } - } - - return vFilesToCreate; +/* + +DISKSPD + +Copyright(c) Microsoft Corporation +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +//FUTURE EXTENSION: make it compile with /W4 + +// Windows 7 +#ifndef _WIN32_WINNT +#define _WIN32_WINNT 0x0601 +#endif + +#include "common.h" +#include "IORequestGenerator.h" + +#include +#include +#include //DISK_GEOMETRY +#include +#include + +#include //WNODE_HEADER + +#include "etw.h" +#include +#include "ThroughputMeter.h" +#include "OverlappedQueue.h" + +// Flags for RtlFlushNonVolatileMemory +#ifndef FLUSH_NV_MEMORY_IN_FLAG_NO_DRAIN +#define FLUSH_NV_MEMORY_IN_FLAG_NO_DRAIN (0x00000001) +#endif + +/*****************************************************************************/ +// gets size of a dynamic volume, return zero on failure +// +UINT64 GetDynamicPartitionSize(HANDLE hFile) +{ + assert(NULL != hFile && INVALID_HANDLE_VALUE != hFile); + + UINT64 size = 0; + VOLUME_DISK_EXTENTS diskExt = {0}; + PVOLUME_DISK_EXTENTS pDiskExt = &diskExt; + DWORD bytesReturned; + + DWORD status = ERROR_SUCCESS; + BOOL rslt; + + OVERLAPPED ovlp = {0}; + ovlp.hEvent = CreateEvent(NULL, FALSE, FALSE, NULL); + if (ovlp.hEvent == nullptr) + { + PrintError("ERROR: Failed to create event (error code: %u)\n", GetLastError()); + return 0; + } + + rslt = DeviceIoControl(hFile, + IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS, + NULL, + 0, + pDiskExt, + sizeof(VOLUME_DISK_EXTENTS), + &bytesReturned, + &ovlp); + if (!rslt) { + status = GetLastError(); + if (status == ERROR_MORE_DATA) { + status = ERROR_SUCCESS; + + bytesReturned = sizeof(VOLUME_DISK_EXTENTS) + ((pDiskExt->NumberOfDiskExtents - 1) * sizeof(DISK_EXTENT)); + pDiskExt = (PVOLUME_DISK_EXTENTS)LocalAlloc(LPTR, bytesReturned); + + if (pDiskExt) + { + rslt = DeviceIoControl(hFile, + IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS, + NULL, + 0, + pDiskExt, + bytesReturned, + &bytesReturned, + &ovlp); + if (!rslt) + { + status = GetLastError(); + if (status == ERROR_IO_PENDING) + { + if (WAIT_OBJECT_0 != WaitForSingleObject(ovlp.hEvent, INFINITE)) + { + status = GetLastError(); + PrintError("ERROR: Failed while waiting for event to be signaled (error code: %u)\n", status); + } + else + { + status = ERROR_SUCCESS; + assert(pDiskExt->NumberOfDiskExtents <= 1); + } + } + else + { + PrintError("ERROR: Could not obtain dynamic volume extents (error code: %u)\n", status); + } + } + } + else + { + status = GetLastError(); + PrintError("ERROR: Could not allocate memory (error code: %u)\n", status); + } + } + else if (status == ERROR_IO_PENDING) + { + if (WAIT_OBJECT_0 != WaitForSingleObject(ovlp.hEvent, INFINITE)) + { + status = GetLastError(); + PrintError("ERROR: Failed while waiting for event to be signaled (error code: %u)\n", status); + } + else + { + status = ERROR_SUCCESS; + assert(pDiskExt->NumberOfDiskExtents <= 1); + } + } + else + { + PrintError("ERROR: Could not obtain dynamic volume extents (error code: %u)\n", status); + } + } + else + { + assert(pDiskExt->NumberOfDiskExtents <= 1); + } + + if (status == ERROR_SUCCESS) + { + for (DWORD n = 0; n < pDiskExt->NumberOfDiskExtents; n++) { + size += pDiskExt->Extents[n].ExtentLength.QuadPart; + } + } + + if (pDiskExt && (pDiskExt != &diskExt)) { + LocalFree(pDiskExt); + } + CloseHandle(ovlp.hEvent); + + return size; +} + +/*****************************************************************************/ +// gets partition size, return zero on failure +// +UINT64 GetPartitionSize(HANDLE hFile) +{ + assert(NULL != hFile && INVALID_HANDLE_VALUE != hFile); + + PARTITION_INFORMATION_EX pinf; + OVERLAPPED ovlp = {}; + + ovlp.hEvent = CreateEvent(NULL, FALSE, FALSE, NULL); + if (ovlp.hEvent == nullptr) + { + PrintError("ERROR: Failed to create event (error code: %u)\n", GetLastError()); + return 0; + } + + DWORD rbcnt = 0; + DWORD status = ERROR_SUCCESS; + UINT64 size = 0; + + if (!DeviceIoControl(hFile, + IOCTL_DISK_GET_PARTITION_INFO_EX, + NULL, + 0, + &pinf, + sizeof(pinf), + &rbcnt, + &ovlp) + ) + { + status = GetLastError(); + if (status == ERROR_IO_PENDING) + { + if (WAIT_OBJECT_0 != WaitForSingleObject(ovlp.hEvent, INFINITE)) + { + PrintError("ERROR: Failed while waiting for event to be signaled (error code: %u)\n", GetLastError()); + } + else + { + size = pinf.PartitionLength.QuadPart; + } + } + else + { + size = GetDynamicPartitionSize(hFile); + } + } + else + { + size = pinf.PartitionLength.QuadPart; + } + + CloseHandle(ovlp.hEvent); + + return size; +} + +/*****************************************************************************/ +// gets physical drive size, return zero on failure +// +UINT64 GetPhysicalDriveSize(HANDLE hFile) +{ + assert(NULL != hFile && INVALID_HANDLE_VALUE != hFile); + + DISK_GEOMETRY_EX geom; + OVERLAPPED ovlp = {}; + + ovlp.hEvent = CreateEvent(NULL, FALSE, FALSE, NULL); + if (ovlp.hEvent == nullptr) + { + PrintError("ERROR: Failed to create event (error code: %u)\n", GetLastError()); + return 0; + } + + DWORD rbcnt = 0; + DWORD status = ERROR_SUCCESS; + BOOL rslt; + + rslt = DeviceIoControl(hFile, + IOCTL_DISK_GET_DRIVE_GEOMETRY_EX, + NULL, + 0, + &geom, + sizeof(geom), + &rbcnt, + &ovlp); + + if (!rslt) + { + status = GetLastError(); + if (status == ERROR_IO_PENDING) + { + if (WAIT_OBJECT_0 != WaitForSingleObject(ovlp.hEvent, INFINITE)) + { + PrintError("ERROR: Failed while waiting for event to be signaled (error code: %u)\n", GetLastError()); + } + else + { + rslt = TRUE; + } + } + else + { + PrintError("ERROR: Could not obtain drive geometry (error code: %u)\n", status); + } + } + + CloseHandle(ovlp.hEvent); + + if (!rslt) + { + return 0; + } + + return (UINT64)geom.DiskSize.QuadPart; +} + +/*****************************************************************************/ +// activates specified privilege in process token +// +bool SetPrivilege(LPCSTR pszPrivilege, LPCSTR pszErrorPrefix = "ERROR:") +{ + TOKEN_PRIVILEGES TokenPriv; + HANDLE hToken = INVALID_HANDLE_VALUE; + DWORD dwError; + bool fOk = true; + + if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken)) + { + PrintError("%s Error opening process token (error code: %u)\n", pszErrorPrefix, GetLastError()); + fOk = false; + goto cleanup; + } + + TokenPriv.PrivilegeCount = 1; + TokenPriv.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; + if (!LookupPrivilegeValue(nullptr, pszPrivilege, &TokenPriv.Privileges[0].Luid)) + { + PrintError("%s Error looking up privilege value %s (error code: %u)\n", pszErrorPrefix, pszPrivilege, GetLastError()); + fOk = false; + goto cleanup; + } + + if (!AdjustTokenPrivileges(hToken, FALSE, &TokenPriv, 0, nullptr, nullptr)) + { + PrintError("%s Error adjusting token privileges for %s (error code: %u)\n", pszErrorPrefix, pszPrivilege, GetLastError()); + fOk = false; + goto cleanup; + } + + if (ERROR_SUCCESS != (dwError = GetLastError())) + { + PrintError("%s Error adjusting token privileges for %s (error code: %u)\n", pszErrorPrefix, pszPrivilege, dwError); + fOk = false; + goto cleanup; + } + +cleanup: + if (hToken != INVALID_HANDLE_VALUE) + { + CloseHandle(hToken); + } + + return fOk; +} + +BOOL +DisableLocalCache( + HANDLE h +) +/*++ +Routine Description: + + Disables local caching of I/O to a file by SMB. All reads/writes will flow to the server. + +Arguments: + + h - Handle to the file + +Return Value: + + Returns ERROR_SUCCESS (0) on success, nonzero error code on failure. + +--*/ +{ + DWORD BytesReturned = 0; + OVERLAPPED Overlapped = { 0 }; + DWORD Status = ERROR_SUCCESS; + BOOL Success = false; + + Overlapped.hEvent = CreateEvent(nullptr, true, false, nullptr); + if (!Overlapped.hEvent) + { + return GetLastError(); + } + +#ifndef FSCTL_DISABLE_LOCAL_BUFFERING +#define FSCTL_DISABLE_LOCAL_BUFFERING CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 174, METHOD_BUFFERED, FILE_ANY_ACCESS) +#endif + + Success = DeviceIoControl(h, + FSCTL_DISABLE_LOCAL_BUFFERING, + nullptr, + 0, + nullptr, + 0, + nullptr, + &Overlapped); + + if (!Success) { + Status = GetLastError(); + } + + if (!Success && Status == ERROR_IO_PENDING) + { + if (!GetOverlappedResult(h, &Overlapped, &BytesReturned, true)) + { + Status = GetLastError(); + } + else + { + Status = (DWORD) Overlapped.Internal; + } + } + + if (Overlapped.hEvent) + { + CloseHandle(Overlapped.hEvent); + } + + return Status; +} + +/*****************************************************************************/ +// structures and global variables +// +struct ETWEventCounters g_EtwEventCounters; + +__declspec(align(4)) static LONG volatile g_lRunningThreadsCount = 0; //must be aligned on a 32-bit boundary, otherwise InterlockedIncrement + //and InterlockedDecrement will fail on 64-bit systems + +static BOOL volatile g_bRun; //used for letting threads know that they should stop working + +typedef NTSTATUS (__stdcall *NtQuerySysInfo)(SYSTEM_INFORMATION_CLASS, PVOID, ULONG, PULONG); +static NtQuerySysInfo g_pfnNtQuerySysInfo; + +typedef VOID (__stdcall *RtlCopyMemNonTemporal)(VOID UNALIGNED *, VOID UNALIGNED *, SIZE_T); +static RtlCopyMemNonTemporal g_pfnRtlCopyMemoryNonTemporal; + +typedef NTSTATUS (__stdcall *RtlFlushNvMemory)(PVOID, PVOID, SIZE_T, ULONG); +static RtlFlushNvMemory g_pfnRtlFlushNonVolatileMemory; + +typedef NTSTATUS(__stdcall *RtlGetNvToken)(PVOID, SIZE_T, PVOID *); +static RtlGetNvToken g_pfnRtlGetNonVolatileToken; + +typedef NTSTATUS(__stdcall *RtlFreeNvToken)(PVOID); +static RtlFreeNvToken g_pfnRtlFreeNonVolatileToken; + +static BOOL volatile g_bThreadError = FALSE; //true means that an error has occured in one of the threads +BOOL volatile g_bTracing = TRUE; //true means that ETW is turned on + +// TODO: is this still needed? +__declspec(align(4)) static LONG volatile g_lGeneratorRunning = 0; //used to detect if GenerateRequests is already running + +static BOOL volatile g_bError = FALSE; //true means there was fatal error during intialization and threads shouldn't perform their work + +VOID SetProcGroupMask(WORD wGroupNum, ULONG dwProcNum, PGROUP_AFFINITY pGroupAffinity) +{ + //must zero this structure first, otherwise it fails to set affinity + memset(pGroupAffinity, 0, sizeof(GROUP_AFFINITY)); + + pGroupAffinity->Group = wGroupNum; + pGroupAffinity->Mask = (KAFFINITY)1<Group = wGroupNum; + pGroupAffinity->Mask = Mask; +} + +/*****************************************************************************/ +void IORequestGenerator::_CloseOpenFiles(vector& vhFiles) const +{ + for (size_t x = 0; x < vhFiles.size(); ++x) + { + if ((INVALID_HANDLE_VALUE != vhFiles[x]) && (nullptr != vhFiles[x])) + { + if (!CloseHandle(vhFiles[x])) + { + PrintError("Warning: unable to close file handle (error code: %u)\n", GetLastError()); + } + vhFiles[x] = nullptr; + } + } +} + +/*****************************************************************************/ +// wrapper for stderr +void PrintError(const char *format, ...) +{ + assert(NULL != format); + + va_list listArg; + va_start(listArg, format); + vfprintf(stderr, format, listArg); + va_end(listArg); +} + +/*****************************************************************************/ +// prints the string only if verbose mode is set to true +// +static void PrintVerbose(bool fVerbose, const char *format, ...) +{ + assert(NULL != format); + + if(fVerbose ) + { + SYSTEMTIME now; + char szBuffer[64]; // enough for timestamp+null + int nWritten; + + GetLocalTime(&now); + + if (now.wYear) { + + // Mimic .NET 's' sortable time pattern + nWritten = sprintf_s(szBuffer, _countof(szBuffer), + "%u-%02u-%02uT%02u:%02u:%02u", + now.wYear, + now.wMonth, + now.wDay, + now.wHour, + now.wMinute, + now.wSecond); + assert(nWritten && nWritten < _countof(szBuffer)); + + // no newline + printf("%s: " ,szBuffer); + } + + va_list argList; + va_start(argList, format); + vprintf(format, argList); + va_end(argList); + } +} + +/*****************************************************************************/ +// thread for gathering ETW data (etw functions are defined in etw.cpp) +// +DWORD WINAPI etwThreadFunc(LPVOID cookie) +{ + UNREFERENCED_PARAMETER(cookie); + + g_bTracing = TRUE; + BOOL result = TraceEvents(); + g_bTracing = FALSE; + + return result ? 0 : 1; +} + +/*****************************************************************************/ +bool IORequestGenerator::_LoadDLLs() +{ + _hNTDLL = LoadLibraryExW(L"ntdll.dll", nullptr, 0); + if( nullptr == _hNTDLL ) + { + return false; + } + + g_pfnNtQuerySysInfo = (NtQuerySysInfo)GetProcAddress(_hNTDLL, "NtQuerySystemInformation"); + if( nullptr == g_pfnNtQuerySysInfo ) + { + return false; + } + + g_pfnRtlCopyMemoryNonTemporal = (RtlCopyMemNonTemporal)GetProcAddress(_hNTDLL, "RtlCopyMemoryNonTemporal"); + g_pfnRtlFlushNonVolatileMemory = (RtlFlushNvMemory)GetProcAddress(_hNTDLL, "RtlFlushNonVolatileMemory"); + g_pfnRtlGetNonVolatileToken = (RtlGetNvToken)GetProcAddress(_hNTDLL, "RtlGetNonVolatileToken"); + g_pfnRtlFreeNonVolatileToken = (RtlFreeNvToken)GetProcAddress(_hNTDLL, "RtlFreeNonVolatileToken"); + + return true; +} + +/*****************************************************************************/ +bool IORequestGenerator::_GetSystemPerfInfo(vector& vSPPI, bool fVerbose) const +{ + NTSTATUS Status; + ULONG CpuBase; + WORD Group; + WORD GroupCount; + GROUP_AFFINITY GroupAffinity; + + for (CpuBase = 0, Group = 0, GroupCount = (WORD) g_SystemInformation.processorTopology._vProcessorGroupInformation.size(); + Group < GroupCount; + Group++) + { + ProcessorGroupInformation *pGroup = &g_SystemInformation.processorTopology._vProcessorGroupInformation[Group]; + + // + // Note that an inactive group is not queried (its not clear this is a practical case). + // Correct operation assumes the input SPPI array is prezeroed, which DISKSPD does do via + // default vector(size_t) construction. + // + + if (pGroup->_activeProcessorCount != 0) + { + // + // In multigroup environments, affinitize to the group we're querying counters from. + // + + if (GroupCount > 1) + { + SetGroupMask(Group, pGroup->_activeProcessorMask, &GroupAffinity); + if (!SetThreadGroupAffinity(GetCurrentThread(), &GroupAffinity, nullptr)) + { + PrintError("get system perf info: failed to set affinity to Group %u\n", GroupAffinity.Group); + return false; + } + } + + // + // The SPPI vector should (is) always be sized to span CPUs for all groups, make this explicit. + // + + if (CpuBase + pGroup->_activeProcessorCount > vSPPI.size()) + { + PrintError("get system perf info: unable to return (base CPU %u + group active CPU %u > size %u)\n", + CpuBase, + pGroup->_activeProcessorCount, + vSPPI.size()); + assert(false); + return false; + } + + Status = g_pfnNtQuerySysInfo(SystemProcessorPerformanceInformation, + &vSPPI[CpuBase], + sizeof(SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION) * pGroup->_activeProcessorCount, + nullptr); + + if (!NT_SUCCESS(Status)) + { + PrintError("get system perf info: status 0x%x querying for Group %u (%u CPUs)\n", + Status, + Group, + pGroup->_activeProcessorCount); + return false; + } + + PrintVerbose(fVerbose, + "get system perf info: queried for Group %u (%u CPUs)\n", + Group, + pGroup->_activeProcessorCount); + } + + CpuBase += pGroup->_activeProcessorCount; + } + + return true; +} + +VOID CALLBACK fileIOCompletionRoutine(DWORD dwErrorCode, DWORD dwBytesTransferred, LPOVERLAPPED pOverlapped); + +static bool issueNextIO(ThreadParameters *p, IORequest *pIORequest, DWORD *pdwBytesTransferred, bool useCompletionRoutines) +{ + OVERLAPPED *pOverlapped = pIORequest->GetOverlapped(); + Target *pTarget = pIORequest->GetCurrentTarget(); + size_t iTarget = pIORequest->GetCurrentTargetIndex(); + UINT32 iRequest = pIORequest->GetRequestIndex(); + LARGE_INTEGER li; + BOOL rslt = true; + + // + // Compute next IO + // + + p->vTargetStates[iTarget].NextIORequest(*pIORequest); + + li.LowPart = pIORequest->GetOverlapped()->Offset; + li.HighPart = pIORequest->GetOverlapped()->OffsetHigh; + + if (TraceLoggingProviderEnabled(g_hEtwProvider, + TRACE_LEVEL_VERBOSE, + DISKSPD_TRACE_IO)) + { + GUID ActivityId = p->NextActivityId(); + pIORequest->SetActivityId(ActivityId); + + TraceLoggingWriteActivity(g_hEtwProvider, + "DiskSpd IO", + &ActivityId, + NULL, + TraceLoggingKeyword(DISKSPD_TRACE_IO), + TraceLoggingOpcode(EVENT_TRACE_TYPE_START), + TraceLoggingLevel(TRACE_LEVEL_VERBOSE), + TraceLoggingUInt32(p->ulThreadNo, "Thread"), + TraceLoggingString(pIORequest->GetIoType() == IOOperation::ReadIO ? "Read" : "Write", "IO Type"), + TraceLoggingUInt64(iTarget, "Target"), + TraceLoggingInt32(pTarget->GetBlockSizeInBytes(), "Block Size"), + TraceLoggingInt64(li.QuadPart, "Offset")); + } + +#if 0 + PrintError("t[%u:%u] issuing %u %s @ %I64u)\n", p->ulThreadNo, iTarget, + pTarget->GetBlockSizeInBytes(), + (pIORequest->GetIoType() == IOOperation::ReadIO ? "read" : "write"), + li.QuadPart); +#endif + + if (p->pTimeSpan->GetMeasureLatency() || p->pTimeSpan->GetCalculateIopsStdDev()) + { + pIORequest->SetStartTime(PerfTimer::GetTime()); + } + + if (pIORequest->GetIoType() == IOOperation::ReadIO) + { + if (pTarget->GetMemoryMappedIoMode() == MemoryMappedIoMode::On) + { + if (pTarget->GetWriteThroughMode() == WriteThroughMode::On ) + { + g_pfnRtlCopyMemoryNonTemporal(p->GetReadBuffer(iTarget, iRequest), pTarget->GetMappedView() + li.QuadPart, pTarget->GetBlockSizeInBytes()); + } + else + { + memcpy(p->GetReadBuffer(iTarget, iRequest), pTarget->GetMappedView() + li.QuadPart, pTarget->GetBlockSizeInBytes()); + } + *pdwBytesTransferred = pTarget->GetBlockSizeInBytes(); + } + else + { + if (useCompletionRoutines) + { + rslt = ReadFileEx(p->vhTargets[iTarget], p->GetReadBuffer(iTarget, iRequest), pTarget->GetBlockSizeInBytes(), pOverlapped, fileIOCompletionRoutine); + } + else + { + rslt = ReadFile(p->vhTargets[iTarget], p->GetReadBuffer(iTarget, iRequest), pTarget->GetBlockSizeInBytes(), pdwBytesTransferred, pOverlapped); + } + } + } + else + { + if (pTarget->GetMemoryMappedIoMode() == MemoryMappedIoMode::On) + { + if (pTarget->GetWriteThroughMode() == WriteThroughMode::On) + { + g_pfnRtlCopyMemoryNonTemporal(pTarget->GetMappedView() + li.QuadPart, p->GetWriteBuffer(iTarget, iRequest), pTarget->GetBlockSizeInBytes()); + } + else + { + memcpy(pTarget->GetMappedView() + li.QuadPart, p->GetWriteBuffer(iTarget, iRequest), pTarget->GetBlockSizeInBytes()); + + switch (pTarget->GetMemoryMappedIoFlushMode()) + { + case MemoryMappedIoFlushMode::ViewOfFile: + FlushViewOfFile(pTarget->GetMappedView() + li.QuadPart, pTarget->GetBlockSizeInBytes()); + break; + case MemoryMappedIoFlushMode::NonVolatileMemory: + g_pfnRtlFlushNonVolatileMemory(pTarget->GetMemoryMappedIoNvToken(), pTarget->GetMappedView() + li.QuadPart, pTarget->GetBlockSizeInBytes(), 0); + break; + case MemoryMappedIoFlushMode::NonVolatileMemoryNoDrain: + g_pfnRtlFlushNonVolatileMemory(pTarget->GetMemoryMappedIoNvToken(), pTarget->GetMappedView() + li.QuadPart, pTarget->GetBlockSizeInBytes(), FLUSH_NV_MEMORY_IN_FLAG_NO_DRAIN); + break; + } + } + *pdwBytesTransferred = pTarget->GetBlockSizeInBytes(); + } + else + { + if (useCompletionRoutines) + { + rslt = WriteFileEx(p->vhTargets[iTarget], p->GetWriteBuffer(iTarget, iRequest), pTarget->GetBlockSizeInBytes(), pOverlapped, fileIOCompletionRoutine); + } + else + { + rslt = WriteFile(p->vhTargets[iTarget], p->GetWriteBuffer(iTarget, iRequest), pTarget->GetBlockSizeInBytes(), pdwBytesTransferred, pOverlapped); + } + } + } + + if (p->vThroughputMeters.size() != 0 && p->vThroughputMeters[iTarget].IsRunning()) + { + p->vThroughputMeters[iTarget].Adjust(pTarget->GetBlockSizeInBytes()); + } + + return (rslt) ? true : false; +} + + +void completeIOat(ThreadParameters *p, IORequest *pIORequest, DWORD dwBytesTransferred, UINT64 ullCompletionTime) +{ + if (*p->pfAccountingOn) + { + p->pResults->vTargetResults[pIORequest->GetCurrentTargetIndex()].Add( + dwBytesTransferred, + pIORequest->GetIoType(), + pIORequest->GetStartTime(), + ullCompletionTime, + *(p->pullStartTime), + p->pTimeSpan->GetMeasureLatency(), + p->pTimeSpan->GetCalculateIopsStdDev()); + } + + if (TraceLoggingProviderEnabled(g_hEtwProvider, + TRACE_LEVEL_VERBOSE, + DISKSPD_TRACE_IO)) + { + GUID ActivityId = pIORequest->GetActivityId(); + + TraceLoggingWriteActivity(g_hEtwProvider, + "DiskSpd IO", + &ActivityId, + NULL, + TraceLoggingKeyword(DISKSPD_TRACE_IO), + TraceLoggingOpcode(EVENT_TRACE_TYPE_STOP), + TraceLoggingLevel(TRACE_LEVEL_VERBOSE)); + } + + Target *pTarget = pIORequest->GetCurrentTarget(); + + //check if I/O transferred all of the requested bytes + if (dwBytesTransferred != pTarget->GetBlockSizeInBytes()) + { + PrintError("Warning: thread %u transferred %u bytes instead of %u bytes\n", + p->ulThreadNo, + dwBytesTransferred, + pTarget->GetBlockSizeInBytes()); + } + + // check if we should print a progress dot + if (p->pProfile->GetProgress() != 0) + { + DWORD dwIOCnt = ++p->dwIOCnt; + if (dwIOCnt % p->pProfile->GetProgress() == 0) + { + printf("."); + } + } +} + +void completeIO(ThreadParameters *p, IORequest *pIORequest, DWORD dwBytesTransferred) +{ + if (p->pTimeSpan->GetMeasureLatency() || p->pTimeSpan->GetCalculateIopsStdDev()) + { + completeIOat(p, pIORequest, dwBytesTransferred, PerfTimer::GetTime()); + } + else + { + completeIOat(p, pIORequest, dwBytesTransferred, 0); + } +} + +/*****************************************************************************/ +// function called from worker thread +// performs synch I/O +// +static bool doWorkUsingSynchronousIO(ThreadParameters *p) +{ + BOOL fOk = true; + BOOL rslt = FALSE; + DWORD dwBytesTransferred; + size_t cIORequests = p->vIORequest.size(); + + while(g_bRun && !g_bThreadError) + { + DWORD nIssued = 0; + DWORD dwMinSleepTime = INFINITE; + for (size_t i = 0; i < cIORequests; i++) + { + IORequest *pIORequest = &p->vIORequest[i]; + Target *pTarget = pIORequest->GetNextTarget(); + + if (p->vThroughputMeters.size() != 0) + { + size_t iTarget = pTarget - &p->vTargets[0]; + ThroughputMeter *pThroughputMeter = &p->vThroughputMeters[iTarget]; + + DWORD dwSleepTime = pThroughputMeter->GetSleepTime(); + dwMinSleepTime = min(dwMinSleepTime, dwSleepTime); + if (pThroughputMeter->IsRunning() && dwSleepTime > 0) + { + continue; + } + } + + nIssued += 1; + rslt = issueNextIO(p, pIORequest, &dwBytesTransferred, false); + + if (!rslt) + { + PrintError("t[%u] error during %s error code: %u)\n", (UINT32)i, (pIORequest->GetIoType() == IOOperation::ReadIO ? "read" : "write"), GetLastError()); + fOk = false; + goto cleanup; + } + + completeIO(p, pIORequest, dwBytesTransferred); + } + + // if no IOs were issued, wait for the next scheduling time + if (!nIssued && dwMinSleepTime != INFINITE && dwMinSleepTime != 0) + { + p->pResults->WaitStats.ThrottleSleep += 1; + Sleep(dwMinSleepTime); + } + + assert(!g_bError); // at this point we shouldn't be seeing initialization error + } + +cleanup: + return fOk; +} + +/*****************************************************************************/ +// function called from worker thread +// performs asynch I/O using IO Completion Ports +// +static bool doWorkUsingIOCompletionPorts(ThreadParameters *p, HANDLE hCompletionPort) +{ + assert(nullptr != p); + assert(nullptr != hCompletionPort); + + BOOL fOk = true; + BOOL rslt = FALSE; + DWORD dwBytesTransferred; + OverlappedQueue overlappedQueue; + size_t cIORequests = p->vIORequest.size(); + BOOL fLatencyStats = p->pTimeSpan->GetMeasureLatency() || p->pTimeSpan->GetCalculateIopsStdDev(); + + for (size_t i = 0; i < cIORequests; i++) + { + overlappedQueue.Add(p->vIORequest[i].GetOverlapped()); + } + + // + // perform work + // + DWORD dwMinSleepTime = INFINITE; + DWORD dwWaitTime; + + OVERLAPPED_ENTRY ovlEntry[16]; + const ULONG cOvlEntryMax = _countof(ovlEntry) < (ULONG)cIORequests ? _countof(ovlEntry) : (ULONG)cIORequests; + ULONG cCompleted; + size_t cUntilThrottle = cIORequests; + + while(g_bRun && !g_bThreadError) + { + OVERLAPPED *pReadyOverlapped = overlappedQueue.Remove(); + IORequest *pIORequest = IORequest::OverlappedToIORequest(pReadyOverlapped); + (void) pIORequest->GetNextTarget(); + + // check throttles + if (p->vThroughputMeters.size() != 0) + { + ThroughputMeter *pThroughputMeter = &p->vThroughputMeters[pIORequest->GetCurrentTargetIndex()]; + + cUntilThrottle -= 1; + + DWORD dwSleepTime = pThroughputMeter->GetSleepTime(); + if (pThroughputMeter->IsRunning() && dwSleepTime > 0) + { + dwMinSleepTime = min(dwMinSleepTime, dwSleepTime); + overlappedQueue.Add(pReadyOverlapped); + + // continue if throttle not hit + if (cUntilThrottle) + { + continue; + } + + // at throttle, no IO to dispatch + pIORequest = NULL; + } + } + + // dispatch IO - skipped iff at throttle + if (pIORequest) + { + rslt = issueNextIO(p, pIORequest, &dwBytesTransferred, false); + + if (!rslt && GetLastError() != ERROR_IO_PENDING) + { + UINT32 iIORequest = (UINT32)(pIORequest - &p->vIORequest[0]); + PrintError("t[%u] error during %s error code: %u)\n", iIORequest, (pIORequest->GetIoType()== IOOperation::ReadIO ? "read" : "write"), GetLastError()); + fOk = false; + goto cleanup; + } + + if (rslt && pIORequest->GetCurrentTarget()->GetMemoryMappedIoMode() == MemoryMappedIoMode::On) + { + completeIO(p, pIORequest, dwBytesTransferred); + overlappedQueue.Add(pReadyOverlapped); + + // a completed memory mapped IO resets the throttle so that we traverse + // back to it in fair-order before considering throttle again. + // note this will drop through to lookside for completions, not wait + dwMinSleepTime = INFINITE; + cUntilThrottle = overlappedQueue.GetCount(); + } + } + + // look for IO completion + // queue is fully dispatched: set wait, reset throttle wait + if (!overlappedQueue.GetCount()) + { + assert(!cUntilThrottle); + dwWaitTime = dwMinSleepTime = INFINITE; + p->pResults->WaitStats.Wait += 1; + } + + // queue is not fully dispatched ... + // if at the throttle, wait throttle time and reset + else if (!cUntilThrottle) + { + dwWaitTime = dwMinSleepTime; + dwMinSleepTime = INFINITE; + cUntilThrottle = overlappedQueue.GetCount(); + + if (cIORequests == cUntilThrottle) + { + // all throttled, none dispatched - just sleep + p->pResults->WaitStats.ThrottleSleep += 1; + Sleep(dwWaitTime); + continue; + } + else + { + // throttled, but some dispatched - wait for completions + p->pResults->WaitStats.ThrottleWait += 1; + } + } + + // queue is not fully dispatched ... + // if this run is not for latency stats, optimize for dispatch and + // skip completion lookasides + else if (!fLatencyStats) + { + continue; + } + + // else lookaside + else + { + dwWaitTime = 0; + p->pResults->WaitStats.Lookaside += 1; + } + + if (GetQueuedCompletionStatusEx(hCompletionPort, ovlEntry, cOvlEntryMax, &cCompleted, dwWaitTime, FALSE) != 0) + { + UINT64 ullCompletionTime = 0; + + if (fLatencyStats) + { + // single completion time estimate for all completions + ullCompletionTime = PerfTimer::GetTime(); + } + + for (ULONG i = 0; i < cCompleted; i++) + { + completeIOat(p, IORequest::OverlappedToIORequest(ovlEntry[i].lpOverlapped), ovlEntry[i].dwNumberOfBytesTransferred, ullCompletionTime); + overlappedQueue.Add(ovlEntry[i].lpOverlapped); + } + + // must reevaluate queue in fair order before next throttle + cUntilThrottle = overlappedQueue.GetCount(); + } + else + { + DWORD err = GetLastError(); + if (err != WAIT_TIMEOUT) + { + PrintError("error during overlapped IO operation (error code: %u)\n", err); + fOk = false; + goto cleanup; + } + } + + // stats for lookaside waits + if (dwWaitTime == 0) + { + p->pResults->WaitStats.LookasideCompletion[cCompleted < _countof(p->pResults->WaitStats.LookasideCompletion) ? cCompleted : _countof(p->pResults->WaitStats.LookasideCompletion) - 1] += 1; + } + } // end work loop + +cleanup: + return fOk; +} + +/*****************************************************************************/ +// I/O completion routine. used by ReadFileEx and WriteFileEx +// + +VOID CALLBACK fileIOCompletionRoutine(DWORD dwErrorCode, DWORD dwBytesTransferred, LPOVERLAPPED pOverlapped) +{ + assert(NULL != pOverlapped); + + BOOL rslt = FALSE; + ThreadParameters *p = (ThreadParameters *)pOverlapped->hEvent; + + assert(NULL != p); + + //check error code + if (0 != dwErrorCode) + { + PrintError("Thread %u failed executing an I/O operation (error code: %u)\n", p->ulThreadNo, dwErrorCode); + goto cleanup; + } + + IORequest *pIORequest = IORequest::OverlappedToIORequest(pOverlapped); + + completeIO(p, pIORequest, dwBytesTransferred); + + // start a new IO operation + if (g_bRun && !g_bThreadError) + { + (void) pIORequest->GetNextTarget(); + rslt = issueNextIO(p, pIORequest, NULL, true); + + if (!rslt) + { + PrintError("t[%u:%u] error during %s error code: %u)\n", p->ulThreadNo, pIORequest->GetCurrentTargetIndex(), (pIORequest->GetIoType() == IOOperation::ReadIO ? "read" : "write"), GetLastError()); + goto cleanup; + } + } + +cleanup: + return; +} + +/*****************************************************************************/ +// function called from worker thread +// performs asynch I/O using IO Completion Routines (ReadFileEx, WriteFileEx) +// +static bool doWorkUsingCompletionRoutines(ThreadParameters *p) +{ + assert(NULL != p); + bool fOk = true; + BOOL rslt = FALSE; + + // start IO operations + // completion routines will reissue 1:1 + UINT32 cIORequests = (UINT32)p->vIORequest.size(); + + for (size_t i = 0; i < cIORequests; i++) + { + IORequest *pIORequest = &p->vIORequest[i]; + + rslt = issueNextIO(p, pIORequest, NULL, true); + + if (!rslt) + { + PrintError("t[%u:%u] error during %s error code: %u)\n", p->ulThreadNo, pIORequest->GetCurrentTargetIndex(), (pIORequest->GetIoType() == IOOperation::ReadIO ? "read" : "write"), GetLastError()); + fOk = false; + goto cleanup; + } + } + + DWORD dwWaitResult = 0; + while( g_bRun && !g_bThreadError ) + { + dwWaitResult = WaitForSingleObjectEx(p->hEndEvent, INFINITE, TRUE); + + assert(WAIT_IO_COMPLETION == dwWaitResult || (WAIT_OBJECT_0 == dwWaitResult && (!g_bRun || g_bThreadError))); + + //check WaitForSingleObjectEx status + if( WAIT_IO_COMPLETION != dwWaitResult && WAIT_OBJECT_0 != dwWaitResult ) + { + PrintError("Error in thread %u during WaitForSingleObjectEx (in completion routines)\n", p->ulThreadNo); + fOk = false; + goto cleanup; + } + } +cleanup: + return fOk; +} + +struct UniqueTarget { + string path; + TargetCacheMode caching; + PRIORITY_HINT priority; + DWORD dwDesiredAccess; + DWORD dwFlags; + + bool operator < (const struct UniqueTarget &ut) const { + if (path < ut.path) { + return true; + } + else if (ut.path < path) { + return false; + } + + if (caching < ut.caching) { + return true; + } + else if (ut.caching < caching) { + return false; + } + + if (priority < ut.priority) { + return true; + } + else if (ut.priority < priority) { + return false; + } + + if (dwDesiredAccess < ut.dwDesiredAccess) { + return true; + } + else if (ut.dwDesiredAccess < dwDesiredAccess) { + return false; + } + + if (dwFlags < ut.dwFlags) { + return true; + } + + return false; + } +}; + +/*****************************************************************************/ +// worker thread function +// +DWORD WINAPI threadFunc(LPVOID cookie) +{ + bool fOk = true; + bool fAnyMappedIo = false; + bool fAllMappedIo = true; + ThreadParameters *p = reinterpret_cast(cookie); + HANDLE hCompletionPort = nullptr; + + // + // A single file can be specified in multiple targets, so only open one + // handle for each unique file. + // + + vector vhUniqueHandles; + map mHandleMap; + + bool fCalculateIopsStdDev = p->pTimeSpan->GetCalculateIopsStdDev(); + UINT64 ioBucketDuration = 0; + UINT32 expectedNumberOfBuckets = 0; + if(fCalculateIopsStdDev) + { + UINT32 ioBucketDurationInMilliseconds = p->pTimeSpan->GetIoBucketDurationInMilliseconds(); + ioBucketDuration = PerfTimer::MillisecondsToPerfTime(ioBucketDurationInMilliseconds); + expectedNumberOfBuckets = Util::QuotientCeiling(p->pTimeSpan->GetDuration() * 1000, ioBucketDurationInMilliseconds); + } + + // apply affinity. The specific assignment is provided in the thread profile up front. + if (!p->pTimeSpan->GetDisableAffinity()) + { + GROUP_AFFINITY GroupAffinity; + + PrintVerbose(p->pProfile->GetVerbose(), "thread %u: affinitizing to Group %u / CPU %u\n", p->ulThreadNo, p->wGroupNum, p->bProcNum); + SetProcGroupMask(p->wGroupNum, p->bProcNum, &GroupAffinity); + + HANDLE hThread = GetCurrentThread(); + if (SetThreadGroupAffinity(hThread, &GroupAffinity, nullptr) == FALSE) + { + PrintError("Error setting affinity mask in thread %u\n", p->ulThreadNo); + fOk = false; + goto cleanup; + } + } + + // adjust thread token if large pages are needed + for (auto pTarget = p->vTargets.begin(); pTarget != p->vTargets.end(); pTarget++) + { + if (pTarget->GetUseLargePages()) + { + if (!SetPrivilege(SE_LOCK_MEMORY_NAME)) + { + fOk = false; + goto cleanup; + } + break; + } + } + + UINT32 cIORequests = p->GetTotalRequestCount(); + + size_t iTarget = 0; + for (auto pTarget = p->vTargets.begin(); pTarget != p->vTargets.end(); pTarget++) + { + bool fPhysical = false; + bool fPartition = false; + + string sPath(pTarget->GetPath()); + const char *filename = sPath.c_str(); + + const char *fname = nullptr; //filename (can point to physFN) + char physFN[32]; //disk/partition name + + if (NULL == filename || NULL == *(filename)) + { + PrintError("FATAL ERROR: invalid filename\n"); + fOk = false; + goto cleanup; + } + + //check if it is a physical drive + if ('#' == *filename && NULL != *(filename + 1)) + { + if (pTarget->GetMemoryMappedIoMode() == MemoryMappedIoMode::On) + { + PrintError("Memory mapped I/O is not supported on physical drives\n"); + fOk = false; + goto cleanup; + } + UINT32 nDriveNo = (UINT32)atoi(filename + 1); + fPhysical = true; + sprintf_s(physFN, 32, "\\\\.\\PhysicalDrive%u", nDriveNo); + fname = physFN; + } + + //check if it is a partition + if (!fPhysical && NULL != *(filename + 1) && NULL == *(filename + 2) && isalpha((unsigned char)filename[0]) && ':' == filename[1]) + { + if (pTarget->GetMemoryMappedIoMode() == MemoryMappedIoMode::On) + { + PrintError("Memory mapped I/O is not supported on partitions\n"); + fOk = false; + goto cleanup; + } + fPartition = true; + + sprintf_s(physFN, 32, "\\\\.\\%c:", filename[0]); + fname = physFN; + } + + //check if it is a regular file + if (!fPhysical && !fPartition) + { + fname = sPath.c_str(); + } + + // get/set file flags + DWORD dwFlags = pTarget->GetCreateFlags(cIORequests > 1); + DWORD dwDesiredAccess = 0; + if (pTarget->GetWriteRatio() == 0) + { + dwDesiredAccess = GENERIC_READ; + } + else if (pTarget->GetWriteRatio() == 100) + { + dwDesiredAccess = GENERIC_WRITE; + } + else + { + dwDesiredAccess = GENERIC_READ | GENERIC_WRITE; + } + + if (pTarget->GetMemoryMappedIoMode() == MemoryMappedIoMode::On) + { + dwDesiredAccess = GENERIC_READ | GENERIC_WRITE; + fAnyMappedIo = true; + } + else + { + fAllMappedIo = false; + } + + HANDLE hFile; + UniqueTarget ut; + ut.path = sPath; + ut.priority = pTarget->GetIOPriorityHint(); + ut.caching = pTarget->GetCacheMode(); + ut.dwDesiredAccess = dwDesiredAccess; + ut.dwFlags = dwFlags; + + if (mHandleMap.find(ut) == mHandleMap.end()) { + hFile = CreateFile(fname, + dwDesiredAccess, + FILE_SHARE_READ | FILE_SHARE_WRITE, + nullptr, //security + OPEN_EXISTING, + dwFlags, //flags + nullptr); //template file + if (INVALID_HANDLE_VALUE == hFile) + { + // TODO: error out + PrintError("Error opening file: %s [%u]\n", sPath.c_str(), GetLastError()); + fOk = false; + goto cleanup; + } + + if (pTarget->GetCacheMode() == TargetCacheMode::DisableLocalCache) + { + DWORD Status = DisableLocalCache(hFile); + if (Status != ERROR_SUCCESS) + { + PrintError("Failed to disable local caching (error %u). NOTE: only supported on remote filesystems with Windows 8 or newer.\n", Status); + fOk = false; + goto cleanup; + } + } + + //set IO priority + if (pTarget->GetIOPriorityHint() != IoPriorityHintNormal) + { + _declspec(align(8)) FILE_IO_PRIORITY_HINT_INFO hintInfo; + hintInfo.PriorityHint = pTarget->GetIOPriorityHint(); + if (!SetFileInformationByHandle(hFile, FileIoPriorityHintInfo, &hintInfo, sizeof(hintInfo))) + { + PrintError("Error setting IO priority for file: %s [%u]\n", sPath.c_str(), GetLastError()); + fOk = false; + goto cleanup; + } + } + + mHandleMap[ut] = (UINT32)vhUniqueHandles.size(); + vhUniqueHandles.push_back(hFile); + } + else { + hFile = vhUniqueHandles[mHandleMap[ut]]; + } + + p->vhTargets.push_back(hFile); + + // obtain file/disk/partition size + { + UINT64 fsize = 0; //file size + + //check if it is a disk + if (fPhysical) + { + fsize = GetPhysicalDriveSize(hFile); + } + // check if it is a partition + else if (fPartition) + { + fsize = GetPartitionSize(hFile); + } + // it has to be a regular file + else + { + ULARGE_INTEGER ulsize; + + ulsize.LowPart = GetFileSize(hFile, &ulsize.HighPart); + if (INVALID_FILE_SIZE == ulsize.LowPart && GetLastError() != NO_ERROR) + { + PrintError("Error getting file size\n"); + fOk = false; + goto cleanup; + } + else + { + fsize = ulsize.QuadPart; + } + } + + // check if file size is valid (if it's == 0, it won't be useful) + if (0 == fsize) + { + // TODO: error out + PrintError("ERROR: target size could not be determined\n"); + fOk = false; + goto cleanup; + } + + if (fsize < pTarget->GetMaxFileSize()) + { + PrintError("WARNING: file size %I64u is less than MaxFileSize %I64u\n", fsize, pTarget->GetMaxFileSize()); + } + + // + // Build target state. + // + + p->vTargetStates.emplace_back( + p, + iTarget, + fsize); + + // + // Ensure this thread can start given stride/size of target. + // + + if (!p->vTargetStates[iTarget].CanStart()) + { + PrintError("The file is too small. File: '%s' relative thread %u: file size: %I64u, base offset: %I64u, thread stride: %I64u, block size: %u\n", + pTarget->GetPath().c_str(), + p->ulRelativeThreadNo, + fsize, + pTarget->GetBaseFileOffsetInBytes(), + pTarget->GetThreadStrideInBytes(), + pTarget->GetBlockSizeInBytes()); + fOk = false; + goto cleanup; + } + } + + PrintVerbose(p->pProfile->GetVerbose(), "thread %u: file '%s' relative thread %u (random seed: %u)\n", + p->ulThreadNo, + pTarget->GetPath().c_str(), + p->ulRelativeThreadNo, + p->ulRandSeed); + + if (pTarget->GetRandomRatio() > 0) + { + PrintVerbose(p->pProfile->GetVerbose(), "thread %u: %u%% random IO\n", + p->ulThreadNo, + pTarget->GetRandomRatio()); + } + else + { + PrintVerbose(p->pProfile->GetVerbose(), "thread %u: %ssequential IO\n", + p->ulThreadNo, + pTarget->GetUseInterlockedSequential() ? "interlocked ":""); + } + + // allocate memory for a data buffer + if (!p->AllocateAndFillBufferForTarget(*pTarget)) + { + PrintError("ERROR: Could not allocate a buffer for target '%s'. Error code: 0x%x\n", pTarget->GetPath().c_str(), GetLastError()); + fOk = false; + goto cleanup; + } + + // initialize memory mapped views of files + if (pTarget->GetMemoryMappedIoMode() == MemoryMappedIoMode::On) + { + NTSTATUS status; + PVOID nvToken; + + pTarget->SetMappedViewFileHandle(hFile); + if (!p->InitializeMappedViewForTarget(*pTarget, dwDesiredAccess)) + { + PrintError("ERROR: Could not map view for target '%s'. Error code: 0x%x\n", pTarget->GetPath().c_str(), GetLastError()); + fOk = false; + goto cleanup; + } + + if (pTarget->GetWriteThroughMode() == WriteThroughMode::On && nullptr == g_pfnRtlCopyMemoryNonTemporal) + { + PrintError("ERROR: Windows runtime environment does not support the non-temporal memory copy API for target '%s'.\n", pTarget->GetPath().c_str()); + fOk = false; + goto cleanup; + } + + if ((pTarget->GetMemoryMappedIoFlushMode() == MemoryMappedIoFlushMode::NonVolatileMemory) || (pTarget->GetMemoryMappedIoFlushMode() == MemoryMappedIoFlushMode::NonVolatileMemoryNoDrain)) + { + // RtlGetNonVolatileToken() works only on DAX enabled PMEM devices. + if (g_pfnRtlGetNonVolatileToken != nullptr && g_pfnRtlFreeNonVolatileToken != nullptr) + { + status = g_pfnRtlGetNonVolatileToken(pTarget->GetMappedView(), (SIZE_T) pTarget->GetFileSize(), &nvToken); + if (!NT_SUCCESS(status)) + { + PrintError("ERROR: Could not get non-volatile token for target '%s'. Error code: 0x%x\n", pTarget->GetPath().c_str(), GetLastError()); + fOk = false; + goto cleanup; + } + pTarget->SetMemoryMappedIoNvToken(nvToken); + } + else + { + PrintError("ERROR: Windows runtime environment does not support the non-volatile memory flushing APIs for target '%s'.\n", pTarget->GetPath().c_str()); + fOk = false; + goto cleanup; + } + } + } + + iTarget++; + } + + // TODO: copy parameters for better memory locality? + // TODO: tell the main thread we're ready + + p->pResults->vTargetResults.clear(); + p->pResults->vTargetResults.resize(p->vTargets.size()); + + for (size_t i = 0; i < p->vTargets.size(); i++) + { + p->pResults->vTargetResults[i].sPath = p->vTargets[i].GetPath(); + p->pResults->vTargetResults[i].ullFileSize = p->vTargetStates[i].TargetSize(); + + if(fCalculateIopsStdDev) + { + p->pResults->vTargetResults[i].readBucketizer.Initialize(ioBucketDuration, expectedNumberOfBuckets); + p->pResults->vTargetResults[i].writeBucketizer.Initialize(ioBucketDuration, expectedNumberOfBuckets); + } + + // + // Copy effective distribution range to results for reporting (may be empty) + // + + p->pResults->vTargetResults[i].vDistributionRange = p->vTargetStates[i]._vDistributionRange; + } + + // + // fill the IORequest structures + // + + p->vIORequest.clear(); + + if (p->pTimeSpan->GetThreadCount() != 0 && + p->pTimeSpan->GetRequestCount() != 0) + { + p->vIORequest.resize(cIORequests, IORequest(p->pRand)); + + for (UINT32 iIORequest = 0; iIORequest < cIORequests; iIORequest++) + { + p->vIORequest[iIORequest].SetRequestIndex(iIORequest); + + for (unsigned int iFile = 0; iFile < p->vTargets.size(); iFile++) + { + Target *pTarget = &p->vTargets[iFile]; + const vector vThreadTargets = pTarget->GetThreadTargets(); + UINT32 ulWeight = pTarget->GetWeight(); + + for (UINT32 iThreadTarget = 0; iThreadTarget < vThreadTargets.size(); iThreadTarget++) + { + if (vThreadTargets[iThreadTarget].GetThread() == p->ulRelativeThreadNo) + { + if (vThreadTargets[iThreadTarget].GetWeight() != 0) + { + ulWeight = vThreadTargets[iThreadTarget].GetWeight(); + } + break; + } + } + + // + // Parallel async is not supported with -O for exactly this reason, + // and is validated in the profile before reaching here. Document this + // with the assert in comparison to the code in the non-O case below. + // Parallel depends on the IORequest being for a single file only (the + // seq offset is in the IORequest itself). + // + + assert(pTarget->GetUseParallelAsyncIO() == false); + + p->vIORequest[iIORequest].AddTarget(pTarget, ulWeight); + } + } + } + else + { + for (unsigned int iFile = 0; iFile < p->vTargets.size(); iFile++) + { + Target *pTarget = &p->vTargets[iFile]; + + for (DWORD iRequest = 0; iRequest < pTarget->GetRequestCount(); ++iRequest) + { + IORequest ioRequest(p->pRand); + ioRequest.AddTarget(pTarget, 1); + ioRequest.SetRequestIndex(iRequest); + if (pTarget->GetUseParallelAsyncIO()) + { + p->vTargetStates[iFile].InitializeParallelAsyncIORequest(ioRequest); + } + + p->vIORequest.push_back(ioRequest); + } + } + } + + // + // fill the throughput meter structures + // + size_t cTargets = p->vTargets.size(); + bool fUseThrougputMeter = false; + for (size_t i = 0; i < cTargets; i++) + { + ThroughputMeter throughputMeter; + Target *pTarget = &p->vTargets[i]; + DWORD dwBurstSize = pTarget->GetBurstSize(); + if (p->pTimeSpan->GetThreadCount() > 0) + { + if (pTarget->GetThreadTargets().size() == 0) + { + dwBurstSize /= p->pTimeSpan->GetThreadCount(); + } + else + { + dwBurstSize /= (DWORD)pTarget->GetThreadTargets().size(); + } + } + else + { + dwBurstSize /= pTarget->GetThreadsPerFile(); + } + + if (pTarget->GetThroughputInBytesPerMillisecond() > 0 || pTarget->GetThinkTime() > 0) + { + fUseThrougputMeter = true; + throughputMeter.Start(pTarget->GetThroughputInBytesPerMillisecond(), pTarget->GetBlockSizeInBytes(), pTarget->GetThinkTime(), dwBurstSize); + } + + p->vThroughputMeters.push_back(throughputMeter); + } + + if (!fUseThrougputMeter) + { + p->vThroughputMeters.clear(); + } + + //FUTURE EXTENSION: enable asynchronous I/O even if only 1 outstanding I/O per file (requires another parameter) + if (cIORequests == 1 || fAllMappedIo) + { + //synchronous IO - no setup needed + } + else if (p->pTimeSpan->GetCompletionRoutines() && !fAnyMappedIo) + { + //in case of completion routines hEvent field is not used, + //so we can use it to pass a pointer to the thread parameters + for (UINT32 iIORequest = 0; iIORequest < cIORequests; iIORequest++) { + OVERLAPPED *pOverlapped; + + pOverlapped = p->vIORequest[iIORequest].GetOverlapped(); + pOverlapped->hEvent = (HANDLE)p; + } + } + else + { + // + // create IO completion port if not doing completion routines or synchronous IO + // + for (unsigned int i = 0; i < vhUniqueHandles.size(); i++) + { + hCompletionPort = CreateIoCompletionPort(vhUniqueHandles[i], hCompletionPort, 0, 1); + if (nullptr == hCompletionPort) + { + PrintError("unable to create IO completion port (error code: %u)\n", GetLastError()); + fOk = false; + goto cleanup; + } + } + } + + // + // wait for a signal to start + // + PrintVerbose(p->pProfile->GetVerbose(), "thread %u: waiting for a signal to start\n", p->ulThreadNo); + if( WAIT_FAILED == WaitForSingleObject(p->hStartEvent, INFINITE) ) + { + PrintError("Waiting for a signal to start failed (error code: %u)\n", GetLastError()); + fOk = false; + goto cleanup; + } + PrintVerbose(p->pProfile->GetVerbose(), "thread %u: received signal to start\n", p->ulThreadNo); + + //check if everything is ok + if (g_bError) + { + fOk = false; + goto cleanup; + } + + //error handling and memory freeing is done in doWorkUsingIOCompletionPorts and doWorkUsingCompletionRoutines + if (cIORequests == 1 || fAllMappedIo) + { + // use synchronous IO (it will also clse the event) + if (!doWorkUsingSynchronousIO(p)) + { + fOk = false; + goto cleanup; + } + } + else if (!p->pTimeSpan->GetCompletionRoutines() || fAnyMappedIo) + { + // use IO Completion Ports (it will also close the I/O completion port) + if (!doWorkUsingIOCompletionPorts(p, hCompletionPort)) + { + fOk = false; + goto cleanup; + } + } + else + { + //use completion routines + if (!doWorkUsingCompletionRoutines(p)) + { + fOk = false; + goto cleanup; + } + } + + assert(!g_bError); // at this point we shouldn't be seeing initialization error + + // save results + +cleanup: + if (!fOk) + { + g_bThreadError = TRUE; + } + + // free memory allocated with VirtualAlloc + for (auto i = p->vpDataBuffers.begin(); i != p->vpDataBuffers.end(); i++) + { + if (nullptr != *i) + { +#pragma prefast(suppress:6001, "Prefast does not understand this vector will only contain validly allocated buffer pointers") + VirtualFree(*i, 0, MEM_RELEASE); + } + } + + // free NV tokens + for (auto i = p->vTargets.begin(); i != p->vTargets.end(); i++) + { + if (i->GetMemoryMappedIoNvToken() != nullptr && g_pfnRtlFreeNonVolatileToken != nullptr) + { + g_pfnRtlFreeNonVolatileToken(i->GetMemoryMappedIoNvToken()); + i->SetMemoryMappedIoNvToken(nullptr); + } + } + + // close files + for (auto i = vhUniqueHandles.begin(); i != vhUniqueHandles.end(); i++) + { + CloseHandle(*i); + } + + // close completion ports + if (hCompletionPort != nullptr) + { + CloseHandle(hCompletionPort); + } + + delete p->pRand; + delete p; + + // notify master thread that we've finished + InterlockedDecrement(&g_lRunningThreadsCount); + + return fOk ? 1 : 0; +} + +/*****************************************************************************/ +struct ETWSessionInfo IORequestGenerator::_GetResultETWSession(const EVENT_TRACE_PROPERTIES *pTraceProperties) const +{ + struct ETWSessionInfo session = {}; + if (nullptr != pTraceProperties) + { + session.lAgeLimit = pTraceProperties->AgeLimit; + session.ulBufferSize = pTraceProperties->BufferSize; + session.ulBuffersWritten = pTraceProperties->BuffersWritten; + session.ulEventsLost = pTraceProperties->EventsLost; + session.ulFlushTimer = pTraceProperties->FlushTimer; + session.ulFreeBuffers = pTraceProperties->FreeBuffers; + session.ulLogBuffersLost = pTraceProperties->LogBuffersLost; + session.ulMaximumBuffers = pTraceProperties->MaximumBuffers; + session.ulMinimumBuffers = pTraceProperties->MinimumBuffers; + session.ulNumberOfBuffers = pTraceProperties->NumberOfBuffers; + session.ulRealTimeBuffersLost = pTraceProperties->RealTimeBuffersLost; + } + return session; +} + +DWORD IORequestGenerator::_CreateDirectoryPath(const char *pszPath) const +{ + char *c = nullptr; //variable used to browse the path + char dirPath[MAX_PATH]; //copy of the path (it will be altered) + + //only support absolute paths that specify the drive letter + if (pszPath[0] == '\0' || pszPath[1] != ':') + { + return ERROR_NOT_SUPPORTED; + } + + if (strcpy_s(dirPath, _countof(dirPath), pszPath) != 0) + { + return ERROR_BUFFER_OVERFLOW; + } + + c = dirPath; + while('\0' != *c) + { + if ('\\' == *c) + { + //skip the first one as it will be the drive name + if (c-dirPath >= 3) + { + *c = '\0'; + //create directory if it doesn't exist + if (GetFileAttributes(dirPath) == INVALID_FILE_ATTRIBUTES) + { + if (CreateDirectory(dirPath, NULL) == FALSE) + { + return GetLastError(); + } + } + *c = L'\\'; + } + } + + c++; + } + + return ERROR_SUCCESS; +} + +/*****************************************************************************/ +// create a file of the given size +// +bool IORequestGenerator::_CreateFile(UINT64 ullFileSize, const char *pszFilename, bool fZeroBuffers, bool fVerbose) const +{ + bool fSlowWrites = false; + PrintVerbose(fVerbose, "Creating file '%s' of size %I64u.\n", pszFilename, ullFileSize); + + //enable SE_MANAGE_VOLUME_NAME privilege, required to set valid size of a file + if (!SetPrivilege(SE_MANAGE_VOLUME_NAME, "WARNING:")) + { + PrintError("WARNING: Could not set privileges for setting valid file size; will use a slower method of preparing the file\n", GetLastError()); + fSlowWrites = true; + } + + // there are various forms of paths we do not support creating subdir hierarchies + // for - relative and unc paths specifically. this is fine, and not neccesary to + // warn about. we can add support in the future. + DWORD dwError = _CreateDirectoryPath(pszFilename); + if (dwError != ERROR_SUCCESS && dwError != ERROR_NOT_SUPPORTED) + { + PrintError("WARNING: Could not create intermediate directory (error code: %u)\n", dwError); + } + + // create handle to the file + HANDLE hFile = CreateFile(pszFilename, + GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, + nullptr, + CREATE_ALWAYS, + FILE_ATTRIBUTE_NORMAL, + nullptr); + if (INVALID_HANDLE_VALUE == hFile) + { + PrintError("Could not create the file (error code: %u)\n", GetLastError()); + return false; + } + + if (ullFileSize > 0) + { + LARGE_INTEGER li; + li.QuadPart = ullFileSize; + + LARGE_INTEGER liNewFilePointer; + + if (!SetFilePointerEx(hFile, li, &liNewFilePointer, FILE_BEGIN)) + { + PrintError("Could not set file pointer during file creation when extending file (error code: %u)\n", GetLastError()); + CloseHandle(hFile); + return false; + } + if (liNewFilePointer.QuadPart != li.QuadPart) + { + PrintError("File pointer improperly moved during file creation when extending file\n"); + CloseHandle(hFile); + return false; + } + + //extends file (warning! this is a kind of "reservation" of space; valid size of the file is still 0!) + if (!SetEndOfFile(hFile)) + { + PrintError("Error setting end of file (error code: %u)\n", GetLastError()); + CloseHandle(hFile); + return false; + } + //try setting valid size of the file (privileges for that are enabled before CreateFile) + if (!fSlowWrites && !SetFileValidData(hFile, ullFileSize)) + { + PrintError("WARNING: Could not set valid file size (error code: %u); trying a slower method of filling the file" + " (this does not affect performance, just makes the test preparation longer)\n", + GetLastError()); + fSlowWrites = true; + } + + //if setting valid size couldn't be performed, fill in the file by simply writing to it (slower) + if (fSlowWrites) + { + li.QuadPart = 0; + if (!SetFilePointerEx(hFile, li, &liNewFilePointer, FILE_BEGIN)) + { + PrintError("Could not set file pointer during file creation (error code: %u)\n", GetLastError()); + CloseHandle(hFile); + return false; + } + if (liNewFilePointer.QuadPart != li.QuadPart) + { + PrintError("File pointer improperly moved during file creation\n"); + CloseHandle(hFile); + return false; + } + + UINT32 ulBufSize; + UINT64 ullRemainSize; + + ulBufSize = 1024*1024; + if (ullFileSize < (UINT64)ulBufSize) + { + ulBufSize = (UINT32)ullFileSize; + } + + vector vBuf(ulBufSize); + for (UINT32 i=0; i 0) + { + DWORD dwBytesWritten; + if ((UINT64)ulBufSize > ullRemainSize) + { + ulBufSize = (UINT32)ullRemainSize; + } + + if (!WriteFile(hFile, &vBuf[0], ulBufSize, &dwBytesWritten, NULL)) + { + PrintError("Error while writng during file creation (error code: %u)\n", GetLastError()); + CloseHandle(hFile); + return false; + } + + if (dwBytesWritten != ulBufSize) + { + PrintError("Improperly written data during file creation\n"); + CloseHandle(hFile); + return false; + } + + ullRemainSize -= ulBufSize; + } + } + } + + //if compiled with debug support, check file size +#ifndef NDEBUG + LARGE_INTEGER li; + if( GetFileSizeEx(hFile, &li) ) + { + assert(li.QuadPart == (LONGLONG)ullFileSize); + } +#endif + + CloseHandle(hFile); + + return true; +} + +/*****************************************************************************/ +void IORequestGenerator::_TerminateWorkerThreads(vector& vhThreads) const +{ + for (UINT32 x = 0; x < vhThreads.size(); ++x) + { + assert(NULL != vhThreads[x]); +#pragma warning( push ) +#pragma warning( disable : 6258 ) + if (!TerminateThread(vhThreads[x], 0)) + { + PrintError("Warning: unable to terminate worker thread %u\n", x); + } +#pragma warning( pop ) + } +} +/*****************************************************************************/ +void IORequestGenerator::_AbortWorkerThreads(HANDLE hStartEvent, vector& vhThreads) const +{ + assert(NULL != hStartEvent); + + if (NULL == hStartEvent) + { + return; + } + + g_bError = TRUE; + if (!SetEvent(hStartEvent)) + { + PrintError("Error signaling start event\n"); + _TerminateWorkerThreads(vhThreads); + } + else + { + //FUTURE EXTENSION: maximal timeout may be added here (and below) + while (g_lRunningThreadsCount > 0) + { + Sleep(100); + } + } +} + +/*****************************************************************************/ +bool IORequestGenerator::_StopETW(bool fUseETW, TRACEHANDLE hTraceSession) const +{ + bool fOk = true; + if (fUseETW) + { + PEVENT_TRACE_PROPERTIES pETWSession = StopETWSession(hTraceSession); + if (nullptr == pETWSession) + { + PrintError("Error stopping ETW session\n"); + fOk = false; + } + else + { + free(pETWSession); + } + } + return fOk; +} + +/*****************************************************************************/ +// initializes all global parameters +// +void IORequestGenerator::_InitializeGlobalParameters() +{ + g_lRunningThreadsCount = 0; //number of currently running worker threads + g_bRun = TRUE; //used for letting threads know that they should stop working + + g_bThreadError = FALSE; //true means that an error has occured in one of the threads + g_bTracing = FALSE; //true means that ETW is turned on + + _hNTDLL = nullptr; //handle to ntdll.dll + g_bError = FALSE; //true means there was fatal error during intialization and threads shouldn't perform their work +} + +bool IORequestGenerator::_PrecreateFiles(Profile& profile) const +{ + bool fOk = true; + + if (profile.GetPrecreateFiles() != PrecreateFiles::None) + { + vector vFilesToCreate = _GetFilesToPrecreate(profile); + vector vCreatedFiles; + for (auto file : vFilesToCreate) + { + fOk = _CreateFile(file.ullFileSize, file.sPath.c_str(), file.fZeroWriteBuffers, profile.GetVerbose()); + if (!fOk) + { + break; + } + vCreatedFiles.push_back(file.sPath); + } + + if (fOk) + { + profile.MarkFilesAsPrecreated(vCreatedFiles); + } + } + + return fOk; +} + +bool IORequestGenerator::GenerateRequests(Profile& profile, IResultParser& resultParser, struct Synchronization *pSynch) +{ + bool fOk = _PrecreateFiles(profile); + if (fOk) + { + const vector& vTimeSpans = profile.GetTimeSpans(); + vector vResults(vTimeSpans.size()); + for (size_t i = 0; fOk && (i < vTimeSpans.size()); i++) + { + PrintVerbose(profile.GetVerbose(), "Generating requests for timespan %u.\n", i + 1); + fOk = _GenerateRequestsForTimeSpan(profile, vTimeSpans[i], vResults[i], pSynch); + } + + // TODO: show results only for timespans that succeeded + SystemInformation system; + EtwResultParser::ParseResults(vResults); + string sResults = resultParser.ParseResults(profile, system, vResults); + printf("%s", sResults.c_str()); + fflush(stdout); + } + + return fOk; +} + +bool IORequestGenerator::_GenerateRequestsForTimeSpan(const Profile& profile, const TimeSpan& timeSpan, Results& results, struct Synchronization *pSynch) +{ + //FUTURE EXTENSION: add new I/O capabilities presented in Longhorn + //FUTURE EXTENSION: add a check if the folder is compressed (cache is always enabled in case of compressed folders) + + //check if I/O request generator is already running + LONG lGenState = InterlockedExchange(&g_lGeneratorRunning, 1); + if (1 == lGenState) + { + PrintError("FATAL ERROR: I/O Request Generator already running\n"); + return false; + } + + //initialize all global parameters (in case of second run, after the first one is finished) + _InitializeGlobalParameters(); + + HANDLE hStartEvent = nullptr; // start event (used to inform the worker threads that they should start the work) + HANDLE hEndEvent = nullptr; // end event (used only in case of completin routines (not for IO Completion Ports)) + + memset(&g_EtwEventCounters, 0, sizeof(struct ETWEventCounters)); // reset all etw event counters + + bool fUseETW = profile.GetEtwEnabled(); //true if user wants ETW + + // + // load dlls + // + assert(nullptr == _hNTDLL); + if (!_LoadDLLs()) + { + PrintError("Error loading NtQuerySystemInformation\n"); + return false; + } + + //FUTURE EXTENSION: check for conflicts in alignment (when cache is turned off only sector aligned I/O are permitted) + //FUTURE EXTENSION: check if file sizes are enough to have at least first requests not wrapping around + + Random r; + vector vTargets = timeSpan.GetTargets(); + // allocate memory for random data write buffers + for (auto i = vTargets.begin(); i != vTargets.end(); i++) + { + if ((i->GetRandomDataWriteBufferSize() > 0) && !i->AllocateAndFillRandomDataWriteBuffer(&r)) + { + return false; + } + } + + // check if user wanted to create a file + for (auto i = vTargets.begin(); i != vTargets.end(); i++) + { + if ((i->GetFileSize() > 0) && (i->GetPrecreated() == false)) + { + string str = i->GetPath(); + if (str.empty()) + { + PrintError("You have to provide a filename\n"); + return false; + } + + //skip physical drives and partitions + if ('#' == str[0] || (':' == str[1] && '\0' == str[2])) + { + continue; + } + + //create only regular files + if (!_CreateFile(i->GetFileSize(), str.c_str(), i->GetZeroWriteBuffers(), profile.GetVerbose())) + { + return false; + } + } + } + + // get thread count + UINT32 cThreads = timeSpan.GetThreadCount(); + if (cThreads < 1) + { + for (auto i = vTargets.begin(); i != vTargets.end(); i++) + { + cThreads += i->GetThreadsPerFile(); + } + } + + // allocate memory for thread handles + vector vhThreads(cThreads); + + // + // allocate memory for performance counters + // + vector vPerfInit(g_SystemInformation.processorTopology._ulProcessorCount); + vector vPerfDone(g_SystemInformation.processorTopology._ulProcessorCount); + vector vPerfDiff(g_SystemInformation.processorTopology._ulProcessorCount); + + // + // create start event + // + hStartEvent = CreateEvent(NULL, TRUE, FALSE, ""); + if (NULL == hStartEvent) + { + PrintError("Error creating the start event\n"); + return false; + } + + // + // create end event + // + if (timeSpan.GetCompletionRoutines()) + { + hEndEvent = CreateEvent(NULL, TRUE, FALSE, ""); + if (NULL == hEndEvent) + { + PrintError("Error creating the end event\n"); + return false; + } + } + + // + // set to high priority to ensure the controller thread gets to run immediately + // when signalled. + // + + SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST); + + // + // create the threads + // + + g_bRun = TRUE; + + // gather affinity information, and move to the first active processor + const auto& vAffinity = timeSpan.GetAffinityAssignments(); + WORD wGroupCtr = 0; + BYTE bProcCtr = 0; + g_SystemInformation.processorTopology.GetActiveGroupProcessor(wGroupCtr, bProcCtr, false); + + volatile bool fAccountingOn = false; + UINT64 ullStartTime; //start time + UINT64 ullTimeDiff; //elapsed test time (in units returned by QueryPerformanceCounter) + vector vullSharedSequentialOffsets(vTargets.size(), 0); + + results.vThreadResults.clear(); + results.vThreadResults.resize(cThreads); + for (UINT32 iThread = 0; iThread < cThreads; ++iThread) + { + PrintVerbose(profile.GetVerbose(), "creating thread %u\n", iThread); + ThreadParameters *cookie = new ThreadParameters(); // threadFunc is going to free the memory + if (nullptr == cookie) + { + PrintError("FATAL ERROR: could not allocate memory\n"); + _AbortWorkerThreads(hStartEvent, vhThreads); + return false; + } + + // each thread has a different random seed + Random *pRand = new Random(timeSpan.GetRandSeed() + iThread); + if (nullptr == pRand) + { + PrintError("FATAL ERROR: could not allocate memory\n"); + _AbortWorkerThreads(hStartEvent, vhThreads); + delete cookie; + return false; + } + + UINT32 ulRelativeThreadNo = 0; + + if (timeSpan.GetThreadCount() > 0) + { + // fixed thread mode: threads operate on specified files + // and receive the entire seq index array. + // relative thread number is the same as thread number. + cookie->pullSharedSequentialOffsets = &vullSharedSequentialOffsets[0]; + ulRelativeThreadNo = iThread; + for (auto i = vTargets.begin(); + i != vTargets.end(); + i++) + { + const vector vThreadTargets = i->GetThreadTargets(); + + // no thread targets specified - add to all threads + if (vThreadTargets.size() == 0) + { + cookie->vTargets.push_back(*i); + } + else + { + // check if the target should be added to the current thread + for (UINT32 iThreadTarget = 0; iThreadTarget < vThreadTargets.size(); iThreadTarget++) + { + if (vThreadTargets[iThreadTarget].GetThread() == iThread) + { + // confirm copy constructor? + cookie->vTargets.push_back(*i); + break; + } + } + } + } + } + else + { + size_t cAssignedThreads = 0; + size_t cBaseThread = 0; + auto psi = vullSharedSequentialOffsets.begin(); + for (auto i = vTargets.begin(); + i != vTargets.end(); + i++, psi++) + { + // per-file thread mode: groups of threads operate on individual files + // and receive the specific seq index for their file (note: singular). + // loop up through the targets to assign thread n to the appropriate file. + // relative thread number is file-relative, so keep track of the base + // thread number for the file and calculate relative to that. + // + // ex: two files, two threads per file + // t0: rt0 for f0 (cAssigned = 2, cBase = 0) + // t1: rt1 for f0 (cAssigned = 2, cBase = 0) + // t2: rt0 for f1 (cAssigned = 4, cBase = 2) + // t3: rt1 for f1 (cAssigned = 4, cBase = 2) + + cAssignedThreads += i->GetThreadsPerFile(); + if (iThread < cAssignedThreads) + { + // confirm copy constructor? + cookie->vTargets.push_back(*i); + cookie->pullSharedSequentialOffsets = &(*psi); + ulRelativeThreadNo = (iThread - cBaseThread) % i->GetThreadsPerFile(); + + PrintVerbose(profile.GetVerbose(), "thread %u is relative thread %u for %s\n", iThread, ulRelativeThreadNo, i->GetPath().c_str()); + break; + } + cBaseThread += i->GetThreadsPerFile(); + } + } + + cookie->pProfile = &profile; + cookie->pTimeSpan = &timeSpan; + cookie->hStartEvent = hStartEvent; + cookie->hEndEvent = hEndEvent; + cookie->ulThreadNo = iThread; + cookie->ulRelativeThreadNo = ulRelativeThreadNo; + cookie->pfAccountingOn = &fAccountingOn; + cookie->pullStartTime = &ullStartTime; + cookie->ulRandSeed = timeSpan.GetRandSeed() + iThread; // each thread has a different random seed + cookie->pRand = pRand; + + //Set thread group and proc affinity + + // Default: Round robin cpus in order of groups, starting at group 0. + // Fill each group before moving to next. + if (vAffinity.size() == 0) + { + cookie->wGroupNum = wGroupCtr; + cookie->bProcNum = bProcCtr; + + // advance to next active + g_SystemInformation.processorTopology.GetActiveGroupProcessor(wGroupCtr, bProcCtr, true); + } + // Assigned affinity. Round robin through the assignment list. + else + { + ULONG i = iThread % vAffinity.size(); + + cookie->wGroupNum = vAffinity[i].wGroup; + cookie->bProcNum = vAffinity[i].bProc; + } + + //create thread + cookie->pResults = &results.vThreadResults[iThread]; + + InterlockedIncrement(&g_lRunningThreadsCount); + DWORD dwThreadId; + HANDLE hThread = CreateThread(NULL, 64 * 1024, threadFunc, cookie, 0, &dwThreadId); + if (NULL == hThread) + { + //in case of error terminate running worker threads + PrintError("ERROR: unable to create thread (error code: %u)\n", GetLastError()); + InterlockedDecrement(&g_lRunningThreadsCount); + _AbortWorkerThreads(hStartEvent, vhThreads); + delete pRand; + delete cookie; + return false; + } + + //store handle to the thread + vhThreads[iThread] = hThread; + } + + if (STRUCT_SYNCHRONIZATION_SUPPORTS(pSynch, hStartEvent) && (NULL != pSynch->hStartEvent)) + { + if (WAIT_OBJECT_0 != WaitForSingleObject(pSynch->hStartEvent, INFINITE)) + { + PrintError("Error during WaitForSingleObject\n"); + _AbortWorkerThreads(hStartEvent, vhThreads); + return false; + } + } + + // + // get cycle count (it will be used to calculate actual work time) + // + DWORD dwWaitStatus = 0; + + //bAccountingOn = FALSE; // clear the accouning flag so that threads didn't count what they do while in the warmup phase + + BOOL bSynchStop = STRUCT_SYNCHRONIZATION_SUPPORTS(pSynch, hStopEvent) && (NULL != pSynch->hStopEvent); + BOOL bBreak = FALSE; + PEVENT_TRACE_PROPERTIES pETWSession = NULL; + + // + // send start signal + // + if (!SetEvent(hStartEvent)) + { + PrintError("Error signaling start event\n"); + // stopETW(bUseETW, hTraceSession); + _TerminateWorkerThreads(vhThreads); //FUTURE EXTENSION: timeout for worker threads + return false; + } + + // + // wait specified amount of time in each phase (warm up, test, cool down) + // + if (timeSpan.GetWarmup() > 0) + { + TraceLoggingActivity WarmActivity; + TraceLoggingWriteStart(WarmActivity, "Warm Up"); + PrintVerbose(profile.GetVerbose(), "starting warm up for %us...\n", timeSpan.GetWarmup()); + + if (bSynchStop) + { + assert(NULL != pSynch->hStopEvent); + dwWaitStatus = WaitForSingleObject(pSynch->hStopEvent, 1000 * timeSpan.GetWarmup()); + if (WAIT_OBJECT_0 != dwWaitStatus && WAIT_TIMEOUT != dwWaitStatus) + { + PrintError("Error during WaitForSingleObject\n"); + _TerminateWorkerThreads(vhThreads); + return false; + } + bBreak = (WAIT_TIMEOUT != dwWaitStatus); + } + else + { + Sleep(1000 * timeSpan.GetWarmup()); + } + + TraceLoggingWriteStop(WarmActivity, "Warm Up"); + } + + if (!bBreak) // proceed only if user didn't break the test + { + //FUTURE EXTENSION: starting ETW session shouldn't be done brutally here, should be done before warmup and here just a fast signal to start logging (see also stopping ETW session) + //FUTURE EXTENSION: put an ETW mark here, for easier parsing by external tools + + // + // start etw session + // + TRACEHANDLE hTraceSession = NULL; + if (fUseETW) + { + PrintVerbose(profile.GetVerbose(), "starting trace session\n"); + hTraceSession = StartETWSession(profile); + if (NULL == hTraceSession) + { + PrintError("Could not start ETW session\n"); + _TerminateWorkerThreads(vhThreads); + return false; + } + + if (NULL == CreateThread(NULL, 64 * 1024, etwThreadFunc, NULL, 0, NULL)) + { + PrintError("Warning: unable to create thread for ETW session\n"); + _TerminateWorkerThreads(vhThreads); + return false; + } + PrintVerbose(profile.GetVerbose(), "tracing events\n"); + } + + // + // notify the front-end that the test is about to start; + // do it before starting timing in order not to perturb measurements + // + if (STRUCT_SYNCHRONIZATION_SUPPORTS(pSynch, pfnCallbackTestStarted) && (NULL != pSynch->pfnCallbackTestStarted)) + { + pSynch->pfnCallbackTestStarted(); + } + + // + // read performance counters + // + if (_GetSystemPerfInfo(vPerfInit, profile.GetVerbose()) == FALSE) + { + PrintError("Error reading performance counters\n"); + _StopETW(fUseETW, hTraceSession); + _TerminateWorkerThreads(vhThreads); + return false; + } + + TraceLoggingActivity RunActivity; + TraceLoggingWriteStart(RunActivity, "Run Time"); + + PrintVerbose(profile.GetVerbose(), "starting measurements for %us...\n", timeSpan.GetDuration()); + + //get cycle count (it will be used to calculate actual work time) + ullStartTime = PerfTimer::GetTime(); + fAccountingOn = true; + + assert(timeSpan.GetDuration() > 0); + if (bSynchStop) + { + assert(NULL != pSynch->hStopEvent); + dwWaitStatus = WaitForSingleObject(pSynch->hStopEvent, 1000 * timeSpan.GetDuration()); + if (WAIT_OBJECT_0 != dwWaitStatus && WAIT_TIMEOUT != dwWaitStatus) + { + PrintError("Error during WaitForSingleObject\n"); + _StopETW(fUseETW, hTraceSession); + _TerminateWorkerThreads(vhThreads); //FUTURE EXTENSION: worker threads should have a chance to free allocated memory (see also other places calling terminateWorkerThreads()) + return FALSE; + } + bBreak = (WAIT_TIMEOUT != dwWaitStatus); + } + else + { + Sleep(1000 * timeSpan.GetDuration()); + } + + //get cycle count and perf counters + fAccountingOn = false; + ullTimeDiff = PerfTimer::GetTime() - ullStartTime; + PrintVerbose(profile.GetVerbose(), "stopped measurements, total measured time %.2lfs...\n", PerfTimer::PerfTimeToSeconds(ullTimeDiff)); + + TraceLoggingWriteStop(RunActivity, "Run Time"); + + if (_GetSystemPerfInfo(vPerfDone, profile.GetVerbose()) == FALSE) + { + PrintError("Error getting performance counters\n"); + _StopETW(fUseETW, hTraceSession); + _TerminateWorkerThreads(vhThreads); + return false; + } + + // + // notify the front-end that the test has just finished; + // do it after stopping timing in order not to perturb measurements + // + if (STRUCT_SYNCHRONIZATION_SUPPORTS(pSynch, pfnCallbackTestFinished) && (NULL != pSynch->pfnCallbackTestFinished)) + { + pSynch->pfnCallbackTestFinished(); + } + + // + // stop etw session + // + if (fUseETW) + { + PrintVerbose(profile.GetVerbose(), "stopping ETW session\n"); + pETWSession = StopETWSession(hTraceSession); + if (NULL == pETWSession) + { + PrintError("Error stopping ETW session\n"); + return false; + } + } + } + else + { + ullTimeDiff = 0; // mark that no test was run + } + + if ((timeSpan.GetCooldown() > 0) && !bBreak) + { + TraceLoggingActivity CoolActivity; + TraceLoggingWriteStart(CoolActivity, "Cool Down"); + PrintVerbose(profile.GetVerbose(), "starting cool down for %us...\n", timeSpan.GetCooldown()); + + if (bSynchStop) + { + assert(NULL != pSynch->hStopEvent); + dwWaitStatus = WaitForSingleObject(pSynch->hStopEvent, 1000 * timeSpan.GetCooldown()); + if (WAIT_OBJECT_0 != dwWaitStatus && WAIT_TIMEOUT != dwWaitStatus) + { + PrintError("Error during WaitForSingleObject\n"); + // stopETW(bUseETW, hTraceSession); + _TerminateWorkerThreads(vhThreads); + return false; + } + } + else + { + Sleep(1000 * timeSpan.GetCooldown()); + } + + TraceLoggingWriteStop(CoolActivity, "Cool Down"); + } + PrintVerbose(profile.GetVerbose(), "finished test...\n"); + + // + // signal the threads to finish + // + g_bRun = FALSE; + if (timeSpan.GetCompletionRoutines()) + { + if (!SetEvent(hEndEvent)) + { + PrintError("Error signaling end event\n"); + // stopETW(bUseETW, hTraceSession); + return false; + } + } + + // + // wait till all of the threads finish + // +#pragma warning( push ) +#pragma warning( disable : 28112 ) + while (g_lRunningThreadsCount > 0) + { + Sleep(10); //FUTURE EXTENSION: a timeout should be implemented + } +#pragma warning( pop ) + + + //check if there has been an error during threads execution + if (g_bThreadError) + { + PrintError("There has been an error during threads execution\n"); + return false; + } + + // + // close events' handles + // + CloseHandle(hStartEvent); + hStartEvent = NULL; + + if (NULL != hEndEvent) + { + CloseHandle(hEndEvent); + hEndEvent = NULL; + } + //FUTURE EXTENSION: hStartEvent and hEndEvent should be closed in case of error too + + // + // compute time spent by each cpu + // + for (DWORD p = 0; p < g_SystemInformation.processorTopology._ulProcessorCount; ++p) + { + assert(vPerfDone[p].IdleTime.QuadPart >= vPerfInit[p].IdleTime.QuadPart); + assert(vPerfDone[p].KernelTime.QuadPart >= vPerfInit[p].KernelTime.QuadPart); + assert(vPerfDone[p].UserTime.QuadPart >= vPerfInit[p].UserTime.QuadPart); + + vPerfDiff[p].IdleTime.QuadPart = vPerfDone[p].IdleTime.QuadPart - vPerfInit[p].IdleTime.QuadPart; + vPerfDiff[p].KernelTime.QuadPart = vPerfDone[p].KernelTime.QuadPart - vPerfInit[p].KernelTime.QuadPart; + vPerfDiff[p].UserTime.QuadPart = vPerfDone[p].UserTime.QuadPart - vPerfInit[p].UserTime.QuadPart; + + // + // Handle clock measurement jitter; if the difference is negative, set it to 0. This is usually seen + // as a -10000000 (full second of 100ns units) difference over very short runs. + // + // If the sum of kernel and user time is 0, treat it as a full idle with placeholder values. This provides + // a nonzero denominator for the CPU utilization calculation and avoids divide by zero -> INF results. + // Note that system clock convention is that kernel time includes idle time. + // + + if (vPerfDiff[p].IdleTime.QuadPart < 0) + { + PrintVerbose(profile.GetVerbose(), "time fixup: IdleTime < 0 @ %u : ticks %lld - %lld\n", p, vPerfDone[p].IdleTime.QuadPart, vPerfInit[p].IdleTime.QuadPart); + vPerfDiff[p].IdleTime.QuadPart = 0; + } + + if (vPerfDiff[p].KernelTime.QuadPart < 0) + { + PrintVerbose(profile.GetVerbose(), "time fixup: KernelTime < 0 @ %u : ticks %lld - %lld\n", p, vPerfDone[p].KernelTime.QuadPart, vPerfInit[p].KernelTime.QuadPart); + vPerfDiff[p].KernelTime.QuadPart = 0; + } + + if (vPerfDiff[p].UserTime.QuadPart < 0) + { + PrintVerbose(profile.GetVerbose(), "time fixup: UserTime < 0 @ %u : ticks %lld - %lld\n", p, vPerfDone[p].UserTime.QuadPart, vPerfInit[p].UserTime.QuadPart); + vPerfDiff[p].UserTime.QuadPart = 0; + } + + if (vPerfDiff[p].KernelTime.QuadPart + vPerfDiff[p].UserTime.QuadPart == 0) + { + PrintVerbose(profile.GetVerbose(), "time fixup: KernelTime+UserTime = 0 @ %u : ticks K (%lld - %lld) + U (%lld - %lld)\n", p, + vPerfDone[p].KernelTime.QuadPart, vPerfInit[p].KernelTime.QuadPart, + vPerfDone[p].UserTime.QuadPart, vPerfInit[p].UserTime.QuadPart); + + vPerfDiff[p].IdleTime.QuadPart = vPerfDiff[p].KernelTime.QuadPart = 1; + } + } + + // + // process results and pass them to the result parser + // + + // get processors perf. info + results.vSystemProcessorPerfInfo = vPerfDiff; + results.ullTimeCount = ullTimeDiff; + + // + // create structure containing etw results and properties + // + results.fUseETW = fUseETW; + if (fUseETW) + { + results.EtwEventCounters = g_EtwEventCounters; + results.EtwSessionInfo = _GetResultETWSession(pETWSession); + + // TODO: refactor to a separate function + results.EtwMask.bProcess = profile.GetEtwProcess(); + results.EtwMask.bThread = profile.GetEtwThread(); + results.EtwMask.bImageLoad = profile.GetEtwImageLoad(); + results.EtwMask.bDiskIO = profile.GetEtwDiskIO(); + results.EtwMask.bMemoryPageFaults = profile.GetEtwMemoryPageFaults(); + results.EtwMask.bMemoryHardFaults = profile.GetEtwMemoryHardFaults(); + results.EtwMask.bNetwork = profile.GetEtwNetwork(); + results.EtwMask.bRegistry = profile.GetEtwRegistry(); + results.EtwMask.bUsePagedMemory = profile.GetEtwUsePagedMemory(); + results.EtwMask.bUsePerfTimer = profile.GetEtwUsePerfTimer(); + results.EtwMask.bUseSystemTimer = profile.GetEtwUseSystemTimer(); + results.EtwMask.bUseCyclesCounter = profile.GetEtwUseCyclesCounter(); + + free(pETWSession); + } + + // free memory used by random data write buffers + for (auto i = vTargets.begin(); i != vTargets.end(); i++) + { + i->FreeRandomDataWriteBuffer(); + } + + // TODO: this won't catch error cases, which exit early + InterlockedExchange(&g_lGeneratorRunning, 0); + return true; +} + +vector IORequestGenerator::_GetFilesToPrecreate(const Profile& profile) const +{ + vector vFilesToCreate; + const vector& vTimeSpans = profile.GetTimeSpans(); + map> filesMap; + for (const auto& timeSpan : vTimeSpans) + { + vector vTargets(timeSpan.GetTargets()); + for (const auto& target : vTargets) + { + struct CreateFileParameters createFileParameters; + createFileParameters.sPath = target.GetPath(); + createFileParameters.ullFileSize = target.GetFileSize(); + createFileParameters.fZeroWriteBuffers = target.GetZeroWriteBuffers(); + + filesMap[createFileParameters.sPath].push_back(createFileParameters); + } + } + + PrecreateFiles filter = profile.GetPrecreateFiles(); + for (auto fileMapEntry : filesMap) + { + if (fileMapEntry.second.size() > 0) + { + UINT64 ullLastNonZeroSize = fileMapEntry.second[0].ullFileSize; + UINT64 ullMaxSize = fileMapEntry.second[0].ullFileSize; + bool fLastZeroWriteBuffers = fileMapEntry.second[0].fZeroWriteBuffers; + bool fHasZeroSizes = false; + bool fConstantSize = true; + bool fConstantZeroWriteBuffers = true; + for (auto file : fileMapEntry.second) + { + ullMaxSize = max(ullMaxSize, file.ullFileSize); + if (ullLastNonZeroSize == 0) + { + ullLastNonZeroSize = file.ullFileSize; + } + if (file.ullFileSize == 0) + { + fHasZeroSizes = true; + } + if ((file.ullFileSize != 0) && (file.ullFileSize != ullLastNonZeroSize)) + { + fConstantSize = false; + } + if (file.fZeroWriteBuffers != fLastZeroWriteBuffers) + { + fConstantZeroWriteBuffers = false; + } + if (file.ullFileSize != 0) + { + ullLastNonZeroSize = file.ullFileSize; + } + fLastZeroWriteBuffers = file.fZeroWriteBuffers; + } + + if (fConstantZeroWriteBuffers && ullMaxSize > 0) + { + struct CreateFileParameters file = fileMapEntry.second[0]; + file.ullFileSize = ullMaxSize; + if (filter == PrecreateFiles::UseMaxSize) + { + vFilesToCreate.push_back(file); + } + else if ((filter == PrecreateFiles::OnlyFilesWithConstantSizes) && fConstantSize && !fHasZeroSizes) + { + vFilesToCreate.push_back(file); + } + else if ((filter == PrecreateFiles::OnlyFilesWithConstantOrZeroSizes) && fConstantSize) + { + vFilesToCreate.push_back(file); + } + } + } + } + + return vFilesToCreate; } \ No newline at end of file diff --git a/IORequestGenerator/ThroughputMeter.cpp b/IORequestGenerator/ThroughputMeter.cpp index 6b3fcf2..726e740 100644 --- a/IORequestGenerator/ThroughputMeter.cpp +++ b/IORequestGenerator/ThroughputMeter.cpp @@ -27,6 +27,7 @@ SOFTWARE. */ +#include "Common.h" #include "ThroughputMeter.h" ThroughputMeter::ThroughputMeter(void) : @@ -100,8 +101,31 @@ DWORD ThroughputMeter::GetSleepTime(void) const DWORD ThroughputMeter::_GetThrottleTime(void) const { - ULONGLONG cbExpected = (GetTickCount64() - _ullStartTimestamp) * _cBytesPerMillisecond; - return cbExpected >= (_cbCompleted + _cbBlockSize) ? 0 : 1; + if ((g_ExperimentFlags & EXPERIMENT_TPUT_CALC) == 0) + { + ULONGLONG cbExpected = (GetTickCount64() - _ullStartTimestamp) * _cBytesPerMillisecond; + return cbExpected >= (_cbCompleted + _cbBlockSize) ? 0 : 1; + } + else + { + // prototype to calculate an actual sleep time + // under higher loads the ideal delay is likely in the microsecond range, but the minimum sleep is 1 ms + // however, at low rates it may be reasonable to calculate the delay and use it if > 1ms + + ULONGLONG elapsed = GetTickCount64() - _ullStartTimestamp; + ULONGLONG bytesNext = _cbCompleted + _cbBlockSize; + + if (elapsed * _cBytesPerMillisecond > bytesNext) + { + // below rate - no sleep + return 0; + } + + // above rate - sleep at least 1 ms + ULONGLONG sleepTarget = (bytesNext / _cBytesPerMillisecond) - elapsed; + + return max((DWORD)sleepTarget, 1); + } } void ThroughputMeter::Adjust(size_t cb) diff --git a/README.md b/README.md index 683e465..334c19f 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,27 @@ What's New? ## DISKSPD +# DISKSPD 2.2 6/3/2024 + +**NOTE:** changes to the asynchronous IO loop will require rebaselining results with queue depths greater than 1. +The new design drains the completion queue more aggressively, shrinking delays that impacted latency measurement +especially on faster storage targeting higher queue depths. Latency measurement is improved at a very small cost +to rates, as well as improving rates when latency measurement is not used (`-D` or `-L`) due to batch dequeue. + +Smaller IO sizes will see the most change. + +* New: Socket, NUMA, Core and Power Efficiency Class (big/little cores) added to processor topology reporting (XML and text output) + * topology elements only displayed in text results when > 1 are present (e.g. multi-socket systems) + * CPU numbering remains group relative, as is the new Core numbering + * highest Power Efficiency Class is marked with a `P` suffix (this will usually be `1P` v. `0`) + * **NOTE**: efficiency classes can have major impact; work on heterogenous systems **must** be aware of core properties in combination thread affinity rules (see `-a` and `-n `) +* New: active power scheme reporting +* New: base target offset switch now allows range specification: `-Bbase[:length]`; replaces `-B` and `-f` +* post-run latency histogram processing now significantly faster +* verbose output is more consistent; includes actual warmup, measured and cooldown intervals v. expected + +Binary release supports down to Windows 8 and Windows Server 2012; now uses the Universal CRT. + # DISKSPD 2.1 7/1/2021 * New `-gi` form allowing throughput limit specification in units of IOPS (per specified blocksize) diff --git a/ResultParser/ResultParser.cpp b/ResultParser/ResultParser.cpp index a33d46a..7c5c945 100644 --- a/ResultParser/ResultParser.cpp +++ b/ResultParser/ResultParser.cpp @@ -1,1271 +1,1313 @@ -/* - -DISKSPD - -Copyright(c) Microsoft Corporation -All rights reserved. - -MIT License - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - -*/ - -// ResultParser.cpp : Defines the entry point for the DLL application. -// -#include "ResultParser.h" - -#include "common.h" -#include - -#include -#include -#include //ntdll.dll - -#include //WNODE_HEADER -#include - -#include - -// TODO: refactor to a single function shared with the XmlResultParser -// Note: not thread safe (avoid 4K on the stack) - -static char printBuffer[4096] = {}; - -void ResultParser::_Print(const char *format, ...) -{ - assert(nullptr != format); - va_list listArg; - va_start(listArg, format); - vsprintf_s(printBuffer, _countof(printBuffer), format, listArg); - va_end(listArg); - _sResult += printBuffer; -} - -/*****************************************************************************/ -// display file size in a user-friendly form -// - -struct { - UINT32 sizeShift; - PCHAR name; -} sizeUnits[] = { - { 40, "TiB" }, - { 30, "GiB" }, - { 20, "MiB" }, - { 10, "KiB" } -}; - -void ResultParser::_DisplayFileSize(UINT64 fsize, UINT32 align) -{ - char fmtbuf[16]; - - for (auto& s : sizeUnits) - { - UINT64 sz = (UINT64)1 << s.sizeShift; - if (fsize >= sz) - { - // Even multiple? - if ((fsize & (sz - 1)) == 0) - { - // note: guaranteed no loss of precision - TB shift guarantees - // 0 in high 32bits. - UINT32 f = static_cast(fsize >> s.sizeShift); - - if (align) - { - // "%u%s" - _snprintf_s(fmtbuf, sizeof(fmtbuf), "%%%uu%%s", align); - _Print(fmtbuf, f, s.name); - } - else - { - _Print("%u%s", f, s.name); - } - return; - } - - // Not even, use fp. - double f = static_cast(fsize) / sz; - - if (align) - { - // "%.2f%s" - _snprintf_s(fmtbuf, sizeof(fmtbuf), "%%%u.2f%%s", align); - _Print(fmtbuf, f, s.name); - } - else - { - _Print("%0.2f%s", f, s.name); - } - return; - } - } - - _Print("%I64u", fsize); -} - -/*****************************************************************************/ -void ResultParser::_DisplayETWSessionInfo(struct ETWSessionInfo sessionInfo) -{ - _Print("\n\n"); - _Print(" ETW Buffer Settings & Statistics\n"); - _Print("--------------------------------------------------------\n"); - _Print("(KB) Buffers (Secs) (Mins)\n"); - _Print("Size | Min | Max | Free | Written | Flush Age\n"); - - _Print("%-5lu %5lu %-5lu %-2lu %8lu %8lu %8d\n\n", - sessionInfo.ulBufferSize, - sessionInfo.ulMinimumBuffers, - sessionInfo.ulMaximumBuffers, - sessionInfo.ulFreeBuffers, - sessionInfo.ulBuffersWritten, - sessionInfo.ulFlushTimer, - sessionInfo.lAgeLimit); - - _Print("Allocated Buffers:\t%lu\n", - sessionInfo.ulNumberOfBuffers); - - _Print("Lost Events:\t\t%lu\n", - sessionInfo.ulEventsLost); - - _Print("Lost Log Buffers:\t%lu\n", - sessionInfo.ulLogBuffersLost); - - _Print("Lost Real Time Buffers:\t%lu\n", - sessionInfo.ulRealTimeBuffersLost); -} - -/*****************************************************************************/ -void ResultParser::_DisplayETW(struct ETWMask ETWMask, struct ETWEventCounters EtwEventCounters) -{ - _Print("\n\n\nETW:\n"); - _Print("----\n\n"); - - if (ETWMask.bDiskIO) - { - _Print("\tDisk I/O\n"); - - _Print("\t\tRead: %I64u\n", EtwEventCounters.ullIORead); - _Print("\t\tWrite: %I64u\n", EtwEventCounters.ullIOWrite); - } - if (ETWMask.bImageLoad) - { - _Print("\tLoad Image\n"); - - _Print("\t\tLoad Image: %I64u\n", EtwEventCounters.ullImageLoad); - } - if (ETWMask.bMemoryPageFaults) - { - _Print("\tMemory Page Faults\n"); - - _Print("\t\tCopy on Write: %I64u\n", EtwEventCounters.ullMMCopyOnWrite); - _Print("\t\tDemand Zero fault: %I64u\n", EtwEventCounters.ullMMDemandZeroFault); - _Print("\t\tGuard Page fault: %I64u\n", EtwEventCounters.ullMMGuardPageFault); - _Print("\t\tHard page fault: %I64u\n", EtwEventCounters.ullMMHardPageFault); - _Print("\t\tTransition fault: %I64u\n", EtwEventCounters.ullMMTransitionFault); - } - if (ETWMask.bMemoryHardFaults && !ETWMask.bMemoryPageFaults ) - { - _Print("\tMemory Hard Faults\n"); - _Print("\t\tHard page fault: %I64u\n", EtwEventCounters.ullMMHardPageFault); - } - if (ETWMask.bNetwork) - { - _Print("\tNetwork\n"); - - _Print("\t\tAccept: %I64u\n", EtwEventCounters.ullNetAccept); - _Print("\t\tConnect: %I64u\n", EtwEventCounters.ullNetConnect); - _Print("\t\tDisconnect: %I64u\n", EtwEventCounters.ullNetDisconnect); - _Print("\t\tReconnect: %I64u\n", EtwEventCounters.ullNetReconnect); - _Print("\t\tRetransmit: %I64u\n", EtwEventCounters.ullNetRetransmit); - _Print("\t\tTCP/IP Send: %I64u\n", EtwEventCounters.ullNetTcpSend); - _Print("\t\tTCP/IP Receive: %I64u\n", EtwEventCounters.ullNetTcpReceive); - _Print("\t\tUDP/IP Send: %I64u\n", EtwEventCounters.ullNetUdpSend); - _Print("\t\tUDP/IP Receive: %I64u\n", EtwEventCounters.ullNetUdpReceive); - } - if (ETWMask.bProcess) - { - _Print("\tProcess\n"); - - _Print("\t\tStart: %I64u\n", EtwEventCounters.ullProcessStart); - _Print("\t\tEnd: %I64u\n", EtwEventCounters.ullProcessEnd); - } - if (ETWMask.bRegistry) - { - _Print("\tRegistry\n"); - - _Print("\t\tNtCreateKey: %I64u\n", - EtwEventCounters.ullRegCreate); - - _Print("\t\tNtDeleteKey: %I64u\n", - EtwEventCounters.ullRegDelete); - - _Print("\t\tNtDeleteValueKey: %I64u\n", - EtwEventCounters.ullRegDeleteValue); - - _Print("\t\tNtEnumerateKey: %I64u\n", - EtwEventCounters.ullRegEnumerateKey); - - _Print("\t\tNtEnumerateValueKey: %I64u\n", - EtwEventCounters.ullRegEnumerateValueKey); - - _Print("\t\tNtFlushKey: %I64u\n", - EtwEventCounters.ullRegFlush); - - _Print("\t\tNtOpenKey: %I64u\n", - EtwEventCounters.ullRegOpen); - - _Print("\t\tNtQueryKey: %I64u\n", - EtwEventCounters.ullRegQuery); - - _Print("\t\tNtQueryMultipleValueKey: %I64u\n", - EtwEventCounters.ullRegQueryMultipleValue); - - _Print("\t\tNtQueryValueKey: %I64u\n", - EtwEventCounters.ullRegQueryValue); - - _Print("\t\tNtSetInformationKey: %I64u\n", - EtwEventCounters.ullRegSetInformation); - - _Print("\t\tNtSetValueKey: %I64u\n", - EtwEventCounters.ullRegSetValue); - } - if (ETWMask.bThread) - { - _Print("\tThread\n"); - - _Print("\t\tStart: %I64u\n", EtwEventCounters.ullThreadStart); - _Print("\t\tEnd: %I64u\n", EtwEventCounters.ullThreadEnd); - } -} - -void ResultParser::_PrintDistribution(DistributionType dT, const vector& v, char* spc) -{ - if (dT == DistributionType::None) - { - return; - } - - switch (dT) - { - case DistributionType::Percent: - for (const auto &r : v) - { - _Print(spc); - _Print(" %3u%% of IO => [%2I64u%% - %3I64u%%) of target\n", - r._span, - r._dst.first, - r._dst.first + r._dst.second - ); - } - break; - - case DistributionType::Absolute: - { - const DistributionRange& last = *v.rbegin(); - UINT32 max = last._src + last._span; - - for (const auto &r : v) - { - _Print(spc); - // If this is a trimmed distribution (target was smaller than its range) - // then we need to rescale the trimmed IO% to 100%. Present this with a - // single decimal point, which may of course show rounding. - if (max < 100) - { - _Print(" %0.1f%% of IO => [", (double) 100 * r._span / max); - } - // Otherwise it is a simple 1-100% and can avoid rounding artifacts. - else - { - _Print(" %3u%% of IO => [", r._span); - } - - if (r._dst.first == 0) - { - // directly emit leading zero so we can align it - _Print(" 0 "); - } - else - { - _DisplayFileSize(r._dst.first, 6); - } - _Print(" - "); - // zero length occurs (only) in specification is a placeholder for end of target - if (r._dst.second) - { - _DisplayFileSize(r._dst.first + r._dst.second, 6); - _Print(")\n"); - } - else - { - _Print(" end)\n"); - } - } - } - break; - } -} - -class DistributionRef { -public: - - DistributionRef( - const string &TargetPath, - UINT32 Thread - ) - { - set s; - s.insert(Thread); - - _mTargetThreads.emplace(make_pair(TargetPath, std::move(s))); - } - - // - // Map a target to the set of threads referencing it with a given distribution - // - - map> _mTargetThreads; -}; - -namespace std -{ - template<> - struct less *> - { - // map by pointer, compare with the distributions - bool operator()(const vector * const &lhs, const vector * const &rhs) const - { - return *lhs < *rhs; - } - }; -} - -void ResultParser::_PrintEffectiveDistributions(const Results& results) -{ - // - // Effective distributions can be distinct per target if they vary in size. - // While not possible at the command line, more complex configurations can - // in general specify a distribution per target per thread. - // - // This deduplicates the effective distributions so that we report each - // with the target/thread list which used the (equivalent) distribution - // to access the target. - // - - bool header = false; - UINT32 threadNo = 0; - map *, DistributionRef> m; - - for (auto& thResult : results.vThreadResults) - { - for (auto& tgtResult : thResult.vTargetResults) - { - if (tgtResult.vDistributionRange.size()) - { - auto it = m.find(const_cast *>(&tgtResult.vDistributionRange)); - if (it == m.end()) - { - m.emplace(make_pair(const_cast *>(&tgtResult.vDistributionRange), - DistributionRef(tgtResult.sPath, threadNo))); - } - else - { - it->second._mTargetThreads[tgtResult.sPath].insert(threadNo); - } - } - } - - ++threadNo; - } - - for (auto& r : m) - { - if (!header) - { - header = true; - _Print("\nEffective IO Distributions\n--------------------------\n"); - } - // _Print("target: %s\n", r.second._sTargets.cbegin()->c_str()); - for (auto& tgt : r.second._mTargetThreads) - { - _Print("target: %s [thread:", tgt.first.c_str()); - - UINT32 lastTh = MAXUINT, runLen = 0; - - for (auto& th : tgt.second) - { - if (lastTh != MAXUINT) - { - // accumulate run? - if (lastTh + 1 == th) { - lastTh = th; - ++runLen; - continue; - } - - // end of run - indicate ellision of actual runs - if (runLen > 1) - { - _Print(" -"); - } - _Print(" %u", lastTh); - } - - // start new run (may be singular) - _Print(" %u", th); - lastTh = th; - runLen = 0; - } - - // terminate final run - if (runLen > 1) - { - _Print(" -"); - } - // don't show last thread twice if it terminated run - if (runLen) - { - _Print(" %u", lastTh); - } - - _Print("]\n"); - } - _PrintDistribution(DistributionType::Absolute, *r.first, ""); - } -} - -void ResultParser::_PrintTarget(const Target &target, bool fUseThreadsPerFile, bool fUseRequestsPerFile, bool fCompletionRoutines) -{ - if (target.GetPath().c_str()[0] == TEMPLATE_TARGET_PREFIX) - { - _Print("\tpath: template target '%s'\n", target.GetPath().c_str() + 1); - } - else - { - _Print("\tpath: '%s'\n", target.GetPath().c_str()); - } - _Print("\t\tthink time: %ums\n", target.GetThinkTime()); - _Print("\t\tburst size: %u\n", target.GetBurstSize()); - // TODO: completion routines/ports - - switch (target.GetCacheMode()) - { - case TargetCacheMode::Cached: - _Print("\t\tusing software cache\n"); - break; - case TargetCacheMode::DisableLocalCache: - _Print("\t\tlocal software cache disabled, remote cache enabled\n"); - break; - case TargetCacheMode::DisableOSCache: - _Print("\t\tsoftware cache disabled\n"); - break; - } - - if (target.GetWriteThroughMode() == WriteThroughMode::On) - { - // context-appropriate comment on writethrough - // if sw cache is disabled, commenting on sw write cache is possibly confusing - switch (target.GetCacheMode()) - { - case TargetCacheMode::Cached: - case TargetCacheMode::DisableLocalCache: - _Print("\t\thardware and software write caches disabled, writethrough on\n"); - break; - case TargetCacheMode::DisableOSCache: - _Print("\t\thardware write cache disabled, writethrough on\n"); - break; - } - } - else - { - _Print("\t\tusing hardware write cache, writethrough off\n"); - } - - if (target.GetMemoryMappedIoMode() == MemoryMappedIoMode::On) - { - _Print("\t\tmemory mapped I/O enabled"); - switch(target.GetMemoryMappedIoFlushMode()) - { - case MemoryMappedIoFlushMode::ViewOfFile: - _Print(", flush mode: FlushViewOfFile"); - break; - case MemoryMappedIoFlushMode::NonVolatileMemory: - _Print(", flush mode: FlushNonVolatileMemory"); - break; - case MemoryMappedIoFlushMode::NonVolatileMemoryNoDrain: - _Print(", flush mode: FlushNonVolatileMemory with no drain"); - break; - } - _Print("\n"); - } - - if (target.GetZeroWriteBuffers()) - { - _Print("\t\tzeroing write buffers\n"); - } - - if (target.GetRandomDataWriteBufferSize() > 0) - { - _Print("\t\twrite buffer size: "); - _DisplayFileSize(target.GetRandomDataWriteBufferSize()); - _Print("\n"); - - string sWriteBufferSourcePath = target.GetRandomDataWriteBufferSourcePath(); - if (!sWriteBufferSourcePath.empty()) - { - _Print("\t\twrite buffer source: '%s'\n", sWriteBufferSourcePath.c_str()); - } - else - { - _Print("\t\twrite buffer source: random fill\n"); - } - } - - if (target.GetUseParallelAsyncIO()) - { - _Print("\t\tusing parallel async I/O\n"); - } - - if (target.GetWriteRatio() == 0) - { - _Print("\t\tperforming read test\n"); - } - else if (target.GetWriteRatio() == 100) - { - _Print("\t\tperforming write test\n"); - } - else - { - _Print("\t\tperforming mix test (read/write ratio: %d/%d)\n", 100 - target.GetWriteRatio(), target.GetWriteRatio()); - } - - _Print("\t\tblock size: "); - _DisplayFileSize(target.GetBlockSizeInBytes()); - _Print("\n"); - - if (target.GetRandomRatio() == 100) - { - _Print("\t\tusing random I/O (alignment: "); - } - else - { - if (target.GetRandomRatio() > 0) - { - _Print("\t\tusing mixed random/sequential I/O (%u%% random) (alignment/stride: ", target.GetRandomRatio()); - } - else - { - _Print("\t\tusing%s sequential I/O (stride: ", target.GetUseInterlockedSequential() ? " interlocked":""); - } - } - _DisplayFileSize(target.GetBlockAlignmentInBytes()); - _Print(")\n"); - - if (fUseRequestsPerFile) - { - _Print("\t\tnumber of outstanding I/O operations per thread: %d\n", target.GetRequestCount()); - } - else - { - _Print("\t\trelative IO weight in thread pool: %u\n", target.GetWeight()); - } - - if (0 != target.GetBaseFileOffsetInBytes()) - { - _Print("\t\tbase file offset: "); - _DisplayFileSize(target.GetBaseFileOffsetInBytes()); - _Print("\n"); - } - - if (0 != target.GetMaxFileSize()) - { - _Print("\t\tmax file size: "); - _DisplayFileSize(target.GetMaxFileSize()); - _Print("\n"); - } - - if (0 != target.GetThreadStrideInBytes()) - { - _Print("\t\tthread stride size: "); - _DisplayFileSize(target.GetThreadStrideInBytes()); - _Print("\n"); - } - - if (target.GetSequentialScanHint()) - { - _Print("\t\tusing FILE_FLAG_SEQUENTIAL_SCAN hint\n"); - } - - if (target.GetRandomAccessHint()) - { - _Print("\t\tusing FILE_FLAG_RANDOM_ACCESS hint\n"); - } - - if (target.GetTemporaryFileHint()) - { - _Print("\t\tusing FILE_ATTRIBUTE_TEMPORARY hint\n"); - } - - if (fUseThreadsPerFile) - { - _Print("\t\tthreads per file: %d\n", target.GetThreadsPerFile()); - } - if (target.GetRequestCount() > 1 && fUseThreadsPerFile) - { - if (fCompletionRoutines) - { - _Print("\t\tusing completion routines (ReadFileEx/WriteFileEx)\n"); - } - else - { - _Print("\t\tusing I/O Completion Ports\n"); - } - } - - if (target.GetIOPriorityHint() == IoPriorityHintVeryLow) - { - _Print("\t\tIO priority: very low\n"); - } - else if (target.GetIOPriorityHint() == IoPriorityHintLow) - { - _Print("\t\tIO priority: low\n"); - } - else if (target.GetIOPriorityHint() == IoPriorityHintNormal) - { - _Print("\t\tIO priority: normal\n"); - } - else - { - _Print("\t\tIO priority: unknown\n"); - } - - if (target.GetThroughputIOPS()) - { - _Print("\t\tthroughput rate-limited to %u IOPS\n", target.GetThroughputIOPS()); - } - else if (target.GetThroughputInBytesPerMillisecond()) - { - _Print("\t\tthroughput rate-limited to %u B/ms\n", target.GetThroughputInBytesPerMillisecond()); - } - - if (target.GetDistributionRange().size()) - { - _Print("\t\tIO Distribution:\n"); - _PrintDistribution(target.GetDistributionType(), target.GetDistributionRange(), "\t\t"); - } -} - -void ResultParser::_PrintTimeSpan(const TimeSpan& timeSpan) -{ - _Print("\tduration: %us\n", timeSpan.GetDuration()); - _Print("\twarm up time: %us\n", timeSpan.GetWarmup()); - _Print("\tcool down time: %us\n", timeSpan.GetCooldown()); - if (timeSpan.GetDisableAffinity()) - { - _Print("\taffinity disabled\n"); - } - if (timeSpan.GetMeasureLatency()) - { - _Print("\tmeasuring latency\n"); - } - if (timeSpan.GetCalculateIopsStdDev()) - { - _Print("\tgathering IOPS at intervals of %ums\n", timeSpan.GetIoBucketDurationInMilliseconds()); - } - _Print("\trandom seed: %u\n", timeSpan.GetRandSeed()); - if (timeSpan.GetThreadCount() != 0) - { - _Print("\tthread pool with %u threads\n", timeSpan.GetThreadCount()); - _Print("\tnumber of outstanding I/O operations per thread: %d\n", timeSpan.GetRequestCount()); - } - - const auto& vAffinity = timeSpan.GetAffinityAssignments(); - if ( vAffinity.size() > 0) - { - _Print("\tadvanced affinity round robin (group/core): "); - for (unsigned int x = 0; x < vAffinity.size(); ++x) - { - _Print("%u/%u", vAffinity[x].wGroup, vAffinity[x].bProc); - if (x < vAffinity.size() - 1) - { - _Print(", "); - } - } - _Print("\n"); - } - - if (timeSpan.GetRandomWriteData()) - { - _Print("\tgenerating random data for each write IO\n"); - _Print("\t WARNING: this increases the CPU cost of issuing writes and should only\n"); - _Print("\t be compared to other results using the -Zr flag\n"); - } - - vector vTargets(timeSpan.GetTargets()); - for (auto i = vTargets.begin(); i != vTargets.end(); i++) - { - _PrintTarget(*i, (timeSpan.GetThreadCount() == 0), (timeSpan.GetThreadCount() == 0 || timeSpan.GetRequestCount() == 0), timeSpan.GetCompletionRoutines()); - } -} - -void ResultParser::_PrintProfile(const Profile& profile) -{ - _Print("\nCommand Line: %s\n", profile.GetCmdLine().c_str()); - _Print("\n"); - _Print("Input parameters:\n\n"); - if (profile.GetVerbose()) - { - _Print("\tusing verbose mode\n"); - } - - const vector& vTimeSpans = profile.GetTimeSpans(); - int c = 1; - for (auto i = vTimeSpans.begin(); i != vTimeSpans.end(); i++) - { - _Print("\ttimespan: %3d\n", c++); - _Print("\t-------------\n"); - _PrintTimeSpan(*i); - _Print("\n"); - } -} - -void ResultParser::_PrintSystemInfo(const SystemInformation& system) -{ - _Print(system.GetText().c_str()); -} - -void ResultParser::_PrintCpuUtilization(const Results& results, const SystemInformation& system) -{ - size_t ulProcCount = results.vSystemProcessorPerfInfo.size(); - size_t ulBaseProc = 0; - size_t ulActiveProcCount = 0; - size_t ulNumGroups = system.processorTopology._vProcessorGroupInformation.size(); - - char szFloatBuffer[1024]; - - if (ulNumGroups == 1) { - _Print("\nCPU | Usage | User | Kernel | Idle\n"); - } - else { - _Print("\nGroup | CPU | Usage | User | Kernel | Idle\n"); - } - _Print("-------------------------------------------\n"); - - double busyTime = 0; - double totalIdleTime = 0; - double totalUserTime = 0; - double totalKrnlTime = 0; - - for (unsigned int ulGroup = 0; ulGroup < ulNumGroups; ulGroup++) { - const ProcessorGroupInformation *pGroup = &system.processorTopology._vProcessorGroupInformation[ulGroup]; - - // System has multiple groups but we only have counters for the first one - if (ulBaseProc >= ulProcCount) { - break; - } - - for (unsigned int ulProcessor = 0; ulProcessor < pGroup->_maximumProcessorCount; ulProcessor++) { - double idleTime; - double userTime; - double krnlTime; - double thisTime; - - if (!pGroup->IsProcessorActive((BYTE)ulProcessor)) { - continue; - } - - long long fTime = results.vSystemProcessorPerfInfo[ulBaseProc + ulProcessor].KernelTime.QuadPart + - results.vSystemProcessorPerfInfo[ulBaseProc + ulProcessor].UserTime.QuadPart; - - idleTime = 100.0 * results.vSystemProcessorPerfInfo[ulBaseProc + ulProcessor].IdleTime.QuadPart / fTime; - krnlTime = 100.0 * results.vSystemProcessorPerfInfo[ulBaseProc + ulProcessor].KernelTime.QuadPart / fTime; - userTime = 100.0 * results.vSystemProcessorPerfInfo[ulBaseProc + ulProcessor].UserTime.QuadPart / fTime; - - thisTime = (krnlTime + userTime) - idleTime; - - if (ulNumGroups == 1) { - sprintf_s(szFloatBuffer, sizeof(szFloatBuffer), "%4u| %6.2lf%%| %6.2lf%%| %6.2lf%%| %6.2lf%%\n", - ulProcessor, - thisTime, - userTime, - krnlTime - idleTime, - idleTime); - } - else { - sprintf_s(szFloatBuffer, sizeof(szFloatBuffer), "%6u| %4u| %6.2lf%%| %6.2lf%%| %6.2lf%%| %6.2lf%%\n", - ulGroup, - ulProcessor, - thisTime, - userTime, - krnlTime - idleTime, - idleTime); - } - - _Print("%s", szFloatBuffer); - - busyTime += thisTime; - totalIdleTime += idleTime; - totalUserTime += userTime; - totalKrnlTime += krnlTime; - ulActiveProcCount += 1; - } - - ulBaseProc += pGroup->_maximumProcessorCount; - } - - if (ulActiveProcCount == 0) { - ulActiveProcCount = 1; - } - - _Print("-------------------------------------------\n"); - - sprintf_s(szFloatBuffer, sizeof(szFloatBuffer), - ulNumGroups == 1 ? - "avg.| %6.2lf%%| %6.2lf%%| %6.2lf%%| %6.2lf%%\n" : - " avg.| %6.2lf%%| %6.2lf%%| %6.2lf%%| %6.2lf%%\n", - busyTime / ulActiveProcCount, - totalUserTime / ulActiveProcCount, - (totalKrnlTime - totalIdleTime) / ulActiveProcCount, - totalIdleTime / ulActiveProcCount); - _Print("%s", szFloatBuffer); -} - -void ResultParser::_PrintSectionFieldNames(const TimeSpan& timeSpan) -{ - _Print("thread | bytes | I/Os | MiB/s | I/O per s %s%s%s| file\n", - timeSpan.GetMeasureLatency() ? "| AvgLat " : "", - timeSpan.GetCalculateIopsStdDev() ? "| IopsStdDev " : "", - timeSpan.GetMeasureLatency() ? "| LatStdDev " : ""); -} - -void ResultParser::_PrintSectionBorderLine(const TimeSpan& timeSpan) -{ - _Print("------------------------------------------------------------------%s%s%s------------\n", - timeSpan.GetMeasureLatency() ? "-----------" : "" , - timeSpan.GetCalculateIopsStdDev() ? "-------------" : "", - timeSpan.GetMeasureLatency() ? "------------" : ""); -} - -void ResultParser::_PrintSection(_SectionEnum section, const TimeSpan& timeSpan, const Results& results) -{ - double fTime = PerfTimer::PerfTimeToSeconds(results.ullTimeCount); - double fBucketTime = timeSpan.GetIoBucketDurationInMilliseconds() / 1000.0; - UINT64 ullTotalBytesCount = 0; - UINT64 ullTotalIOCount = 0; - Histogram totalLatencyHistogram; - IoBucketizer totalIoBucketizer; - - _PrintSectionFieldNames(timeSpan); - - _PrintSectionBorderLine(timeSpan); - - for (unsigned int iThread = 0; iThread < results.vThreadResults.size(); ++iThread) - { - const ThreadResults& threadResults = results.vThreadResults[iThread]; - for (unsigned int iFile = 0; iFile < threadResults.vTargetResults.size(); iFile++) - { - const TargetResults& targetResults = threadResults.vTargetResults[iFile]; - - UINT64 ullBytesCount = 0; - UINT64 ullIOCount = 0; - - Histogram latencyHistogram; - IoBucketizer ioBucketizer; - - if ((section == _SectionEnum::WRITE) || (section == _SectionEnum::TOTAL)) - { - ullBytesCount += targetResults.ullWriteBytesCount; - ullIOCount += targetResults.ullWriteIOCount; - - if (timeSpan.GetMeasureLatency()) - { - latencyHistogram.Merge(targetResults.writeLatencyHistogram); - totalLatencyHistogram.Merge(targetResults.writeLatencyHistogram); - } - - if (timeSpan.GetCalculateIopsStdDev()) - { - ioBucketizer.Merge(targetResults.writeBucketizer); - totalIoBucketizer.Merge(targetResults.writeBucketizer); - } - } - - if ((section == _SectionEnum::READ) || (section == _SectionEnum::TOTAL)) - { - ullBytesCount += targetResults.ullReadBytesCount; - ullIOCount += targetResults.ullReadIOCount; - - if (timeSpan.GetMeasureLatency()) - { - latencyHistogram.Merge(targetResults.readLatencyHistogram); - totalLatencyHistogram.Merge(targetResults.readLatencyHistogram); - } - - if (timeSpan.GetCalculateIopsStdDev()) - { - ioBucketizer.Merge(targetResults.readBucketizer); - totalIoBucketizer.Merge(targetResults.readBucketizer); - } - } - - _Print("%6u | %15llu | %12llu | %10.2f | %10.2f", - iThread, - ullBytesCount, - ullIOCount, - (double)ullBytesCount / 1024 / 1024 / fTime, - (double)ullIOCount / fTime); - - if (timeSpan.GetMeasureLatency()) - { - double avgLat = latencyHistogram.GetAvg()/1000; - _Print(" | %8.3f", avgLat); - } - - if (timeSpan.GetCalculateIopsStdDev()) - { - double iopsStdDev = ioBucketizer.GetStandardDeviationIOPS() / fBucketTime; - _Print(" | %10.2f", iopsStdDev); - } - - if (timeSpan.GetMeasureLatency()) - { - if (latencyHistogram.GetSampleSize() > 0) - { - double latStdDev = latencyHistogram.GetStandardDeviation() / 1000; - _Print(" | %8.3f", latStdDev); - } - else - { - _Print(" | N/A"); - } - } - - _Print(" | %s (", targetResults.sPath.c_str()); - - _DisplayFileSize(targetResults.ullFileSize); - _Print(")\n"); - - ullTotalBytesCount += ullBytesCount; - ullTotalIOCount += ullIOCount; - } - } - - _PrintSectionBorderLine(timeSpan); - - double totalAvgLat = 0; - - if (timeSpan.GetMeasureLatency()) - { - totalAvgLat = totalLatencyHistogram.GetAvg()/1000; - } - - _Print("total: %15llu | %12llu | %10.2f | %10.2f", - ullTotalBytesCount, - ullTotalIOCount, - (double)ullTotalBytesCount / 1024 / 1024 / fTime, - (double)ullTotalIOCount / fTime); - - if (timeSpan.GetMeasureLatency()) - { - _Print(" | %8.3f", totalAvgLat); - } - - if (timeSpan.GetCalculateIopsStdDev()) - { - double iopsStdDev = totalIoBucketizer.GetStandardDeviationIOPS() / fBucketTime; - _Print(" | %10.2f", iopsStdDev); - } - - if (timeSpan.GetMeasureLatency()) - { - if (totalLatencyHistogram.GetSampleSize() > 0) - { - double latStdDev = totalLatencyHistogram.GetStandardDeviation() / 1000; - _Print(" | %8.3f", latStdDev); - } - else - { - _Print(" | N/A"); - } - } - - _Print("\n"); -} - -void ResultParser::_PrintLatencyPercentiles(const Results& results) -{ - //Print one chart for each target IF more than one target - unordered_map> perTargetReadHistogram; - unordered_map> perTargetWriteHistogram; - unordered_map> perTargetTotalHistogram; - - for (const auto& thread : results.vThreadResults) - { - for (const auto& target : thread.vTargetResults) - { - std::string path = target.sPath; - - perTargetReadHistogram[path].Merge(target.readLatencyHistogram); - - perTargetWriteHistogram[path].Merge(target.writeLatencyHistogram); - - perTargetTotalHistogram[path].Merge(target.readLatencyHistogram); - perTargetTotalHistogram[path].Merge(target.writeLatencyHistogram); - } - } - - //Skip if only one target - if (perTargetTotalHistogram.size() > 1) { - for (auto i : perTargetTotalHistogram) - { - std::string path = i.first; - _Print("\n%s\n", path.c_str()); - _PrintLatencyChart(perTargetReadHistogram[path], - perTargetWriteHistogram[path], - perTargetTotalHistogram[path]); - } - } - - //Print one chart for the latencies aggregated across all targets - Histogram readLatencyHistogram; - Histogram writeLatencyHistogram; - Histogram totalLatencyHistogram; - - for (const auto& thread : results.vThreadResults) - { - for (const auto& target : thread.vTargetResults) - { - readLatencyHistogram.Merge(target.readLatencyHistogram); - - writeLatencyHistogram.Merge(target.writeLatencyHistogram); - - totalLatencyHistogram.Merge(target.writeLatencyHistogram); - totalLatencyHistogram.Merge(target.readLatencyHistogram); - } - } - - _Print("\ntotal:\n"); - _PrintLatencyChart(readLatencyHistogram, writeLatencyHistogram, totalLatencyHistogram); -} - -void ResultParser::_PrintLatencyChart(const Histogram& readLatencyHistogram, - const Histogram& writeLatencyHistogram, - const Histogram& totalLatencyHistogram) -{ - bool fHasReads = readLatencyHistogram.GetSampleSize() > 0; - bool fHasWrites = writeLatencyHistogram.GetSampleSize() > 0; - - _Print(" %%-ile | Read (ms) | Write (ms) | Total (ms)\n"); - _Print("----------------------------------------------\n"); - - string readMin = - fHasReads ? - Util::DoubleToStringHelper(readLatencyHistogram.GetMin()/1000) : - "N/A"; - - string writeMin = - fHasWrites ? - Util::DoubleToStringHelper(writeLatencyHistogram.GetMin() / 1000) : - "N/A"; - - _Print(" min | %10s | %10s | %10.3lf\n", - readMin.c_str(), writeMin.c_str(), totalLatencyHistogram.GetMin()/1000); - - PercentileDescriptor percentiles[] = - { - { 0.25, "25th" }, - { 0.50, "50th" }, - { 0.75, "75th" }, - { 0.90, "90th" }, - { 0.95, "95th" }, - { 0.99, "99th" }, - { 0.999, "3-nines" }, - { 0.9999, "4-nines" }, - { 0.99999, "5-nines" }, - { 0.999999, "6-nines" }, - { 0.9999999, "7-nines" }, - { 0.99999999, "8-nines" }, - { 0.999999999, "9-nines" }, - }; - - for (auto p : percentiles) - { - string readPercentile = - fHasReads ? - Util::DoubleToStringHelper(readLatencyHistogram.GetPercentile(p.Percentile) / 1000) : - "N/A"; - - string writePercentile = - fHasWrites ? - Util::DoubleToStringHelper(writeLatencyHistogram.GetPercentile(p.Percentile) / 1000) : - "N/A"; - - _Print("%7s | %10s | %10s | %10.3lf\n", - p.Name.c_str(), - readPercentile.c_str(), - writePercentile.c_str(), - totalLatencyHistogram.GetPercentile(p.Percentile)/1000); - } - - string readMax = Util::DoubleToStringHelper(readLatencyHistogram.GetMax() / 1000); - string writeMax = Util::DoubleToStringHelper(writeLatencyHistogram.GetMax() / 1000); - - _Print(" max | %10s | %10s | %10.3lf\n", - fHasReads ? readMax.c_str() : "N/A", - fHasWrites ? writeMax.c_str() : "N/A", - totalLatencyHistogram.GetMax()/1000); -} - -string ResultParser::ParseProfile(const Profile& profile) -{ - _sResult.clear(); - _PrintProfile(profile); - return _sResult; -} - -string ResultParser::ParseResults(const Profile& profile, const SystemInformation& system, vector vResults) -{ - _sResult.clear(); - - _PrintProfile(profile); - _PrintSystemInfo(system); - - for (size_t iResult = 0; iResult < vResults.size(); iResult++) - { - _Print("\n\nResults for timespan %d:\n", iResult + 1); - _Print("*******************************************************************************\n"); - - const Results& results = vResults[iResult]; - const TimeSpan& timeSpan = profile.GetTimeSpans()[iResult]; - - unsigned int ulProcCount = system.processorTopology._ulActiveProcCount; - double fTime = PerfTimer::PerfTimeToSeconds(results.ullTimeCount); //test duration - - char szFloatBuffer[1024]; - - // There either is a fixed number of threads for all files to share (GetThreadCount() > 0) or a number of threads per file. - // In the latter case vThreadResults.size() == number of threads per file * file count - size_t ulThreadCnt = (timeSpan.GetThreadCount() > 0) ? timeSpan.GetThreadCount() : results.vThreadResults.size(); - - if (fTime < 0.0000001) - { - _Print("The test was interrupted before the measurements began. No results are displayed.\n"); - } - else - { - // TODO: parameters.bCreateFile; - - _Print("\n"); - sprintf_s(szFloatBuffer, sizeof(szFloatBuffer), "actual test time:\t%.2lfs\n", fTime); - _Print("%s", szFloatBuffer); - _Print("thread count:\t\t%u\n", ulThreadCnt); - - if (timeSpan.GetThreadCount() != 0 && timeSpan.GetRequestCount() != 0) { - _Print("request count:\t\t%u\n", timeSpan.GetRequestCount()); - } - - _Print("proc count:\t\t%u\n", ulProcCount); - _PrintCpuUtilization(results, system); - - _PrintEffectiveDistributions(results); - - _Print("\nTotal IO\n"); - _PrintSection(_SectionEnum::TOTAL, timeSpan, results); - - _Print("\nRead IO\n"); - _PrintSection(_SectionEnum::READ, timeSpan, results); - - _Print("\nWrite IO\n"); - _PrintSection(_SectionEnum::WRITE, timeSpan, results); - - if (timeSpan.GetMeasureLatency()) - { - _Print("\n\n"); - _PrintLatencyPercentiles(results); - } - - //etw - if (results.fUseETW) - { - _DisplayETW(results.EtwMask, results.EtwEventCounters); - _DisplayETWSessionInfo(results.EtwSessionInfo); - } - } - } - - if (vResults.size() > 1) - { - _Print("\n\nTotals:\n"); - _Print("*******************************************************************************\n\n"); - _Print("type | bytes | I/Os | MiB/s | I/O per s\n"); - _Print("-------------------------------------------------------------------------------\n"); - - - UINT64 cbTotalWritten = 0; - UINT64 cbTotalRead = 0; - UINT64 cTotalWriteIO = 0; - UINT64 cTotalReadIO = 0; - UINT64 cTotalTicks = 0; - for (auto pResults = vResults.begin(); pResults != vResults.end(); pResults++) - { - double time = PerfTimer::PerfTimeToSeconds(pResults->ullTimeCount); - if (time >= 0.0000001) // skip timespans that were interrupted - { - cTotalTicks += pResults->ullTimeCount; - auto vThreadResults = pResults->vThreadResults; - for (auto pThreadResults = vThreadResults.begin(); pThreadResults != vThreadResults.end(); pThreadResults++) - { - for (auto pTargetResults = pThreadResults->vTargetResults.begin(); pTargetResults != pThreadResults->vTargetResults.end(); pTargetResults++) - { - cbTotalRead += pTargetResults->ullReadBytesCount; - cbTotalWritten += pTargetResults->ullWriteBytesCount; - cTotalReadIO += pTargetResults->ullReadIOCount; - cTotalWriteIO += pTargetResults->ullWriteIOCount; - } - } - } - } - - double totalTime = PerfTimer::PerfTimeToSeconds(cTotalTicks); - - _Print("write | %15I64u | %12I64u | %10.2lf | %10.2lf\n", - cbTotalWritten, - cTotalWriteIO, - (double)cbTotalWritten / 1024 / 1024 / totalTime, - (double)cTotalWriteIO / totalTime); - - _Print("read | %15I64u | %12I64u | %10.2lf | %10.2lf\n", - cbTotalRead, - cTotalReadIO, - (double)cbTotalRead / 1024 / 1024 / totalTime, - (double)cTotalReadIO / totalTime); - _Print("-------------------------------------------------------------------------------\n"); - _Print("total | %15I64u | %12I64u | %10.2lf | %10.2lf\n\n", - cbTotalRead + cbTotalWritten, - cTotalReadIO + cTotalWriteIO, - (double)(cbTotalRead + cbTotalWritten) / 1024 / 1024 / totalTime, - (double)(cTotalReadIO + cTotalWriteIO) / totalTime); - - _Print("total test time:\t%.2lfs\n", totalTime); - } - - return _sResult; -} +/* + +DISKSPD + +Copyright(c) Microsoft Corporation +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +// ResultParser.cpp : Defines the entry point for the DLL application. +// +#include "ResultParser.h" + +#include "common.h" +#include + +#include +#include +#include //ntdll.dll + +#include //WNODE_HEADER +#include + +#include + +// TODO: refactor to a single function shared with the XmlResultParser +// Note: not thread safe (avoid 4K on the stack) + +static char printBuffer[4096] = {}; + +void ResultParser::_Print(const char *format, ...) +{ + assert(nullptr != format); + va_list listArg; + va_start(listArg, format); + vsprintf_s(printBuffer, _countof(printBuffer), format, listArg); + va_end(listArg); + _sResult += printBuffer; +} + +/*****************************************************************************/ +// display file size in a user-friendly form +// + +struct { + UINT32 sizeShift; + PCHAR name; +} sizeUnits[] = { + { 40, "TiB" }, + { 30, "GiB" }, + { 20, "MiB" }, + { 10, "KiB" } +}; + +void ResultParser::_DisplayFileSize(UINT64 fsize, UINT32 align) +{ + char fmtbuf[16]; + + for (auto& s : sizeUnits) + { + UINT64 sz = (UINT64)1 << s.sizeShift; + if (fsize >= sz) + { + // Even multiple? + if ((fsize & (sz - 1)) == 0) + { + // note: guaranteed no loss of precision - TB shift guarantees + // 0 in high 32bits. + UINT32 f = static_cast(fsize >> s.sizeShift); + + if (align) + { + // "%u%s" + _snprintf_s(fmtbuf, sizeof(fmtbuf), "%%%uu%%s", align); + _Print(fmtbuf, f, s.name); + } + else + { + _Print("%u%s", f, s.name); + } + return; + } + + // Not even, use fp. + double f = static_cast(fsize) / sz; + + if (align) + { + // "%.2f%s" + _snprintf_s(fmtbuf, sizeof(fmtbuf), "%%%u.2f%%s", align); + _Print(fmtbuf, f, s.name); + } + else + { + _Print("%0.2f%s", f, s.name); + } + return; + } + } + + _Print("%I64u", fsize); +} + +/*****************************************************************************/ +void ResultParser::_DisplayETWSessionInfo(struct ETWSessionInfo sessionInfo) +{ + _Print("\n\n"); + _Print(" ETW Buffer Settings & Statistics\n"); + _Print("--------------------------------------------------------\n"); + _Print("(KB) Buffers (Secs) (Mins)\n"); + _Print("Size | Min | Max | Free | Written | Flush Age\n"); + + _Print("%-5lu %5lu %-5lu %-2lu %8lu %8lu %8d\n\n", + sessionInfo.ulBufferSize, + sessionInfo.ulMinimumBuffers, + sessionInfo.ulMaximumBuffers, + sessionInfo.ulFreeBuffers, + sessionInfo.ulBuffersWritten, + sessionInfo.ulFlushTimer, + sessionInfo.lAgeLimit); + + _Print("Allocated Buffers:\t%lu\n", + sessionInfo.ulNumberOfBuffers); + + _Print("Lost Events:\t\t%lu\n", + sessionInfo.ulEventsLost); + + _Print("Lost Log Buffers:\t%lu\n", + sessionInfo.ulLogBuffersLost); + + _Print("Lost Real Time Buffers:\t%lu\n", + sessionInfo.ulRealTimeBuffersLost); +} + +/*****************************************************************************/ +void ResultParser::_DisplayETW(struct ETWMask ETWMask, struct ETWEventCounters EtwEventCounters) +{ + _Print("\n\n\nETW:\n"); + _Print("----\n\n"); + + if (ETWMask.bDiskIO) + { + _Print("\tDisk I/O\n"); + + _Print("\t\tRead: %I64u\n", EtwEventCounters.ullIORead); + _Print("\t\tWrite: %I64u\n", EtwEventCounters.ullIOWrite); + } + if (ETWMask.bImageLoad) + { + _Print("\tLoad Image\n"); + + _Print("\t\tLoad Image: %I64u\n", EtwEventCounters.ullImageLoad); + } + if (ETWMask.bMemoryPageFaults) + { + _Print("\tMemory Page Faults\n"); + + _Print("\t\tCopy on Write: %I64u\n", EtwEventCounters.ullMMCopyOnWrite); + _Print("\t\tDemand Zero fault: %I64u\n", EtwEventCounters.ullMMDemandZeroFault); + _Print("\t\tGuard Page fault: %I64u\n", EtwEventCounters.ullMMGuardPageFault); + _Print("\t\tHard page fault: %I64u\n", EtwEventCounters.ullMMHardPageFault); + _Print("\t\tTransition fault: %I64u\n", EtwEventCounters.ullMMTransitionFault); + } + if (ETWMask.bMemoryHardFaults && !ETWMask.bMemoryPageFaults ) + { + _Print("\tMemory Hard Faults\n"); + _Print("\t\tHard page fault: %I64u\n", EtwEventCounters.ullMMHardPageFault); + } + if (ETWMask.bNetwork) + { + _Print("\tNetwork\n"); + + _Print("\t\tAccept: %I64u\n", EtwEventCounters.ullNetAccept); + _Print("\t\tConnect: %I64u\n", EtwEventCounters.ullNetConnect); + _Print("\t\tDisconnect: %I64u\n", EtwEventCounters.ullNetDisconnect); + _Print("\t\tReconnect: %I64u\n", EtwEventCounters.ullNetReconnect); + _Print("\t\tRetransmit: %I64u\n", EtwEventCounters.ullNetRetransmit); + _Print("\t\tTCP/IP Send: %I64u\n", EtwEventCounters.ullNetTcpSend); + _Print("\t\tTCP/IP Receive: %I64u\n", EtwEventCounters.ullNetTcpReceive); + _Print("\t\tUDP/IP Send: %I64u\n", EtwEventCounters.ullNetUdpSend); + _Print("\t\tUDP/IP Receive: %I64u\n", EtwEventCounters.ullNetUdpReceive); + } + if (ETWMask.bProcess) + { + _Print("\tProcess\n"); + + _Print("\t\tStart: %I64u\n", EtwEventCounters.ullProcessStart); + _Print("\t\tEnd: %I64u\n", EtwEventCounters.ullProcessEnd); + } + if (ETWMask.bRegistry) + { + _Print("\tRegistry\n"); + + _Print("\t\tNtCreateKey: %I64u\n", + EtwEventCounters.ullRegCreate); + + _Print("\t\tNtDeleteKey: %I64u\n", + EtwEventCounters.ullRegDelete); + + _Print("\t\tNtDeleteValueKey: %I64u\n", + EtwEventCounters.ullRegDeleteValue); + + _Print("\t\tNtEnumerateKey: %I64u\n", + EtwEventCounters.ullRegEnumerateKey); + + _Print("\t\tNtEnumerateValueKey: %I64u\n", + EtwEventCounters.ullRegEnumerateValueKey); + + _Print("\t\tNtFlushKey: %I64u\n", + EtwEventCounters.ullRegFlush); + + _Print("\t\tNtOpenKey: %I64u\n", + EtwEventCounters.ullRegOpen); + + _Print("\t\tNtQueryKey: %I64u\n", + EtwEventCounters.ullRegQuery); + + _Print("\t\tNtQueryMultipleValueKey: %I64u\n", + EtwEventCounters.ullRegQueryMultipleValue); + + _Print("\t\tNtQueryValueKey: %I64u\n", + EtwEventCounters.ullRegQueryValue); + + _Print("\t\tNtSetInformationKey: %I64u\n", + EtwEventCounters.ullRegSetInformation); + + _Print("\t\tNtSetValueKey: %I64u\n", + EtwEventCounters.ullRegSetValue); + } + if (ETWMask.bThread) + { + _Print("\tThread\n"); + + _Print("\t\tStart: %I64u\n", EtwEventCounters.ullThreadStart); + _Print("\t\tEnd: %I64u\n", EtwEventCounters.ullThreadEnd); + } +} + +void ResultParser::_PrintDistribution(DistributionType dT, const vector& v, char* spc) +{ + if (dT == DistributionType::None) + { + return; + } + + switch (dT) + { + case DistributionType::Percent: + for (const auto &r : v) + { + _Print(spc); + _Print(" %3u%% of IO => [%2I64u%% - %3I64u%%) of target\n", + r._span, + r._dst.first, + r._dst.first + r._dst.second + ); + } + break; + + case DistributionType::Absolute: + { + const DistributionRange& last = *v.rbegin(); + UINT32 max = last._src + last._span; + + for (const auto &r : v) + { + _Print(spc); + // If this is a trimmed distribution (target was smaller than its range) + // then we need to rescale the trimmed IO% to 100%. Present this with a + // single decimal point, which may of course show rounding. + if (max < 100) + { + _Print(" %0.1f%% of IO => [", (double) 100 * r._span / max); + } + // Otherwise it is a simple 1-100% and can avoid rounding artifacts. + else + { + _Print(" %3u%% of IO => [", r._span); + } + + if (r._dst.first == 0) + { + // directly emit leading zero so we can align it + _Print(" 0 "); + } + else + { + _DisplayFileSize(r._dst.first, 6); + } + _Print(" - "); + // zero length occurs (only) in specification is a placeholder for end of target + if (r._dst.second) + { + _DisplayFileSize(r._dst.first + r._dst.second, 6); + _Print(")\n"); + } + else + { + _Print(" end)\n"); + } + } + } + break; + } +} + +class DistributionRef { +public: + + DistributionRef( + const string &TargetPath, + UINT32 Thread + ) + { + set s; + s.insert(Thread); + + _mTargetThreads.emplace(make_pair(TargetPath, std::move(s))); + } + + // + // Map a target to the set of threads referencing it with a given distribution + // + + map> _mTargetThreads; +}; + +namespace std +{ + template<> + struct less *> + { + // map by pointer, compare with the distributions + bool operator()(const vector * const &lhs, const vector * const &rhs) const + { + return *lhs < *rhs; + } + }; +} + +void ResultParser::_PrintEffectiveDistributions(const Results& results) +{ + // + // Effective distributions can be distinct per target if they vary in size. + // While not possible at the command line, more complex configurations can + // in general specify a distribution per target per thread. + // + // This deduplicates the effective distributions so that we report each + // with the target/thread list which used the (equivalent) distribution + // to access the target. + // + + bool header = false; + UINT32 threadNo = 0; + map *, DistributionRef> m; + + for (auto& thResult : results.vThreadResults) + { + for (auto& tgtResult : thResult.vTargetResults) + { + if (tgtResult.vDistributionRange.size()) + { + auto it = m.find(const_cast *>(&tgtResult.vDistributionRange)); + if (it == m.end()) + { + m.emplace(make_pair(const_cast *>(&tgtResult.vDistributionRange), + DistributionRef(tgtResult.sPath, threadNo))); + } + else + { + it->second._mTargetThreads[tgtResult.sPath].insert(threadNo); + } + } + } + + ++threadNo; + } + + for (auto& r : m) + { + if (!header) + { + header = true; + _Print("\nEffective IO Distributions\n--------------------------\n"); + } + // _Print("target: %s\n", r.second._sTargets.cbegin()->c_str()); + for (auto& tgt : r.second._mTargetThreads) + { + _Print("target: %s [thread:", tgt.first.c_str()); + + UINT32 lastTh = MAXUINT, runLen = 0; + + for (auto& th : tgt.second) + { + if (lastTh != MAXUINT) + { + // accumulate run? + if (lastTh + 1 == th) { + lastTh = th; + ++runLen; + continue; + } + + // end of run - indicate ellision of actual runs + if (runLen > 1) + { + _Print(" -"); + } + _Print(" %u", lastTh); + } + + // start new run (may be singular) + _Print(" %u", th); + lastTh = th; + runLen = 0; + } + + // terminate final run + if (runLen > 1) + { + _Print(" -"); + } + // don't show last thread twice if it terminated run + if (runLen) + { + _Print(" %u", lastTh); + } + + _Print("]\n"); + } + _PrintDistribution(DistributionType::Absolute, *r.first, ""); + } +} + +void ResultParser::_PrintTarget(const Target &target, bool fUseThreadsPerFile, bool fUseRequestsPerFile, bool fCompletionRoutines) +{ + if (target.GetPath().c_str()[0] == TEMPLATE_TARGET_PREFIX) + { + _Print("\tpath: template target '%s'\n", target.GetPath().c_str() + 1); + } + else + { + _Print("\tpath: '%s'\n", target.GetPath().c_str()); + } + _Print("\t\tthink time: %ums\n", target.GetThinkTime()); + _Print("\t\tburst size: %u\n", target.GetBurstSize()); + // TODO: completion routines/ports + + switch (target.GetCacheMode()) + { + case TargetCacheMode::Cached: + _Print("\t\tusing software cache\n"); + break; + case TargetCacheMode::DisableLocalCache: + _Print("\t\tlocal software cache disabled, remote cache enabled\n"); + break; + case TargetCacheMode::DisableOSCache: + _Print("\t\tsoftware cache disabled\n"); + break; + } + + if (target.GetWriteThroughMode() == WriteThroughMode::On) + { + // context-appropriate comment on writethrough + // if sw cache is disabled, commenting on sw write cache is possibly confusing + switch (target.GetCacheMode()) + { + case TargetCacheMode::Cached: + case TargetCacheMode::DisableLocalCache: + _Print("\t\thardware and software write caches disabled, writethrough on\n"); + break; + case TargetCacheMode::DisableOSCache: + _Print("\t\thardware write cache disabled, writethrough on\n"); + break; + } + } + else + { + _Print("\t\tusing hardware write cache, writethrough off\n"); + } + + if (target.GetMemoryMappedIoMode() == MemoryMappedIoMode::On) + { + _Print("\t\tmemory mapped I/O enabled"); + switch(target.GetMemoryMappedIoFlushMode()) + { + case MemoryMappedIoFlushMode::ViewOfFile: + _Print(", flush mode: FlushViewOfFile"); + break; + case MemoryMappedIoFlushMode::NonVolatileMemory: + _Print(", flush mode: FlushNonVolatileMemory"); + break; + case MemoryMappedIoFlushMode::NonVolatileMemoryNoDrain: + _Print(", flush mode: FlushNonVolatileMemory with no drain"); + break; + } + _Print("\n"); + } + + if (target.GetZeroWriteBuffers()) + { + _Print("\t\tzeroing write buffers\n"); + } + + if (target.GetRandomDataWriteBufferSize() > 0) + { + _Print("\t\twrite buffer size: "); + _DisplayFileSize(target.GetRandomDataWriteBufferSize()); + _Print("\n"); + + string sWriteBufferSourcePath = target.GetRandomDataWriteBufferSourcePath(); + if (!sWriteBufferSourcePath.empty()) + { + _Print("\t\twrite buffer source: '%s'\n", sWriteBufferSourcePath.c_str()); + } + else + { + _Print("\t\twrite buffer source: random fill\n"); + } + } + + if (target.GetUseParallelAsyncIO()) + { + _Print("\t\tusing parallel async I/O\n"); + } + + if (target.GetWriteRatio() == 0) + { + _Print("\t\tperforming read test\n"); + } + else if (target.GetWriteRatio() == 100) + { + _Print("\t\tperforming write test\n"); + } + else + { + _Print("\t\tperforming mix test (read/write ratio: %d/%d)\n", 100 - target.GetWriteRatio(), target.GetWriteRatio()); + } + + _Print("\t\tblock size: "); + _DisplayFileSize(target.GetBlockSizeInBytes()); + _Print("\n"); + + if (target.GetRandomRatio() == 100) + { + _Print("\t\tusing random I/O (alignment: "); + } + else + { + if (target.GetRandomRatio() > 0) + { + _Print("\t\tusing mixed random/sequential I/O (%u%% random) (alignment/stride: ", target.GetRandomRatio()); + } + else + { + _Print("\t\tusing%s sequential I/O (stride: ", target.GetUseInterlockedSequential() ? " interlocked":""); + } + } + _DisplayFileSize(target.GetBlockAlignmentInBytes()); + _Print(")\n"); + + if (fUseRequestsPerFile) + { + _Print("\t\tnumber of outstanding I/O operations per thread: %d\n", target.GetRequestCount()); + } + else + { + _Print("\t\trelative IO weight in thread pool: %u\n", target.GetWeight()); + } + + if (0 != target.GetBaseFileOffsetInBytes()) + { + _Print("\t\tbase file offset: "); + _DisplayFileSize(target.GetBaseFileOffsetInBytes()); + _Print("\n"); + } + + if (0 != target.GetMaxFileSize()) + { + _Print("\t\tmax file size: "); + _DisplayFileSize(target.GetMaxFileSize()); + _Print("\n"); + } + + if (0 != target.GetThreadStrideInBytes()) + { + _Print("\t\tthread stride size: "); + _DisplayFileSize(target.GetThreadStrideInBytes()); + _Print("\n"); + } + + if (target.GetSequentialScanHint()) + { + _Print("\t\tusing FILE_FLAG_SEQUENTIAL_SCAN hint\n"); + } + + if (target.GetRandomAccessHint()) + { + _Print("\t\tusing FILE_FLAG_RANDOM_ACCESS hint\n"); + } + + if (target.GetTemporaryFileHint()) + { + _Print("\t\tusing FILE_ATTRIBUTE_TEMPORARY hint\n"); + } + + if (fUseThreadsPerFile) + { + _Print("\t\tthreads per file: %d\n", target.GetThreadsPerFile()); + } + if (target.GetRequestCount() > 1 && fUseThreadsPerFile) + { + if (fCompletionRoutines) + { + _Print("\t\tusing completion routines (ReadFileEx/WriteFileEx)\n"); + } + else + { + _Print("\t\tusing I/O Completion Ports\n"); + } + } + + if (target.GetIOPriorityHint() == IoPriorityHintVeryLow) + { + _Print("\t\tIO priority: very low\n"); + } + else if (target.GetIOPriorityHint() == IoPriorityHintLow) + { + _Print("\t\tIO priority: low\n"); + } + else if (target.GetIOPriorityHint() == IoPriorityHintNormal) + { + _Print("\t\tIO priority: normal\n"); + } + else + { + _Print("\t\tIO priority: unknown\n"); + } + + if (target.GetThroughputIOPS()) + { + _Print("\t\tthroughput rate-limited to %u IOPS\n", target.GetThroughputIOPS()); + } + else if (target.GetThroughputInBytesPerMillisecond()) + { + _Print("\t\tthroughput rate-limited to %u B/ms\n", target.GetThroughputInBytesPerMillisecond()); + } + + if (target.GetDistributionRange().size()) + { + _Print("\t\tIO Distribution:\n"); + _PrintDistribution(target.GetDistributionType(), target.GetDistributionRange(), "\t\t"); + } +} + +void ResultParser::_PrintTimeSpan(const TimeSpan& timeSpan) +{ + _Print("\tduration: %us\n", timeSpan.GetDuration()); + _Print("\twarm up time: %us\n", timeSpan.GetWarmup()); + _Print("\tcool down time: %us\n", timeSpan.GetCooldown()); + if (timeSpan.GetDisableAffinity()) + { + _Print("\taffinity disabled\n"); + } + if (timeSpan.GetMeasureLatency()) + { + _Print("\tmeasuring latency\n"); + } + if (timeSpan.GetCalculateIopsStdDev()) + { + _Print("\tgathering IOPS at intervals of %ums\n", timeSpan.GetIoBucketDurationInMilliseconds()); + } + _Print("\trandom seed: %u\n", timeSpan.GetRandSeed()); + if (timeSpan.GetThreadCount() != 0) + { + _Print("\tthread pool with %u threads\n", timeSpan.GetThreadCount()); + _Print("\tnumber of outstanding I/O operations per thread: %d\n", timeSpan.GetRequestCount()); + } + + const auto& vAffinity = timeSpan.GetAffinityAssignments(); + if ( vAffinity.size() > 0) + { + _Print("\tadvanced affinity round robin (group/core): "); + for (unsigned int x = 0; x < vAffinity.size(); ++x) + { + _Print("%u/%u", vAffinity[x].wGroup, vAffinity[x].bProc); + if (x < vAffinity.size() - 1) + { + _Print(", "); + } + } + _Print("\n"); + } + + if (timeSpan.GetRandomWriteData()) + { + _Print("\tgenerating random data for each write IO\n"); + _Print("\t WARNING: this increases the CPU cost of issuing writes and should only\n"); + _Print("\t be compared to other results using the -Zr flag\n"); + } + + vector vTargets(timeSpan.GetTargets()); + for (auto i = vTargets.begin(); i != vTargets.end(); i++) + { + _PrintTarget(*i, (timeSpan.GetThreadCount() == 0), (timeSpan.GetThreadCount() == 0 || timeSpan.GetRequestCount() == 0), timeSpan.GetCompletionRoutines()); + } +} + +void ResultParser::_PrintProfile(const Profile& profile) +{ + _Print("\nCommand Line: %s\n", profile.GetCmdLine().c_str()); + _Print("\n"); + if (g_ExperimentFlags) + { + _Print("Experiment Flags: 0x%x (%u)\n", g_ExperimentFlags, g_ExperimentFlags); + _Print("\n"); + } + _Print("Input parameters:\n\n"); + if (profile.GetVerbose()) + { + _Print("\tusing verbose mode\n"); + } + + const vector& vTimeSpans = profile.GetTimeSpans(); + int c = 1; + for (auto i = vTimeSpans.begin(); i != vTimeSpans.end(); i++) + { + _Print("\ttimespan: %3d\n", c++); + _Print("\t-------------\n"); + _PrintTimeSpan(*i); + _Print("\n"); + } +} + +void ResultParser::_PrintSystemInfo(const SystemInformation& system) +{ + _Print(system.GetText().c_str()); +} + +void ResultParser::_PrintCpuUtilization(const Results& results, const SystemInformation& system) +{ + const auto& topo = system.processorTopology; + size_t procCount = results.vSystemProcessorPerfInfo.size(); + size_t baseProc = 0; + BYTE efficiencyClass = 0; + BYTE processorCore = 0; + + bool fMultiSocket = topo._vProcessorSocketInformation.size() > 1; + bool fMultiNode = topo._vProcessorNumaInformation.size() > 1; + bool fMultiGroup = topo._vProcessorGroupInformation.size() > 1; + + // + // Columns dynamically expand based on whether the system has multiple of the following, + // in hierarchical order, followed by CPU #: + // + // Socket NUMA Group Core Class + // + // Note that core & cpu number are group-relative, not absolute (or NUMA or socket relative) + // + + _Print("\n"); + if (fMultiSocket) { _Print("Socket | "); } + if (fMultiNode) { _Print("Node | "); } + if (fMultiGroup) { _Print("Group | "); } + if (topo._fSMT) { _Print("Core | "); } + if (topo._ubPerformanceEfficiencyClass) { _Print("Class | "); } + _Print("CPU | Usage | User | Kernel | Idle\n"); + if (fMultiSocket) { _Print("---------"); } + if (fMultiNode) { _Print("-------"); } + if (fMultiGroup) { _Print("--------"); } + if (topo._fSMT) { _Print("-------"); } + if (topo._ubPerformanceEfficiencyClass) { _Print("--------"); } + _Print("----------------------------------------\n"); + + double busyTime = 0; + double totalIdleTime = 0; + double totalUserTime = 0; + double totalKrnlTime = 0; + + for (const auto& group : topo._vProcessorGroupInformation) { + + // Sanity assert - results are sized to the sum of active processors + assert(baseProc + group._activeProcessorCount <= procCount); + + for (BYTE processor = 0; processor < group._activeProcessorCount; processor++) { + + long long fTime = results.vSystemProcessorPerfInfo[baseProc + processor].KernelTime.QuadPart + + results.vSystemProcessorPerfInfo[baseProc + processor].UserTime.QuadPart; + + double idleTime = 100.0 * results.vSystemProcessorPerfInfo[baseProc + processor].IdleTime.QuadPart / fTime; + double krnlTime = 100.0 * results.vSystemProcessorPerfInfo[baseProc + processor].KernelTime.QuadPart / fTime; + double userTime = 100.0 * results.vSystemProcessorPerfInfo[baseProc + processor].UserTime.QuadPart / fTime; + double usedTime = (krnlTime - idleTime) + userTime; + + if (fMultiSocket) { + _Print("%7u| ", topo.GetSocketOfProcessor(group._groupNumber, processor)); + } + if (fMultiNode) { + _Print("%5u| ", topo.GetNumaOfProcessor(group._groupNumber, processor)); + } + if (fMultiGroup) { + _Print("%6u| ", group._groupNumber); + } + processorCore = topo.GetCoreOfProcessor(group._groupNumber, processor, efficiencyClass); + if (topo._fSMT){ + _Print("%5u| ", processorCore); + } + if (topo._ubPerformanceEfficiencyClass) { + _Print("%5u%c| ", + efficiencyClass, + efficiencyClass == topo._ubPerformanceEfficiencyClass ? 'P' : ' '); + } + + _Print("%4u| %6.2lf%%| %6.2lf%%| %6.2lf%%| %6.2lf%%\n", + processor, + usedTime, + userTime, + krnlTime - idleTime, + idleTime); + + busyTime += usedTime; + totalIdleTime += idleTime; + totalUserTime += userTime; + totalKrnlTime += krnlTime; + } + + baseProc += group._activeProcessorCount; + } + + assert(baseProc == procCount); + + if (fMultiSocket) { _Print("---------"); } + if (fMultiNode) { _Print("-------"); } + if (fMultiGroup) { _Print("--------"); } + if (topo._fSMT) { _Print("-------"); } + if (topo._ubPerformanceEfficiencyClass) { _Print("--------"); } + _Print("----------------------------------------\n"); + + if (fMultiSocket) { _Print(" "); } + if (fMultiNode) { _Print(" "); } + if (fMultiGroup) { _Print(" "); } + if (topo._fSMT) { _Print(" "); } + if (topo._ubPerformanceEfficiencyClass) { _Print(" "); } + + _Print("avg.| %6.2lf%%| %6.2lf%%| %6.2lf%%| %6.2lf%%\n", + busyTime / procCount, + totalUserTime / procCount, + (totalKrnlTime - totalIdleTime) / procCount, + totalIdleTime / procCount); +} + +void ResultParser::_PrintSectionFieldNames(const TimeSpan& timeSpan) +{ + _Print("thread | bytes | I/Os | MiB/s | I/O per s %s%s%s| file\n", + timeSpan.GetMeasureLatency() ? "| AvgLat " : "", + timeSpan.GetCalculateIopsStdDev() ? "| IopsStdDev " : "", + timeSpan.GetMeasureLatency() ? "| LatStdDev " : ""); +} + +void ResultParser::_PrintSectionBorderLine(const TimeSpan& timeSpan) +{ + _Print("------------------------------------------------------------------%s%s%s------------\n", + timeSpan.GetMeasureLatency() ? "-----------" : "" , + timeSpan.GetCalculateIopsStdDev() ? "-------------" : "", + timeSpan.GetMeasureLatency() ? "------------" : ""); +} + +void ResultParser::_PrintSection(_SectionEnum section, const TimeSpan& timeSpan, const Results& results) +{ + double fTime = PerfTimer::PerfTimeToSeconds(results.ullTimeCount); + double fBucketTime = timeSpan.GetIoBucketDurationInMilliseconds() / 1000.0; + UINT64 ullTotalBytesCount = 0; + UINT64 ullTotalIOCount = 0; + Histogram totalLatencyHistogram; + IoBucketizer totalIoBucketizer; + + _PrintSectionFieldNames(timeSpan); + + _PrintSectionBorderLine(timeSpan); + + for (unsigned int iThread = 0; iThread < results.vThreadResults.size(); ++iThread) + { + const ThreadResults& threadResults = results.vThreadResults[iThread]; + for (unsigned int iFile = 0; iFile < threadResults.vTargetResults.size(); iFile++) + { + const TargetResults& targetResults = threadResults.vTargetResults[iFile]; + + UINT64 ullBytesCount = 0; + UINT64 ullIOCount = 0; + + Histogram latencyHistogram; + IoBucketizer ioBucketizer; + + if ((section == _SectionEnum::WRITE) || (section == _SectionEnum::TOTAL)) + { + ullBytesCount += targetResults.ullWriteBytesCount; + ullIOCount += targetResults.ullWriteIOCount; + + if (timeSpan.GetMeasureLatency()) + { + latencyHistogram.Merge(targetResults.writeLatencyHistogram); + totalLatencyHistogram.Merge(targetResults.writeLatencyHistogram); + } + + if (timeSpan.GetCalculateIopsStdDev()) + { + ioBucketizer.Merge(targetResults.writeBucketizer); + totalIoBucketizer.Merge(targetResults.writeBucketizer); + } + } + + if ((section == _SectionEnum::READ) || (section == _SectionEnum::TOTAL)) + { + ullBytesCount += targetResults.ullReadBytesCount; + ullIOCount += targetResults.ullReadIOCount; + + if (timeSpan.GetMeasureLatency()) + { + latencyHistogram.Merge(targetResults.readLatencyHistogram); + totalLatencyHistogram.Merge(targetResults.readLatencyHistogram); + } + + if (timeSpan.GetCalculateIopsStdDev()) + { + ioBucketizer.Merge(targetResults.readBucketizer); + totalIoBucketizer.Merge(targetResults.readBucketizer); + } + } + + _Print("%6u | %15llu | %12llu | %10.2f | %10.2f", + iThread, + ullBytesCount, + ullIOCount, + (double)ullBytesCount / 1024 / 1024 / fTime, + (double)ullIOCount / fTime); + + if (timeSpan.GetMeasureLatency()) + { + _Print(" | %8.3f", latencyHistogram.GetAvg()/1000); + } + + if (timeSpan.GetCalculateIopsStdDev()) + { + double iopsStdDev = ioBucketizer.GetStandardDeviationIOPS() / fBucketTime; + _Print(" | %10.2f", iopsStdDev); + } + + if (timeSpan.GetMeasureLatency()) + { + if (latencyHistogram.GetSampleSize() > 0) + { + double latStdDev = latencyHistogram.GetStandardDeviation() / 1000; + _Print(" | %8.3f", latStdDev); + } + else + { + _Print(" | N/A"); + } + } + + _Print(" | %s (", targetResults.sPath.c_str()); + + _DisplayFileSize(targetResults.ullFileSize); + _Print(")\n"); + + ullTotalBytesCount += ullBytesCount; + ullTotalIOCount += ullIOCount; + } + } + + _PrintSectionBorderLine(timeSpan); + + _Print("total: %15llu | %12llu | %10.2f | %10.2f", + ullTotalBytesCount, + ullTotalIOCount, + (double)ullTotalBytesCount / 1024 / 1024 / fTime, + (double)ullTotalIOCount / fTime); + + if (timeSpan.GetMeasureLatency()) + { + _Print(" | %8.3f", totalLatencyHistogram.GetAvg()/1000); + } + + if (timeSpan.GetCalculateIopsStdDev()) + { + double iopsStdDev = totalIoBucketizer.GetStandardDeviationIOPS() / fBucketTime; + _Print(" | %10.2f", iopsStdDev); + } + + if (timeSpan.GetMeasureLatency()) + { + if (totalLatencyHistogram.GetSampleSize() > 0) + { + double latStdDev = totalLatencyHistogram.GetStandardDeviation() / 1000; + _Print(" | %8.3f", latStdDev); + } + else + { + _Print(" | N/A"); + } + } + + _Print("\n"); +} + +void ResultParser::_PrintLatencyPercentiles(const Results& results) +{ + //Print one chart for each target IF more than one target + unordered_map> perTargetReadHistogram; + unordered_map> perTargetWriteHistogram; + unordered_map> perTargetTotalHistogram; + + for (const auto& thread : results.vThreadResults) + { + for (const auto& target : thread.vTargetResults) + { + std::string path = target.sPath; + + perTargetReadHistogram[path].Merge(target.readLatencyHistogram); + + perTargetWriteHistogram[path].Merge(target.writeLatencyHistogram); + + perTargetTotalHistogram[path].Merge(target.readLatencyHistogram); + perTargetTotalHistogram[path].Merge(target.writeLatencyHistogram); + } + } + + //Skip if only one target + if (perTargetTotalHistogram.size() > 1) { + for (auto i : perTargetTotalHistogram) + { + std::string path = i.first; + _Print("\nLatency distribution: %s\n", path.c_str()); + _PrintLatencyChart(perTargetReadHistogram[path], + perTargetWriteHistogram[path], + perTargetTotalHistogram[path]); + } + } + + //Print one chart for the latencies aggregated across all targets + Histogram readLatencyHistogram; + Histogram writeLatencyHistogram; + Histogram totalLatencyHistogram; + + for (const auto& thread : results.vThreadResults) + { + for (const auto& target : thread.vTargetResults) + { + readLatencyHistogram.Merge(target.readLatencyHistogram); + + writeLatencyHistogram.Merge(target.writeLatencyHistogram); + + totalLatencyHistogram.Merge(target.writeLatencyHistogram); + totalLatencyHistogram.Merge(target.readLatencyHistogram); + } + } + + _Print("\nTotal latency distribution:\n"); + _PrintLatencyChart(readLatencyHistogram, writeLatencyHistogram, totalLatencyHistogram); +} + +void ResultParser::_PrintLatencyChart(const Histogram& readLatencyHistogram, + const Histogram& writeLatencyHistogram, + const Histogram& totalLatencyHistogram) +{ + bool fHasReads = readLatencyHistogram.GetSampleSize() > 0; + bool fHasWrites = writeLatencyHistogram.GetSampleSize() > 0; + + _Print(" %%-ile | Read (ms) | Write (ms) | Total (ms)\n"); + _Print("----------------------------------------------\n"); + + string readMin = + fHasReads ? + Util::DoubleToStringHelper(readLatencyHistogram.GetMin()/1000) : + "N/A"; + + string writeMin = + fHasWrites ? + Util::DoubleToStringHelper(writeLatencyHistogram.GetMin() / 1000) : + "N/A"; + + _Print(" min | %10s | %10s | %10.3lf\n", + readMin.c_str(), writeMin.c_str(), totalLatencyHistogram.GetMin()/1000); + + PercentileDescriptor percentiles[] = + { + { 0.25, "25th" }, + { 0.50, "50th" }, + { 0.75, "75th" }, + { 0.90, "90th" }, + { 0.95, "95th" }, + { 0.99, "99th" }, + { 0.999, "3-nines" }, + { 0.9999, "4-nines" }, + { 0.99999, "5-nines" }, + { 0.999999, "6-nines" }, + { 0.9999999, "7-nines" }, + { 0.99999999, "8-nines" }, + { 0.999999999, "9-nines" }, + }; + + for (auto p : percentiles) + { + string readPercentile = + fHasReads ? + Util::DoubleToStringHelper(readLatencyHistogram.GetPercentile(p.Percentile) / 1000) : + "N/A"; + + string writePercentile = + fHasWrites ? + Util::DoubleToStringHelper(writeLatencyHistogram.GetPercentile(p.Percentile) / 1000) : + "N/A"; + + _Print("%7s | %10s | %10s | %10.3lf\n", + p.Name.c_str(), + readPercentile.c_str(), + writePercentile.c_str(), + totalLatencyHistogram.GetPercentile(p.Percentile)/1000); + } + + string readMax = Util::DoubleToStringHelper(readLatencyHistogram.GetMax() / 1000); + string writeMax = Util::DoubleToStringHelper(writeLatencyHistogram.GetMax() / 1000); + + _Print(" max | %10s | %10s | %10.3lf\n", + fHasReads ? readMax.c_str() : "N/A", + fHasWrites ? writeMax.c_str() : "N/A", + totalLatencyHistogram.GetMax()/1000); +} + +string ResultParser::ParseProfile(const Profile& profile) +{ + _sResult.clear(); + _PrintProfile(profile); + return _sResult; +} + +void ResultParser::_PrintWaitStats(const Results &results) +{ + _Print("Wait Statistics\n"); + _Print("thread | completion wait | throttle wait - sleep | lookaside | 0 - 7+ complete per lookaside\n"); + _Print("-----------------------------------------------------------------------------------------------\n"); + for (unsigned int iThread = 0; iThread < results.vThreadResults.size(); ++iThread) + { + const ThreadResults& threadResults = results.vThreadResults[iThread]; + _Print( + "%6u | %15llu | %13llu - %6llu | %9llu | %llu %llu %llu %llu %llu %llu %llu %llu\n", + iThread, + threadResults.WaitStats.Wait, + threadResults.WaitStats.ThrottleWait, + threadResults.WaitStats.ThrottleSleep, + threadResults.WaitStats.Lookaside, + threadResults.WaitStats.LookasideCompletion[0], + threadResults.WaitStats.LookasideCompletion[1], + threadResults.WaitStats.LookasideCompletion[2], + threadResults.WaitStats.LookasideCompletion[3], + threadResults.WaitStats.LookasideCompletion[4], + threadResults.WaitStats.LookasideCompletion[5], + threadResults.WaitStats.LookasideCompletion[6], + threadResults.WaitStats.LookasideCompletion[7]); + } +} + +string ResultParser::ParseResults(const Profile& profile, const SystemInformation& system, vector vResults) +{ + _sResult.clear(); + + _PrintProfile(profile); + _PrintSystemInfo(system); + + for (size_t iResult = 0; iResult < vResults.size(); iResult++) + { + _Print("\nResults for timespan %d:\n", iResult + 1); + _Print("*******************************************************************************\n"); + + const Results& results = vResults[iResult]; + const TimeSpan& timeSpan = profile.GetTimeSpans()[iResult]; + + double fTime = PerfTimer::PerfTimeToSeconds(results.ullTimeCount); //test duration + + char szFloatBuffer[1024]; + + // There either is a fixed number of threads for all files to share (GetThreadCount() > 0) or a number of threads per file. + // In the latter case vThreadResults.size() == number of threads per file * file count + size_t ulThreadCnt = (timeSpan.GetThreadCount() > 0) ? timeSpan.GetThreadCount() : results.vThreadResults.size(); + + if (fTime < 0.0000001) + { + _Print("The test was interrupted before the measurements began. No results are displayed.\n"); + } + else + { + // TODO: parameters.bCreateFile; + + _Print("\n"); + sprintf_s(szFloatBuffer, sizeof(szFloatBuffer), "actual test time:\t%.2lfs\n", fTime); + _Print("%s", szFloatBuffer); + _Print("thread count:\t\t%u\n", ulThreadCnt); + + if (timeSpan.GetThreadCount() != 0 && timeSpan.GetRequestCount() != 0) { + _Print("request count:\t\t%u\n", timeSpan.GetRequestCount()); + } + + _PrintCpuUtilization(results, system); + _PrintEffectiveDistributions(results); + + _Print("\nTotal IO\n"); + _PrintSection(_SectionEnum::TOTAL, timeSpan, results); + + _Print("\nRead IO\n"); + _PrintSection(_SectionEnum::READ, timeSpan, results); + + _Print("\nWrite IO\n"); + _PrintSection(_SectionEnum::WRITE, timeSpan, results); + + if (timeSpan.GetMeasureLatency()) + { + _PrintLatencyPercentiles(results); + } + + //etw + if (results.fUseETW) + { + _DisplayETW(results.EtwMask, results.EtwEventCounters); + _DisplayETWSessionInfo(results.EtwSessionInfo); + } + + // wait stats + if (profile.GetVerboseStats()) + { + _Print("\n"); + _PrintWaitStats(results); + } + } + } + + if (vResults.size() > 1) + { + _Print("\n\nTotals:\n"); + _Print("*******************************************************************************\n\n"); + _Print("type | bytes | I/Os | MiB/s | I/O per s\n"); + _Print("-------------------------------------------------------------------------------\n"); + + + UINT64 cbTotalWritten = 0; + UINT64 cbTotalRead = 0; + UINT64 cTotalWriteIO = 0; + UINT64 cTotalReadIO = 0; + UINT64 cTotalTicks = 0; + for (auto pResults = vResults.begin(); pResults != vResults.end(); pResults++) + { + double time = PerfTimer::PerfTimeToSeconds(pResults->ullTimeCount); + if (time >= 0.0000001) // skip timespans that were interrupted + { + cTotalTicks += pResults->ullTimeCount; + auto vThreadResults = pResults->vThreadResults; + for (auto pThreadResults = vThreadResults.begin(); pThreadResults != vThreadResults.end(); pThreadResults++) + { + for (auto pTargetResults = pThreadResults->vTargetResults.begin(); pTargetResults != pThreadResults->vTargetResults.end(); pTargetResults++) + { + cbTotalRead += pTargetResults->ullReadBytesCount; + cbTotalWritten += pTargetResults->ullWriteBytesCount; + cTotalReadIO += pTargetResults->ullReadIOCount; + cTotalWriteIO += pTargetResults->ullWriteIOCount; + } + } + } + } + + double totalTime = PerfTimer::PerfTimeToSeconds(cTotalTicks); + + _Print("write | %15I64u | %12I64u | %10.2lf | %10.2lf\n", + cbTotalWritten, + cTotalWriteIO, + (double)cbTotalWritten / 1024 / 1024 / totalTime, + (double)cTotalWriteIO / totalTime); + + _Print("read | %15I64u | %12I64u | %10.2lf | %10.2lf\n", + cbTotalRead, + cTotalReadIO, + (double)cbTotalRead / 1024 / 1024 / totalTime, + (double)cTotalReadIO / totalTime); + _Print("-------------------------------------------------------------------------------\n"); + _Print("total | %15I64u | %12I64u | %10.2lf | %10.2lf\n\n", + cbTotalRead + cbTotalWritten, + cTotalReadIO + cTotalWriteIO, + (double)(cbTotalRead + cbTotalWritten) / 1024 / 1024 / totalTime, + (double)(cTotalReadIO + cTotalWriteIO) / totalTime); + + _Print("total test time:\t%.2lfs\n", totalTime); + } + + return _sResult; +} diff --git a/UnitTests/CmdLineParser/CmdLineParser.UnitTests.cpp b/UnitTests/CmdLineParser/CmdLineParser.UnitTests.cpp index a555cf4..036e18c 100644 --- a/UnitTests/CmdLineParser/CmdLineParser.UnitTests.cpp +++ b/UnitTests/CmdLineParser/CmdLineParser.UnitTests.cpp @@ -654,6 +654,161 @@ namespace UnitTests VERIFY_ARE_EQUAL(t.GetThroughputInBytesPerMillisecond(), (DWORD)0); } + void CmdLineParserUnitTests::TestParseCmdLineBaseMaxTarget() + { + CmdLineParser p; + struct Synchronization s = {}; + + { + Profile profile; + const char *argv[] = { "foo", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == true); + VERIFY_IS_TRUE(profile.GetCmdLine().compare("foo testfile.dat") == 0); + + vector vSpans(profile.GetTimeSpans()); + VERIFY_ARE_EQUAL(vSpans.size(), (size_t)1); + + vector vTargets(vSpans[0].GetTargets()); + VERIFY_ARE_EQUAL(vTargets.size(), (size_t)1); + + // defaults = 0 + const auto& t(vTargets[0]); + VERIFY_ARE_EQUAL(t.GetBaseFileOffsetInBytes(), (UINT64) 0); + VERIFY_ARE_EQUAL(t.GetMaxFileSize(), (UINT64) 0); + } + + { + // base 5MiB + Profile profile; + const char *argv[] = { "foo", "-B5m", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == true); + VERIFY_IS_TRUE(profile.GetCmdLine().compare("foo -B5m testfile.dat") == 0); + + vector vSpans(profile.GetTimeSpans()); + VERIFY_ARE_EQUAL(vSpans.size(), (size_t)1); + + vector vTargets(vSpans[0].GetTargets()); + VERIFY_ARE_EQUAL(vTargets.size(), (size_t)1); + + const auto& t(vTargets[0]); + VERIFY_ARE_EQUAL(t.GetBaseFileOffsetInBytes(), (UINT64)(5 * 1024 * 1024)); + VERIFY_ARE_EQUAL(t.GetMaxFileSize(), (UINT64) 0); + } + + { + // base 5MiB, length 1MiB -> 6MiB + Profile profile; + const char *argv[] = { "foo", "-B5m:1m", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == true); + VERIFY_IS_TRUE(profile.GetCmdLine().compare("foo -B5m:1m testfile.dat") == 0); + + vector vSpans(profile.GetTimeSpans()); + VERIFY_ARE_EQUAL(vSpans.size(), (size_t)1); + + vector vTargets(vSpans[0].GetTargets()); + VERIFY_ARE_EQUAL(vTargets.size(), (size_t)1); + + const auto& t(vTargets[0]); + VERIFY_ARE_EQUAL(t.GetBaseFileOffsetInBytes(), (UINT64)(5 * 1024 * 1024)); + VERIFY_ARE_EQUAL(t.GetMaxFileSize(), (UINT64)(6 * 1024 * 1024)); + } + + { + // base 5MiB, max 6MiB + Profile profile; + const char *argv[] = { "foo", "-B5m", "-f6m", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == true); + VERIFY_IS_TRUE(profile.GetCmdLine().compare("foo -B5m -f6m testfile.dat") == 0); + + vector vSpans(profile.GetTimeSpans()); + VERIFY_ARE_EQUAL(vSpans.size(), (size_t)1); + + vector vTargets(vSpans[0].GetTargets()); + VERIFY_ARE_EQUAL(vTargets.size(), (size_t)1); + + const auto& t(vTargets[0]); + VERIFY_ARE_EQUAL(t.GetBaseFileOffsetInBytes(), (UINT64)(5 * 1024 * 1024)); + VERIFY_ARE_EQUAL(t.GetMaxFileSize(), (UINT64)(6 * 1024 * 1024)); + } + + { + // cannot specify -f/-Bb:l together + Profile profile; + const char *argv[] = { "foo", "-B5m:1m", "-f6m", "testfile.dat" }; + VERIFY_IS_FALSE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == true); + } + + { + // cannot specify -B twice (2x b:l) + Profile profile; + const char *argv[] = { "foo", "-B5m:1m", "-B5m:1m", "testfile.dat" }; + VERIFY_IS_FALSE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == true); + } + + { + // cannot specify -B twice (b:l and b) + Profile profile; + const char *argv[] = { "foo", "-B5m:1m", "-B5m", "testfile.dat" }; + VERIFY_IS_FALSE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == true); + } + + { + // cannot specify -B twice (b and b) + Profile profile; + const char *argv[] = { "foo", "-B5m", "-B5m", "testfile.dat" }; + VERIFY_IS_FALSE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == true); + } + + { + // cannot specify -f twice (f and f) + Profile profile; + const char *argv[] = { "foo", "-f5m", "-f6m", "testfile.dat" }; + VERIFY_IS_FALSE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == true); + } + + { + // cannot specify max twice (b:l and f) + Profile profile; + const char *argv[] = { "foo", "-B5m:1m", "-f6m", "testfile.dat" }; + VERIFY_IS_FALSE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == true); + } + + { + // junk after -Bbase + Profile profile; + const char *argv[] = { "foo", "-B5mx", "testfile.dat" }; + VERIFY_IS_FALSE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == true); + } + + { + // sep but no length + Profile profile; + const char *argv[] = { "foo", "-B5m:", "testfile.dat" }; + VERIFY_IS_FALSE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == true); + } + + { + // sep but junk length + Profile profile; + const char *argv[] = { "foo", "-B5m:j", "testfile.dat" }; + VERIFY_IS_FALSE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == true); + } + + { + // sep but bad length spec + Profile profile; + const char *argv[] = { "foo", "-B5m:1x", "testfile.dat" }; + VERIFY_IS_FALSE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == true); + } + + { + // sep but extra after length spec + Profile profile; + const char *argv[] = { "foo", "-B5m:1mx", "testfile.dat" }; + VERIFY_IS_FALSE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == true); + } + } + void CmdLineParserUnitTests::TestParseCmdLineHintFlag() { CmdLineParser p; diff --git a/UnitTests/CmdLineParser/CmdLineParser.UnitTests.h b/UnitTests/CmdLineParser/CmdLineParser.UnitTests.h index dba9812..d31cae4 100644 --- a/UnitTests/CmdLineParser/CmdLineParser.UnitTests.h +++ b/UnitTests/CmdLineParser/CmdLineParser.UnitTests.h @@ -85,6 +85,7 @@ namespace UnitTests TEST_METHOD(TestParseCmdLineEtwUseSystemTimer); TEST_METHOD(TestParseCmdLineGroupAffinity); TEST_METHOD(TestParseCmdLineHintFlag); + TEST_METHOD(TestParseCmdLineBaseMaxTarget); TEST_METHOD(TestParseCmdLineInterlockedSequential); TEST_METHOD(TestParseCmdLineInterlockedSequentialWithStride); TEST_METHOD(TestParseCmdLineIOPriority); diff --git a/UnitTests/CmdLineParser/CmdLineParser.rc b/UnitTests/CmdLineParser/CmdLineParser.rc index 255dacd..53d2e74 100644 --- a/UnitTests/CmdLineParser/CmdLineParser.rc +++ b/UnitTests/CmdLineParser/CmdLineParser.rc @@ -1,5 +1,5 @@ #include -#include "Common.h" +#include "Version.h" #include diff --git a/UnitTests/Common/Common.UnitTests.cpp b/UnitTests/Common/Common.UnitTests.cpp index 6aae922..6f4eff8 100644 --- a/UnitTests/Common/Common.UnitTests.cpp +++ b/UnitTests/Common/Common.UnitTests.cpp @@ -1,1073 +1,1229 @@ -/* - -DISKSPD - -Copyright(c) Microsoft Corporation -All rights reserved. - -MIT License - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - -*/ - -#include "StdAfx.h" -#include "Common.UnitTests.h" -#include "Common.h" -#include - -using namespace WEX::TestExecution; -using namespace WEX::Logging; - -namespace UnitTests -{ - void PerfTimerUnitTests::Test_Freq() - { - VERIFY_IS_TRUE(PerfTimer::TIMER_FREQ > 0); - } - - void PerfTimerUnitTests::Test_GetTime() - { - VERIFY_IS_TRUE(PerfTimer::GetTime() > 0); - } - - void PerfTimerUnitTests::Test_PerfTimeToSeconds() - { - double d = PerfTimer::PerfTimeToSeconds(PerfTimer::TIMER_FREQ); - printf("tos %f %a ==? %f %a\n", d, d, 1.0, 1.0); - VERIFY_IS_TRUE(d == 1.0); - } - - void PerfTimerUnitTests::Test_PerfTimeToMilliseconds() - { - double d = PerfTimer::PerfTimeToMilliseconds(PerfTimer::TIMER_FREQ); - printf("toms %f %a ==? %f %a\n", d, d, 1000.0, 1000.0); - VERIFY_IS_TRUE(d == 1000.0); - } - - void PerfTimerUnitTests::Test_PerfTimeToMicroseconds() - { - double d = PerfTimer::PerfTimeToMicroseconds(PerfTimer::TIMER_FREQ); - printf("tous %f %a ==? %f %a\n", d, d, 1000000.0, 1000000.0); - VERIFY_IS_TRUE(d == 1000000.0); - } - - void PerfTimerUnitTests::Test_SecondsToPerfTime() - { - UINT64 u = PerfTimer::SecondsToPerfTime(1.0); - VERIFY_IS_TRUE(u == PerfTimer::TIMER_FREQ); - } - - void PerfTimerUnitTests::Test_MillisecondsToPerfTime() - { - UINT64 u = PerfTimer::MillisecondsToPerfTime(1000.0); - VERIFY_IS_TRUE(u == PerfTimer::TIMER_FREQ); - } - - void PerfTimerUnitTests::Test_MicrosecondsToPerfTime() - { - UINT64 u = PerfTimer::MicrosecondsToPerfTime(1000000.0); - VERIFY_IS_TRUE(u == PerfTimer::TIMER_FREQ); - } - - void HistogramUnitTests::Test_Empty() - { - Histogram h; - VERIFY_ARE_EQUAL(h.GetSampleSize(), (unsigned)0); - } - - void HistogramUnitTests::Test_Add() - { - Histogram h; - h.Add(42); - VERIFY_ARE_EQUAL(h.GetSampleSize(), (unsigned)1); - } - - void HistogramUnitTests::Test_Clear() - { - Histogram h; - h.Add(42); - h.Clear(); - VERIFY_ARE_EQUAL(h.GetSampleSize(), (unsigned)0); - } - - void HistogramUnitTests::Test_MinMax() - { - Histogram h; - h.Add(1); - h.Add(2); - VERIFY_ARE_EQUAL(h.GetMin(), 1); - VERIFY_ARE_EQUAL(h.GetMax(), 2); - } - - void HistogramUnitTests::Test_GetPercentile() - { - Histogram h; - h.Add(1); - h.Add(2); - h.Add(3); - VERIFY_ARE_EQUAL(h.GetPercentile(0.5), 2); - } - - void HistogramUnitTests::Test_GetMean() - { - Histogram h; - h.Add(2); - h.Add(4); - VERIFY_ARE_EQUAL(h.GetMean(), 3); - } - - void HistogramUnitTests::Test_Merge() - { - Histogram h1; - h1.Add(1); - - Histogram h2; - h2.Add(2); - - h1.Merge(h2); - - VERIFY_ARE_EQUAL(h1.GetSampleSize(), (unsigned)2); - } - - void IoBucketizerUnitTests::Test_Empty() - { - IoBucketizer b; - VERIFY_ARE_EQUAL(b.GetNumberOfValidBuckets(), (size_t)0); - } - - void IoBucketizerUnitTests::Test_Add() - { - IoBucketizer b; - b.Initialize(10, 4); - - b.Add(5, 1); - b.Add(8, 2); - VERIFY_ARE_EQUAL(b.GetNumberOfValidBuckets(), (size_t)1); - - b.Add(15, 3); - VERIFY_ARE_EQUAL(b.GetNumberOfValidBuckets(), (size_t)2); - - b.Add(18, 5); - VERIFY_ARE_EQUAL(b.GetNumberOfValidBuckets(), (size_t)2); - - b.Add(45, 4); - VERIFY_ARE_EQUAL(b.GetNumberOfValidBuckets(), (size_t)4); - - VERIFY_ARE_EQUAL(b.GetIoBucketCount(0), (unsigned int)2); - VERIFY_ARE_EQUAL(b.GetIoBucketMinDurationUsec(0), 1L); - VERIFY_ARE_EQUAL(b.GetIoBucketMaxDurationUsec(0), 2L); - VERIFY_ARE_EQUAL(b.GetIoBucketAvgDurationUsec(0), 1.5L); - VERIFY_ARE_EQUAL(b.GetIoBucketDurationStdDevUsec(0), 0.5L); - VERIFY_ARE_EQUAL(b.GetIoBucketCount(1), (unsigned int)2); - VERIFY_ARE_EQUAL(b.GetIoBucketMinDurationUsec(1), 3L); - VERIFY_ARE_EQUAL(b.GetIoBucketMaxDurationUsec(1), 5L); - VERIFY_ARE_EQUAL(b.GetIoBucketAvgDurationUsec(1), 4L); - VERIFY_ARE_EQUAL(b.GetIoBucketDurationStdDevUsec(1), 1L); - VERIFY_ARE_EQUAL(b.GetIoBucketCount(2), (unsigned int)0); - VERIFY_ARE_EQUAL(b.GetIoBucketMinDurationUsec(2), 0); - VERIFY_ARE_EQUAL(b.GetIoBucketMaxDurationUsec(2), 0); - VERIFY_ARE_EQUAL(b.GetIoBucketAvgDurationUsec(2), 0); - VERIFY_ARE_EQUAL(b.GetIoBucketDurationStdDevUsec(2), 0); - VERIFY_ARE_EQUAL(b.GetIoBucketCount(3), (unsigned int)0); - VERIFY_ARE_EQUAL(b.GetIoBucketMinDurationUsec(3), 0); - VERIFY_ARE_EQUAL(b.GetIoBucketMaxDurationUsec(3), 0); - VERIFY_ARE_EQUAL(b.GetIoBucketAvgDurationUsec(3), 0); - VERIFY_ARE_EQUAL(b.GetIoBucketDurationStdDevUsec(3), 0); - } - - void IoBucketizerUnitTests::Test_Merge() - { - IoBucketizer b1; - IoBucketizer b2; - b1.Initialize(10, 3); - b2.Initialize(10, 3); - - // b1 buckets: 2,0,1 - b1.Add(0, 0); - b1.Add(1, 0); - b1.Add(20, 0); - b1.Add(30, 0); - - VERIFY_ARE_EQUAL(b1.GetNumberOfValidBuckets(), (size_t)3); - VERIFY_ARE_EQUAL(b1.GetIoBucketCount(0), (unsigned int)2); - VERIFY_ARE_EQUAL(b1.GetIoBucketCount(1), (unsigned int)0); - VERIFY_ARE_EQUAL(b1.GetIoBucketCount(2), (unsigned int)1); - - // b2 buckets: 1,3 - b2.Add(0, 0); - b2.Add(10, 0); - b2.Add(11, 0); - b2.Add(12, 0); - - VERIFY_ARE_EQUAL(b2.GetNumberOfValidBuckets(), (size_t)2); - VERIFY_ARE_EQUAL(b2.GetIoBucketCount(0), (unsigned int)1); - VERIFY_ARE_EQUAL(b2.GetIoBucketCount(1), (unsigned int)3); - - b1.Merge(b2); - - // Merged buckets: 3,3,1 - VERIFY_ARE_EQUAL(b1.GetNumberOfValidBuckets(), (size_t)3); - VERIFY_ARE_EQUAL(b1.GetIoBucketCount(0), (unsigned int)3); - VERIFY_ARE_EQUAL(b1.GetIoBucketCount(1), (unsigned int)3); - VERIFY_ARE_EQUAL(b1.GetIoBucketCount(2), (unsigned int)1); - - // Source unchanged. - VERIFY_ARE_EQUAL(b2.GetNumberOfValidBuckets(), (size_t)2); - VERIFY_ARE_EQUAL(b2.GetIoBucketCount(0), (unsigned int)1); - VERIFY_ARE_EQUAL(b2.GetIoBucketCount(1), (unsigned int)3); - - // Merge into empty bucketizer - IoBucketizer b3; - - // Its empty. - VERIFY_ARE_EQUAL(b3.GetNumberOfValidBuckets(), (size_t)0); - - b3.Merge(b1); - - // Merged buckets: 3,3,1 - VERIFY_ARE_EQUAL(b3.GetNumberOfValidBuckets(), (size_t)3); - VERIFY_ARE_EQUAL(b3.GetIoBucketCount(0), (unsigned int)3); - VERIFY_ARE_EQUAL(b3.GetIoBucketCount(1), (unsigned int)3); - VERIFY_ARE_EQUAL(b3.GetIoBucketCount(2), (unsigned int)1); - } - - void IoBucketizerUnitTests::Test_GetStandardDeviation() - { - IoBucketizer b; - b.Initialize(10, 2); - - // b buckets: 1,2 - b.Add(0, 0); - b.Add(10, 0); - b.Add(11, 0); - b.Add(20, 0); - - // Standard deviation from valid buckets (the first two) is STDDEV(1,2) = 0.5 - VERIFY_ARE_EQUAL(b.GetStandardDeviationIOPS(), 0.5L); - } - - void ProfileUnitTests::Test_GetXmlEmptyProfile() - { - Profile profile; - string sXml = profile.GetXml(0); - //printf("'%s'\n", sXml.c_str()); - VERIFY_IS_TRUE(sXml == "\n" - " 0\n" - " text\n" - " false\n" - " \n" - " \n" - "\n"); - } - - void ProfileUnitTests::Test_GetXmlPrecreateFilesUseMaxSize() - { - Profile profile; - profile.SetPrecreateFiles(PrecreateFiles::UseMaxSize); - string sXml = profile.GetXml(0); - //printf("'%s'\n", sXml.c_str()); - VERIFY_IS_TRUE(sXml == "\n" - " 0\n" - " text\n" - " false\n" - " UseMaxSize\n" - " \n" - " \n" - "\n"); - } - - void ProfileUnitTests::Test_GetXmlPrecreateFilesOnlyFilesWithConstantSizes() - { - Profile profile; - profile.SetPrecreateFiles(PrecreateFiles::OnlyFilesWithConstantSizes); - string sXml = profile.GetXml(0); - //printf("'%s'\n", sXml.c_str()); - VERIFY_IS_TRUE(sXml == "\n" - " 0\n" - " text\n" - " false\n" - " CreateOnlyFilesWithConstantSizes\n" - " \n" - " \n" - "\n"); - } - - void ProfileUnitTests::Test_GetXmlPrecreateFilesOnlyFilesWithConstantOrZeroSizes() - { - Profile profile; - profile.SetPrecreateFiles(PrecreateFiles::OnlyFilesWithConstantOrZeroSizes); - string sXml = profile.GetXml(0); - //printf("'%s'\n", sXml.c_str()); - VERIFY_IS_TRUE(sXml == "\n" - " 0\n" - " text\n" - " false\n" - " CreateOnlyFilesWithConstantOrZeroSizes\n" - " \n" - " \n" - "\n"); - } - - void ProfileUnitTests::Test_MarkFilesAsCreated() - { - Target target1; - target1.SetPath("file1.txt"); - - Target target2; - target2.SetPath("file2.txt"); - - Target target3; - target3.SetPath("file1.txt"); - - Target target4; - target4.SetPath("file3.txt"); - - Target target5; - target5.SetPath("file2.txt"); - - Target target6; - target6.SetPath("file2.txt"); - - TimeSpan timeSpan1; - timeSpan1.AddTarget(target1); - timeSpan1.AddTarget(target2); - - TimeSpan timeSpan2; - timeSpan2.AddTarget(target3); - timeSpan2.AddTarget(target4); - timeSpan2.AddTarget(target5); - timeSpan2.AddTarget(target6); - - Profile profile; - profile.AddTimeSpan(timeSpan1); - profile.AddTimeSpan(timeSpan2); - - vector vFiles; - vFiles.push_back("file1.txt"); - vFiles.push_back("file2.txt"); - - VERIFY_IS_FALSE(profile._vTimeSpans[0]._vTargets[0]._fPrecreated); - VERIFY_IS_FALSE(profile._vTimeSpans[0]._vTargets[1]._fPrecreated); - VERIFY_IS_FALSE(profile._vTimeSpans[1]._vTargets[0]._fPrecreated); - VERIFY_IS_FALSE(profile._vTimeSpans[1]._vTargets[1]._fPrecreated); - VERIFY_IS_FALSE(profile._vTimeSpans[1]._vTargets[2]._fPrecreated); - VERIFY_IS_FALSE(profile._vTimeSpans[1]._vTargets[3]._fPrecreated); - - profile.MarkFilesAsPrecreated(vFiles); - VERIFY_IS_TRUE(profile._vTimeSpans[0]._vTargets[0]._fPrecreated); - VERIFY_IS_TRUE(profile._vTimeSpans[0]._vTargets[1]._fPrecreated); - VERIFY_IS_TRUE(profile._vTimeSpans[1]._vTargets[0]._fPrecreated); - VERIFY_IS_FALSE(profile._vTimeSpans[1]._vTargets[1]._fPrecreated); - VERIFY_IS_TRUE(profile._vTimeSpans[1]._vTargets[2]._fPrecreated); - VERIFY_IS_TRUE(profile._vTimeSpans[1]._vTargets[3]._fPrecreated); - } - - void ProfileUnitTests::Test_Validate() - { - TimeSpan timeSpan; - Target target; - - target.SetBaseFileOffsetInBytes(1000); - target.SetBlockAlignmentInBytes(500); - target.SetBlockSizeInBytes(1000); - target.SetThreadStrideInBytes(5000); - timeSpan.AddTarget(target); - - Profile profile; - profile.AddTimeSpan(timeSpan); - - // thread stride errors if only one thread used (default) - // both the single spec assumption and full should behave the same - VERIFY_IS_FALSE(profile.Validate(true)); - VERIFY_IS_FALSE(profile.Validate(false)); - - profile._vTimeSpans[0].SetThreadCount(2); - VERIFY_IS_TRUE(profile.Validate(true)); - VERIFY_IS_TRUE(profile.Validate(false)); - - // now turning on interlocked sequential, fail since thread stride is set - profile._vTimeSpans[0]._vTargets[0].SetUseInterlockedSequential(true); - VERIFY_IS_FALSE(profile.Validate(true)); - VERIFY_IS_FALSE(profile.Validate(false)); - - profile._vTimeSpans[0]._vTargets[0].SetThreadStrideInBytes(0); - VERIFY_IS_TRUE(profile.Validate(true)); - VERIFY_IS_TRUE(profile.Validate(false)); - } - - void ProfileUnitTests::Test_ValidateSystem() - { - // processor topology validation for affinity assignments - // 2 group, 2 procs/group - SystemInformation system; - system.processorTopology._vProcessorGroupInformation.clear(); - system.processorTopology._vProcessorGroupInformation.emplace_back((BYTE)2, (BYTE)2, (WORD)0, (KAFFINITY)0x3); - system.processorTopology._vProcessorGroupInformation.emplace_back((BYTE)2, (BYTE)2, (WORD)1, (KAFFINITY)0x3); - - TimeSpan timeSpan; - Profile profile; - - // assign to each proc - profile.ClearTimeSpans(); - timeSpan.ClearAffinityAssignment(); - timeSpan.AddAffinityAssignment(0, 0); - timeSpan.AddAffinityAssignment(0, 1); - timeSpan.AddAffinityAssignment(1, 0); - timeSpan.AddAffinityAssignment(1, 1); - profile.AddTimeSpan(timeSpan); - VERIFY_IS_TRUE(profile.Validate(true, &system)); - - // shrink active mask - system.processorTopology._vProcessorGroupInformation.clear(); - system.processorTopology._vProcessorGroupInformation.emplace_back((BYTE)2, (BYTE)2, (WORD)0, (KAFFINITY)0x1); - system.processorTopology._vProcessorGroupInformation.emplace_back((BYTE)2, (BYTE)2, (WORD)1, (KAFFINITY)0x1); - - // fail assignment to inactive procs - VERIFY_IS_FALSE(profile.Validate(true, &system)); - - // shrink procs, still fail - system.processorTopology._vProcessorGroupInformation.clear(); - system.processorTopology._vProcessorGroupInformation.emplace_back((BYTE)1, (BYTE)1, (WORD)0, (KAFFINITY)0x1); - system.processorTopology._vProcessorGroupInformation.emplace_back((BYTE)1, (BYTE)1, (WORD)1, (KAFFINITY)0x1); - - // now fail - VERIFY_IS_FALSE(profile.Validate(true, &system)); - - // assign to low procs, and succeed - profile.ClearTimeSpans(); - timeSpan.ClearAffinityAssignment(); - timeSpan.AddAffinityAssignment(0, 0); - timeSpan.AddAffinityAssignment(1, 0); - profile.AddTimeSpan(timeSpan); - VERIFY_IS_TRUE(profile.Validate(true, &system)); - - // shrink groups - system.processorTopology._vProcessorGroupInformation.clear(); - system.processorTopology._vProcessorGroupInformation.emplace_back((BYTE)1, (BYTE)1, (WORD)0, (KAFFINITY)0x1); - - // now fail - VERIFY_IS_FALSE(profile.Validate(true, &system)); - - // assign to low proc, and succeed - profile.ClearTimeSpans(); - timeSpan.ClearAffinityAssignment(); - timeSpan.AddAffinityAssignment(0, 0); - profile.AddTimeSpan(timeSpan); - VERIFY_IS_TRUE(profile.Validate(true, &system)); - - // assign to invalid group - profile.ClearTimeSpans(); - timeSpan.ClearAffinityAssignment(); - timeSpan.AddAffinityAssignment(1, 0); - profile.AddTimeSpan(timeSpan); - VERIFY_IS_FALSE(profile.Validate(true, &system)); - } - - void TargetUnitTests::TestGetSetRandomDataWriteBufferSize() - { - Target t; - VERIFY_ARE_EQUAL(t.GetRandomDataWriteBufferSize(), 0); - t.SetRandomDataWriteBufferSize(1234); - VERIFY_ARE_EQUAL(t.GetRandomDataWriteBufferSize(), 1234); - } - - void TargetUnitTests::TestGetSetRandomDataWriteBufferSourcePath() - { - Target t; - VERIFY_ARE_EQUAL(t.GetRandomDataWriteBufferSourcePath(), ""); - t.SetRandomDataWriteBufferSourcePath("x:\\foo\\bar.dat"); - VERIFY_ARE_EQUAL(t.GetRandomDataWriteBufferSourcePath(), "x:\\foo\\bar.dat"); - } - - void TargetUnitTests::Test_TargetGetXmlWriteBufferContentSequential() - { - Target target; - string sXml = target.GetXml(0); - VERIFY_IS_TRUE(sXml == "\n" - " \n" - " 65536\n" - " 0\n" - " false\n" - " false\n" - " false\n" - " false\n" - " \n" - " sequential\n" - " \n" - " false\n" - " 65536\n" - " false\n" - " 0\n" - " 0\n" - " 2\n" - " 0\n" - " 0\n" - " 1\n" - " 3\n" - " 1\n" - "\n"); - } - - void TargetUnitTests::Test_TargetGetXmlWriteBufferContentZero() - { - Target target; - target.SetZeroWriteBuffers(true); - string sXml = target.GetXml(0); - VERIFY_IS_TRUE(sXml == "\n" - " \n" - " 65536\n" - " 0\n" - " false\n" - " false\n" - " false\n" - " false\n" - " \n" - " zero\n" - " \n" - " false\n" - " 65536\n" - " false\n" - " 0\n" - " 0\n" - " 2\n" - " 0\n" - " 0\n" - " 1\n" - " 3\n" - " 1\n" - "\n"); - } - - void TargetUnitTests::Test_TargetGetXmlWriteBufferContentRandomNoFilePath() - { - Target target; - target.SetRandomDataWriteBufferSize(224433); - string sXml = target.GetXml(0); - VERIFY_IS_TRUE(sXml == "\n" - " \n" - " 65536\n" - " 0\n" - " false\n" - " false\n" - " false\n" - " false\n" - " \n" - " random\n" - " \n" - " 224433\n" - " \n" - " \n" - " false\n" - " 65536\n" - " false\n" - " 0\n" - " 0\n" - " 2\n" - " 0\n" - " 0\n" - " 1\n" - " 3\n" - " 1\n" - "\n"); - } - - void TargetUnitTests::Test_TargetGetXmlWriteBufferContentRandomWithFilePath() - { - Target target; - target.SetRandomDataWriteBufferSize(224433); - target.SetRandomDataWriteBufferSourcePath("x:\\foo\\bar.baz"); - string sXml = target.GetXml(0); - VERIFY_IS_TRUE(sXml == "\n" - " \n" - " 65536\n" - " 0\n" - " false\n" - " false\n" - " false\n" - " false\n" - " \n" - " random\n" - " \n" - " 224433\n" - " x:\\foo\\bar.baz\n" - " \n" - " \n" - " false\n" - " 65536\n" - " false\n" - " 0\n" - " 0\n" - " 2\n" - " 0\n" - " 0\n" - " 1\n" - " 3\n" - " 1\n" - "\n"); - } - - void TargetUnitTests::Test_TargetGetXmlDisableAllCache() - { - Target target; - target.SetCacheMode(TargetCacheMode::DisableOSCache); - target.SetWriteThroughMode(WriteThroughMode::On); - string sXml = target.GetXml(0); - VERIFY_IS_TRUE(sXml == "\n" - " \n" - " 65536\n" - " 0\n" - " false\n" - " false\n" - " false\n" - " false\n" - " true\n" - " true\n" - " \n" - " sequential\n" - " \n" - " false\n" - " 65536\n" - " false\n" - " 0\n" - " 0\n" - " 2\n" - " 0\n" - " 0\n" - " 1\n" - " 3\n" - " 1\n" - "\n"); - } - - void TargetUnitTests::Test_TargetGetXmlDisableLocalCache() - { - Target target; - target.SetCacheMode(TargetCacheMode::DisableLocalCache); - string sXml = target.GetXml(0); - VERIFY_IS_TRUE(sXml == "\n" - " \n" - " 65536\n" - " 0\n" - " false\n" - " false\n" - " false\n" - " false\n" - " true\n" - " \n" - " sequential\n" - " \n" - " false\n" - " 65536\n" - " false\n" - " 0\n" - " 0\n" - " 2\n" - " 0\n" - " 0\n" - " 1\n" - " 3\n" - " 1\n" - "\n"); - } - - void TargetUnitTests::Test_TargetGetXmlDisableOSCache() - { - Target target; - target.SetCacheMode(TargetCacheMode::DisableOSCache); - string sXml = target.GetXml(0); - VERIFY_IS_TRUE(sXml == "\n" - " \n" - " 65536\n" - " 0\n" - " false\n" - " false\n" - " false\n" - " false\n" - " true\n" - " \n" - " sequential\n" - " \n" - " false\n" - " 65536\n" - " false\n" - " 0\n" - " 0\n" - " 2\n" - " 0\n" - " 0\n" - " 1\n" - " 3\n" - " 1\n" - "\n"); - } - - void TargetUnitTests::Test_TargetGetXmlBufferedWriteThrough() - { - Target target; - target.SetWriteThroughMode(WriteThroughMode::On); - string sXml = target.GetXml(0); - VERIFY_IS_TRUE(sXml == "\n" - " \n" - " 65536\n" - " 0\n" - " false\n" - " false\n" - " false\n" - " false\n" - " true\n" - " \n" - " sequential\n" - " \n" - " false\n" - " 65536\n" - " false\n" - " 0\n" - " 0\n" - " 2\n" - " 0\n" - " 0\n" - " 1\n" - " 3\n" - " 1\n" - "\n"); - } - - void TargetUnitTests::Test_TargetGetXmlMemoryMappedIo() - { - Target target; - target.SetMemoryMappedIoMode(MemoryMappedIoMode::On); - string sXml = target.GetXml(0); - VERIFY_IS_TRUE(sXml == "\n" - " \n" - " 65536\n" - " 0\n" - " false\n" - " false\n" - " false\n" - " false\n" - " true\n" - " \n" - " sequential\n" - " \n" - " false\n" - " 65536\n" - " false\n" - " 0\n" - " 0\n" - " 2\n" - " 0\n" - " 0\n" - " 1\n" - " 3\n" - " 1\n" - "\n"); - } - - void TargetUnitTests::Test_TargetGetXmlMemoryMappedIoFlushModeViewOfFile() - { - Target target; - target.SetMemoryMappedIoMode(MemoryMappedIoMode::On); - target.SetMemoryMappedIoFlushMode(MemoryMappedIoFlushMode::ViewOfFile); - string sXml = target.GetXml(0); - VERIFY_IS_TRUE(sXml == "\n" - " \n" - " 65536\n" - " 0\n" - " false\n" - " false\n" - " false\n" - " false\n" - " true\n" - " ViewOfFile\n" - " \n" - " sequential\n" - " \n" - " false\n" - " 65536\n" - " false\n" - " 0\n" - " 0\n" - " 2\n" - " 0\n" - " 0\n" - " 1\n" - " 3\n" - " 1\n" - "\n"); - } - - void TargetUnitTests::Test_TargetGetXmlMemoryMappedIoFlushModeNonVolatileMemory() - { - Target target; - target.SetMemoryMappedIoMode(MemoryMappedIoMode::On); - target.SetMemoryMappedIoFlushMode(MemoryMappedIoFlushMode::NonVolatileMemory); - string sXml = target.GetXml(0); - VERIFY_IS_TRUE(sXml == "\n" - " \n" - " 65536\n" - " 0\n" - " false\n" - " false\n" - " false\n" - " false\n" - " true\n" - " NonVolatileMemory\n" - " \n" - " sequential\n" - " \n" - " false\n" - " 65536\n" - " false\n" - " 0\n" - " 0\n" - " 2\n" - " 0\n" - " 0\n" - " 1\n" - " 3\n" - " 1\n" - "\n"); - } - - void TargetUnitTests::Test_TargetGetXmlMemoryMappedIoFlushModeNonVolatileMemoryNoDrain() - { - Target target; - target.SetMemoryMappedIoMode(MemoryMappedIoMode::On); - target.SetMemoryMappedIoFlushMode(MemoryMappedIoFlushMode::NonVolatileMemoryNoDrain); - string sXml = target.GetXml(0); - VERIFY_IS_TRUE(sXml == "\n" - " \n" - " 65536\n" - " 0\n" - " false\n" - " false\n" - " false\n" - " false\n" - " true\n" - " NonVolatileMemoryNoDrain\n" - " \n" - " sequential\n" - " \n" - " false\n" - " 65536\n" - " false\n" - " 0\n" - " 0\n" - " 2\n" - " 0\n" - " 0\n" - " 1\n" - " 3\n" - " 1\n" - "\n"); - } - - void TargetUnitTests::Test_TargetGetXmlRandomAccessHint() - { - Target target; - target.SetRandomAccessHint(true); - string sXml = target.GetXml(0); - VERIFY_IS_TRUE(sXml == "\n" - " \n" - " 65536\n" - " 0\n" - " false\n" - " true\n" - " false\n" - " false\n" - " \n" - " sequential\n" - " \n" - " false\n" - " 65536\n" - " false\n" - " 0\n" - " 0\n" - " 2\n" - " 0\n" - " 0\n" - " 1\n" - " 3\n" - " 1\n" - "\n"); - } - - void TargetUnitTests::Test_TargetGetXmlSequentialScanHint() - { - Target target; - target.SetSequentialScanHint(true); - string sXml = target.GetXml(0); - VERIFY_IS_TRUE(sXml == "\n" - " \n" - " 65536\n" - " 0\n" - " true\n" - " false\n" - " false\n" - " false\n" - " \n" - " sequential\n" - " \n" - " false\n" - " 65536\n" - " false\n" - " 0\n" - " 0\n" - " 2\n" - " 0\n" - " 0\n" - " 1\n" - " 3\n" - " 1\n" - "\n"); - } - - void TargetUnitTests::Test_TargetGetXmlCombinedAccessHint() - { - Target target; - target.SetSequentialScanHint(true); - target.SetTemporaryFileHint(true); - string sXml = target.GetXml(0); - VERIFY_IS_TRUE(sXml == "\n" - " \n" - " 65536\n" - " 0\n" - " true\n" - " false\n" - " true\n" - " false\n" - " \n" - " sequential\n" - " \n" - " false\n" - " 65536\n" - " false\n" - " 0\n" - " 0\n" - " 2\n" - " 0\n" - " 0\n" - " 1\n" - " 3\n" - " 1\n" - "\n"); - } - - void TargetUnitTests::Test_AllocateAndFillRandomDataWriteBuffer() - { - Random r; - Target t; - VERIFY_IS_FALSE(t.AllocateAndFillRandomDataWriteBuffer(&r)); - VERIFY_ARE_EQUAL(t._pRandomDataWriteBuffer, nullptr); - - size_t cb = 12345; - t.SetRandomDataWriteBufferSize(cb); - VERIFY_IS_TRUE(t.AllocateAndFillRandomDataWriteBuffer(&r)); - VERIFY_IS_TRUE(t._pRandomDataWriteBuffer != nullptr); - // see if the test crashes if we try to write to every byte of the buffer - - for (size_t i = 0; i < cb; i++) - { - t._pRandomDataWriteBuffer[i] = (i % 256); - } - - for (size_t i = 0; i < cb; i++) - { - if (t._pRandomDataWriteBuffer[i] != (i % 256)) - { - // don't call VERIFY_ARE_EQUAL on each item because it prints to the screen and makes the test take - // too long - VERIFY_IS_TRUE(false); - } - } - } - - void TargetUnitTests::Test_AllocateAndFillRandomDataWriteBufferFromFile() - { - char szTempDirPath[MAX_PATH] = {}; - DWORD cch = GetTempPathA(_countof(szTempDirPath), szTempDirPath); - VERIFY_IS_TRUE(cch != 0); - string sTempFilePath(szTempDirPath); - sTempFilePath += "diskspd-random-data-file.dat"; - DeleteFileA(sTempFilePath.c_str()); - - printf("path: '%s'\n", sTempFilePath.c_str()); - FILE *pFile; - fopen_s(&pFile, sTempFilePath.c_str(), "wb"); - VERIFY_IS_TRUE(pFile != nullptr); - char buffer[256]; - for (int i = 0; i < 256; i++) - { - buffer[i] = static_cast(0xFF - i); - } - VERIFY_ARE_EQUAL(fwrite(buffer, sizeof(buffer), 1, pFile), (size_t)1); - fclose(pFile); - pFile = nullptr; - - Random r; - Target t; - size_t cbBuffer = 1024 * 1024; - t.SetRandomDataWriteBufferSize(cbBuffer); - t.SetRandomDataWriteBufferSourcePath(sTempFilePath.c_str()); - VERIFY_IS_TRUE(t.AllocateAndFillRandomDataWriteBuffer(&r)); - VERIFY_IS_TRUE(t._pRandomDataWriteBuffer != nullptr); - for (size_t i = 0; i < cbBuffer; i++) - { - if (t._pRandomDataWriteBuffer[i] != (0xFF - (i % 256))) - { - // don't call VERIFY_ARE_EQUAL on each item because it prints to the screen and makes the test take - // too long - VERIFY_IS_TRUE(false); - } - } - - DeleteFileA(sTempFilePath.c_str()); - } - - void ThreadParametersUnitTests::Test_AllocateAndFillBufferForTarget() - { - TimeSpan ts; - Target t; - Random r; - t.SetBlockSizeInBytes(12345); - t.SetRequestCount(12); - ThreadParameters tp; - tp.pTimeSpan = &ts; - tp.pRand = &r; - VERIFY_IS_TRUE(tp.AllocateAndFillBufferForTarget(t)); - - // see if the test crashes if we try to write to every byte of the buffer - size_t cb = t.GetBlockSizeInBytes() * t.GetRequestCount(); - for (size_t i = 0; i < cb; i++) - { - tp.vpDataBuffers[0][i] = (i % 256); - } - - for (size_t i = 0; i < cb; i++) - { - if (tp.vpDataBuffers[0][i] != (i % 256)) - { - // don't call VERIFY_ARE_EQUAL on each item because it prints to the screen and makes the test take - // too long - VERIFY_IS_TRUE(false); - } - } - } -} +/* + +DISKSPD + +Copyright(c) Microsoft Corporation +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +#include "StdAfx.h" +#include "Common.UnitTests.h" +#include "Common.h" +#include + +using namespace WEX::TestExecution; +using namespace WEX::Logging; + +namespace UnitTests +{ + void PerfTimerUnitTests::Test_Freq() + { + VERIFY_IS_TRUE(PerfTimer::TIMER_FREQ > 0); + } + + void PerfTimerUnitTests::Test_GetTime() + { + VERIFY_IS_TRUE(PerfTimer::GetTime() > 0); + } + + void PerfTimerUnitTests::Test_PerfTimeToSeconds() + { + double d = PerfTimer::PerfTimeToSeconds(PerfTimer::TIMER_FREQ); + printf("tos %f %a ==? %f %a\n", d, d, 1.0, 1.0); + VERIFY_IS_TRUE(d == 1.0); + } + + void PerfTimerUnitTests::Test_PerfTimeToMilliseconds() + { + double d = PerfTimer::PerfTimeToMilliseconds(PerfTimer::TIMER_FREQ); + printf("toms %f %a ==? %f %a\n", d, d, 1000.0, 1000.0); + VERIFY_IS_TRUE(d == 1000.0); + } + + void PerfTimerUnitTests::Test_PerfTimeToMicroseconds() + { + double d = PerfTimer::PerfTimeToMicroseconds(PerfTimer::TIMER_FREQ); + printf("tous %f %a ==? %f %a\n", d, d, 1000000.0, 1000000.0); + VERIFY_IS_TRUE(d == 1000000.0); + } + + void PerfTimerUnitTests::Test_SecondsToPerfTime() + { + UINT64 u = PerfTimer::SecondsToPerfTime(1.0); + VERIFY_IS_TRUE(u == PerfTimer::TIMER_FREQ); + } + + void PerfTimerUnitTests::Test_MillisecondsToPerfTime() + { + UINT64 u = PerfTimer::MillisecondsToPerfTime(1000.0); + VERIFY_IS_TRUE(u == PerfTimer::TIMER_FREQ); + } + + void PerfTimerUnitTests::Test_MicrosecondsToPerfTime() + { + UINT64 u = PerfTimer::MicrosecondsToPerfTime(1000000.0); + VERIFY_IS_TRUE(u == PerfTimer::TIMER_FREQ); + } + + void HistogramUnitTests::Test_Empty() + { + Histogram h; + VERIFY_ARE_EQUAL(h.GetSampleSize(), (unsigned)0); + } + + void HistogramUnitTests::Test_Add() + { + Histogram h; + h.Add(42); + VERIFY_ARE_EQUAL(h.GetSampleSize(), (unsigned)1); + VERIFY_ARE_EQUAL(h.GetSampleBuckets(), (unsigned)1); + + h.Add(42); + VERIFY_ARE_EQUAL(h.GetSampleSize(), (unsigned)2); + VERIFY_ARE_EQUAL(h.GetSampleBuckets(), (unsigned)1); + + h.Add(0); + VERIFY_ARE_EQUAL(h.GetSampleSize(), (unsigned)3); + VERIFY_ARE_EQUAL(h.GetSampleBuckets(), (unsigned)2); + + // seal/reset count + (void) h.GetMin(); + VERIFY_ARE_EQUAL(h.GetSampleSize(), (unsigned)3); + VERIFY_ARE_EQUAL(h.GetSampleBuckets(), (unsigned)2); + + h.Add(0); + VERIFY_ARE_EQUAL(h.GetSampleSize(), (unsigned)1); + VERIFY_ARE_EQUAL(h.GetSampleBuckets(), (unsigned)1); + + (void) h.GetMin(); + VERIFY_ARE_EQUAL(h.GetSampleSize(), (unsigned)1); + VERIFY_ARE_EQUAL(h.GetSampleBuckets(), (unsigned)1); + } + + void HistogramUnitTests::Test_Clear() + { + Histogram h; + h.Add(42); + h.Clear(); + VERIFY_ARE_EQUAL(h.GetSampleSize(), (unsigned)0); + } + + void HistogramUnitTests::Test_MinMax() + { + // use unsigned here for the sake of a compact empty "min" + // signed would be ~0 as negative int + Histogram h; + h.Add(1); + h.Add(3); + VERIFY_ARE_EQUAL(h.GetMin(), (unsigned)1); + VERIFY_ARE_EQUAL(h.GetMax(), (unsigned)3); + VERIFY_ARE_EQUAL(h.GetSampleSize(), (unsigned)2); + + // seal/reset + h.Add(2); + VERIFY_ARE_EQUAL(h.GetMin(), (unsigned)2); + VERIFY_ARE_EQUAL(h.GetMax(), (unsigned)2); + VERIFY_ARE_EQUAL(h.GetSampleSize(), (unsigned)1); + + // empty case + h.Clear(); + VERIFY_ARE_EQUAL(h.GetMin(), (unsigned)0); + VERIFY_ARE_EQUAL(h.GetMax(), (unsigned)0); + } + + void HistogramUnitTests::Test_GetPercentile() + { + Histogram h; + h.Add(1); + h.Add(2); + h.Add(3); + VERIFY_ARE_EQUAL(h.GetSampleSize(), (unsigned)3); + VERIFY_ARE_EQUAL(h.GetPercentile(0.0), 1); + VERIFY_ARE_EQUAL(h.GetPercentile(0.5), 2); + VERIFY_ARE_EQUAL(h.GetPercentile(1.0), 3); + VERIFY_ARE_EQUAL(h.GetSampleSize(), (unsigned)3); + + // single sample buckets + for (int i = 1; i < 100; i++) + { + h.Add(i); + } + // double query at same val, forward, back and again + // stresses iterator save correctness + VERIFY_ARE_EQUAL(h.GetSampleSize(), (unsigned)99); + VERIFY_ARE_EQUAL(h.GetPercentile(0.0), 1); + VERIFY_ARE_EQUAL(h.GetPercentile(0.5), 50); + VERIFY_ARE_EQUAL(h.GetPercentile(0.5), 50); + VERIFY_ARE_EQUAL(h.GetPercentile(0.6), 60); + VERIFY_ARE_EQUAL(h.GetPercentile(0.1), 10); + VERIFY_ARE_EQUAL(h.GetPercentile(0.5), 50); + VERIFY_ARE_EQUAL(h.GetPercentile(0.5), 50); + VERIFY_ARE_EQUAL(h.GetPercentile(0.6), 60); + VERIFY_ARE_EQUAL(h.GetPercentile(1.0), 99); + VERIFY_ARE_EQUAL(h.GetSampleSize(), (unsigned)99); + + // multiple sample buckets - all same (2) + for (int i = 1; i < 100; i++) + { + h.Add(i); + h.Add(i); + } + // double query at same val, forward, back and again + // stresses iterator save correctness + VERIFY_ARE_EQUAL(h.GetSampleSize(), (unsigned)198); + VERIFY_ARE_EQUAL(h.GetPercentile(0.0), 1); + VERIFY_ARE_EQUAL(h.GetPercentile(0.5), 50); + VERIFY_ARE_EQUAL(h.GetPercentile(0.5), 50); + VERIFY_ARE_EQUAL(h.GetPercentile(0.6), 60); + VERIFY_ARE_EQUAL(h.GetPercentile(0.1), 10); + VERIFY_ARE_EQUAL(h.GetPercentile(0.5), 50); + VERIFY_ARE_EQUAL(h.GetPercentile(0.5), 50); + VERIFY_ARE_EQUAL(h.GetPercentile(0.6), 60); + VERIFY_ARE_EQUAL(h.GetPercentile(1.0), 99); + VERIFY_ARE_EQUAL(h.GetSampleSize(), (unsigned)198); + + // multiple sample buckets - extra weights on low end shift things lower + for (int i = 1; i < 100; i++) + { + h.Add(i); + + if (i < 50) + { + h.Add(i); + } + } + // double query at same val, forward, back and again + // stresses iterator save correctness + VERIFY_ARE_EQUAL(h.GetSampleSize(), (unsigned)148); + VERIFY_ARE_EQUAL(h.GetPercentile(0.0), 1); + VERIFY_ARE_EQUAL(h.GetPercentile(0.5), 37); + VERIFY_ARE_EQUAL(h.GetPercentile(0.5), 37); + VERIFY_ARE_EQUAL(h.GetPercentile(0.6), 45); + VERIFY_ARE_EQUAL(h.GetPercentile(0.1), 8); + VERIFY_ARE_EQUAL(h.GetPercentile(0.5), 37); + VERIFY_ARE_EQUAL(h.GetPercentile(0.5), 37); + VERIFY_ARE_EQUAL(h.GetPercentile(0.6), 45); + VERIFY_ARE_EQUAL(h.GetPercentile(1.0), 99); + VERIFY_ARE_EQUAL(h.GetSampleSize(), (unsigned)148); + } + + void HistogramUnitTests::Test_GetMean() + { + Histogram h; + h.Add(2); + h.Add(4); + VERIFY_ARE_EQUAL(h.GetMean(), 3); + } + + void HistogramUnitTests::Test_Merge() + { + Histogram h1; + h1.Add(1); + + Histogram h2; + h2.Add(2); + + h1.Merge(h2); + + VERIFY_ARE_EQUAL(h1.GetSampleSize(), (unsigned)2); + } + + void IoBucketizerUnitTests::Test_Empty() + { + IoBucketizer b; + VERIFY_ARE_EQUAL(b.GetNumberOfValidBuckets(), (size_t)0); + } + + void IoBucketizerUnitTests::Test_Add() + { + IoBucketizer b; + b.Initialize(10, 4); + + b.Add(5, 1); + b.Add(8, 2); + VERIFY_ARE_EQUAL(b.GetNumberOfValidBuckets(), (size_t)1); + + b.Add(15, 3); + VERIFY_ARE_EQUAL(b.GetNumberOfValidBuckets(), (size_t)2); + + b.Add(18, 5); + VERIFY_ARE_EQUAL(b.GetNumberOfValidBuckets(), (size_t)2); + + b.Add(45, 4); + VERIFY_ARE_EQUAL(b.GetNumberOfValidBuckets(), (size_t)4); + + VERIFY_ARE_EQUAL(b.GetIoBucketCount(0), (unsigned int)2); + VERIFY_ARE_EQUAL(b.GetIoBucketMinDurationUsec(0), 1L); + VERIFY_ARE_EQUAL(b.GetIoBucketMaxDurationUsec(0), 2L); + VERIFY_ARE_EQUAL(b.GetIoBucketAvgDurationUsec(0), 1.5L); + VERIFY_ARE_EQUAL(b.GetIoBucketDurationStdDevUsec(0), 0.5L); + VERIFY_ARE_EQUAL(b.GetIoBucketCount(1), (unsigned int)2); + VERIFY_ARE_EQUAL(b.GetIoBucketMinDurationUsec(1), 3L); + VERIFY_ARE_EQUAL(b.GetIoBucketMaxDurationUsec(1), 5L); + VERIFY_ARE_EQUAL(b.GetIoBucketAvgDurationUsec(1), 4L); + VERIFY_ARE_EQUAL(b.GetIoBucketDurationStdDevUsec(1), 1L); + VERIFY_ARE_EQUAL(b.GetIoBucketCount(2), (unsigned int)0); + VERIFY_ARE_EQUAL(b.GetIoBucketMinDurationUsec(2), 0); + VERIFY_ARE_EQUAL(b.GetIoBucketMaxDurationUsec(2), 0); + VERIFY_ARE_EQUAL(b.GetIoBucketAvgDurationUsec(2), 0); + VERIFY_ARE_EQUAL(b.GetIoBucketDurationStdDevUsec(2), 0); + VERIFY_ARE_EQUAL(b.GetIoBucketCount(3), (unsigned int)0); + VERIFY_ARE_EQUAL(b.GetIoBucketMinDurationUsec(3), 0); + VERIFY_ARE_EQUAL(b.GetIoBucketMaxDurationUsec(3), 0); + VERIFY_ARE_EQUAL(b.GetIoBucketAvgDurationUsec(3), 0); + VERIFY_ARE_EQUAL(b.GetIoBucketDurationStdDevUsec(3), 0); + } + + void IoBucketizerUnitTests::Test_Merge() + { + IoBucketizer b1; + IoBucketizer b2; + b1.Initialize(10, 3); + b2.Initialize(10, 3); + + // b1 buckets: 2,0,1 + b1.Add(0, 0); + b1.Add(1, 0); + b1.Add(20, 0); + b1.Add(30, 0); + + VERIFY_ARE_EQUAL(b1.GetNumberOfValidBuckets(), (size_t)3); + VERIFY_ARE_EQUAL(b1.GetIoBucketCount(0), (unsigned int)2); + VERIFY_ARE_EQUAL(b1.GetIoBucketCount(1), (unsigned int)0); + VERIFY_ARE_EQUAL(b1.GetIoBucketCount(2), (unsigned int)1); + + // b2 buckets: 1,3 + b2.Add(0, 0); + b2.Add(10, 0); + b2.Add(11, 0); + b2.Add(12, 0); + + VERIFY_ARE_EQUAL(b2.GetNumberOfValidBuckets(), (size_t)2); + VERIFY_ARE_EQUAL(b2.GetIoBucketCount(0), (unsigned int)1); + VERIFY_ARE_EQUAL(b2.GetIoBucketCount(1), (unsigned int)3); + + b1.Merge(b2); + + // Merged buckets: 3,3,1 + VERIFY_ARE_EQUAL(b1.GetNumberOfValidBuckets(), (size_t)3); + VERIFY_ARE_EQUAL(b1.GetIoBucketCount(0), (unsigned int)3); + VERIFY_ARE_EQUAL(b1.GetIoBucketCount(1), (unsigned int)3); + VERIFY_ARE_EQUAL(b1.GetIoBucketCount(2), (unsigned int)1); + + // Source unchanged. + VERIFY_ARE_EQUAL(b2.GetNumberOfValidBuckets(), (size_t)2); + VERIFY_ARE_EQUAL(b2.GetIoBucketCount(0), (unsigned int)1); + VERIFY_ARE_EQUAL(b2.GetIoBucketCount(1), (unsigned int)3); + + // Merge into empty bucketizer + IoBucketizer b3; + + // Its empty. + VERIFY_ARE_EQUAL(b3.GetNumberOfValidBuckets(), (size_t)0); + + b3.Merge(b1); + + // Merged buckets: 3,3,1 + VERIFY_ARE_EQUAL(b3.GetNumberOfValidBuckets(), (size_t)3); + VERIFY_ARE_EQUAL(b3.GetIoBucketCount(0), (unsigned int)3); + VERIFY_ARE_EQUAL(b3.GetIoBucketCount(1), (unsigned int)3); + VERIFY_ARE_EQUAL(b3.GetIoBucketCount(2), (unsigned int)1); + } + + void IoBucketizerUnitTests::Test_GetStandardDeviation() + { + IoBucketizer b; + b.Initialize(10, 2); + + // b buckets: 1,2 + b.Add(0, 0); + b.Add(10, 0); + b.Add(11, 0); + b.Add(20, 0); + + // Standard deviation from valid buckets (the first two) is STDDEV(1,2) = 0.5 + VERIFY_ARE_EQUAL(b.GetStandardDeviationIOPS(), 0.5L); + } + + void ProfileUnitTests::Test_GetXmlEmptyProfile() + { + Profile profile; + string sXml = profile.GetXml(0); + //printf("'%s'\n", sXml.c_str()); + VERIFY_IS_TRUE(sXml == "\n" + " 0\n" + " text\n" + " false\n" + " \n" + " \n" + "\n"); + } + + void ProfileUnitTests::Test_GetXmlPrecreateFilesUseMaxSize() + { + Profile profile; + profile.SetPrecreateFiles(PrecreateFiles::UseMaxSize); + string sXml = profile.GetXml(0); + //printf("'%s'\n", sXml.c_str()); + VERIFY_IS_TRUE(sXml == "\n" + " 0\n" + " text\n" + " false\n" + " UseMaxSize\n" + " \n" + " \n" + "\n"); + } + + void ProfileUnitTests::Test_GetXmlPrecreateFilesOnlyFilesWithConstantSizes() + { + Profile profile; + profile.SetPrecreateFiles(PrecreateFiles::OnlyFilesWithConstantSizes); + string sXml = profile.GetXml(0); + //printf("'%s'\n", sXml.c_str()); + VERIFY_IS_TRUE(sXml == "\n" + " 0\n" + " text\n" + " false\n" + " CreateOnlyFilesWithConstantSizes\n" + " \n" + " \n" + "\n"); + } + + void ProfileUnitTests::Test_GetXmlPrecreateFilesOnlyFilesWithConstantOrZeroSizes() + { + Profile profile; + profile.SetPrecreateFiles(PrecreateFiles::OnlyFilesWithConstantOrZeroSizes); + string sXml = profile.GetXml(0); + //printf("'%s'\n", sXml.c_str()); + VERIFY_IS_TRUE(sXml == "\n" + " 0\n" + " text\n" + " false\n" + " CreateOnlyFilesWithConstantOrZeroSizes\n" + " \n" + " \n" + "\n"); + } + + void ProfileUnitTests::Test_MarkFilesAsCreated() + { + Target target1; + target1.SetPath("file1.txt"); + + Target target2; + target2.SetPath("file2.txt"); + + Target target3; + target3.SetPath("file1.txt"); + + Target target4; + target4.SetPath("file3.txt"); + + Target target5; + target5.SetPath("file2.txt"); + + Target target6; + target6.SetPath("file2.txt"); + + TimeSpan timeSpan1; + timeSpan1.AddTarget(target1); + timeSpan1.AddTarget(target2); + + TimeSpan timeSpan2; + timeSpan2.AddTarget(target3); + timeSpan2.AddTarget(target4); + timeSpan2.AddTarget(target5); + timeSpan2.AddTarget(target6); + + Profile profile; + profile.AddTimeSpan(timeSpan1); + profile.AddTimeSpan(timeSpan2); + + vector vFiles; + vFiles.push_back("file1.txt"); + vFiles.push_back("file2.txt"); + + VERIFY_IS_FALSE(profile._vTimeSpans[0]._vTargets[0]._fPrecreated); + VERIFY_IS_FALSE(profile._vTimeSpans[0]._vTargets[1]._fPrecreated); + VERIFY_IS_FALSE(profile._vTimeSpans[1]._vTargets[0]._fPrecreated); + VERIFY_IS_FALSE(profile._vTimeSpans[1]._vTargets[1]._fPrecreated); + VERIFY_IS_FALSE(profile._vTimeSpans[1]._vTargets[2]._fPrecreated); + VERIFY_IS_FALSE(profile._vTimeSpans[1]._vTargets[3]._fPrecreated); + + profile.MarkFilesAsPrecreated(vFiles); + VERIFY_IS_TRUE(profile._vTimeSpans[0]._vTargets[0]._fPrecreated); + VERIFY_IS_TRUE(profile._vTimeSpans[0]._vTargets[1]._fPrecreated); + VERIFY_IS_TRUE(profile._vTimeSpans[1]._vTargets[0]._fPrecreated); + VERIFY_IS_FALSE(profile._vTimeSpans[1]._vTargets[1]._fPrecreated); + VERIFY_IS_TRUE(profile._vTimeSpans[1]._vTargets[2]._fPrecreated); + VERIFY_IS_TRUE(profile._vTimeSpans[1]._vTargets[3]._fPrecreated); + } + + void ProfileUnitTests::Test_Validate() + { + TimeSpan timeSpan; + Target target; + + target.SetBaseFileOffsetInBytes(1000); + target.SetBlockAlignmentInBytes(500); + target.SetBlockSizeInBytes(1000); + target.SetThreadStrideInBytes(5000); + timeSpan.AddTarget(target); + + Profile profile; + profile.AddTimeSpan(timeSpan); + + // thread stride errors if only one thread used (default) + // both the single spec assumption and full should behave the same + VERIFY_IS_FALSE(profile.Validate(true)); + VERIFY_IS_FALSE(profile.Validate(false)); + + profile._vTimeSpans[0].SetThreadCount(2); + VERIFY_IS_TRUE(profile.Validate(true)); + VERIFY_IS_TRUE(profile.Validate(false)); + + // now turning on interlocked sequential, fail since thread stride is set + profile._vTimeSpans[0]._vTargets[0].SetUseInterlockedSequential(true); + VERIFY_IS_FALSE(profile.Validate(true)); + VERIFY_IS_FALSE(profile.Validate(false)); + + profile._vTimeSpans[0]._vTargets[0].SetThreadStrideInBytes(0); + VERIFY_IS_TRUE(profile.Validate(true)); + VERIFY_IS_TRUE(profile.Validate(false)); + } + + void ProfileUnitTests::Test_ValidateSystem() + { + // processor topology validation for affinity assignments + // 2 group, 2 procs/group + SystemInformation system; + system.processorTopology._vProcessorGroupInformation.clear(); + system.processorTopology._vProcessorGroupInformation.emplace_back((WORD)0, (BYTE)2, (BYTE)2, (KAFFINITY)0x3); + system.processorTopology._vProcessorGroupInformation.emplace_back((WORD)1, (BYTE)2, (BYTE)2, (KAFFINITY)0x3); + + TimeSpan timeSpan; + Profile profile; + + // assign to each proc + profile.ClearTimeSpans(); + timeSpan.ClearAffinityAssignment(); + timeSpan.AddAffinityAssignment(0, 0); + timeSpan.AddAffinityAssignment(0, 1); + timeSpan.AddAffinityAssignment(1, 0); + timeSpan.AddAffinityAssignment(1, 1); + profile.AddTimeSpan(timeSpan); + VERIFY_IS_TRUE(profile.Validate(true, &system)); + + // shrink active mask + system.processorTopology._vProcessorGroupInformation.clear(); + system.processorTopology._vProcessorGroupInformation.emplace_back((WORD)0, (BYTE)2, (BYTE)2, (KAFFINITY)0x1); + system.processorTopology._vProcessorGroupInformation.emplace_back((WORD)1, (BYTE)2, (BYTE)2, (KAFFINITY)0x1); + + // fail assignment to inactive procs + VERIFY_IS_FALSE(profile.Validate(true, &system)); + + // shrink procs, still fail + system.processorTopology._vProcessorGroupInformation.clear(); + system.processorTopology._vProcessorGroupInformation.emplace_back((WORD)0, (BYTE)1, (BYTE)1, (KAFFINITY)0x1); + system.processorTopology._vProcessorGroupInformation.emplace_back((WORD)1, (BYTE)1, (BYTE)1, (KAFFINITY)0x1); + + // now fail + VERIFY_IS_FALSE(profile.Validate(true, &system)); + + // assign to low procs, and succeed + profile.ClearTimeSpans(); + timeSpan.ClearAffinityAssignment(); + timeSpan.AddAffinityAssignment(0, 0); + timeSpan.AddAffinityAssignment(1, 0); + profile.AddTimeSpan(timeSpan); + VERIFY_IS_TRUE(profile.Validate(true, &system)); + + // shrink groups + system.processorTopology._vProcessorGroupInformation.clear(); + system.processorTopology._vProcessorGroupInformation.emplace_back((WORD)0, (BYTE)1, (BYTE)1, (KAFFINITY)0x1); + + // now fail + VERIFY_IS_FALSE(profile.Validate(true, &system)); + + // assign to low proc, and succeed + profile.ClearTimeSpans(); + timeSpan.ClearAffinityAssignment(); + timeSpan.AddAffinityAssignment(0, 0); + profile.AddTimeSpan(timeSpan); + VERIFY_IS_TRUE(profile.Validate(true, &system)); + + // assign to invalid group + profile.ClearTimeSpans(); + timeSpan.ClearAffinityAssignment(); + timeSpan.AddAffinityAssignment(1, 0); + profile.AddTimeSpan(timeSpan); + VERIFY_IS_FALSE(profile.Validate(true, &system)); + } + + void TargetUnitTests::TestGetSetRandomDataWriteBufferSize() + { + Target t; + VERIFY_ARE_EQUAL(t.GetRandomDataWriteBufferSize(), 0); + t.SetRandomDataWriteBufferSize(1234); + VERIFY_ARE_EQUAL(t.GetRandomDataWriteBufferSize(), 1234); + } + + void TargetUnitTests::TestGetSetRandomDataWriteBufferSourcePath() + { + Target t; + VERIFY_ARE_EQUAL(t.GetRandomDataWriteBufferSourcePath(), ""); + t.SetRandomDataWriteBufferSourcePath("x:\\foo\\bar.dat"); + VERIFY_ARE_EQUAL(t.GetRandomDataWriteBufferSourcePath(), "x:\\foo\\bar.dat"); + } + + void TargetUnitTests::Test_TargetGetXmlWriteBufferContentSequential() + { + Target target; + string sXml = target.GetXml(0); + VERIFY_IS_TRUE(sXml == "\n" + " \n" + " 65536\n" + " 0\n" + " false\n" + " false\n" + " false\n" + " false\n" + " \n" + " sequential\n" + " \n" + " false\n" + " 65536\n" + " false\n" + " 0\n" + " 0\n" + " 2\n" + " 0\n" + " 0\n" + " 1\n" + " 3\n" + " 1\n" + "\n"); + } + + void TargetUnitTests::Test_TargetGetXmlWriteBufferContentZero() + { + Target target; + target.SetZeroWriteBuffers(true); + string sXml = target.GetXml(0); + VERIFY_IS_TRUE(sXml == "\n" + " \n" + " 65536\n" + " 0\n" + " false\n" + " false\n" + " false\n" + " false\n" + " \n" + " zero\n" + " \n" + " false\n" + " 65536\n" + " false\n" + " 0\n" + " 0\n" + " 2\n" + " 0\n" + " 0\n" + " 1\n" + " 3\n" + " 1\n" + "\n"); + } + + void TargetUnitTests::Test_TargetGetXmlWriteBufferContentRandomNoFilePath() + { + Target target; + target.SetRandomDataWriteBufferSize(224433); + string sXml = target.GetXml(0); + VERIFY_IS_TRUE(sXml == "\n" + " \n" + " 65536\n" + " 0\n" + " false\n" + " false\n" + " false\n" + " false\n" + " \n" + " random\n" + " \n" + " 224433\n" + " \n" + " \n" + " false\n" + " 65536\n" + " false\n" + " 0\n" + " 0\n" + " 2\n" + " 0\n" + " 0\n" + " 1\n" + " 3\n" + " 1\n" + "\n"); + } + + void TargetUnitTests::Test_TargetGetXmlWriteBufferContentRandomWithFilePath() + { + Target target; + target.SetRandomDataWriteBufferSize(224433); + target.SetRandomDataWriteBufferSourcePath("x:\\foo\\bar.baz"); + string sXml = target.GetXml(0); + VERIFY_IS_TRUE(sXml == "\n" + " \n" + " 65536\n" + " 0\n" + " false\n" + " false\n" + " false\n" + " false\n" + " \n" + " random\n" + " \n" + " 224433\n" + " x:\\foo\\bar.baz\n" + " \n" + " \n" + " false\n" + " 65536\n" + " false\n" + " 0\n" + " 0\n" + " 2\n" + " 0\n" + " 0\n" + " 1\n" + " 3\n" + " 1\n" + "\n"); + } + + void TargetUnitTests::Test_TargetGetXmlDisableAllCache() + { + Target target; + target.SetCacheMode(TargetCacheMode::DisableOSCache); + target.SetWriteThroughMode(WriteThroughMode::On); + string sXml = target.GetXml(0); + VERIFY_IS_TRUE(sXml == "\n" + " \n" + " 65536\n" + " 0\n" + " false\n" + " false\n" + " false\n" + " false\n" + " true\n" + " true\n" + " \n" + " sequential\n" + " \n" + " false\n" + " 65536\n" + " false\n" + " 0\n" + " 0\n" + " 2\n" + " 0\n" + " 0\n" + " 1\n" + " 3\n" + " 1\n" + "\n"); + } + + void TargetUnitTests::Test_TargetGetXmlDisableLocalCache() + { + Target target; + target.SetCacheMode(TargetCacheMode::DisableLocalCache); + string sXml = target.GetXml(0); + VERIFY_IS_TRUE(sXml == "\n" + " \n" + " 65536\n" + " 0\n" + " false\n" + " false\n" + " false\n" + " false\n" + " true\n" + " \n" + " sequential\n" + " \n" + " false\n" + " 65536\n" + " false\n" + " 0\n" + " 0\n" + " 2\n" + " 0\n" + " 0\n" + " 1\n" + " 3\n" + " 1\n" + "\n"); + } + + void TargetUnitTests::Test_TargetGetXmlDisableOSCache() + { + Target target; + target.SetCacheMode(TargetCacheMode::DisableOSCache); + string sXml = target.GetXml(0); + VERIFY_IS_TRUE(sXml == "\n" + " \n" + " 65536\n" + " 0\n" + " false\n" + " false\n" + " false\n" + " false\n" + " true\n" + " \n" + " sequential\n" + " \n" + " false\n" + " 65536\n" + " false\n" + " 0\n" + " 0\n" + " 2\n" + " 0\n" + " 0\n" + " 1\n" + " 3\n" + " 1\n" + "\n"); + } + + void TargetUnitTests::Test_TargetGetXmlBufferedWriteThrough() + { + Target target; + target.SetWriteThroughMode(WriteThroughMode::On); + string sXml = target.GetXml(0); + VERIFY_IS_TRUE(sXml == "\n" + " \n" + " 65536\n" + " 0\n" + " false\n" + " false\n" + " false\n" + " false\n" + " true\n" + " \n" + " sequential\n" + " \n" + " false\n" + " 65536\n" + " false\n" + " 0\n" + " 0\n" + " 2\n" + " 0\n" + " 0\n" + " 1\n" + " 3\n" + " 1\n" + "\n"); + } + + void TargetUnitTests::Test_TargetGetXmlMemoryMappedIo() + { + Target target; + target.SetMemoryMappedIoMode(MemoryMappedIoMode::On); + string sXml = target.GetXml(0); + VERIFY_IS_TRUE(sXml == "\n" + " \n" + " 65536\n" + " 0\n" + " false\n" + " false\n" + " false\n" + " false\n" + " true\n" + " \n" + " sequential\n" + " \n" + " false\n" + " 65536\n" + " false\n" + " 0\n" + " 0\n" + " 2\n" + " 0\n" + " 0\n" + " 1\n" + " 3\n" + " 1\n" + "\n"); + } + + void TargetUnitTests::Test_TargetGetXmlMemoryMappedIoFlushModeViewOfFile() + { + Target target; + target.SetMemoryMappedIoMode(MemoryMappedIoMode::On); + target.SetMemoryMappedIoFlushMode(MemoryMappedIoFlushMode::ViewOfFile); + string sXml = target.GetXml(0); + VERIFY_IS_TRUE(sXml == "\n" + " \n" + " 65536\n" + " 0\n" + " false\n" + " false\n" + " false\n" + " false\n" + " true\n" + " ViewOfFile\n" + " \n" + " sequential\n" + " \n" + " false\n" + " 65536\n" + " false\n" + " 0\n" + " 0\n" + " 2\n" + " 0\n" + " 0\n" + " 1\n" + " 3\n" + " 1\n" + "\n"); + } + + void TargetUnitTests::Test_TargetGetXmlMemoryMappedIoFlushModeNonVolatileMemory() + { + Target target; + target.SetMemoryMappedIoMode(MemoryMappedIoMode::On); + target.SetMemoryMappedIoFlushMode(MemoryMappedIoFlushMode::NonVolatileMemory); + string sXml = target.GetXml(0); + VERIFY_IS_TRUE(sXml == "\n" + " \n" + " 65536\n" + " 0\n" + " false\n" + " false\n" + " false\n" + " false\n" + " true\n" + " NonVolatileMemory\n" + " \n" + " sequential\n" + " \n" + " false\n" + " 65536\n" + " false\n" + " 0\n" + " 0\n" + " 2\n" + " 0\n" + " 0\n" + " 1\n" + " 3\n" + " 1\n" + "\n"); + } + + void TargetUnitTests::Test_TargetGetXmlMemoryMappedIoFlushModeNonVolatileMemoryNoDrain() + { + Target target; + target.SetMemoryMappedIoMode(MemoryMappedIoMode::On); + target.SetMemoryMappedIoFlushMode(MemoryMappedIoFlushMode::NonVolatileMemoryNoDrain); + string sXml = target.GetXml(0); + VERIFY_IS_TRUE(sXml == "\n" + " \n" + " 65536\n" + " 0\n" + " false\n" + " false\n" + " false\n" + " false\n" + " true\n" + " NonVolatileMemoryNoDrain\n" + " \n" + " sequential\n" + " \n" + " false\n" + " 65536\n" + " false\n" + " 0\n" + " 0\n" + " 2\n" + " 0\n" + " 0\n" + " 1\n" + " 3\n" + " 1\n" + "\n"); + } + + void TargetUnitTests::Test_TargetGetXmlRandomAccessHint() + { + Target target; + target.SetRandomAccessHint(true); + string sXml = target.GetXml(0); + VERIFY_IS_TRUE(sXml == "\n" + " \n" + " 65536\n" + " 0\n" + " false\n" + " true\n" + " false\n" + " false\n" + " \n" + " sequential\n" + " \n" + " false\n" + " 65536\n" + " false\n" + " 0\n" + " 0\n" + " 2\n" + " 0\n" + " 0\n" + " 1\n" + " 3\n" + " 1\n" + "\n"); + } + + void TargetUnitTests::Test_TargetGetXmlSequentialScanHint() + { + Target target; + target.SetSequentialScanHint(true); + string sXml = target.GetXml(0); + VERIFY_IS_TRUE(sXml == "\n" + " \n" + " 65536\n" + " 0\n" + " true\n" + " false\n" + " false\n" + " false\n" + " \n" + " sequential\n" + " \n" + " false\n" + " 65536\n" + " false\n" + " 0\n" + " 0\n" + " 2\n" + " 0\n" + " 0\n" + " 1\n" + " 3\n" + " 1\n" + "\n"); + } + + void TargetUnitTests::Test_TargetGetXmlCombinedAccessHint() + { + Target target; + target.SetSequentialScanHint(true); + target.SetTemporaryFileHint(true); + string sXml = target.GetXml(0); + VERIFY_IS_TRUE(sXml == "\n" + " \n" + " 65536\n" + " 0\n" + " true\n" + " false\n" + " true\n" + " false\n" + " \n" + " sequential\n" + " \n" + " false\n" + " 65536\n" + " false\n" + " 0\n" + " 0\n" + " 2\n" + " 0\n" + " 0\n" + " 1\n" + " 3\n" + " 1\n" + "\n"); + } + + void TargetUnitTests::Test_AllocateAndFillRandomDataWriteBuffer() + { + Random r; + Target t; + VERIFY_IS_FALSE(t.AllocateAndFillRandomDataWriteBuffer(&r)); + VERIFY_ARE_EQUAL(t._pRandomDataWriteBuffer, nullptr); + + size_t cb = 12345; + t.SetRandomDataWriteBufferSize(cb); + VERIFY_IS_TRUE(t.AllocateAndFillRandomDataWriteBuffer(&r)); + VERIFY_IS_TRUE(t._pRandomDataWriteBuffer != nullptr); + // see if the test crashes if we try to write to every byte of the buffer + + for (size_t i = 0; i < cb; i++) + { + t._pRandomDataWriteBuffer[i] = (i % 256); + } + + for (size_t i = 0; i < cb; i++) + { + if (t._pRandomDataWriteBuffer[i] != (i % 256)) + { + // don't call VERIFY_ARE_EQUAL on each item because it prints to the screen and makes the test take + // too long + VERIFY_IS_TRUE(false); + } + } + } + + void TargetUnitTests::Test_AllocateAndFillRandomDataWriteBufferFromFile() + { + char szTempDirPath[MAX_PATH] = {}; + DWORD cch = GetTempPathA(_countof(szTempDirPath), szTempDirPath); + VERIFY_IS_TRUE(cch != 0); + string sTempFilePath(szTempDirPath); + sTempFilePath += "diskspd-random-data-file.dat"; + DeleteFileA(sTempFilePath.c_str()); + + printf("path: '%s'\n", sTempFilePath.c_str()); + FILE *pFile; + fopen_s(&pFile, sTempFilePath.c_str(), "wb"); + VERIFY_IS_TRUE(pFile != nullptr); + char buffer[256]; + for (int i = 0; i < 256; i++) + { + buffer[i] = static_cast(0xFF - i); + } + VERIFY_ARE_EQUAL(fwrite(buffer, sizeof(buffer), 1, pFile), (size_t)1); + fclose(pFile); + pFile = nullptr; + + Random r; + Target t; + size_t cbBuffer = 1024 * 1024; + t.SetRandomDataWriteBufferSize(cbBuffer); + t.SetRandomDataWriteBufferSourcePath(sTempFilePath.c_str()); + VERIFY_IS_TRUE(t.AllocateAndFillRandomDataWriteBuffer(&r)); + VERIFY_IS_TRUE(t._pRandomDataWriteBuffer != nullptr); + for (size_t i = 0; i < cbBuffer; i++) + { + if (t._pRandomDataWriteBuffer[i] != (0xFF - (i % 256))) + { + // don't call VERIFY_ARE_EQUAL on each item because it prints to the screen and makes the test take + // too long + VERIFY_IS_TRUE(false); + } + } + + DeleteFileA(sTempFilePath.c_str()); + } + + void ThreadParametersUnitTests::Test_AllocateAndFillBufferForTarget() + { + TimeSpan ts; + Target t; + Random r; + t.SetBlockSizeInBytes(12345); + t.SetRequestCount(12); + ThreadParameters tp; + tp.pTimeSpan = &ts; + tp.pRand = &r; + VERIFY_IS_TRUE(tp.AllocateAndFillBufferForTarget(t)); + + // see if the test crashes if we try to write to every byte of the buffer + size_t cb = t.GetBlockSizeInBytes() * t.GetRequestCount(); + for (size_t i = 0; i < cb; i++) + { + tp.vpDataBuffers[0][i] = (i % 256); + } + + for (size_t i = 0; i < cb; i++) + { + if (tp.vpDataBuffers[0][i] != (i % 256)) + { + // don't call VERIFY_ARE_EQUAL on each item because it prints to the screen and makes the test take + // too long + VERIFY_IS_TRUE(false); + } + } + } + + void TopologyUnitTests::Test_MaskCount() + { + ULONG kaff_bits = sizeof(KAFFINITY) * 8; + + // a complete enumeration could be interesting, but a nibble is enough to test the algorithm. + // take the given mask and its width (ordinal distance to the upper 1), shift it through the + // range of KAFFINITY to verify the popcnt is correct at all positions + // + // note that unique masks have msb/lsb set for all combinations of the interior bits. we don't + // test "10" (0x2) as a mask, because it's not unique - its the same as the first shift-up of + // "1" (0x1), etc. + + struct { + KAFFINITY mask; + ULONG width; + ULONG bits; + } tests[] = { // msb ... lsb + { 0x1, 1, 1 }, // 1 + { 0x3, 2, 2 }, // 11 + { 0x5, 3, 2 }, // 101 + { 0x7, 3, 3 }, // 111 + { 0x9, 4, 2 }, // 1001 + { 0xb, 4, 3 }, // 1011 + { 0xd, 4, 3 }, // 1101 + { 0xf, 4, 4 } // 1111 + }; + + for (const auto &test : tests) + { + KAFFINITY mask = test.mask; + for (ULONG i = 0; i < kaff_bits - test.width; i++) + { + VERIFY_ARE_EQUAL(ProcessorTopology::MaskCount(mask), test.bits); + mask <<= 1; + } + } + + // ... and a few explicit true/false + VERIFY_ARE_NOT_EQUAL(ProcessorTopology::MaskCount(0x0), (ULONG)1); + VERIFY_ARE_NOT_EQUAL(ProcessorTopology::MaskCount(0x3), (ULONG)0); + VERIFY_ARE_NOT_EQUAL(ProcessorTopology::MaskCount(0x5), (ULONG)3); + + VERIFY_ARE_EQUAL(ProcessorTopology::MaskCount(0x0), (ULONG)0); + VERIFY_ARE_EQUAL(ProcessorTopology::MaskCount(0x3), (ULONG)2); + VERIFY_ARE_EQUAL(ProcessorTopology::MaskCount(0x5), (ULONG)2); + + VERIFY_ARE_EQUAL(ProcessorTopology::MaskCount(0xffff), (ULONG)16); + VERIFY_ARE_EQUAL(ProcessorTopology::MaskCount(0xfeef), (ULONG)14); + VERIFY_ARE_EQUAL(ProcessorTopology::MaskCount(0xfeef00ff), (ULONG)22); + VERIFY_ARE_EQUAL(ProcessorTopology::MaskCount(0xfe0000ff), (ULONG)15); + VERIFY_ARE_EQUAL(ProcessorTopology::MaskCount(0x7e0000ff), (ULONG)14); + } +} diff --git a/UnitTests/Common/Common.UnitTests.h b/UnitTests/Common/Common.UnitTests.h index f4119ab..31d26a0 100644 --- a/UnitTests/Common/Common.UnitTests.h +++ b/UnitTests/Common/Common.UnitTests.h @@ -117,8 +117,14 @@ namespace UnitTests TEST_CLASS(ThreadParametersUnitTests); TEST_METHOD(Test_AllocateAndFillBufferForTarget); }; + + class TopologyUnitTests : public WEX::TestClass + { + public: + TEST_CLASS(TopologyUnitTests); + TEST_METHOD(Test_MaskCount); + }; } // TODO: ThreadParameters::GetWriteBuffer // TODO: Target::GetRandomDataWriteBuffer(); - diff --git a/UnitTests/Common/Common.rc b/UnitTests/Common/Common.rc index dfe440b..0c601ae 100644 --- a/UnitTests/Common/Common.rc +++ b/UnitTests/Common/Common.rc @@ -1,5 +1,5 @@ #include -#include "Common.h" +#include "Version.h" #include diff --git a/UnitTests/IORequestGenerator/IORequestGenerator.rc b/UnitTests/IORequestGenerator/IORequestGenerator.rc index b25d067..bcbe27b 100644 --- a/UnitTests/IORequestGenerator/IORequestGenerator.rc +++ b/UnitTests/IORequestGenerator/IORequestGenerator.rc @@ -1,5 +1,5 @@ #include -#include "Common.h" +#include "Version.h" #include diff --git a/UnitTests/ResultParser/ResultParser.UnitTests.cpp b/UnitTests/ResultParser/ResultParser.UnitTests.cpp index fd5dca3..d5e2934 100644 --- a/UnitTests/ResultParser/ResultParser.UnitTests.cpp +++ b/UnitTests/ResultParser/ResultParser.UnitTests.cpp @@ -92,10 +92,31 @@ namespace UnitTests SystemInformation system; system.sComputerName.clear(); system.ResetTime(); - system.processorTopology._ulProcCount = 1; - system.processorTopology._ulActiveProcCount = 1; - system.processorTopology._vProcessorGroupInformation[0]._maximumProcessorCount = 1; - system.processorTopology._vProcessorGroupInformation[0]._activeProcessorCount = 1; + + // and power plan + system.sActivePolicyName.clear(); + system.sActivePolicyGuid.clear(); + + system.processorTopology._ulProcessorCount = 1; + system.processorTopology._ubPerformanceEfficiencyClass = 0; + system.processorTopology._fSMT = false; + + system.processorTopology._vProcessorGroupInformation.clear(); + system.processorTopology._vProcessorGroupInformation.emplace_back((WORD)0, (BYTE)1, (BYTE)1, (KAFFINITY)0x1); + + ProcessorNumaInformation node; + node._nodeNumber = 0; + node._vProcessorMasks.emplace_back((WORD)0, (KAFFINITY)0x1); + system.processorTopology._vProcessorNumaInformation.clear(); + system.processorTopology._vProcessorNumaInformation.push_back(node); + + ProcessorSocketInformation socket; + socket._vProcessorMasks.emplace_back((WORD)0, (KAFFINITY)0x1); + system.processorTopology._vProcessorSocketInformation.clear(); + system.processorTopology._vProcessorSocketInformation.push_back(socket); + + system.processorTopology._vProcessorCoreInformation.clear(); + system.processorTopology._vProcessorCoreInformation.emplace_back((WORD)0, (KAFFINITY)0x1, (BYTE)0); // finally, add the timespan to the profile and dump. profile.AddTimeSpan(timeSpan); @@ -122,18 +143,26 @@ namespace UnitTests "\tcomputer name: \n" "\tstart time: \n" "\n" + "\tcpu count:\t\t1\n" + "\tcore count:\t\t1\n" + "\tgroup count:\t\t1\n" + "\tnode count:\t\t1\n" + "\tsocket count:\t\t1\n" + "\theterogeneous cores:\tn\n" + "\n" + "\tactive power scheme:\t\n" + "\n" "Results for timespan 1:\n" "*******************************************************************************\n" "\n" "actual test time:\t120.00s\n" "thread count:\t\t1\n" - "proc count:\t\t1\n" "\n" - "CPU | Usage | User | Kernel | Idle\n" - "-------------------------------------------\n" - " 0| 55.00%| 30.00%| 25.00%| 45.00%\n" - "-------------------------------------------\n" - "avg.| 55.00%| 30.00%| 25.00%| 45.00%\n" + "CPU | Usage | User | Kernel | Idle\n" + "----------------------------------------\n" + " 0| 55.00%| 30.00%| 25.00%| 45.00%\n" + "----------------------------------------\n" + "avg.| 55.00%| 30.00%| 25.00%| 45.00%\n" "\n" "Total IO\n" "thread | bytes | I/Os | MiB/s | I/O per s | IopsStdDev | file\n" @@ -732,7 +761,7 @@ namespace UnitTests TargetResults targetResults; targetResults.vDistributionRange = tts._vDistributionRange; - + targetResults.sPath = "testfile.dat"; threadResults.vTargetResults.push_back(targetResults); results.vThreadResults.push_back(threadResults); @@ -769,7 +798,7 @@ namespace UnitTests TargetResults targetResults; targetResults.vDistributionRange = tts._vDistributionRange; - + targetResults.sPath = "testfile.dat"; threadResults.vTargetResults.push_back(targetResults); results.vThreadResults.push_back(threadResults); @@ -847,7 +876,7 @@ namespace UnitTests { // the first matches the corresponding IORequestGenerator effdist UT ResultParser parser; - + Target target; target.SetBlockAlignmentInBytes(4*KB); target.SetBlockSizeInBytes(4*KB); diff --git a/UnitTests/ResultParser/ResultParser.rc b/UnitTests/ResultParser/ResultParser.rc index c3a2f1d..63e9e63 100644 --- a/UnitTests/ResultParser/ResultParser.rc +++ b/UnitTests/ResultParser/ResultParser.rc @@ -1,5 +1,5 @@ #include -#include "Common.h" +#include "Version.h" #include diff --git a/UnitTests/XmlProfileParser/XmlProfileParser.rc b/UnitTests/XmlProfileParser/XmlProfileParser.rc index a826c8c..b13c31b 100644 --- a/UnitTests/XmlProfileParser/XmlProfileParser.rc +++ b/UnitTests/XmlProfileParser/XmlProfileParser.rc @@ -1,5 +1,5 @@ #include -#include "Common.h" +#include "Version.h" DISKSPD.XSD HTML "..\\XmlProfileParser\\diskspd.xsd" diff --git a/UnitTests/XmlResultParser/XmlResultParser.UnitTests.cpp b/UnitTests/XmlResultParser/XmlResultParser.UnitTests.cpp index a12a903..2accbde 100644 --- a/UnitTests/XmlResultParser/XmlResultParser.UnitTests.cpp +++ b/UnitTests/XmlResultParser/XmlResultParser.UnitTests.cpp @@ -52,29 +52,28 @@ namespace UnitTests double fTime = 120.0; results.ullTimeCount = PerfTimer::SecondsToPerfTime(fTime); - // First group has 1 core - SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION systemProcessorInfo = {}; + // First group has 1 active cpu + // 30% user, 45% idle, 25% non-idle kernel (45% + 25% = 70%) + SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION systemProcessorInfo = { 0 }; systemProcessorInfo.UserTime.QuadPart = static_cast(fTime * 30 * 100000); systemProcessorInfo.IdleTime.QuadPart = static_cast(fTime * 45 * 100000); systemProcessorInfo.KernelTime.QuadPart = static_cast(fTime * 70 * 100000); results.vSystemProcessorPerfInfo.push_back(systemProcessorInfo); - // Second group has a maximum of 4 cores with 2 active - SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION zeroSystemProcessorInfo = { 0 }; - zeroSystemProcessorInfo.UserTime.QuadPart = static_cast(fTime * 0 * 100000); - zeroSystemProcessorInfo.IdleTime.QuadPart = static_cast(fTime * 100 * 100000); - zeroSystemProcessorInfo.KernelTime.QuadPart = static_cast(fTime * 100 * 100000); - results.vSystemProcessorPerfInfo.push_back(zeroSystemProcessorInfo); - results.vSystemProcessorPerfInfo.push_back(zeroSystemProcessorInfo); - results.vSystemProcessorPerfInfo.push_back(zeroSystemProcessorInfo); - results.vSystemProcessorPerfInfo.push_back(zeroSystemProcessorInfo); - + // Second group has 2 active + // 100% idle + systemProcessorInfo.UserTime.QuadPart = static_cast(fTime * 0 * 100000); + systemProcessorInfo.IdleTime.QuadPart = static_cast(fTime * 100 * 100000); + systemProcessorInfo.KernelTime.QuadPart = static_cast(fTime * 100 * 100000); + results.vSystemProcessorPerfInfo.push_back(systemProcessorInfo); + results.vSystemProcessorPerfInfo.push_back(systemProcessorInfo); + // TODO: multiple target cases, full profile/result variations target.SetPath("testfile1.dat"); target.SetCacheMode(TargetCacheMode::DisableOSCache); target.SetWriteThroughMode(WriteThroughMode::On); target.SetThroughputIOPS(1000); - + timeSpan.AddTarget(target); timeSpan.SetCalculateIopsStdDev(true); @@ -106,32 +105,41 @@ namespace UnitTests vector vResults; vResults.push_back(results); - // just throw away the computername and reset the timestamp - for the ut, it's - // as useful (and simpler) to verify statics as anything else. Reconstruct - // processor topo to a fixed example as well. + // Just throw away the computername, pp and reset the timestamp - for the ut, it's + // as useful (and simpler) to verify statics as anything else. Reconstruct the + // processor topo to a fixed example as well. Note that the performance + // efficiency class must be placed since it is calculated on the fly during + // the actual GLPIEx enumeration. If we could shim GLPIEx ... SystemInformation system; system.ResetTime(); system.sComputerName.clear(); - system.processorTopology._ulProcCount = 5; - system.processorTopology._ulActiveProcCount = 3; + system.sActivePolicyName.clear(); + system.sActivePolicyGuid.clear(); + + system.processorTopology._ulProcessorCount = 3; + system.processorTopology._ubPerformanceEfficiencyClass = 1; + system.processorTopology._fSMT = true; system.processorTopology._vProcessorGroupInformation.clear(); - system.processorTopology._vProcessorGroupInformation.emplace_back((BYTE)1, (BYTE)1, (WORD)0, (KAFFINITY)0x1); - system.processorTopology._vProcessorGroupInformation.emplace_back((BYTE)4, (BYTE)2, (WORD)1, (KAFFINITY)0x6); + system.processorTopology._vProcessorGroupInformation.emplace_back((WORD)0, (BYTE)1, (BYTE)1, (KAFFINITY)0x1); + system.processorTopology._vProcessorGroupInformation.emplace_back((WORD)1, (BYTE)4, (BYTE)2, (KAFFINITY)0x3); + ProcessorNumaInformation node; + node._nodeNumber = 0; + node._vProcessorMasks.emplace_back((WORD)0, (KAFFINITY)0x1); + node._vProcessorMasks.emplace_back((WORD)1, (KAFFINITY)0x3); system.processorTopology._vProcessorNumaInformation.clear(); - system.processorTopology._vProcessorNumaInformation.emplace_back((DWORD)0, (WORD)0, (KAFFINITY)0x1); - system.processorTopology._vProcessorNumaInformation.emplace_back((DWORD)1, (WORD)1, (KAFFINITY)0x6); + system.processorTopology._vProcessorNumaInformation.push_back(node); ProcessorSocketInformation socket; socket._vProcessorMasks.emplace_back((WORD)0, (KAFFINITY)0x1); - socket._vProcessorMasks.emplace_back((WORD)1, (KAFFINITY)0x6); + socket._vProcessorMasks.emplace_back((WORD)1, (KAFFINITY)0x3); system.processorTopology._vProcessorSocketInformation.clear(); system.processorTopology._vProcessorSocketInformation.push_back(socket); - system.processorTopology._vProcessorHyperThreadInformation.clear(); - system.processorTopology._vProcessorHyperThreadInformation.emplace_back((WORD)0, (KAFFINITY)0x1); - system.processorTopology._vProcessorHyperThreadInformation.emplace_back((WORD)1, (KAFFINITY)0x6); + system.processorTopology._vProcessorCoreInformation.clear(); + system.processorTopology._vProcessorCoreInformation.emplace_back((WORD)0, (KAFFINITY)0x1, (BYTE)0); + system.processorTopology._vProcessorCoreInformation.emplace_back((WORD)1, (KAFFINITY)0x3, (BYTE)1); // finally, add the timespan to the profile and dump. profile.AddTimeSpan(timeSpan); @@ -150,17 +158,20 @@ namespace UnitTests " " DISKSPD_DATE_VERSION_STRING "\n" " \n" " \n" - " \n" + " \n" + " \n" " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" " \n" - " \n" - " \n" + " \n" + " \n" " \n" " \n" " \n" @@ -217,7 +228,11 @@ namespace UnitTests " 3\n" " \n" " \n" + " 0\n" + " 0\n" " 0\n" + " 0\n" + " 0\n" " 0\n" " 55.00\n" " 30.00\n" @@ -225,16 +240,24 @@ namespace UnitTests " 45.00\n" " \n" " \n" + " 0\n" + " 0\n" " 1\n" - " 1\n" + " 0\n" + " 1\n" + " 0\n" " 0.00\n" " 0.00\n" " 0.00\n" " 100.00\n" " \n" " \n" + " 0\n" + " 0\n" " 1\n" - " 2\n" + " 0\n" + " 1\n" + " 1\n" " 0.00\n" " 0.00\n" " 0.00\n" @@ -411,9 +434,9 @@ namespace UnitTests target.SetPath("testfile1.dat"); target.SetCacheMode(TargetCacheMode::DisableOSCache); target.SetWriteThroughMode(WriteThroughMode::On); - + // Base case - no limit - + nWritten = sprintf_s(pszExpectedOutput, sizeof(pszExpectedOutput), pcszOutputTemplate, "", "0"); VERIFY_IS_GREATER_THAN(nWritten, 0); @@ -428,7 +451,7 @@ namespace UnitTests VERIFY_IS_GREATER_THAN(nWritten, 0); sResults = target.GetXml(0); VERIFY_ARE_EQUAL(sResults, pszExpectedOutput); - + // BPMS - not specified with units in output target.SetThroughput(1000); diff --git a/UnitTests/XmlResultParser/XmlResultParser.rc b/UnitTests/XmlResultParser/XmlResultParser.rc index 39e4de0..286016e 100644 --- a/UnitTests/XmlResultParser/XmlResultParser.rc +++ b/UnitTests/XmlResultParser/XmlResultParser.rc @@ -1,5 +1,5 @@ #include -#include "Common.h" +#include "Version.h" #include diff --git a/XmlProfileParser/XmlProfileParser.cpp b/XmlProfileParser/XmlProfileParser.cpp index 53ca937..be75dc6 100644 --- a/XmlProfileParser/XmlProfileParser.cpp +++ b/XmlProfileParser/XmlProfileParser.cpp @@ -85,7 +85,7 @@ bool XmlProfileParser::ParseFile(const char *pszPath, Profile *pProfile, vector< assert(hSchemaXml != NULL); LPVOID pSchemaXml = LockResource(hSchemaXml); assert(pSchemaXml != NULL); - + // convert from utf-8 produced by the xsd authoring tool to utf-16 int cchSchemaXml = MultiByteToWideChar(CP_UTF8, 0, (LPCSTR)pSchemaXml, -1, NULL, 0); vector vWideSchemaXml(cchSchemaXml); @@ -200,6 +200,26 @@ bool XmlProfileParser::ParseFile(const char *pszPath, Profile *pProfile, vector< } } + if (SUCCEEDED(hr)) + { + bool b; + hr = _GetBool(spXmlDoc, "//Profile/VerboseStats", &b); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + pProfile->SetVerboseStats(b); + } + } + + if (SUCCEEDED(hr)) + { + DWORD i; + hr = _GetDWORD(spXmlDoc, "//Profile/ExperimentFlags", &i); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + g_ExperimentFlags = i; + } + } + if (SUCCEEDED(hr)) { DWORD i; @@ -265,7 +285,7 @@ bool XmlProfileParser::ParseFile(const char *pszPath, Profile *pProfile, vector< // Generate an error for each unused substitution. // - if (SUCCEEDED(hr)) + if (SUCCEEDED(hr)) { for (size_t i = 1; i <= vSubsts.size(); ++i) { @@ -661,7 +681,7 @@ HRESULT XmlProfileParser::_ParseTargets(IXMLDOMNode *pXmlNode, TimeSpan *pTimeSp } if (SUCCEEDED(hr)) { - pTimeSpan->AddTarget(target); + pTimeSpan->AddTarget(target); } } @@ -773,7 +793,7 @@ HRESULT XmlProfileParser::_ParseTarget(IXMLDOMNode *pXmlNode, Target *pTarget) pTarget->SetBlockSizeInBytes(dwBlockSize); } } - + if (SUCCEEDED(hr)) { bool fInterlockedSequential; @@ -1299,10 +1319,10 @@ HRESULT XmlProfileParser::_ParseDistribution(IXMLDOMNode *pXmlNode, Target *pTar } if (SUCCEEDED(hr) && (hr != S_FALSE)) - { + { pTarget->SetDistributionRange(v, type.t); } - + // if we parsed into the element, we are done (success or failure) - only one type is possible. return hr; } @@ -1393,7 +1413,7 @@ HRESULT XmlProfileParser::_ParseAffinityGroupAssignment(IXMLDOMNode *pXmlNode, T if (SUCCEEDED(hr)) { pTimeSpan->AddAffinityAssignment((WORD)dwGroup, (BYTE)dwProc); } - + } } } diff --git a/XmlProfileParser/diskspd.xsd b/XmlProfileParser/diskspd.xsd index 7d47c05..f32eab3 100644 --- a/XmlProfileParser/diskspd.xsd +++ b/XmlProfileParser/diskspd.xsd @@ -17,64 +17,121 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - - - + + + @@ -86,9 +143,9 @@ - - - + + + @@ -97,8 +154,8 @@ - - + + @@ -108,43 +165,62 @@ - + - + - + + + + + + + + + + + + + + + + - + - + - + - + + + + + - + -p start async (overlapped) I/O operations with the same offset (makes sense only with -o2 or greater) --> + - + - + - + - + - + - + - + - + - + - + - + - - + + @@ -214,16 +290,16 @@ - + - + - + - - + + @@ -232,16 +308,20 @@ - + + - + + + + - - + + @@ -251,29 +331,29 @@ - + - + - + - + - + - + - + - + - + - + - + - + @@ -283,9 +363,9 @@ - - - + + + @@ -295,7 +375,26 @@ - - + + + + + + + + + + + + + + + + + + + + + diff --git a/XmlResultParser/XmlResultParser.cpp b/XmlResultParser/XmlResultParser.cpp index 441e112..f6119d6 100644 --- a/XmlResultParser/XmlResultParser.cpp +++ b/XmlResultParser/XmlResultParser.cpp @@ -1,586 +1,609 @@ -/* - -DISKSPD - -Copyright(c) Microsoft Corporation -All rights reserved. - -MIT License - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - -*/ - -#include "xmlresultparser.h" - -// TODO: refactor to a single function shared with the ResultParser -char printBuffer[4096] = {}; -void XmlResultParser::_Print(const char *format, ...) -{ - assert(nullptr != format); - va_list listArg; - va_start(listArg, format); - _sResult.append(_indent, ' '); - vsprintf_s(printBuffer, _countof(printBuffer), format, listArg); - va_end(listArg); - _sResult += printBuffer; -} - -void XmlResultParser::_PrintInc(const char *format, ...) -{ - assert(nullptr != format); - va_list listArg; - va_start(listArg, format); - - // Print & Increment Indent - // e.g., - - _Print(format, listArg); - _indent += 2; - va_end(listArg); -} - -void XmlResultParser::_PrintDec(const char *format, ...) -{ - assert(nullptr != format); - va_list listArg; - va_start(listArg, format); - - // Decrement Indent & Print - // e.g., - - _indent -= 2; - _Print(format, listArg); - va_end(listArg); -} - -void XmlResultParser::_PrintTargetResults(const TargetResults& results) -{ - // TODO: results.readBucketizer; - // TODO: results.writeBucketizer; - - _Print("%s\n", results.sPath.c_str()); - _Print("%I64u\n", results.ullBytesCount); - _Print("%I64u\n", results.ullFileSize); - _Print("%I64u\n", results.ullIOCount); - _Print("%I64u\n", results.ullReadBytesCount); - _Print("%I64u\n", results.ullReadIOCount); - _Print("%I64u\n", results.ullWriteBytesCount); - _Print("%I64u\n", results.ullWriteIOCount); - - if (results.vDistributionRange.size()) - { - _PrintInc("\n"); - _PrintInc("\n"); - - // - // Render hole(s) in effective distribution. Keep track of the expected base - // of the next range and render a hole (IO = 0) over the gap as needed. - // - - UINT64 expectBase = 0; - for (auto& r : results.vDistributionRange) - { - if (r._dst.first != expectBase) - { - _Print("%I64u\n", 0, r._dst.first - expectBase); - } - - _Print("%I64u\n", r._span, r._dst.second); - expectBase = r._dst.first + r._dst.second; - } - - _PrintDec("\n"); - _PrintDec("\n"); - } -} - -void XmlResultParser::_PrintTargetLatency(const TargetResults& results) -{ - if (results.readLatencyHistogram.GetSampleSize() > 0) - { - _Print("%.3f\n", results.readLatencyHistogram.GetAvg() / 1000); - _Print("%.3f\n", results.readLatencyHistogram.GetStandardDeviation() / 1000); - } - if (results.writeLatencyHistogram.GetSampleSize() > 0) - { - _Print("%.3f\n", results.writeLatencyHistogram.GetAvg() / 1000); - _Print("%.3f\n", results.writeLatencyHistogram.GetStandardDeviation() / 1000); - } - Histogram totalLatencyHistogram; - totalLatencyHistogram.Merge(results.readLatencyHistogram); - totalLatencyHistogram.Merge(results.writeLatencyHistogram); - if (totalLatencyHistogram.GetSampleSize() > 0) - { - _Print("%.3f\n", totalLatencyHistogram.GetAvg() / 1000); - _Print("%.3f\n", totalLatencyHistogram.GetStandardDeviation() / 1000); - } -} - -void XmlResultParser::_PrintTargetIops(const IoBucketizer& readBucketizer, const IoBucketizer& writeBucketizer, UINT32 bucketTimeInMs) -{ - _PrintInc("\n"); - - IoBucketizer totalIoBucketizer; - totalIoBucketizer.Merge(readBucketizer); - totalIoBucketizer.Merge(writeBucketizer); - - if (readBucketizer.GetNumberOfValidBuckets() > 0) - { - _Print("%.3f\n", readBucketizer.GetStandardDeviationIOPS() / (bucketTimeInMs / 1000.0)); - } - if (writeBucketizer.GetNumberOfValidBuckets() > 0) - { - _Print("%.3f\n", writeBucketizer.GetStandardDeviationIOPS() / (bucketTimeInMs / 1000.0)); - } - if (totalIoBucketizer.GetNumberOfValidBuckets() > 0) - { - _Print("%.3f\n", totalIoBucketizer.GetStandardDeviationIOPS() / (bucketTimeInMs / 1000.0)); - } - _PrintIops(readBucketizer, writeBucketizer, bucketTimeInMs); - _PrintDec("\n"); -} - -void XmlResultParser::_PrintETWSessionInfo(struct ETWSessionInfo sessionInfo) -{ - _PrintInc("\n"); - _Print("%lu\n", sessionInfo.ulBufferSize); - _Print("%lu\n", sessionInfo.ulMinimumBuffers); - _Print("%lu\n", sessionInfo.ulMaximumBuffers); - _Print("%lu", sessionInfo.ulFreeBuffers); - _Print("%lu\n", sessionInfo.ulBuffersWritten); - _Print("%lu\n", sessionInfo.ulFlushTimer); - _Print("%d\n", sessionInfo.lAgeLimit); - - _Print("%lu\n", sessionInfo.ulNumberOfBuffers); - _Print("%lu\n", sessionInfo.ulEventsLost); - _Print("%lu\n", sessionInfo.ulLogBuffersLost); - _Print("%lu\n", sessionInfo.ulRealTimeBuffersLost); - _PrintDec("\n"); -} - -void XmlResultParser::_PrintETW(struct ETWMask ETWMask, struct ETWEventCounters EtwEventCounters) -{ - _PrintInc("\n"); - if (ETWMask.bDiskIO) - { - _PrintInc("\n"); - _Print("%I64u\n", EtwEventCounters.ullIORead); - _Print("%I64u\n", EtwEventCounters.ullIOWrite); - _PrintDec("\n"); - } - if (ETWMask.bImageLoad) - { - _Print("%I64u\n", EtwEventCounters.ullImageLoad); - } - if (ETWMask.bMemoryPageFaults) - { - _PrintInc("\n"); - _Print("%I64u\n", EtwEventCounters.ullMMCopyOnWrite); - _Print("%I64u\n", EtwEventCounters.ullMMDemandZeroFault); - _Print("%I64u\n", EtwEventCounters.ullMMGuardPageFault); - _Print("%I64u\n", EtwEventCounters.ullMMHardPageFault); - _Print("%I64u\n", EtwEventCounters.ullMMTransitionFault); - _PrintDec("\n"); - } - if (ETWMask.bMemoryHardFaults && !ETWMask.bMemoryPageFaults) - { - _Print("%I64u\n", EtwEventCounters.ullMMHardPageFault); - } - if (ETWMask.bNetwork) - { - _PrintInc("\n"); - _Print("%I64u\n", EtwEventCounters.ullNetAccept); - _Print("%I64u\n", EtwEventCounters.ullNetConnect); - _Print("%I64u\n", EtwEventCounters.ullNetDisconnect); - _Print("%I64u\n", EtwEventCounters.ullNetReconnect); - _Print("%I64u\n", EtwEventCounters.ullNetRetransmit); - _Print("%I64u\n", EtwEventCounters.ullNetTcpSend); - _Print("%I64u\n", EtwEventCounters.ullNetTcpReceive); - _Print("%I64u\n", EtwEventCounters.ullNetUdpSend); - _Print("%I64u\n", EtwEventCounters.ullNetUdpReceive); - _PrintDec("\n"); - } - if (ETWMask.bProcess) - { - _PrintInc("\n"); - _Print("%I64u\n", EtwEventCounters.ullProcessStart); - _Print("%I64u\n", EtwEventCounters.ullProcessEnd); - _PrintDec("\n"); - } - if (ETWMask.bRegistry) - { - _PrintInc("\n"); - _Print("%I64u\n", EtwEventCounters.ullRegCreate); - _Print("%I64u\n", EtwEventCounters.ullRegDelete); - _Print("%I64u\n", EtwEventCounters.ullRegDeleteValue); - _Print("%I64u\n", EtwEventCounters.ullRegEnumerateKey); - _Print("%I64u\n", EtwEventCounters.ullRegEnumerateValueKey); - _Print("%I64u\n", EtwEventCounters.ullRegFlush); - _Print("%I64u\n", EtwEventCounters.ullRegOpen); - _Print("%I64u\n", EtwEventCounters.ullRegQuery); - _Print("%I64u\n", EtwEventCounters.ullRegQueryMultipleValue); - _Print("%I64u\n", EtwEventCounters.ullRegQueryValue); - _Print("%I64u\n", EtwEventCounters.ullRegSetInformation); - _Print("%I64u\n", EtwEventCounters.ullRegSetValue); - _PrintDec("\n"); - } - if (ETWMask.bThread) - { - _PrintInc("\n"); - _Print("%I64u\n", EtwEventCounters.ullThreadStart); - _Print("%I64u\n", EtwEventCounters.ullThreadEnd); - _PrintDec("\n"); - } - _PrintDec("\n"); -} - -void XmlResultParser::_PrintCpuUtilization(const Results& results, const SystemInformation& system) -{ - size_t ulProcCount = results.vSystemProcessorPerfInfo.size(); - size_t ulBaseProc = 0; - size_t ulActiveProcCount = 0; - size_t ulNumGroups = system.processorTopology._vProcessorGroupInformation.size(); - - _PrintInc("\n"); - - double busyTime = 0; - double totalIdleTime = 0; - double totalUserTime = 0; - double totalKrnlTime = 0; - - for (unsigned int ulGroup = 0; ulGroup < ulNumGroups; ulGroup++) { - const ProcessorGroupInformation *pGroup = &system.processorTopology._vProcessorGroupInformation[ulGroup]; - - // System has multiple groups but we only have counters for the first one - if (ulBaseProc >= ulProcCount) { - break; - } - - for (unsigned int ulProcessor = 0; ulProcessor < pGroup->_maximumProcessorCount; ulProcessor++) { - double idleTime; - double userTime; - double krnlTime; - double thisTime; - - if (!pGroup->IsProcessorActive((BYTE)ulProcessor)) { - continue; - } - - long long fTime = results.vSystemProcessorPerfInfo[ulBaseProc + ulProcessor].KernelTime.QuadPart + - results.vSystemProcessorPerfInfo[ulBaseProc + ulProcessor].UserTime.QuadPart; - - idleTime = 100.0 * results.vSystemProcessorPerfInfo[ulBaseProc + ulProcessor].IdleTime.QuadPart / fTime; - krnlTime = 100.0 * results.vSystemProcessorPerfInfo[ulBaseProc + ulProcessor].KernelTime.QuadPart / fTime; - userTime = 100.0 * results.vSystemProcessorPerfInfo[ulBaseProc + ulProcessor].UserTime.QuadPart / fTime; - - thisTime = (krnlTime + userTime) - idleTime; - - _PrintInc("\n"); - _Print("%d\n", ulGroup); - _Print("%d\n", ulProcessor); - _Print("%.2f\n", thisTime); - _Print("%.2f\n", userTime); - _Print("%.2f\n", krnlTime - idleTime); - _Print("%.2f\n", idleTime); - _PrintDec("\n"); - - busyTime += thisTime; - totalIdleTime += idleTime; - totalUserTime += userTime; - totalKrnlTime += krnlTime; - - ulActiveProcCount++; - } - - ulBaseProc += pGroup->_maximumProcessorCount; - } - - if (ulActiveProcCount == 0) { - ulActiveProcCount = 1; - } - - _PrintInc("\n"); - _Print("%.2f\n", busyTime / ulActiveProcCount); - _Print("%.2f\n", totalUserTime / ulActiveProcCount); - _Print("%.2f\n", (totalKrnlTime - totalIdleTime) / ulActiveProcCount); - _Print("%.2f\n", totalIdleTime / ulActiveProcCount); - _PrintDec("\n"); - - _PrintDec("\n"); -} - -// emit the iops time series (this obviates needing perfmon counters, in common cases, and provides file level data) -void XmlResultParser::_PrintIops(const IoBucketizer& readBucketizer, const IoBucketizer& writeBucketizer, UINT32 bucketTimeInMs) -{ - bool done = false; - for (size_t i = 0; !done; i++) - { - done = true; - - double r = 0.0; - double r_min = 0.0; - double r_max = 0.0; - double r_avg = 0.0; - double r_stddev = 0.0; - - double w = 0.0; - double w_min = 0.0; - double w_max = 0.0; - double w_avg = 0.0; - double w_stddev = 0.0; - - if (readBucketizer.GetNumberOfValidBuckets() > i) - { - r = readBucketizer.GetIoBucketCount(i) / (bucketTimeInMs / 1000.0); - r_min = readBucketizer.GetIoBucketMinDurationUsec(i) / 1000.0; - r_max = readBucketizer.GetIoBucketMaxDurationUsec(i) / 1000.0; - r_avg = readBucketizer.GetIoBucketAvgDurationUsec(i) / 1000.0; - r_stddev = readBucketizer.GetIoBucketDurationStdDevUsec(i) / 1000.0; - done = false; - } - if (writeBucketizer.GetNumberOfValidBuckets() > i) - { - w = writeBucketizer.GetIoBucketCount(i) / (bucketTimeInMs / 1000.0); - w_min = writeBucketizer.GetIoBucketMinDurationUsec(i) / 1000.0; - w_max = writeBucketizer.GetIoBucketMaxDurationUsec(i) / 1000.0; - w_avg = writeBucketizer.GetIoBucketAvgDurationUsec(i) / 1000.0; - w_stddev = writeBucketizer.GetIoBucketDurationStdDevUsec(i) / 1000.0; - done = false; - } - if (!done) - { - _Print("\n", - bucketTimeInMs*(i + 1), r, w, r + w, - r_min, r_max, r_avg, r_stddev, - w_min, w_max, w_avg, w_stddev); - } - } -} - -void XmlResultParser::_PrintOverallIops(const Results& results, UINT32 bucketTimeInMs) -{ - IoBucketizer readBucketizer; - IoBucketizer writeBucketizer; - - for (const auto& thread : results.vThreadResults) - { - for (const auto& target : thread.vTargetResults) - { - readBucketizer.Merge(target.readBucketizer); - writeBucketizer.Merge(target.writeBucketizer); - } - } - - _PrintTargetIops(readBucketizer, writeBucketizer, bucketTimeInMs); -} - -void XmlResultParser::_PrintLatencyPercentiles(const Results& results) -{ - Histogram readLatencyHistogram; - Histogram writeLatencyHistogram; - Histogram totalLatencyHistogram; - - for (const auto& thread : results.vThreadResults) - { - for (const auto& target : thread.vTargetResults) - { - readLatencyHistogram.Merge(target.readLatencyHistogram); - - writeLatencyHistogram.Merge(target.writeLatencyHistogram); - - totalLatencyHistogram.Merge(target.writeLatencyHistogram); - totalLatencyHistogram.Merge(target.readLatencyHistogram); - } - } - - _PrintInc("\n"); - if (readLatencyHistogram.GetSampleSize() > 0) - { - _Print("%.3f\n", readLatencyHistogram.GetAvg() / 1000); - _Print("%.3f\n", readLatencyHistogram.GetStandardDeviation() / 1000); - } - if (writeLatencyHistogram.GetSampleSize() > 0) - { - _Print("%.3f\n", writeLatencyHistogram.GetAvg() / 1000); - _Print("%.3f\n", writeLatencyHistogram.GetStandardDeviation() / 1000); - } - if (totalLatencyHistogram.GetSampleSize() > 0) - { - _Print("%.3f\n", totalLatencyHistogram.GetAvg() / 1000); - _Print("%.3f\n", totalLatencyHistogram.GetStandardDeviation() / 1000); - } - - _PrintInc("\n"); - _Print("0\n"); - if (readLatencyHistogram.GetSampleSize() > 0) - { - _Print("%.3f\n", readLatencyHistogram.GetMin() / 1000); - } - if (writeLatencyHistogram.GetSampleSize() > 0) - { - _Print("%.3f\n", writeLatencyHistogram.GetMin() / 1000); - } - if (totalLatencyHistogram.GetSampleSize() > 0) - { - _Print("%.3f\n", totalLatencyHistogram.GetMin() / 1000); - } - _PrintDec("\n"); - - // Construct vector of percentiles and decimal precision to squelch trailing zeroes. This is more - // detailed than summary text output, and does not contain the decorated names (15th, etc.) - - vector> vPercentiles; - for (int p = 1; p <= 99; p++) - { - vPercentiles.push_back(make_pair(0, p)); - } - - vPercentiles.push_back(make_pair(1, 99.9)); - vPercentiles.push_back(make_pair(2, 99.99)); - vPercentiles.push_back(make_pair(3, 99.999)); - vPercentiles.push_back(make_pair(4, 99.9999)); - vPercentiles.push_back(make_pair(5, 99.99999)); - vPercentiles.push_back(make_pair(6, 99.999999)); - vPercentiles.push_back(make_pair(7, 99.9999999)); - - for (auto p : vPercentiles) - { - _PrintInc("\n"); - _Print("%.*f\n", p.first, p.second); - if (readLatencyHistogram.GetSampleSize() > 0) - { - _Print("%.3f\n", readLatencyHistogram.GetPercentile(p.second / 100) / 1000); - } - if (writeLatencyHistogram.GetSampleSize() > 0) - { - _Print("%.3f\n", writeLatencyHistogram.GetPercentile(p.second / 100) / 1000); - } - if (totalLatencyHistogram.GetSampleSize() > 0) - { - _Print("%.3f\n", totalLatencyHistogram.GetPercentile(p.second / 100) / 1000); - } - _PrintDec("\n"); - } - - _PrintInc("\n"); - _Print("100\n"); - if (readLatencyHistogram.GetSampleSize() > 0) - { - _Print("%.3f\n", readLatencyHistogram.GetMax() / 1000); - } - if (writeLatencyHistogram.GetSampleSize() > 0) - { - _Print("%.3f\n", writeLatencyHistogram.GetMax() / 1000); - } - if (totalLatencyHistogram.GetSampleSize() > 0) - { - _Print("%.3f\n", totalLatencyHistogram.GetMax() / 1000); - } - _PrintDec("\n"); - _PrintDec("\n"); -} - -string XmlResultParser::ParseProfile(const Profile& profile) -{ - _sResult = profile.GetXml(0); - return _sResult; -} - -string XmlResultParser::ParseResults(const Profile& profile, const SystemInformation& system, vector vResults) -{ - _sResult.clear(); - - _PrintInc("\n"); - - _sResult += system.GetXml(_indent); - _sResult += profile.GetXml(_indent); - for (size_t iResults = 0; iResults < vResults.size(); iResults++) - { - const Results& results = vResults[iResults]; - const TimeSpan& timeSpan = profile.GetTimeSpans()[iResults]; - - _PrintInc("\n"); - - double fTime = PerfTimer::PerfTimeToSeconds(results.ullTimeCount); //test duration - if (fTime >= 0.0000001) - { - // There either is a fixed number of threads for all files to share (GetThreadCount() > 0) or a number of threads per file. - // In the latter case vThreadResults.size() == number of threads per file * file count - size_t ulThreadCnt = (timeSpan.GetThreadCount() > 0) ? timeSpan.GetThreadCount() : results.vThreadResults.size(); - unsigned int ulProcCount = system.processorTopology._ulActiveProcCount; - - _Print("%.2f\n", fTime); - _Print("%u\n", ulThreadCnt); - _Print("%u\n", timeSpan.GetRequestCount()); - _Print("%u\n", ulProcCount); - - _PrintCpuUtilization(results, system); - - if (timeSpan.GetMeasureLatency()) - { - _PrintLatencyPercentiles(results); - } - - if (timeSpan.GetCalculateIopsStdDev()) - { - _PrintOverallIops(results, timeSpan.GetIoBucketDurationInMilliseconds()); - } - - if (results.fUseETW) - { - _PrintETW(results.EtwMask, results.EtwEventCounters); - _PrintETWSessionInfo(results.EtwSessionInfo); - } - - for (size_t iThread = 0; iThread < results.vThreadResults.size(); iThread++) - { - const ThreadResults& threadResults = results.vThreadResults[iThread]; - _PrintInc("\n"); - _Print("%u\n", iThread); - for (const auto& targetResults : threadResults.vTargetResults) - { - _PrintInc("\n"); - _PrintTargetResults(targetResults); - if (timeSpan.GetMeasureLatency()) - { - _PrintTargetLatency(targetResults); - } - if (timeSpan.GetCalculateIopsStdDev()) - { - _PrintTargetIops(targetResults.readBucketizer, targetResults.writeBucketizer, timeSpan.GetIoBucketDurationInMilliseconds()); - } - _PrintDec("\n"); - } - _PrintDec("\n"); - } - } - else - { - _Print("The test was interrupted before the measurements began. No results are displayed.\n"); - } - - _PrintDec("\n"); - } - - _PrintDec(""); - return _sResult; -} +/* + +DISKSPD + +Copyright(c) Microsoft Corporation +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +#include "xmlresultparser.h" + +// TODO: refactor to a single function shared with the ResultParser +char printBuffer[4096] = {}; + +void XmlResultParser::_PrintV(const char *format, va_list listArg) +{ + _sResult.append(_indent, ' '); + vsprintf_s(printBuffer, _countof(printBuffer), format, listArg); + _sResult += printBuffer; +} + +void XmlResultParser::_Print(const char *format, ...) +{ + assert(nullptr != format); + va_list listArg; + va_start(listArg, format); + + _PrintV(format, listArg); + va_end(listArg); +} + +void XmlResultParser::_PrintInc(const char *format, ...) +{ + assert(nullptr != format); + va_list listArg; + va_start(listArg, format); + + // Print & Increment Indent + // e.g., + + _PrintV(format, listArg); + _indent += 2; + va_end(listArg); +} + +void XmlResultParser::_PrintDec(const char *format, ...) +{ + assert(nullptr != format); + va_list listArg; + va_start(listArg, format); + + // Decrement Indent & Print + // e.g., + + _indent -= 2; + _PrintV(format, listArg); + va_end(listArg); +} + +void XmlResultParser::_PrintTargetResults(const TargetResults& results) +{ + // TODO: results.readBucketizer; + // TODO: results.writeBucketizer; + + _Print("%s\n", results.sPath.c_str()); + _Print("%I64u\n", results.ullBytesCount); + _Print("%I64u\n", results.ullFileSize); + _Print("%I64u\n", results.ullIOCount); + _Print("%I64u\n", results.ullReadBytesCount); + _Print("%I64u\n", results.ullReadIOCount); + _Print("%I64u\n", results.ullWriteBytesCount); + _Print("%I64u\n", results.ullWriteIOCount); + + if (results.vDistributionRange.size()) + { + _PrintInc("\n"); + _PrintInc("\n"); + + // + // Render hole(s) in effective distribution. Keep track of the expected base + // of the next range and render a hole (IO = 0) over the gap as needed. + // + + UINT64 expectBase = 0; + for (auto& r : results.vDistributionRange) + { + if (r._dst.first != expectBase) + { + _Print("%I64u\n", 0, r._dst.first - expectBase); + } + + _Print("%I64u\n", r._span, r._dst.second); + expectBase = r._dst.first + r._dst.second; + } + + _PrintDec("\n"); + _PrintDec("\n"); + } +} + +void XmlResultParser::_PrintTargetLatency(const TargetResults& results) +{ + if (results.readLatencyHistogram.GetSampleSize() > 0) + { + _Print("%.3f\n", results.readLatencyHistogram.GetAvg() / 1000); + _Print("%.3f\n", results.readLatencyHistogram.GetStandardDeviation() / 1000); + } + if (results.writeLatencyHistogram.GetSampleSize() > 0) + { + _Print("%.3f\n", results.writeLatencyHistogram.GetAvg() / 1000); + _Print("%.3f\n", results.writeLatencyHistogram.GetStandardDeviation() / 1000); + } + Histogram totalLatencyHistogram; + totalLatencyHistogram.Merge(results.readLatencyHistogram); + totalLatencyHistogram.Merge(results.writeLatencyHistogram); + if (totalLatencyHistogram.GetSampleSize() > 0) + { + _Print("%.3f\n", totalLatencyHistogram.GetAvg() / 1000); + _Print("%.3f\n", totalLatencyHistogram.GetStandardDeviation() / 1000); + } +} + +void XmlResultParser::_PrintTargetIops(const IoBucketizer& readBucketizer, const IoBucketizer& writeBucketizer, UINT32 bucketTimeInMs) +{ + _PrintInc("\n"); + + IoBucketizer totalIoBucketizer; + totalIoBucketizer.Merge(readBucketizer); + totalIoBucketizer.Merge(writeBucketizer); + + if (readBucketizer.GetNumberOfValidBuckets() > 0) + { + _Print("%.3f\n", readBucketizer.GetStandardDeviationIOPS() / (bucketTimeInMs / 1000.0)); + } + if (writeBucketizer.GetNumberOfValidBuckets() > 0) + { + _Print("%.3f\n", writeBucketizer.GetStandardDeviationIOPS() / (bucketTimeInMs / 1000.0)); + } + if (totalIoBucketizer.GetNumberOfValidBuckets() > 0) + { + _Print("%.3f\n", totalIoBucketizer.GetStandardDeviationIOPS() / (bucketTimeInMs / 1000.0)); + } + _PrintIops(readBucketizer, writeBucketizer, bucketTimeInMs); + _PrintDec("\n"); +} + +void XmlResultParser::_PrintETWSessionInfo(struct ETWSessionInfo sessionInfo) +{ + _PrintInc("\n"); + _Print("%lu\n", sessionInfo.ulBufferSize); + _Print("%lu\n", sessionInfo.ulMinimumBuffers); + _Print("%lu\n", sessionInfo.ulMaximumBuffers); + _Print("%lu", sessionInfo.ulFreeBuffers); + _Print("%lu\n", sessionInfo.ulBuffersWritten); + _Print("%lu\n", sessionInfo.ulFlushTimer); + _Print("%d\n", sessionInfo.lAgeLimit); + + _Print("%lu\n", sessionInfo.ulNumberOfBuffers); + _Print("%lu\n", sessionInfo.ulEventsLost); + _Print("%lu\n", sessionInfo.ulLogBuffersLost); + _Print("%lu\n", sessionInfo.ulRealTimeBuffersLost); + _PrintDec("\n"); +} + +void XmlResultParser::_PrintETW(struct ETWMask ETWMask, struct ETWEventCounters EtwEventCounters) +{ + _PrintInc("\n"); + if (ETWMask.bDiskIO) + { + _PrintInc("\n"); + _Print("%I64u\n", EtwEventCounters.ullIORead); + _Print("%I64u\n", EtwEventCounters.ullIOWrite); + _PrintDec("\n"); + } + if (ETWMask.bImageLoad) + { + _Print("%I64u\n", EtwEventCounters.ullImageLoad); + } + if (ETWMask.bMemoryPageFaults) + { + _PrintInc("\n"); + _Print("%I64u\n", EtwEventCounters.ullMMCopyOnWrite); + _Print("%I64u\n", EtwEventCounters.ullMMDemandZeroFault); + _Print("%I64u\n", EtwEventCounters.ullMMGuardPageFault); + _Print("%I64u\n", EtwEventCounters.ullMMHardPageFault); + _Print("%I64u\n", EtwEventCounters.ullMMTransitionFault); + _PrintDec("\n"); + } + if (ETWMask.bMemoryHardFaults && !ETWMask.bMemoryPageFaults) + { + _Print("%I64u\n", EtwEventCounters.ullMMHardPageFault); + } + if (ETWMask.bNetwork) + { + _PrintInc("\n"); + _Print("%I64u\n", EtwEventCounters.ullNetAccept); + _Print("%I64u\n", EtwEventCounters.ullNetConnect); + _Print("%I64u\n", EtwEventCounters.ullNetDisconnect); + _Print("%I64u\n", EtwEventCounters.ullNetReconnect); + _Print("%I64u\n", EtwEventCounters.ullNetRetransmit); + _Print("%I64u\n", EtwEventCounters.ullNetTcpSend); + _Print("%I64u\n", EtwEventCounters.ullNetTcpReceive); + _Print("%I64u\n", EtwEventCounters.ullNetUdpSend); + _Print("%I64u\n", EtwEventCounters.ullNetUdpReceive); + _PrintDec("\n"); + } + if (ETWMask.bProcess) + { + _PrintInc("\n"); + _Print("%I64u\n", EtwEventCounters.ullProcessStart); + _Print("%I64u\n", EtwEventCounters.ullProcessEnd); + _PrintDec("\n"); + } + if (ETWMask.bRegistry) + { + _PrintInc("\n"); + _Print("%I64u\n", EtwEventCounters.ullRegCreate); + _Print("%I64u\n", EtwEventCounters.ullRegDelete); + _Print("%I64u\n", EtwEventCounters.ullRegDeleteValue); + _Print("%I64u\n", EtwEventCounters.ullRegEnumerateKey); + _Print("%I64u\n", EtwEventCounters.ullRegEnumerateValueKey); + _Print("%I64u\n", EtwEventCounters.ullRegFlush); + _Print("%I64u\n", EtwEventCounters.ullRegOpen); + _Print("%I64u\n", EtwEventCounters.ullRegQuery); + _Print("%I64u\n", EtwEventCounters.ullRegQueryMultipleValue); + _Print("%I64u\n", EtwEventCounters.ullRegQueryValue); + _Print("%I64u\n", EtwEventCounters.ullRegSetInformation); + _Print("%I64u\n", EtwEventCounters.ullRegSetValue); + _PrintDec("\n"); + } + if (ETWMask.bThread) + { + _PrintInc("\n"); + _Print("%I64u\n", EtwEventCounters.ullThreadStart); + _Print("%I64u\n", EtwEventCounters.ullThreadEnd); + _PrintDec("\n"); + } + _PrintDec("\n"); +} + +void XmlResultParser::_PrintCpuUtilization(const Results& results, const SystemInformation& system) +{ + const auto& topo = system.processorTopology; + size_t procCount = results.vSystemProcessorPerfInfo.size(); + size_t baseProc = 0; + BYTE efficiencyClass = 0; + BYTE processorCore = 0; + + _PrintInc("\n"); + + double busyTime = 0; + double totalIdleTime = 0; + double totalUserTime = 0; + double totalKrnlTime = 0; + + for (const auto& group : topo._vProcessorGroupInformation) { + + // Sanity assert - results are sized to the sum of active processors + assert(baseProc + group._activeProcessorCount <= procCount); + + for (BYTE processor = 0; processor < group._activeProcessorCount; processor++) { + + long long fTime = results.vSystemProcessorPerfInfo[baseProc + processor].KernelTime.QuadPart + + results.vSystemProcessorPerfInfo[baseProc + processor].UserTime.QuadPart; + + double idleTime = 100.0 * results.vSystemProcessorPerfInfo[baseProc + processor].IdleTime.QuadPart / fTime; + double krnlTime = 100.0 * results.vSystemProcessorPerfInfo[baseProc + processor].KernelTime.QuadPart / fTime; + double userTime = 100.0 * results.vSystemProcessorPerfInfo[baseProc + processor].UserTime.QuadPart / fTime; + double usedTime = (krnlTime - idleTime) + userTime; + + _PrintInc("\n"); + _Print("%d\n", topo.GetSocketOfProcessor(group._groupNumber, processor)); + _Print("%d\n", topo.GetNumaOfProcessor(group._groupNumber, processor)); + _Print("%d\n", group._groupNumber); + processorCore = topo.GetCoreOfProcessor(group._groupNumber, processor, efficiencyClass); + _Print("%d\n", processorCore); + _Print("%d\n", efficiencyClass); + _Print("%d\n", processor); + _Print("%.2f\n", usedTime); + _Print("%.2f\n", userTime); + _Print("%.2f\n", krnlTime - idleTime); + _Print("%.2f\n", idleTime); + _PrintDec("\n"); + + busyTime += usedTime; + totalIdleTime += idleTime; + totalUserTime += userTime; + totalKrnlTime += krnlTime; + } + + baseProc += group._activeProcessorCount; + } + + assert(baseProc == procCount); + + _PrintInc("\n"); + _Print("%.2f\n", busyTime / procCount); + _Print("%.2f\n", totalUserTime / procCount); + _Print("%.2f\n", (totalKrnlTime - totalIdleTime) / procCount); + _Print("%.2f\n", totalIdleTime / procCount); + _PrintDec("\n"); + + _PrintDec("\n"); +} + +// emit the iops time series (this obviates needing perfmon counters, in common cases, and provides file level data) +void XmlResultParser::_PrintIops(const IoBucketizer& readBucketizer, const IoBucketizer& writeBucketizer, UINT32 bucketTimeInMs) +{ + bool done = false; + for (size_t i = 0; !done; i++) + { + done = true; + + double r = 0.0; + double r_min = 0.0; + double r_max = 0.0; + double r_avg = 0.0; + double r_stddev = 0.0; + + double w = 0.0; + double w_min = 0.0; + double w_max = 0.0; + double w_avg = 0.0; + double w_stddev = 0.0; + + if (readBucketizer.GetNumberOfValidBuckets() > i) + { + r = readBucketizer.GetIoBucketCount(i) / (bucketTimeInMs / 1000.0); + r_min = readBucketizer.GetIoBucketMinDurationUsec(i) / 1000.0; + r_max = readBucketizer.GetIoBucketMaxDurationUsec(i) / 1000.0; + r_avg = readBucketizer.GetIoBucketAvgDurationUsec(i) / 1000.0; + r_stddev = readBucketizer.GetIoBucketDurationStdDevUsec(i) / 1000.0; + done = false; + } + if (writeBucketizer.GetNumberOfValidBuckets() > i) + { + w = writeBucketizer.GetIoBucketCount(i) / (bucketTimeInMs / 1000.0); + w_min = writeBucketizer.GetIoBucketMinDurationUsec(i) / 1000.0; + w_max = writeBucketizer.GetIoBucketMaxDurationUsec(i) / 1000.0; + w_avg = writeBucketizer.GetIoBucketAvgDurationUsec(i) / 1000.0; + w_stddev = writeBucketizer.GetIoBucketDurationStdDevUsec(i) / 1000.0; + done = false; + } + if (!done) + { + _Print("\n", + bucketTimeInMs*(i + 1), r, w, r + w, + r_min, r_max, r_avg, r_stddev, + w_min, w_max, w_avg, w_stddev); + } + } +} + +void XmlResultParser::_PrintOverallIops(const Results& results, UINT32 bucketTimeInMs) +{ + IoBucketizer readBucketizer; + IoBucketizer writeBucketizer; + + for (const auto& thread : results.vThreadResults) + { + for (const auto& target : thread.vTargetResults) + { + readBucketizer.Merge(target.readBucketizer); + writeBucketizer.Merge(target.writeBucketizer); + } + } + + _PrintTargetIops(readBucketizer, writeBucketizer, bucketTimeInMs); +} + +void XmlResultParser::_PrintLatencyPercentiles(const Results& results) +{ + Histogram readLatencyHistogram; + Histogram writeLatencyHistogram; + Histogram totalLatencyHistogram; + + for (const auto& thread : results.vThreadResults) + { + for (const auto& target : thread.vTargetResults) + { + readLatencyHistogram.Merge(target.readLatencyHistogram); + + writeLatencyHistogram.Merge(target.writeLatencyHistogram); + + totalLatencyHistogram.Merge(target.writeLatencyHistogram); + totalLatencyHistogram.Merge(target.readLatencyHistogram); + } + } + + _PrintInc("\n", + readLatencyHistogram.GetSampleBuckets(), + writeLatencyHistogram.GetSampleBuckets(), + totalLatencyHistogram.GetSampleBuckets()); + + if (readLatencyHistogram.GetSampleSize() > 0) + { + _Print("%.3f\n", readLatencyHistogram.GetAvg() / 1000); + _Print("%.3f\n", readLatencyHistogram.GetStandardDeviation() / 1000); + } + if (writeLatencyHistogram.GetSampleSize() > 0) + { + _Print("%.3f\n", writeLatencyHistogram.GetAvg() / 1000); + _Print("%.3f\n", writeLatencyHistogram.GetStandardDeviation() / 1000); + } + if (totalLatencyHistogram.GetSampleSize() > 0) + { + _Print("%.3f\n", totalLatencyHistogram.GetAvg() / 1000); + _Print("%.3f\n", totalLatencyHistogram.GetStandardDeviation() / 1000); + } + + _PrintInc("\n"); + _Print("0\n"); + if (readLatencyHistogram.GetSampleSize() > 0) + { + _Print("%.3f\n", readLatencyHistogram.GetMin() / 1000); + } + if (writeLatencyHistogram.GetSampleSize() > 0) + { + _Print("%.3f\n", writeLatencyHistogram.GetMin() / 1000); + } + if (totalLatencyHistogram.GetSampleSize() > 0) + { + _Print("%.3f\n", totalLatencyHistogram.GetMin() / 1000); + } + _PrintDec("\n"); + + // Construct vector of percentiles and decimal precision to squelch trailing zeroes. This is more + // detailed than summary text output, and does not contain the decorated names (15th, etc.) + + vector> vPercentiles; + for (int p = 1; p <= 99; p++) + { + vPercentiles.push_back(make_pair(0, p)); + } + + vPercentiles.push_back(make_pair(1, 99.9)); + vPercentiles.push_back(make_pair(2, 99.99)); + vPercentiles.push_back(make_pair(3, 99.999)); + vPercentiles.push_back(make_pair(4, 99.9999)); + vPercentiles.push_back(make_pair(5, 99.99999)); + vPercentiles.push_back(make_pair(6, 99.999999)); + vPercentiles.push_back(make_pair(7, 99.9999999)); + + for (auto p : vPercentiles) + { + _PrintInc("\n"); + _Print("%.*f\n", p.first, p.second); + if (readLatencyHistogram.GetSampleSize() > 0) + { + _Print("%.3f\n", readLatencyHistogram.GetPercentile(p.second / 100) / 1000); + } + if (writeLatencyHistogram.GetSampleSize() > 0) + { + _Print("%.3f\n", writeLatencyHistogram.GetPercentile(p.second / 100) / 1000); + } + if (totalLatencyHistogram.GetSampleSize() > 0) + { + _Print("%.3f\n", totalLatencyHistogram.GetPercentile(p.second / 100) / 1000); + } + _PrintDec("\n"); + } + + _PrintInc("\n"); + _Print("100\n"); + if (readLatencyHistogram.GetSampleSize() > 0) + { + _Print("%.3f\n", readLatencyHistogram.GetMax() / 1000); + } + if (writeLatencyHistogram.GetSampleSize() > 0) + { + _Print("%.3f\n", writeLatencyHistogram.GetMax() / 1000); + } + if (totalLatencyHistogram.GetSampleSize() > 0) + { + _Print("%.3f\n", totalLatencyHistogram.GetMax() / 1000); + } + _PrintDec("\n"); + _PrintDec("\n"); +} + +string XmlResultParser::ParseProfile(const Profile& profile) +{ + _sResult = profile.GetXml(0); + return _sResult; +} + +void XmlResultParser::_PrintWaitStats(const ThreadResults &threadResult) +{ + _PrintInc("\n"); + _Print("%llu\n", threadResult.WaitStats.Wait); + _Print("%llu\n", threadResult.WaitStats.ThrottleWait); + _Print("%llu\n", threadResult.WaitStats.ThrottleSleep); + _Print("%llu\n", threadResult.WaitStats.Lookaside); + _Print("%llu %llu %llu %llu %llu %llu %llu %llu\n", + threadResult.WaitStats.LookasideCompletion[0], + threadResult.WaitStats.LookasideCompletion[1], + threadResult.WaitStats.LookasideCompletion[2], + threadResult.WaitStats.LookasideCompletion[3], + threadResult.WaitStats.LookasideCompletion[4], + threadResult.WaitStats.LookasideCompletion[5], + threadResult.WaitStats.LookasideCompletion[6], + threadResult.WaitStats.LookasideCompletion[7]); + _PrintDec("\n"); +} + +string XmlResultParser::ParseResults(const Profile& profile, const SystemInformation& system, vector vResults) +{ + _sResult.clear(); + + _PrintInc("\n"); + + _sResult += system.GetXml(_indent); + _sResult += profile.GetXml(_indent); + for (size_t iResults = 0; iResults < vResults.size(); iResults++) + { + const Results& results = vResults[iResults]; + const TimeSpan& timeSpan = profile.GetTimeSpans()[iResults]; + + _PrintInc("\n"); + + double fTime = PerfTimer::PerfTimeToSeconds(results.ullTimeCount); //test duration + if (fTime >= 0.0000001) + { + // There either is a fixed number of threads for all files to share (GetThreadCount() > 0) or a number of threads per file. + // In the latter case vThreadResults.size() == number of threads per file * file count + size_t ulThreadCnt = (timeSpan.GetThreadCount() > 0) ? timeSpan.GetThreadCount() : results.vThreadResults.size(); + + _Print("%.2f\n", fTime); + _Print("%u\n", ulThreadCnt); + _Print("%u\n", timeSpan.GetRequestCount()); + _Print("%u\n", system.processorTopology._ulProcessorCount); + + _PrintCpuUtilization(results, system); + + if (timeSpan.GetMeasureLatency()) + { + _PrintLatencyPercentiles(results); + } + + if (timeSpan.GetCalculateIopsStdDev()) + { + _PrintOverallIops(results, timeSpan.GetIoBucketDurationInMilliseconds()); + } + + if (results.fUseETW) + { + _PrintETW(results.EtwMask, results.EtwEventCounters); + _PrintETWSessionInfo(results.EtwSessionInfo); + } + + for (size_t iThread = 0; iThread < results.vThreadResults.size(); iThread++) + { + const ThreadResults& threadResults = results.vThreadResults[iThread]; + _PrintInc("\n"); + _Print("%u\n", iThread); + for (const auto& targetResults : threadResults.vTargetResults) + { + _PrintInc("\n"); + _PrintTargetResults(targetResults); + if (timeSpan.GetMeasureLatency()) + { + _PrintTargetLatency(targetResults); + } + if (timeSpan.GetCalculateIopsStdDev()) + { + _PrintTargetIops(targetResults.readBucketizer, targetResults.writeBucketizer, timeSpan.GetIoBucketDurationInMilliseconds()); + } + _PrintDec("\n"); + } + if (profile.GetVerboseStats()) + { + _PrintWaitStats(threadResults); + } + _PrintDec("\n"); + } + } + else + { + _Print("The test was interrupted before the measurements began. No results are displayed.\n"); + } + + _PrintDec("\n"); + } + + _PrintDec(""); + return _sResult; +} diff --git a/diskspd_vs/CmdLineParser/CmdLineParser.vcxproj b/diskspd_vs/CmdLineParser/CmdLineParser.vcxproj index 9f61248..20c47ae 100644 --- a/diskspd_vs/CmdLineParser/CmdLineParser.vcxproj +++ b/diskspd_vs/CmdLineParser/CmdLineParser.vcxproj @@ -1,148 +1,144 @@ - - - - - Debug - Win32 - - - Debug - x64 - - - Release - Win32 - - - Release - x64 - - - - {0EF5CE78-8E92-4A1B-A255-0F544AADA291} - CmdLineParser - 10.0 - - - - StaticLibrary - true - MultiByte - v142 - - - StaticLibrary - true - MultiByte - v142 - - - StaticLibrary - false - true - MultiByte - v142 - - - StaticLibrary - false - true - MultiByte - v142 - - - - - - - - - - - - - - - - - - - $(SolutionDir)\..\Common;$(IncludePath) - - - $(SolutionDir)\..\Common;$(IncludePath) - - - $(SolutionDir)\..\Common;$(IncludePath) - - - $(SolutionDir)\..\Common;$(IncludePath) - - - - Level3 - Disabled - true - false - true - MultiThreadedDebug - - - true - - - - - Level3 - Disabled - true - false - true - MultiThreadedDebug - - - true - - - - - Level3 - MaxSpeed - true - true - true - true - MultiThreaded - - - true - true - true - - - - - Level3 - MaxSpeed - true - true - true - true - MultiThreaded - - - true - true - true - - - - - - - - - - - + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {0EF5CE78-8E92-4A1B-A255-0F544AADA291} + CmdLineParser + 10.0 + + + + StaticLibrary + true + MultiByte + v143 + + + StaticLibrary + true + MultiByte + v143 + + + StaticLibrary + false + true + MultiByte + v143 + + + StaticLibrary + false + true + MultiByte + v143 + + + + + + + + + + + + + + + + + + + $(SolutionDir)\..\Common;$(IncludePath) + + + $(SolutionDir)\..\Common;$(IncludePath) + + + $(SolutionDir)\..\Common;$(IncludePath) + + + $(SolutionDir)\..\Common;$(IncludePath) + + + + Level3 + Disabled + true + false + true + + + true + + + + + Level3 + Disabled + true + false + true + + + true + + + + + Level3 + MaxSpeed + true + true + true + true + + + true + true + true + + + + + Level3 + MaxSpeed + true + true + true + true + + + true + true + true + + + + + + + + + + + \ No newline at end of file diff --git a/diskspd_vs/CmdRequestCreator/CmdRequestCreator.vcxproj b/diskspd_vs/CmdRequestCreator/CmdRequestCreator.vcxproj index fa03d7c..af9d15c 100644 --- a/diskspd_vs/CmdRequestCreator/CmdRequestCreator.vcxproj +++ b/diskspd_vs/CmdRequestCreator/CmdRequestCreator.vcxproj @@ -1,166 +1,162 @@ - - - - - Debug - Win32 - - - Debug - x64 - - - Release - Win32 - - - Release - x64 - - - - {D238F8AA-DE12-49E7-B4A7-9B69579A69C0} - CmdRequestCreator - 10.0 - - - - Application - true - MultiByte - v142 - - - Application - true - MultiByte - v142 - - - Application - false - true - MultiByte - v142 - - - Application - false - true - MultiByte - v142 - - - - - - - - - - - - - - - - - - - $(SolutionDir)\..\Common;$(IncludePath) - diskspd - - - $(SolutionDir)\..\Common;$(IncludePath) - diskspd - false - - - $(SolutionDir)\..\Common;$(IncludePath) - diskspd - - - $(SolutionDir)\..\Common;$(IncludePath) - diskspd - - - - Level3 - Disabled - true - false - true - MultiThreadedDebug - - - true - $(SolutionDir)$(Configuration)\xmlprofileparser.lib;$(SolutionDir)$(Configuration)\iorequestgenerator.lib;$(SolutionDir)$(Configuration)\resultparser.lib;$(SolutionDir)$(Configuration)\xmlresultparser.lib;$(SolutionDir)$(Configuration)\common.lib;msxml6.lib;%(AdditionalDependencies) - - - - - Level3 - Disabled - true - false - true - MultiThreadedDebug - - - true - $(SolutionDir)$(Platform)\$(Configuration)\xmlprofileparser.lib;$(SolutionDir)$(Platform)\$(Configuration)\iorequestgenerator.lib;$(SolutionDir)$(Platform)\$(Configuration)\resultparser.lib;$(SolutionDir)$(Platform)\$(Configuration)\xmlresultparser.lib;$(SolutionDir)$(Platform)\$(Configuration)\common.lib;msxml6.lib;%(AdditionalDependencies) - - - - - Level3 - MaxSpeed - true - true - true - true - MultiThreaded - - - true - true - true - $(SolutionDir)$(Configuration)\xmlprofileparser.lib;$(SolutionDir)$(Configuration)\iorequestgenerator.lib;$(SolutionDir)$(Configuration)\resultparser.lib;$(SolutionDir)$(Configuration)\xmlresultparser.lib;$(SolutionDir)$(Configuration)\common.lib;msxml6.lib;%(AdditionalDependencies) - - - - - Level3 - MaxSpeed - true - true - true - true - MultiThreaded - - - true - true - true - $(SolutionDir)$(Platform)\$(Configuration)\xmlprofileparser.lib;$(SolutionDir)$(Platform)\$(Configuration)\iorequestgenerator.lib;$(SolutionDir)$(Platform)\$(Configuration)\resultparser.lib;$(SolutionDir)$(Platform)\$(Configuration)\xmlresultparser.lib;$(SolutionDir)$(Platform)\$(Configuration)\common.lib;msxml6.lib;%(AdditionalDependencies) - - - - - {0ef5ce78-8e92-4a1b-a255-0f544aada291} - - - - - - - - - - - - - - - + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {D238F8AA-DE12-49E7-B4A7-9B69579A69C0} + CmdRequestCreator + 10.0 + + + + Application + true + MultiByte + v143 + + + Application + true + MultiByte + v143 + + + Application + false + true + MultiByte + v143 + + + Application + false + true + MultiByte + v143 + + + + + + + + + + + + + + + + + + + $(SolutionDir)\..\Common;$(IncludePath) + diskspd + + + $(SolutionDir)\..\Common;$(IncludePath) + diskspd + false + + + $(SolutionDir)\..\Common;$(IncludePath) + diskspd + + + $(SolutionDir)\..\Common;$(IncludePath) + diskspd + + + + Level3 + Disabled + true + false + true + + + true + $(SolutionDir)$(Configuration)\xmlprofileparser.lib;$(SolutionDir)$(Configuration)\iorequestgenerator.lib;$(SolutionDir)$(Configuration)\resultparser.lib;$(SolutionDir)$(Configuration)\xmlresultparser.lib;$(SolutionDir)$(Configuration)\common.lib;powrprof.lib;msxml6.lib;%(AdditionalDependencies) + + + + + Level3 + Disabled + true + false + true + + + true + $(SolutionDir)$(Platform)\$(Configuration)\xmlprofileparser.lib;$(SolutionDir)$(Platform)\$(Configuration)\iorequestgenerator.lib;$(SolutionDir)$(Platform)\$(Configuration)\resultparser.lib;$(SolutionDir)$(Platform)\$(Configuration)\xmlresultparser.lib;$(SolutionDir)$(Platform)\$(Configuration)\common.lib;powrprof.lib;msxml6.lib;%(AdditionalDependencies) + + + + + Level3 + MaxSpeed + true + true + true + true + + + true + true + true + $(SolutionDir)$(Configuration)\xmlprofileparser.lib;$(SolutionDir)$(Configuration)\iorequestgenerator.lib;$(SolutionDir)$(Configuration)\resultparser.lib;$(SolutionDir)$(Configuration)\xmlresultparser.lib;$(SolutionDir)$(Configuration)\common.lib;powrprof.lib;msxml6.lib;%(AdditionalDependencies) + + + + + Level3 + MaxSpeed + true + true + true + true + + + true + true + true + $(SolutionDir)$(Platform)\$(Configuration)\xmlprofileparser.lib;$(SolutionDir)$(Platform)\$(Configuration)\iorequestgenerator.lib;$(SolutionDir)$(Platform)\$(Configuration)\resultparser.lib;$(SolutionDir)$(Platform)\$(Configuration)\xmlresultparser.lib;$(SolutionDir)$(Platform)\$(Configuration)\common.lib;powrprof.lib;msxml6.lib;%(AdditionalDependencies) + + + + + {0ef5ce78-8e92-4a1b-a255-0f544aada291} + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/diskspd_vs/Common/Common.vcxproj b/diskspd_vs/Common/Common.vcxproj index ffe6406..f6c71af 100644 --- a/diskspd_vs/Common/Common.vcxproj +++ b/diskspd_vs/Common/Common.vcxproj @@ -1,140 +1,136 @@ - - - - - Debug - Win32 - - - Debug - x64 - - - Release - Win32 - - - Release - x64 - - - - {B253AB42-F482-417A-82CE-EDAFCD26F366} - Common - 10.0 - - - - StaticLibrary - true - MultiByte - v142 - - - StaticLibrary - true - MultiByte - v142 - - - StaticLibrary - false - true - MultiByte - v142 - - - StaticLibrary - false - true - MultiByte - v142 - - - - - - - - - - - - - - - - - - - - - Level3 - Disabled - true - false - true - MultiThreadedDebug - - - true - - - - - Level3 - Disabled - true - false - true - MultiThreadedDebug - - - true - - - - - Level3 - MaxSpeed - true - true - true - true - MultiThreaded - - - true - true - true - - - - - Level3 - MaxSpeed - true - true - true - true - MultiThreaded - - - true - true - true - - - - - - - - - - - - - - + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {B253AB42-F482-417A-82CE-EDAFCD26F366} + Common + 10.0 + + + + StaticLibrary + true + MultiByte + v143 + + + StaticLibrary + true + MultiByte + v143 + + + StaticLibrary + false + true + MultiByte + v143 + + + StaticLibrary + false + true + MultiByte + v143 + + + + + + + + + + + + + + + + + + + + + Level3 + Disabled + true + false + true + + + true + + + + + Level3 + Disabled + true + false + true + + + true + + + + + Level3 + MaxSpeed + true + true + true + true + + + true + true + true + + + + + Level3 + MaxSpeed + true + true + true + true + + + true + true + true + + + + + + + + + + + + + + \ No newline at end of file diff --git a/diskspd_vs/IORequestGenerator/IORequestGenerator.vcxproj b/diskspd_vs/IORequestGenerator/IORequestGenerator.vcxproj index aedc0c5..1d8e470 100644 --- a/diskspd_vs/IORequestGenerator/IORequestGenerator.vcxproj +++ b/diskspd_vs/IORequestGenerator/IORequestGenerator.vcxproj @@ -1,154 +1,150 @@ - - - - - Debug - Win32 - - - Debug - x64 - - - Release - Win32 - - - Release - x64 - - - - {62DB1E99-FBA0-45FD-9355-423059BA03B8} - IORequestGenerator - 10.0 - - - - StaticLibrary - true - MultiByte - v142 - - - StaticLibrary - true - MultiByte - v142 - - - StaticLibrary - false - true - MultiByte - v142 - - - StaticLibrary - false - true - MultiByte - v142 - - - - - - - - - - - - - - - - - - - $(SolutionDir)\..\Common;$(IncludePath) - - - $(SolutionDir)\..\Common;$(IncludePath) - - - $(SolutionDir)\..\Common;$(IncludePath) - - - $(SolutionDir)\..\Common;$(IncludePath) - - - - Level3 - Disabled - true - false - true - MultiThreadedDebug - - - true - - - - - Level3 - Disabled - true - false - true - MultiThreadedDebug - - - true - - - - - Level3 - MaxSpeed - true - true - true - true - MultiThreaded - - - true - true - true - - - - - Level3 - MaxSpeed - true - true - true - true - MultiThreaded - - - true - true - true - - - - - - - - - - - - - - - - - + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {62DB1E99-FBA0-45FD-9355-423059BA03B8} + IORequestGenerator + 10.0 + + + + StaticLibrary + true + MultiByte + v143 + + + StaticLibrary + true + MultiByte + v143 + + + StaticLibrary + false + true + MultiByte + v143 + + + StaticLibrary + false + true + MultiByte + v143 + + + + + + + + + + + + + + + + + + + $(SolutionDir)\..\Common;$(IncludePath) + + + $(SolutionDir)\..\Common;$(IncludePath) + + + $(SolutionDir)\..\Common;$(IncludePath) + + + $(SolutionDir)\..\Common;$(IncludePath) + + + + Level3 + Disabled + true + false + true + + + true + + + + + Level3 + Disabled + true + false + true + + + true + + + + + Level3 + MaxSpeed + true + true + true + true + + + true + true + true + + + + + Level3 + MaxSpeed + true + true + true + true + + + true + true + true + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/diskspd_vs/ResultParser/ResultParser.vcxproj b/diskspd_vs/ResultParser/ResultParser.vcxproj index b61f02e..6bb1857 100644 --- a/diskspd_vs/ResultParser/ResultParser.vcxproj +++ b/diskspd_vs/ResultParser/ResultParser.vcxproj @@ -1,148 +1,144 @@ - - - - - Debug - Win32 - - - Debug - x64 - - - Release - Win32 - - - Release - x64 - - - - {F6C211DC-B076-4716-BCDC-D7DE88973B66} - ResultParser - 10.0 - - - - StaticLibrary - true - MultiByte - v142 - - - StaticLibrary - true - MultiByte - v142 - - - StaticLibrary - false - true - MultiByte - v142 - - - StaticLibrary - false - true - MultiByte - v142 - - - - - - - - - - - - - - - - - - - $(SolutionDir)\..\Common;$(IncludePath) - - - $(SolutionDir)\..\Common;$(IncludePath) - - - $(SolutionDir)\..\Common;$(IncludePath) - - - $(SolutionDir)\..\Common;$(IncludePath) - - - - Level3 - Disabled - true - false - true - MultiThreadedDebug - - - true - - - - - Level3 - Disabled - true - false - true - MultiThreadedDebug - - - true - - - - - Level3 - MaxSpeed - true - true - true - true - MultiThreaded - - - true - true - true - - - - - Level3 - MaxSpeed - true - true - true - true - MultiThreaded - - - true - true - true - - - - - - - - - - - + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {F6C211DC-B076-4716-BCDC-D7DE88973B66} + ResultParser + 10.0 + + + + StaticLibrary + true + MultiByte + v143 + + + StaticLibrary + true + MultiByte + v143 + + + StaticLibrary + false + true + MultiByte + v143 + + + StaticLibrary + false + true + MultiByte + v143 + + + + + + + + + + + + + + + + + + + $(SolutionDir)\..\Common;$(IncludePath) + + + $(SolutionDir)\..\Common;$(IncludePath) + + + $(SolutionDir)\..\Common;$(IncludePath) + + + $(SolutionDir)\..\Common;$(IncludePath) + + + + Level3 + Disabled + true + false + true + + + true + + + + + Level3 + Disabled + true + false + true + + + true + + + + + Level3 + MaxSpeed + true + true + true + true + + + true + true + true + + + + + Level3 + MaxSpeed + true + true + true + true + + + true + true + true + + + + + + + + + + + \ No newline at end of file diff --git a/diskspd_vs/UnitTests/CmdLineParser/CmdLineParser.vcxproj b/diskspd_vs/UnitTests/CmdLineParser/CmdLineParser.vcxproj index 2b39861..50b4bb5 100644 --- a/diskspd_vs/UnitTests/CmdLineParser/CmdLineParser.vcxproj +++ b/diskspd_vs/UnitTests/CmdLineParser/CmdLineParser.vcxproj @@ -1,174 +1,174 @@ - - - - - Debug - Win32 - - - Release - Win32 - - - Debug - x64 - - - Release - x64 - - - - - - - - - - - - - - 15.0 - {54186266-8BA1-438C-AE76-AD64503CA6E9} - Win32Proj - CmdLineParser - 10.0 - - - - DynamicLibrary - true - v142 - Unicode - - - DynamicLibrary - false - v142 - true - Unicode - - - DynamicLibrary - true - v142 - Unicode - - - DynamicLibrary - false - v142 - true - Unicode - - - - - - - - - - - - - - - - - - - - - true - $(ProjectName).UnitTests - $(SolutionDir)..\Common;$(WindowsSDKDir)\Testing\Development\inc;$(IncludePath) - $(WindowsSDKDir)\Testing\Development\lib\$(PlatformTarget);$(LibraryPath) - - - true - $(ProjectName).UnitTests - $(SolutionDir)..\Common;$(WindowsSDKDir)\Testing\Development\inc;$(IncludePath) - $(WindowsSDKDir)\Testing\Development\lib\$(PlatformTarget);$(LibraryPath) - - - false - $(ProjectName).UnitTests - $(SolutionDir)..\Common;$(WindowsSDKDir)\Testing\Development\inc;$(IncludePath) - $(WindowsSDKDir)\Testing\Development\lib\$(PlatformTarget);$(LibraryPath) - - - false - $(ProjectName).UnitTests - $(SolutionDir)..\Common;$(WindowsSDKDir)\Testing\Development\inc;$(IncludePath) - $(WindowsSDKDir)\Testing\Development\lib\$(PlatformTarget);$(LibraryPath) - - - - Level3 - Disabled - true - false - - - Windows - true - Common.lib;CmdLineParser.lib;XmlProfileParser.lib;%(AdditionalDependencies) - $(OutDir);%(AdditionalLibraryDirectories) - - - - - Level3 - Disabled - true - false - - - Windows - true - Common.lib;CmdLineParser.lib;XmlProfileParser.lib;%(AdditionalDependencies) - $(OutDir);%(AdditionalLibraryDirectories) - - - - - Level3 - MaxSpeed - true - true - true - false - - - Windows - true - true - true - Common.lib;CmdLineParser.lib;XmlProfileParser.lib;%(AdditionalDependencies) - $(OutDir);%(AdditionalLibraryDirectories) - - - - - Level3 - MaxSpeed - true - true - true - false - - - Windows - true - true - true - Common.lib;CmdLineParser.lib;XmlProfileParser.lib;%(AdditionalDependencies) - $(OutDir);%(AdditionalLibraryDirectories) - - - - - + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + + + + + + + + + + + 15.0 + {54186266-8BA1-438C-AE76-AD64503CA6E9} + Win32Proj + CmdLineParser + 10.0 + + + + DynamicLibrary + true + v143 + Unicode + + + DynamicLibrary + false + v143 + true + Unicode + + + DynamicLibrary + true + v143 + Unicode + + + DynamicLibrary + false + v143 + true + Unicode + + + + + + + + + + + + + + + + + + + + + true + $(ProjectName).UnitTests + $(SolutionDir)..\Common;$(WindowsSDKDir)\Testing\Development\inc;$(IncludePath) + $(WindowsSDKDir)\Testing\Development\lib\$(PlatformTarget);$(LibraryPath) + + + true + $(ProjectName).UnitTests + $(SolutionDir)..\Common;$(WindowsSDKDir)\Testing\Development\inc;$(IncludePath) + $(WindowsSDKDir)\Testing\Development\lib\$(PlatformTarget);$(LibraryPath) + + + false + $(ProjectName).UnitTests + $(SolutionDir)..\Common;$(WindowsSDKDir)\Testing\Development\inc;$(IncludePath) + $(WindowsSDKDir)\Testing\Development\lib\$(PlatformTarget);$(LibraryPath) + + + false + $(ProjectName).UnitTests + $(SolutionDir)..\Common;$(WindowsSDKDir)\Testing\Development\inc;$(IncludePath) + $(WindowsSDKDir)\Testing\Development\lib\$(PlatformTarget);$(LibraryPath) + + + + Level3 + Disabled + true + false + + + Windows + true + Common.lib;CmdLineParser.lib;XmlProfileParser.lib;powrprof.lib;%(AdditionalDependencies) + $(OutDir);%(AdditionalLibraryDirectories) + + + + + Level3 + Disabled + true + false + + + Windows + true + Common.lib;CmdLineParser.lib;XmlProfileParser.lib;powrprof.lib;%(AdditionalDependencies) + $(OutDir);%(AdditionalLibraryDirectories) + + + + + Level3 + MaxSpeed + true + true + true + false + + + Windows + true + true + true + Common.lib;CmdLineParser.lib;XmlProfileParser.lib;powrprof.lib;%(AdditionalDependencies) + $(OutDir);%(AdditionalLibraryDirectories) + + + + + Level3 + MaxSpeed + true + true + true + false + + + Windows + true + true + true + Common.lib;CmdLineParser.lib;XmlProfileParser.lib;powrprof.lib;%(AdditionalDependencies) + $(OutDir);%(AdditionalLibraryDirectories) + + + + + \ No newline at end of file diff --git a/diskspd_vs/UnitTests/Common/Common.vcxproj b/diskspd_vs/UnitTests/Common/Common.vcxproj index 67466e6..ee19fd6 100644 --- a/diskspd_vs/UnitTests/Common/Common.vcxproj +++ b/diskspd_vs/UnitTests/Common/Common.vcxproj @@ -1,174 +1,174 @@ - - - - - Debug - Win32 - - - Release - Win32 - - - Debug - x64 - - - Release - x64 - - - - - - - - - - - - - - 15.0 - {BA9F561C-B103-48C9-A7C8-CE2B6BD89511} - Win32Proj - Common - 10.0 - - - - DynamicLibrary - true - v142 - Unicode - - - DynamicLibrary - false - v142 - true - Unicode - - - DynamicLibrary - true - v142 - Unicode - - - DynamicLibrary - false - v142 - true - Unicode - - - - - - - - - - - - - - - - - - - - - true - $(ProjectName).UnitTests - $(SolutionDir)..\Common;$(WindowsSDKDir)\Testing\Development\inc;$(IncludePath) - $(WindowsSDKDir)\Testing\Development\lib\$(PlatformTarget);$(LibraryPath) - - - true - $(ProjectName).UnitTests - $(SolutionDir)..\Common;$(WindowsSDKDir)\Testing\Development\inc;$(IncludePath) - $(WindowsSDKDir)\Testing\Development\lib\$(PlatformTarget);$(LibraryPath) - - - false - $(ProjectName).UnitTests - $(SolutionDir)..\Common;$(WindowsSDKDir)\Testing\Development\inc;$(IncludePath) - $(WindowsSDKDir)\Testing\Development\lib\$(PlatformTarget);$(LibraryPath) - - - false - $(ProjectName).UnitTests - $(SolutionDir)..\Common;$(WindowsSDKDir)\Testing\Development\inc;$(IncludePath) - $(WindowsSDKDir)\Testing\Development\lib\$(PlatformTarget);$(LibraryPath) - - - - Level3 - Disabled - true - false - - - Windows - true - $(OutDir);%(AdditionalLibraryDirectories) - Common.lib;%(AdditionalDependencies) - - - - - Level3 - Disabled - true - false - - - Windows - true - $(OutDir);%(AdditionalLibraryDirectories) - Common.lib;%(AdditionalDependencies) - - - - - Level3 - MaxSpeed - true - true - true - false - - - Windows - true - true - true - $(OutDir);%(AdditionalLibraryDirectories) - Common.lib;%(AdditionalDependencies) - - - - - Level3 - MaxSpeed - true - true - true - false - - - Windows - true - true - true - $(OutDir);%(AdditionalLibraryDirectories) - Common.lib;%(AdditionalDependencies) - - - - - + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + + + + + + + + + + + 15.0 + {BA9F561C-B103-48C9-A7C8-CE2B6BD89511} + Win32Proj + Common + 10.0 + + + + DynamicLibrary + true + v143 + Unicode + + + DynamicLibrary + false + v143 + true + Unicode + + + DynamicLibrary + true + v143 + Unicode + + + DynamicLibrary + false + v143 + true + Unicode + + + + + + + + + + + + + + + + + + + + + true + $(ProjectName).UnitTests + $(SolutionDir)..\Common;$(WindowsSDKDir)\Testing\Development\inc;$(IncludePath) + $(WindowsSDKDir)\Testing\Development\lib\$(PlatformTarget);$(LibraryPath) + + + true + $(ProjectName).UnitTests + $(SolutionDir)..\Common;$(WindowsSDKDir)\Testing\Development\inc;$(IncludePath) + $(WindowsSDKDir)\Testing\Development\lib\$(PlatformTarget);$(LibraryPath) + + + false + $(ProjectName).UnitTests + $(SolutionDir)..\Common;$(WindowsSDKDir)\Testing\Development\inc;$(IncludePath) + $(WindowsSDKDir)\Testing\Development\lib\$(PlatformTarget);$(LibraryPath) + + + false + $(ProjectName).UnitTests + $(SolutionDir)..\Common;$(WindowsSDKDir)\Testing\Development\inc;$(IncludePath) + $(WindowsSDKDir)\Testing\Development\lib\$(PlatformTarget);$(LibraryPath) + + + + Level3 + Disabled + true + false + + + Windows + true + $(OutDir);%(AdditionalLibraryDirectories) + Common.lib;powrprof.lib;%(AdditionalDependencies) + + + + + Level3 + Disabled + true + false + + + Windows + true + $(OutDir);%(AdditionalLibraryDirectories) + Common.lib;powrprof.lib;%(AdditionalDependencies) + + + + + Level3 + MaxSpeed + true + true + true + false + + + Windows + true + true + true + $(OutDir);%(AdditionalLibraryDirectories) + Common.lib;powrprof.lib;%(AdditionalDependencies) + + + + + Level3 + MaxSpeed + true + true + true + false + + + Windows + true + true + true + $(OutDir);%(AdditionalLibraryDirectories) + Common.lib;powrprof.lib;%(AdditionalDependencies) + + + + + \ No newline at end of file diff --git a/diskspd_vs/UnitTests/IORequestGenerator/IORequestGenerator.vcxproj b/diskspd_vs/UnitTests/IORequestGenerator/IORequestGenerator.vcxproj index 922078d..1e4561b 100644 --- a/diskspd_vs/UnitTests/IORequestGenerator/IORequestGenerator.vcxproj +++ b/diskspd_vs/UnitTests/IORequestGenerator/IORequestGenerator.vcxproj @@ -1,174 +1,174 @@ - - - - - Debug - Win32 - - - Release - Win32 - - - Debug - x64 - - - Release - x64 - - - - - - - - - - - - - - 15.0 - {13683A8B-2641-4287-9D66-A87834885057} - Win32Proj - IORequestGenerator - 10.0 - - - - DynamicLibrary - true - v142 - Unicode - - - DynamicLibrary - false - v142 - true - Unicode - - - DynamicLibrary - true - v142 - Unicode - - - DynamicLibrary - false - v142 - true - Unicode - - - - - - - - - - - - - - - - - - - - - true - $(ProjectName).UnitTests - $(SolutionDir)..\Common;$(WindowsSDKDir)\Testing\Development\inc;$(IncludePath) - $(WindowsSDKDir)\Testing\Development\lib\$(PlatformTarget);$(LibraryPath) - - - true - $(ProjectName).UnitTests - $(SolutionDir)..\Common;$(WindowsSDKDir)\Testing\Development\inc;$(IncludePath) - $(WindowsSDKDir)\Testing\Development\lib\$(PlatformTarget);$(LibraryPath) - - - false - $(ProjectName).UnitTests - $(SolutionDir)..\Common;$(WindowsSDKDir)\Testing\Development\inc;$(IncludePath) - $(WindowsSDKDir)\Testing\Development\lib\$(PlatformTarget);$(LibraryPath) - - - false - $(ProjectName).UnitTests - $(SolutionDir)..\Common;$(WindowsSDKDir)\Testing\Development\inc;$(IncludePath) - $(WindowsSDKDir)\Testing\Development\lib\$(PlatformTarget);$(LibraryPath) - - - - Level3 - Disabled - true - false - - - Windows - true - $(OutDir);%(AdditionalLibraryDirectories) - Common.lib;IORequestGenerator.lib;%(AdditionalDependencies) - - - - - Level3 - Disabled - true - false - - - Windows - true - $(OutDir);%(AdditionalLibraryDirectories) - Common.lib;IORequestGenerator.lib;%(AdditionalDependencies) - - - - - Level3 - MaxSpeed - true - true - true - false - - - Windows - true - true - true - $(OutDir);%(AdditionalLibraryDirectories) - Common.lib;IORequestGenerator.lib;%(AdditionalDependencies) - - - - - Level3 - MaxSpeed - true - true - true - false - - - Windows - true - true - true - $(OutDir);%(AdditionalLibraryDirectories) - Common.lib;IORequestGenerator.lib;%(AdditionalDependencies) - - - - - + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + + + + + + + + + + + 15.0 + {13683A8B-2641-4287-9D66-A87834885057} + Win32Proj + IORequestGenerator + 10.0 + + + + DynamicLibrary + true + v143 + Unicode + + + DynamicLibrary + false + v143 + true + Unicode + + + DynamicLibrary + true + v143 + Unicode + + + DynamicLibrary + false + v143 + true + Unicode + + + + + + + + + + + + + + + + + + + + + true + $(ProjectName).UnitTests + $(SolutionDir)..\Common;$(WindowsSDKDir)\Testing\Development\inc;$(IncludePath) + $(WindowsSDKDir)\Testing\Development\lib\$(PlatformTarget);$(LibraryPath) + + + true + $(ProjectName).UnitTests + $(SolutionDir)..\Common;$(WindowsSDKDir)\Testing\Development\inc;$(IncludePath) + $(WindowsSDKDir)\Testing\Development\lib\$(PlatformTarget);$(LibraryPath) + + + false + $(ProjectName).UnitTests + $(SolutionDir)..\Common;$(WindowsSDKDir)\Testing\Development\inc;$(IncludePath) + $(WindowsSDKDir)\Testing\Development\lib\$(PlatformTarget);$(LibraryPath) + + + false + $(ProjectName).UnitTests + $(SolutionDir)..\Common;$(WindowsSDKDir)\Testing\Development\inc;$(IncludePath) + $(WindowsSDKDir)\Testing\Development\lib\$(PlatformTarget);$(LibraryPath) + + + + Level3 + Disabled + true + false + + + Windows + true + $(OutDir);%(AdditionalLibraryDirectories) + Common.lib;IORequestGenerator.lib;powrprof.lib;%(AdditionalDependencies) + + + + + Level3 + Disabled + true + false + + + Windows + true + $(OutDir);%(AdditionalLibraryDirectories) + Common.lib;IORequestGenerator.lib;powrprof.lib;%(AdditionalDependencies) + + + + + Level3 + MaxSpeed + true + true + true + false + + + Windows + true + true + true + $(OutDir);%(AdditionalLibraryDirectories) + Common.lib;IORequestGenerator.lib;powrprof.lib;%(AdditionalDependencies) + + + + + Level3 + MaxSpeed + true + true + true + false + + + Windows + true + true + true + $(OutDir);%(AdditionalLibraryDirectories) + Common.lib;IORequestGenerator.lib;powrprof.lib;%(AdditionalDependencies) + + + + + \ No newline at end of file diff --git a/diskspd_vs/UnitTests/ResultParser/ResultParser.vcxproj b/diskspd_vs/UnitTests/ResultParser/ResultParser.vcxproj index e17a7bd..dd4cf87 100644 --- a/diskspd_vs/UnitTests/ResultParser/ResultParser.vcxproj +++ b/diskspd_vs/UnitTests/ResultParser/ResultParser.vcxproj @@ -1,174 +1,174 @@ - - - - - Debug - Win32 - - - Release - Win32 - - - Debug - x64 - - - Release - x64 - - - - - - - - - - - - - - 15.0 - {471E64C7-2C65-4E16-A82D-4BF22AE690DD} - Win32Proj - ResultParser - 10.0 - - - - DynamicLibrary - true - v142 - Unicode - - - DynamicLibrary - false - v142 - true - Unicode - - - DynamicLibrary - true - v142 - Unicode - - - DynamicLibrary - false - v142 - true - Unicode - - - - - - - - - - - - - - - - - - - - - true - $(ProjectName).UnitTests - $(SolutionDir)..\Common;$(WindowsSDKDir)\Testing\Development\inc;$(IncludePath) - $(WindowsSDKDir)\Testing\Development\lib\$(PlatformTarget);$(LibraryPath) - - - true - $(ProjectName).UnitTests - $(SolutionDir)..\Common;$(WindowsSDKDir)\Testing\Development\inc;$(IncludePath) - $(WindowsSDKDir)\Testing\Development\lib\$(PlatformTarget);$(LibraryPath) - - - false - $(ProjectName).UnitTests - $(SolutionDir)..\Common;$(WindowsSDKDir)\Testing\Development\inc;$(IncludePath) - $(WindowsSDKDir)\Testing\Development\lib\$(PlatformTarget);$(LibraryPath) - - - false - $(ProjectName).UnitTests - $(SolutionDir)..\Common;$(WindowsSDKDir)\Testing\Development\inc;$(IncludePath) - $(WindowsSDKDir)\Testing\Development\lib\$(PlatformTarget);$(LibraryPath) - - - - Level3 - Disabled - true - false - - - Windows - true - $(OutDir);%(AdditionalLibraryDirectories) - Common.lib;ResultParser.lib;%(AdditionalDependencies) - - - - - Level3 - Disabled - true - false - - - Windows - true - $(OutDir);%(AdditionalLibraryDirectories) - Common.lib;ResultParser.lib;%(AdditionalDependencies) - - - - - Level3 - MaxSpeed - true - true - true - false - - - Windows - true - true - true - $(OutDir);%(AdditionalLibraryDirectories) - Common.lib;ResultParser.lib;%(AdditionalDependencies) - - - - - Level3 - MaxSpeed - true - true - true - false - - - Windows - true - true - true - $(OutDir);%(AdditionalLibraryDirectories) - Common.lib;ResultParser.lib;%(AdditionalDependencies) - - - - - + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + + + + + + + + + + + 15.0 + {471E64C7-2C65-4E16-A82D-4BF22AE690DD} + Win32Proj + ResultParser + 10.0 + + + + DynamicLibrary + true + v143 + Unicode + + + DynamicLibrary + false + v143 + true + Unicode + + + DynamicLibrary + true + v143 + Unicode + + + DynamicLibrary + false + v143 + true + Unicode + + + + + + + + + + + + + + + + + + + + + true + $(ProjectName).UnitTests + $(SolutionDir)..\Common;$(WindowsSDKDir)\Testing\Development\inc;$(IncludePath) + $(WindowsSDKDir)\Testing\Development\lib\$(PlatformTarget);$(LibraryPath) + + + true + $(ProjectName).UnitTests + $(SolutionDir)..\Common;$(WindowsSDKDir)\Testing\Development\inc;$(IncludePath) + $(WindowsSDKDir)\Testing\Development\lib\$(PlatformTarget);$(LibraryPath) + + + false + $(ProjectName).UnitTests + $(SolutionDir)..\Common;$(WindowsSDKDir)\Testing\Development\inc;$(IncludePath) + $(WindowsSDKDir)\Testing\Development\lib\$(PlatformTarget);$(LibraryPath) + + + false + $(ProjectName).UnitTests + $(SolutionDir)..\Common;$(WindowsSDKDir)\Testing\Development\inc;$(IncludePath) + $(WindowsSDKDir)\Testing\Development\lib\$(PlatformTarget);$(LibraryPath) + + + + Level3 + Disabled + true + false + + + Windows + true + $(OutDir);%(AdditionalLibraryDirectories) + Common.lib;ResultParser.lib;powrprof.lib;%(AdditionalDependencies) + + + + + Level3 + Disabled + true + false + + + Windows + true + $(OutDir);%(AdditionalLibraryDirectories) + Common.lib;ResultParser.lib;powrprof.lib;%(AdditionalDependencies) + + + + + Level3 + MaxSpeed + true + true + true + false + + + Windows + true + true + true + $(OutDir);%(AdditionalLibraryDirectories) + Common.lib;ResultParser.lib;powrprof.lib;%(AdditionalDependencies) + + + + + Level3 + MaxSpeed + true + true + true + false + + + Windows + true + true + true + $(OutDir);%(AdditionalLibraryDirectories) + Common.lib;ResultParser.lib;powrprof.lib;%(AdditionalDependencies) + + + + + \ No newline at end of file diff --git a/diskspd_vs/UnitTests/XmlProfileParser/XmlProfileParser.vcxproj b/diskspd_vs/UnitTests/XmlProfileParser/XmlProfileParser.vcxproj index 27253ef..0636935 100644 --- a/diskspd_vs/UnitTests/XmlProfileParser/XmlProfileParser.vcxproj +++ b/diskspd_vs/UnitTests/XmlProfileParser/XmlProfileParser.vcxproj @@ -1,174 +1,174 @@ - - - - - Debug - Win32 - - - Release - Win32 - - - Debug - x64 - - - Release - x64 - - - - - - - - - - - - - - 15.0 - {B20AA8CF-ADFB-487C-B8F9-DBD3037F53AD} - Win32Proj - XmlProfileParser - 10.0 - - - - DynamicLibrary - true - v142 - Unicode - - - DynamicLibrary - false - v142 - true - Unicode - - - DynamicLibrary - true - v142 - Unicode - - - DynamicLibrary - false - v142 - true - Unicode - - - - - - - - - - - - - - - - - - - - - true - $(ProjectName).UnitTests - $(SolutionDir)..\Common;$(WindowsSDKDir)\Testing\Development\inc;$(IncludePath) - $(WindowsSDKDir)\Testing\Development\lib\$(PlatformTarget);$(LibraryPath) - - - true - $(ProjectName).UnitTests - $(SolutionDir)..\Common;$(WindowsSDKDir)\Testing\Development\inc;$(IncludePath) - $(WindowsSDKDir)\Testing\Development\lib\$(PlatformTarget);$(LibraryPath) - - - false - $(ProjectName).UnitTests - $(SolutionDir)..\Common;$(WindowsSDKDir)\Testing\Development\inc;$(IncludePath) - $(WindowsSDKDir)\Testing\Development\lib\$(PlatformTarget);$(LibraryPath) - - - false - $(ProjectName).UnitTests - $(SolutionDir)..\Common;$(WindowsSDKDir)\Testing\Development\inc;$(IncludePath) - $(WindowsSDKDir)\Testing\Development\lib\$(PlatformTarget);$(LibraryPath) - - - - Level3 - Disabled - true - false - - - Windows - true - $(OutDir);%(AdditionalLibraryDirectories) - Common.lib;XmlProfileParser.lib;%(AdditionalDependencies) - - - - - Level3 - Disabled - true - false - - - Windows - true - $(OutDir);%(AdditionalLibraryDirectories) - Common.lib;XmlProfileParser.lib;%(AdditionalDependencies) - - - - - Level3 - MaxSpeed - true - true - true - false - - - Windows - true - true - true - $(OutDir);%(AdditionalLibraryDirectories) - Common.lib;XmlProfileParser.lib;%(AdditionalDependencies) - - - - - Level3 - MaxSpeed - true - true - true - false - - - Windows - true - true - true - $(OutDir);%(AdditionalLibraryDirectories) - Common.lib;XmlProfileParser.lib;%(AdditionalDependencies) - - - - - + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + + + + + + + + + + + 15.0 + {B20AA8CF-ADFB-487C-B8F9-DBD3037F53AD} + Win32Proj + XmlProfileParser + 10.0 + + + + DynamicLibrary + true + v143 + Unicode + + + DynamicLibrary + false + v143 + true + Unicode + + + DynamicLibrary + true + v143 + Unicode + + + DynamicLibrary + false + v143 + true + Unicode + + + + + + + + + + + + + + + + + + + + + true + $(ProjectName).UnitTests + $(SolutionDir)..\Common;$(WindowsSDKDir)\Testing\Development\inc;$(IncludePath) + $(WindowsSDKDir)\Testing\Development\lib\$(PlatformTarget);$(LibraryPath) + + + true + $(ProjectName).UnitTests + $(SolutionDir)..\Common;$(WindowsSDKDir)\Testing\Development\inc;$(IncludePath) + $(WindowsSDKDir)\Testing\Development\lib\$(PlatformTarget);$(LibraryPath) + + + false + $(ProjectName).UnitTests + $(SolutionDir)..\Common;$(WindowsSDKDir)\Testing\Development\inc;$(IncludePath) + $(WindowsSDKDir)\Testing\Development\lib\$(PlatformTarget);$(LibraryPath) + + + false + $(ProjectName).UnitTests + $(SolutionDir)..\Common;$(WindowsSDKDir)\Testing\Development\inc;$(IncludePath) + $(WindowsSDKDir)\Testing\Development\lib\$(PlatformTarget);$(LibraryPath) + + + + Level3 + Disabled + true + false + + + Windows + true + $(OutDir);%(AdditionalLibraryDirectories) + Common.lib;XmlProfileParser.lib;powrprof.lib;%(AdditionalDependencies) + + + + + Level3 + Disabled + true + false + + + Windows + true + $(OutDir);%(AdditionalLibraryDirectories) + Common.lib;XmlProfileParser.lib;powrprof.lib;%(AdditionalDependencies) + + + + + Level3 + MaxSpeed + true + true + true + false + + + Windows + true + true + true + $(OutDir);%(AdditionalLibraryDirectories) + Common.lib;XmlProfileParser.lib;powrprof.lib;%(AdditionalDependencies) + + + + + Level3 + MaxSpeed + true + true + true + false + + + Windows + true + true + true + $(OutDir);%(AdditionalLibraryDirectories) + Common.lib;XmlProfileParser.lib;powrprof.lib;%(AdditionalDependencies) + + + + + \ No newline at end of file diff --git a/diskspd_vs/UnitTests/XmlResultParser/XmlResultParser.vcxproj b/diskspd_vs/UnitTests/XmlResultParser/XmlResultParser.vcxproj index 84991a4..40eed28 100644 --- a/diskspd_vs/UnitTests/XmlResultParser/XmlResultParser.vcxproj +++ b/diskspd_vs/UnitTests/XmlResultParser/XmlResultParser.vcxproj @@ -1,174 +1,174 @@ - - - - - Debug - Win32 - - - Release - Win32 - - - Debug - x64 - - - Release - x64 - - - - - - - - - - - - - - 15.0 - {D52F964B-C636-4DCC-AA7E-EE83A7AC41AD} - Win32Proj - XmlResultParser - 10.0 - - - - DynamicLibrary - true - v142 - Unicode - - - DynamicLibrary - false - v142 - true - Unicode - - - DynamicLibrary - true - v142 - Unicode - - - DynamicLibrary - false - v142 - true - Unicode - - - - - - - - - - - - - - - - - - - - - true - $(ProjectName).UnitTests - $(SolutionDir)..\Common;$(WindowsSDKDir)\Testing\Development\inc;$(IncludePath) - $(WindowsSDKDir)\Testing\Development\lib\$(PlatformTarget);$(LibraryPath) - - - true - $(ProjectName).UnitTests - $(SolutionDir)..\Common;$(WindowsSDKDir)\Testing\Development\inc;$(IncludePath) - $(WindowsSDKDir)\Testing\Development\lib\$(PlatformTarget);$(LibraryPath) - - - false - $(ProjectName).UnitTests - $(SolutionDir)..\Common;$(WindowsSDKDir)\Testing\Development\inc;$(IncludePath) - $(WindowsSDKDir)\Testing\Development\lib\$(PlatformTarget);$(LibraryPath) - - - false - $(ProjectName).UnitTests - $(SolutionDir)..\Common;$(WindowsSDKDir)\Testing\Development\inc;$(IncludePath) - $(WindowsSDKDir)\Testing\Development\lib\$(PlatformTarget);$(LibraryPath) - - - - Level3 - Disabled - true - false - - - Windows - true - $(OutDir);%(AdditionalLibraryDirectories) - Common.lib;XmlResultParser.lib;%(AdditionalDependencies) - - - - - Level3 - Disabled - true - false - - - Windows - true - $(OutDir);%(AdditionalLibraryDirectories) - Common.lib;XmlResultParser.lib;%(AdditionalDependencies) - - - - - Level3 - MaxSpeed - true - true - true - false - - - Windows - true - true - true - $(OutDir);%(AdditionalLibraryDirectories) - Common.lib;XmlResultParser.lib;%(AdditionalDependencies) - - - - - Level3 - MaxSpeed - true - true - true - false - - - Windows - true - true - true - $(OutDir);%(AdditionalLibraryDirectories) - Common.lib;XmlResultParser.lib;%(AdditionalDependencies) - - - - - + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + + + + + + + + + + + 15.0 + {D52F964B-C636-4DCC-AA7E-EE83A7AC41AD} + Win32Proj + XmlResultParser + 10.0 + + + + DynamicLibrary + true + v143 + Unicode + + + DynamicLibrary + false + v143 + true + Unicode + + + DynamicLibrary + true + v143 + Unicode + + + DynamicLibrary + false + v143 + true + Unicode + + + + + + + + + + + + + + + + + + + + + true + $(ProjectName).UnitTests + $(SolutionDir)..\Common;$(WindowsSDKDir)\Testing\Development\inc;$(IncludePath) + $(WindowsSDKDir)\Testing\Development\lib\$(PlatformTarget);$(LibraryPath) + + + true + $(ProjectName).UnitTests + $(SolutionDir)..\Common;$(WindowsSDKDir)\Testing\Development\inc;$(IncludePath) + $(WindowsSDKDir)\Testing\Development\lib\$(PlatformTarget);$(LibraryPath) + + + false + $(ProjectName).UnitTests + $(SolutionDir)..\Common;$(WindowsSDKDir)\Testing\Development\inc;$(IncludePath) + $(WindowsSDKDir)\Testing\Development\lib\$(PlatformTarget);$(LibraryPath) + + + false + $(ProjectName).UnitTests + $(SolutionDir)..\Common;$(WindowsSDKDir)\Testing\Development\inc;$(IncludePath) + $(WindowsSDKDir)\Testing\Development\lib\$(PlatformTarget);$(LibraryPath) + + + + Level3 + Disabled + true + false + + + Windows + true + $(OutDir);%(AdditionalLibraryDirectories) + Common.lib;XmlResultParser.lib;powrprof.lib;%(AdditionalDependencies) + + + + + Level3 + Disabled + true + false + + + Windows + true + $(OutDir);%(AdditionalLibraryDirectories) + Common.lib;XmlResultParser.lib;powrprof.lib;%(AdditionalDependencies) + + + + + Level3 + MaxSpeed + true + true + true + false + + + Windows + true + true + true + $(OutDir);%(AdditionalLibraryDirectories) + Common.lib;XmlResultParser.lib;powrprof.lib;%(AdditionalDependencies) + + + + + Level3 + MaxSpeed + true + true + true + false + + + Windows + true + true + true + $(OutDir);%(AdditionalLibraryDirectories) + Common.lib;XmlResultParser.lib;powrprof.lib;%(AdditionalDependencies) + + + + + \ No newline at end of file diff --git a/diskspd_vs/XmlProfileParser/XmlProfileParser.vcxproj b/diskspd_vs/XmlProfileParser/XmlProfileParser.vcxproj index fefc2b1..dd30503 100644 --- a/diskspd_vs/XmlProfileParser/XmlProfileParser.vcxproj +++ b/diskspd_vs/XmlProfileParser/XmlProfileParser.vcxproj @@ -1,151 +1,147 @@ - - - - - Debug - Win32 - - - Debug - x64 - - - Release - Win32 - - - Release - x64 - - - - {EFF06674-B068-45F1-9661-DB9363B025B3} - XmlProfileParser - 10.0 - - - - StaticLibrary - true - Unicode - v142 - - - StaticLibrary - true - Unicode - v142 - - - StaticLibrary - false - true - Unicode - v142 - - - StaticLibrary - false - true - Unicode - v142 - - - - - - - - - - - - - - - - - - - $(SolutionDir)\..\Common;$(IncludePath) - - - $(SolutionDir)\..\Common;$(IncludePath) - - - $(SolutionDir)\..\Common;$(IncludePath) - - - $(SolutionDir)\..\Common;$(IncludePath) - - - - Level3 - Disabled - true - false - true - MultiThreadedDebug - - - true - - - - - Level3 - Disabled - true - false - true - MultiThreadedDebug - - - true - - - - - Level3 - MaxSpeed - true - true - true - true - MultiThreaded - - - true - true - true - - - - - Level3 - MaxSpeed - true - true - true - true - MultiThreaded - - - true - true - true - - - - - - - - - - - - - - + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {EFF06674-B068-45F1-9661-DB9363B025B3} + XmlProfileParser + 10.0 + + + + StaticLibrary + true + Unicode + v143 + + + StaticLibrary + true + Unicode + v143 + + + StaticLibrary + false + true + Unicode + v143 + + + StaticLibrary + false + true + Unicode + v143 + + + + + + + + + + + + + + + + + + + $(SolutionDir)\..\Common;$(IncludePath) + + + $(SolutionDir)\..\Common;$(IncludePath) + + + $(SolutionDir)\..\Common;$(IncludePath) + + + $(SolutionDir)\..\Common;$(IncludePath) + + + + Level3 + Disabled + true + false + true + + + true + + + + + Level3 + Disabled + true + false + true + + + true + + + + + Level3 + MaxSpeed + true + true + true + true + + + true + true + true + + + + + Level3 + MaxSpeed + true + true + true + true + + + true + true + true + + + + + + + + + + + + + + \ No newline at end of file diff --git a/diskspd_vs/XmlResultParser/XmlResultParser.vcxproj b/diskspd_vs/XmlResultParser/XmlResultParser.vcxproj index d92ed6a..aa93ebc 100644 --- a/diskspd_vs/XmlResultParser/XmlResultParser.vcxproj +++ b/diskspd_vs/XmlResultParser/XmlResultParser.vcxproj @@ -1,165 +1,161 @@ - - - - - Debug - Win32 - - - Debug - x64 - - - Release - Win32 - - - Release - x64 - - - - {60A28E9C-C245-4D99-9C1C-EC911031743F} - Win32Proj - XmlResultParser - 10.0 - - - - StaticLibrary - true - MultiByte - v142 - - - StaticLibrary - true - MultiByte - v142 - - - StaticLibrary - false - true - MultiByte - v142 - - - StaticLibrary - false - true - MultiByte - v142 - - - - - - - - - - - - - - - - - - - $(SolutionDir)\..\Common;$(IncludePath) - - - $(SolutionDir)\..\Common;$(IncludePath) - - - $(SolutionDir)\..\Common;$(IncludePath) - - - $(SolutionDir)\..\Common;$(IncludePath) - - - - - - Level3 - Disabled - WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) - true - false - true - MultiThreadedDebug - - - Windows - true - - - - - - - Level3 - Disabled - WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) - true - false - true - MultiThreadedDebug - - - Windows - true - - - - - Level3 - - - MaxSpeed - true - true - WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) - true - true - MultiThreaded - - - Windows - true - true - true - - - - - Level3 - - - MaxSpeed - true - true - WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) - true - true - MultiThreaded - - - Windows - true - true - true - - - - - - - - - - - + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {60A28E9C-C245-4D99-9C1C-EC911031743F} + Win32Proj + XmlResultParser + 10.0 + + + + StaticLibrary + true + MultiByte + v143 + + + StaticLibrary + true + MultiByte + v143 + + + StaticLibrary + false + true + MultiByte + v143 + + + StaticLibrary + false + true + MultiByte + v143 + + + + + + + + + + + + + + + + + + + $(SolutionDir)\..\Common;$(IncludePath) + + + $(SolutionDir)\..\Common;$(IncludePath) + + + $(SolutionDir)\..\Common;$(IncludePath) + + + $(SolutionDir)\..\Common;$(IncludePath) + + + + + + Level3 + Disabled + WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) + true + false + true + + + Windows + true + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) + true + false + true + + + Windows + true + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) + true + true + + + Windows + true + true + true + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) + true + true + + + Windows + true + true + true + + + + + + + + + + + \ No newline at end of file