From 733042c81802c38e54ce20b582831a7032107771 Mon Sep 17 00:00:00 2001 From: Dan Lovinger Date: Mon, 20 Sep 2021 13:39:56 -0700 Subject: [PATCH] DISKSPD 2.1 (#152) DISKSPD 2.1 7/1/2021 * New `-gi` form allowing throughput limit specification in units of IOPS (per specified blocksize) * New `-rs` to specify mixed random/sequential operation (pct random); geometric distribution of run lengths * New `-rd` to specify non-uniform IO distributions across target * `pct` by target percentage * `abs` by absolute offset * New `-Rp` to show specified parameter set in indicated profile output form; works with -X XML profiles and conventional command line * XML results/profiles are now indented for ease of review * Text result output updates * now shows values in size units (K/M/G, and now TiB) to two decimals * thread stride no longer shown unless specified * -F/-O threadpool parameters shown * XML profiles can now be built more generically * XML profiles can be stated in terms of templated target names (*1, *2), replaced in order from command line invocation * the command line now allows options alongside -X: -v, -z, -R and -W/-d/-C along with template target specs Co-authored-by: Dan Lovinger --- CmdLineParser/CmdLineParser.cpp | 1188 ++++++++++++----- CmdRequestCreator/CmdRequestCreator.cpp | 50 +- Common/CmdLineParser.h | 16 +- Common/Common.cpp | 374 ++++-- Common/Common.h | 981 +++++++++++++- Common/IORequestGenerator.h | 7 +- Common/IoBucketizer.cpp | 1 + Common/ResultParser.h | 7 +- Common/XmlProfileParser.h | 13 +- Common/XmlResultParser.h | 7 +- IORequestGenerator/IORequestGenerator.cpp | 361 ++--- README.md | 17 + ResultParser/ResultParser.cpp | 389 +++++- .../CmdLineParser/CmdLineParser.UnitTests.cpp | 863 ++++++++++-- .../CmdLineParser/CmdLineParser.UnitTests.h | 75 +- UnitTests/Common/Common.UnitTests.cpp | 752 +++++------ .../IORequestGenerator.UnitTests.cpp | 807 +++++++++-- .../IORequestGenerator.UnitTests.h | 14 +- .../ResultParser/ResultParser.UnitTests.cpp | 727 ++++++++-- .../ResultParser/ResultParser.UnitTests.h | 5 + .../XmlProfileParser.UnitTests.cpp | 855 +++++++++++- .../XmlProfileParser.UnitTests.h | 60 +- .../XmlResultParser.UnitTests.cpp | 444 +++--- .../XmlResultParser.UnitTests.h | 2 + XmlProfileParser/XmlProfileParser.cpp | 452 +++++-- XmlResultParser/XmlResultParser.cpp | 156 ++- .../CmdLineParser/CmdLineParser.vcxproj | 10 +- .../CmdRequestCreator.vcxproj | 10 +- diskspd_vs/Common/Common.vcxproj | 10 +- .../IORequestGenerator.vcxproj | 10 +- diskspd_vs/ResultParser/ResultParser.vcxproj | 10 +- .../CmdLineParser/CmdLineParser.vcxproj | 10 +- diskspd_vs/UnitTests/Common/Common.vcxproj | 10 +- .../IORequestGenerator.vcxproj | 10 +- .../ResultParser/ResultParser.vcxproj | 10 +- .../XmlProfileParser/XmlProfileParser.vcxproj | 10 +- .../XmlResultParser/XmlResultParser.vcxproj | 10 +- .../XmlProfileParser/XmlProfileParser.vcxproj | 10 +- .../XmlResultParser/XmlResultParser.vcxproj | 10 +- 39 files changed, 6794 insertions(+), 1959 deletions(-) diff --git a/CmdLineParser/CmdLineParser.cpp b/CmdLineParser/CmdLineParser.cpp index f8762cd..075c0bd 100644 --- a/CmdLineParser/CmdLineParser.cpp +++ b/CmdLineParser/CmdLineParser.cpp @@ -46,64 +46,87 @@ CmdLineParser::~CmdLineParser() { } -// Get size in bytes from a string (it can end with K, M, G for KB, MB, GB and b for block) -bool CmdLineParser::_GetSizeInBytes(const char *pszSize, UINT64& ullSize) const +// 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; - bool fLastCharacterFound = false; - for (char ch = *pszSize; fOk && (ch != '\0'); ch = *(++pszSize)) + UINT64 ullMultiplier = 1; + const char *rest = nullptr; + + fOk = Util::ParseUInt(pszSize, ullResult, rest); + + if (fOk) { - if (fLastCharacterFound) + char ch = static_cast(toupper(*rest)); + + switch (ch) { - fOk = false; + 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); + } + } } - else if ((ch >= '0') && (ch <= '9')) + + if (fOk) { - if (ullResult <= (MAXUINT64 - (ch - '0')) / 10) - { - ullResult = ((ullResult * 10) + (ch - '0')); - } - else + // + // Second chance after parsing valid size qualifier. + // + + if (!pszRest && *rest != '\0') { fOk = false; + fprintf(stderr, "Unrecognized characters after size specification\n"); } - } - else - { - ch = static_cast(toupper(ch)); - if ((ch == 'B') || (ch == 'K') || (ch == 'M') || (ch == 'G')) - { - UINT64 ullMultiplier = 0; - if (ch == 'B') { ullMultiplier = _dwBlockSize; } - else if (ch == 'K') { ullMultiplier = 1024; } - else if (ch == 'M') { ullMultiplier = 1024 * 1024; } - else if (ch == 'G') { ullMultiplier = 1024 * 1024 * 1024; } + // + // Now apply size specifier. + // - if (ullResult <= MAXUINT64 / ullMultiplier) - { - ullResult = ullResult * ullMultiplier; - fLastCharacterFound = true; - } - else - { - // overflow - fOk = false; - } + else if (ullResult <= MAXUINT64 / ullMultiplier) + { + ullResult *= ullMultiplier; } else { + // overflow fOk = false; - fprintf(stderr, "Invalid size specifier '%c'. Valid ones are: K - KB, M - MB, G - GB, B - block\n", ch); + fprintf(stderr, "Overflow applying multipler '%c'\n", ch); } } } + else + { + fprintf(stderr, "Invalid integer\n"); + } if (fOk) { ullSize = ullResult; + + if (pszRest) + { + *pszRest = rest; + } } + return fOk; } @@ -113,12 +136,12 @@ bool CmdLineParser::_GetRandomDataWriteBufferData(const string& sArg, UINT64& cb size_t iComma = sArg.find(','); if (iComma == sArg.npos) { - fOk = _GetSizeInBytes(sArg.c_str(), cb); + fOk = _GetSizeInBytes(sArg.c_str(), cb, nullptr); sPath = ""; } else { - fOk = _GetSizeInBytes(sArg.substr(0, iComma).c_str(), cb); + fOk = _GetSizeInBytes(sArg.substr(0, iComma).c_str(), cb, nullptr); sPath = sArg.substr(iComma + 1); } return fOk; @@ -131,131 +154,165 @@ void CmdLineParser::_DisplayUsageInfo(const char *pszFilename) const 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("Available targets:\n"); - printf(" file_path\n"); - printf(" #\n"); - printf(" :\n"); - printf("\n"); - printf("Available options:\n"); - printf(" -? display usage information\n"); - printf(" -ag group affinity - affinitize threads round-robin to cores in Processor Groups 0 - n.\n"); - printf(" Group 0 is filled before Group 1, and so forth.\n"); - printf(" [default; use -n to disable default affinity]\n"); - printf(" -ag#,#[,#,...]> advanced CPU affinity - affinitize threads round-robin to the CPUs provided. The g# notation\n"); - printf(" specifies Processor Groups for the following CPU core #s. Multiple Processor Groups\n"); - printf(" may be specified, and groups/cores may be repeated. If no group is specified, 0 is assumed.\n"); - printf(" Additional groups/processors may be added, comma separated, or on separate parameters.\n"); - printf(" Examples: -a0,1,2 and -ag0,0,1,2 are equivalent.\n"); - printf(" -ag0,0,1,2,g1,0,1,2 specifies the first three cores in groups 0 and 1.\n"); - printf(" -ag0,0,1,2 -ag1,0,1,2 is equivalent.\n"); - printf(" -b[K|M|G] block size in bytes or KiB/MiB/GiB [default=64K]\n"); - printf(" -B[K|M|G|b] base target offset in bytes or KiB/MiB/GiB/blocks [default=0]\n"); - printf(" (offset from the beginning of the file)\n"); - printf(" -c[K|M|G|b] create files of the given size.\n"); - printf(" Size can be stated in bytes or KiB/MiB/GiB/blocks\n"); - printf(" -C cool down time - duration of the test after measurements finished [default=0s].\n"); - printf(" -D Capture IOPs statistics in intervals of ; these are per-thread\n"); - printf(" per-target: text output provides IOPs standard deviation, XML provides the full\n"); - printf(" IOPs time series in addition. [default=1000, 1 second].\n"); - printf(" -d duration (in seconds) to run test [default=10s]\n"); - printf(" -f[K|M|G|b] target size - use only the first bytes or KiB/MiB/GiB/blocks of the file/disk/partition,\n"); - printf(" for example to test only the first sectors of a disk\n"); - printf(" -f open file with one or more additional access hints\n"); - printf(" r : the FILE_FLAG_RANDOM_ACCESS hint\n"); - printf(" s : the FILE_FLAG_SEQUENTIAL_SCAN hint\n"); - printf(" t : the FILE_ATTRIBUTE_TEMPORARY hint\n"); - printf(" [default: none]\n"); - printf(" -F total number of threads (conflicts with -t)\n"); - printf(" -g throughput per-thread per-target throttled to given bytes per millisecond\n"); - printf(" note that this can not be specified when using completion routines\n"); - printf(" [default inactive]\n"); - printf(" -h deprecated, see -Sh\n"); - printf(" -i number of IOs per burst; see -j [default: inactive]\n"); - printf(" -j interval in between issuing IO bursts; see -i [default: inactive]\n"); - printf(" -I Set IO priority to . Available values are: 1-very low, 2-low, 3-normal (default)\n"); - printf(" -l Use large pages for IO buffers\n"); - printf(" -L measure latency statistics\n"); - printf(" -n disable default affinity (-a)\n"); - printf(" -N specify the flush mode for memory mapped I/O\n"); - printf(" v : uses the FlushViewOfFile API\n"); - printf(" n : uses the RtlFlushNonVolatileMemory API\n"); - printf(" i : uses RtlFlushNonVolatileMemory without waiting for the flush to drain\n"); - printf(" [default: none]\n"); - printf(" -o number of outstanding I/O requests per target per thread\n"); - printf(" (1=synchronous I/O, unless more than 1 thread is specified with -F)\n"); - printf(" [default=2]\n"); - printf(" -O number of outstanding I/O requests per thread - for use with -F\n"); - printf(" (1=synchronous I/O)\n"); - printf(" -p start parallel sequential I/O operations with the same offset\n"); - printf(" (ignored if -r is specified, makes sense only with -o2 or greater)\n"); - printf(" -P enable printing a progress dot after each [default=65536]\n"); - printf(" completed I/O operations, counted separately by each thread \n"); - printf(" -r[K|M|G|b] random I/O aligned to in bytes/KiB/MiB/GiB/blocks (overrides -s)\n"); - printf(" -R output format. Default is text.\n"); - printf(" -s[i][K|M|G|b] sequential stride size, offset between subsequent I/O operations\n"); - printf(" [default access=non-interlocked sequential, default stride=block size]\n"); - printf(" In non-interlocked mode, threads do not coordinate, so the pattern of offsets\n"); - printf(" as seen by the target will not be truly sequential. Under -si the threads\n"); - printf(" manipulate a shared offset with InterlockedIncrement, which may reduce throughput,\n"); - printf(" but promotes a more sequential pattern.\n"); - printf(" (ignored if -r specified, -si conflicts with -T and -p)\n"); - printf(" -S[bhmruw] control caching behavior [default: caching is enabled, no writethrough]\n"); - printf(" non-conflicting flags may be combined in any order; ex: -Sbw, -Suw, -Swu\n"); - printf(" -S equivalent to -Su\n"); - printf(" -Sb enable caching (default, explicitly stated)\n"); - printf(" -Sh equivalent -Suw\n"); - printf(" -Sm enable memory mapped I/O\n"); - printf(" -Su disable software caching, equivalent to FILE_FLAG_NO_BUFFERING\n"); - printf(" -Sr disable local caching, with remote sw caching enabled; only valid for remote filesystems\n"); - printf(" -Sw enable writethrough (no hardware write caching), equivalent to FILE_FLAG_WRITE_THROUGH or\n"); - printf(" non-temporal writes for memory mapped I/O (-Sm)\n"); - printf(" -t number of threads per target (conflicts with -F)\n"); - printf(" -T[K|M|G|b] starting stride between I/O operations performed on the same target by different threads\n"); - printf(" [default=0] (starting offset = base file offset + (thread number * )\n"); - printf(" makes sense only with #threads > 1\n"); - printf(" -v verbose mode\n"); - printf(" -w percentage of write requests (-w and -w0 are equivalent and result in a read-only workload).\n"); - printf(" absence of this switch indicates 100%% reads\n"); - printf(" IMPORTANT: a write test will destroy existing data without a warning\n"); - printf(" -W warm up time - duration of the test before measurements start [default=5s]\n"); - printf(" -x use completion routines instead of I/O Completion Ports\n"); - printf(" -X use an XML file for configuring the workload. Cannot be used with other parameters.\n"); - printf(" -z[seed] set random seed [with no -z, seed=0; with plain -z, seed is based on system run time]\n"); - printf("\n"); - printf("Write buffers:\n"); - printf(" -Z zero buffers used for write tests\n"); - printf(" -Zr per IO random buffers used for write tests - this incurrs additional run-time\n"); - printf(" overhead to create random content and shouln't be compared to results run\n"); - printf(" without -Zr\n"); - printf(" -Z[K|M|G|b] use a buffer filled with random data as a source for write operations.\n"); - printf(" -Z[K|M|G|b], use a buffer filled with data from as a source for write operations.\n"); - printf("\n"); - printf(" By default, the write buffers are filled with a repeating pattern (0, 1, 2, ..., 255, 0, 1, ...)\n"); - printf("\n"); - printf("Synchronization:\n"); - printf(" -ys signals event before starting the actual run (no warmup)\n"); - printf(" (creates a notification event if does not exist)\n"); - printf(" -yf signals event after the actual run finishes (no cooldown)\n"); - printf(" (creates a notification event if does not exist)\n"); - printf(" -yr waits on event before starting the run (including warmup)\n"); - printf(" (creates a notification event if does not exist)\n"); - printf(" -yp stops the run when event is set; CTRL+C is bound to this event\n"); - printf(" (creates a notification event if does not exist)\n"); - printf(" -ye sets event and quits\n"); - printf("\n"); - printf("Event Tracing:\n"); - printf(" -e Use query perf timer (qpc), cycle count, or system timer respectively.\n"); - printf(" [default = q, query perf timer (qpc)]\n"); - printf(" -ep use paged memory for the NT Kernel Logger [default=non-paged memory]\n"); - printf(" -ePROCESS process start & end\n"); - printf(" -eTHREAD thread start & end\n"); - printf(" -eIMAGE_LOAD image load\n"); - printf(" -eDISK_IO physical disk IO\n"); - printf(" -eMEMORY_PAGE_FAULTS all page faults\n"); - printf(" -eMEMORY_HARD_FAULTS hard faults only\n"); - printf(" -eNETWORK TCP/IP, UDP/IP send & receive\n"); - printf(" -eREGISTRY registry calls\n"); - printf("\n\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); @@ -520,51 +577,446 @@ bool CmdLineParser::_ParseFlushParameter(const char *arg, MemoryMappedIoFlushMod return fOk; } -bool CmdLineParser::_ReadParametersFromCmdLine(const int argc, const char *argv[], Profile *pProfile, struct Synchronization *synch) +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) { - /* Process any command-line options */ 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. + // - // create targets vector vTargets; - int iFirstFile = -1; - for (int i = 1; i < argc; i++) + for (int i = 0, inTargets = false; i < nParamCnt; ++i) { - if (argv[i][0] != '-' && argv[i][0] != '/') + if (!_IsSwitchChar(args[i][0])) { - iFirstFile = i; + inTargets = true; + Target target; - target.SetPath(argv[i]); + 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 block size (other parameters may be stated in terms of blocks) - for (int x = 1; x < argc; ++x) + // + // Find composable and dependent parameters as we resolve the parameter set. + // + + for (int i = 0; i < nParamCnt; ++i) { - if ((nullptr != argv[x]) && (('-' == argv[x][0]) || ('/' == argv[x][0])) && ('b' == argv[x][1]) && ('\0' != argv[x][2])) + if (_IsSwitchChar(args[i][0])) { - _dwBlockSize = 0; - UINT64 ullBlockSize; - if (_GetSizeInBytes(&argv[x][2], ullBlockSize)) + const char *arg = &args[i][2]; + + switch(args[i][1]) { - for (auto i = vTargets.begin(); i != vTargets.end(); i++) + + case 'b': + + // Block size does not compose with XML profile spec + if (isXMLSet == ParseState::True) { - // TODO: UINT64->DWORD - i->SetBlockSizeInBytes((DWORD)ullBlockSize); + 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; } } - else - { - fprintf(stderr, "Invalid block size passed to -b\n"); - return false; - } - _dwBlockSize = (DWORD)ullBlockSize; - break; } } + // 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. @@ -573,30 +1025,24 @@ bool CmdLineParser::_ReadParametersFromCmdLine(const int argc, const char *argv[ MemoryMappedIoMode m = MemoryMappedIoMode::Undefined; MemoryMappedIoFlushMode f = MemoryMappedIoFlushMode::Undefined; - TimeSpan timeSpan; bool bExit = false; while (nParamCnt) { const char* arg = *args; - bool fError = false; + const char* const carg = arg; // save for error reporting, arg is modified during parse - // check if it is a parameter or already path - if ('-' != *arg && '/' != *arg) + // Targets follow parameters on command line. If this is a target, we are done now. + if (!_IsSwitchChar(*arg)) { break; } - // skip '-' or '/' + // skip switch character, provide length ++arg; + const size_t argLen = strlen(arg); switch (*arg) { - case '\0': - // back up so that the error complaint mentions the switch char - arg--; - fError = true; - break; - case '?': _DisplayUsageInfo(argv[0]); exit(0); @@ -617,7 +1063,7 @@ bool CmdLineParser::_ReadParametersFromCmdLine(const int argc, const char *argv[ if (*(arg + 1) != '\0') { UINT64 cb; - if (_GetSizeInBytes(arg + 1, cb)) + if (_GetSizeInBytes(arg + 1, cb, nullptr)) { for (auto i = vTargets.begin(); i != vTargets.end(); i++) { @@ -626,7 +1072,7 @@ bool CmdLineParser::_ReadParametersFromCmdLine(const int argc, const char *argv[ } else { - fprintf(stderr, "Invalid base file offset passed to -B\n"); + fprintf(stderr, "ERROR: invalid base file offset passed to -B\n"); fError = true; } } @@ -640,7 +1086,7 @@ bool CmdLineParser::_ReadParametersFromCmdLine(const int argc, const char *argv[ if (*(arg + 1) != '\0') { UINT64 cb; - if (_GetSizeInBytes(arg + 1, cb)) + if (_GetSizeInBytes(arg + 1, cb, nullptr)) { for (auto i = vTargets.begin(); i != vTargets.end(); i++) { @@ -650,7 +1096,7 @@ bool CmdLineParser::_ReadParametersFromCmdLine(const int argc, const char *argv[ } else { - fprintf(stderr, "Invalid file size passed to -c\n"); + fprintf(stderr, "ERROR: invalid file size passed to -c\n"); fError = true; } } @@ -660,32 +1106,10 @@ bool CmdLineParser::_ReadParametersFromCmdLine(const int argc, const char *argv[ } break; - case 'C': //cool down time - { - int c = atoi(arg + 1); - if (c >= 0) - { - timeSpan.SetCooldown(c); - } - else - { - fError = true; - } - } + case 'C': //cool down time - pass 1 composable break; - case 'd': //duration - { - int x = atoi(arg + 1); - if (x > 0) - { - timeSpan.SetDuration(x); - } - else - { - fError = true; - } - } + case 'd': //duration - pass 1 composable break; case 'D': //standard deviation @@ -711,7 +1135,7 @@ bool CmdLineParser::_ReadParametersFromCmdLine(const int argc, const char *argv[ if (isdigit(*(arg + 1))) { UINT64 cb; - if (_GetSizeInBytes(arg + 1, cb)) + if (_GetSizeInBytes(arg + 1, cb, nullptr)) { for (auto i = vTargets.begin(); i != vTargets.end(); i++) { @@ -720,7 +1144,7 @@ bool CmdLineParser::_ReadParametersFromCmdLine(const int argc, const char *argv[ } else { - fprintf(stderr, "Invalid max file size passed to -f\n"); + fprintf(stderr, "ERROR: invalid max file size passed to -f\n"); fError = true; } } @@ -779,20 +1203,42 @@ bool CmdLineParser::_ReadParametersFromCmdLine(const int argc, const char *argv[ } break; - case 'g': //throughput in bytes per millisecond + case 'g': //throughput in bytes per millisecond (gNNN) OR iops (gNNNi) { - int c = atoi(arg + 1); - if (c > 0) + // units? + bool isBpms = false; + if (isdigit(arg[argLen - 1])) { - for (auto i = vTargets.begin(); i != vTargets.end(); i++) - { - i->SetThroughput(c); - } + isBpms = true; } - else + 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; @@ -805,7 +1251,7 @@ bool CmdLineParser::_ReadParametersFromCmdLine(const int argc, const char *argv[ } else { - fprintf(stderr, "-h conflicts with earlier specification of cache/writethrough\n"); + fprintf(stderr, "ERROR: -h conflicts with earlier specification of cache/writethrough\n"); fError = true; } break; @@ -870,7 +1316,7 @@ bool CmdLineParser::_ReadParametersFromCmdLine(const int argc, const char *argv[ i->SetUseLargePages(true); } break; - + case 'L': //measure latency timeSpan.SetMeasureLatency(true); break; @@ -938,44 +1384,117 @@ bool CmdLineParser::_ReadParametersFromCmdLine(const int argc, const char *argv[ case 'r': //random access { - UINT64 cb = _dwBlockSize; - if (*(arg + 1) != '\0') + // mixed random/sequential pct split? + if (*(arg + 1) == 's') { - if (!_GetSizeInBytes(arg + 1, cb) || (cb == 0)) + int c = 0; + + ++arg; + if (*(arg + 1) == '\0') { - fprintf(stderr, "Invalid alignment passed to -r\n"); + fprintf(stderr, "ERROR: no random percentage passed to -rs\n"); fError = true; } - } - if (!fError) - { - for (auto i = vTargets.begin(); i != vTargets.end(); i++) + 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) { - i->SetUseRandomAccessPattern(true); - i->SetBlockAlignmentInBytes(cb); + 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); + } } } - } - break; - case 'R': //custom result parser - if (0 != *(arg + 1)) - { - const char* pszArg = arg + 1; - if (strcmp(pszArg, "xml") == 0) + // random distribution + + else if (*(arg + 1) == 'd') { - pProfile->SetResultsFormat(ResultsFormat::Xml); + // advance past the d + arg += 2; + + fError = !_ParseRandomDistribution(arg, vTargets); } - else if (strcmp(pszArg, "text") != 0) + + // random block alignment + // if mixed random/sequential not already specified, set to 100% + else { - fError = true; - fprintf(stderr, "Invalid results format: '%s'.\n", pszArg); + + 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); + } + } } } - else - { - fError = true; - } + break; + + case 'R': // output profile/results format engine - handled in pass 1 break; case 's': //stride size @@ -987,7 +1506,7 @@ bool CmdLineParser::_ReadParametersFromCmdLine(const int argc, const char *argv[ // 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 + // ISSUE-REVIEW: this does nothing if we are single-threaded for (auto i = vTargets.begin(); i != vTargets.end(); i++) { i->SetUseInterlockedSequential(true); @@ -995,11 +1514,38 @@ bool CmdLineParser::_ReadParametersFromCmdLine(const int argc, const char *argv[ 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; - if (_GetSizeInBytes(arg + idx, 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++) { @@ -1008,10 +1554,19 @@ bool CmdLineParser::_ReadParametersFromCmdLine(const int argc, const char *argv[ } else { - fprintf(stderr, "Invalid stride size passed to -s\n"); + 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; @@ -1032,10 +1587,10 @@ bool CmdLineParser::_ReadParametersFromCmdLine(const int argc, const char *argv[ } else { - fprintf(stderr, "-Sb conflicts with earlier specification of cache mode\n"); + fprintf(stderr, "ERROR: -Sb conflicts with earlier specification of cache mode\n"); fError = true; } - break; + break; case 'h': if (t == TargetCacheMode::Undefined && w == WriteThroughMode::Undefined && @@ -1046,7 +1601,7 @@ bool CmdLineParser::_ReadParametersFromCmdLine(const int argc, const char *argv[ } else { - fprintf(stderr, "-Sh conflicts with earlier specification of cache/writethrough/memory mapped\n"); + fprintf(stderr, "ERROR: -Sh conflicts with earlier specification of cache/writethrough/memory mapped\n"); fError = true; } break; @@ -1058,7 +1613,7 @@ bool CmdLineParser::_ReadParametersFromCmdLine(const int argc, const char *argv[ } else { - fprintf(stderr, "-Sm conflicts with earlier specification of memory mapped IO/unbuffered IO\n"); + fprintf(stderr, "ERROR: -Sm conflicts with earlier specification of memory mapped IO/unbuffered IO\n"); fError = true; } break; @@ -1069,7 +1624,7 @@ bool CmdLineParser::_ReadParametersFromCmdLine(const int argc, const char *argv[ } else { - fprintf(stderr, "-Sr conflicts with earlier specification of cache mode\n"); + fprintf(stderr, "ERROR: -Sr conflicts with earlier specification of cache mode\n"); fError = true; } break; @@ -1081,7 +1636,7 @@ bool CmdLineParser::_ReadParametersFromCmdLine(const int argc, const char *argv[ } else { - fprintf(stderr, "-Su conflicts with earlier specification of cache mode/memory mapped IO\n"); + fprintf(stderr, "ERROR: -Su conflicts with earlier specification of cache mode/memory mapped IO\n"); fError = true; } break; @@ -1092,12 +1647,12 @@ bool CmdLineParser::_ReadParametersFromCmdLine(const int argc, const char *argv[ } else { - fprintf(stderr, "-Sw conflicts with earlier specification of write through\n"); + fprintf(stderr, "ERROR -Sw conflicts with earlier specification of write through\n"); fError = true; } break; default: - fprintf(stderr, "unrecognized option provided to -S\n"); + fprintf(stderr, "ERROR: unrecognized option provided to -S\n"); fError = true; break; } @@ -1113,7 +1668,7 @@ bool CmdLineParser::_ReadParametersFromCmdLine(const int argc, const char *argv[ } else { - fprintf(stderr, "-S conflicts with earlier specification of cache mode\n"); + fprintf(stderr, "ERROR: -S conflicts with earlier specification of cache mode\n"); fError = true; } } @@ -1140,7 +1695,7 @@ bool CmdLineParser::_ReadParametersFromCmdLine(const int argc, const char *argv[ case 'T': //offsets between threads reading the same file { UINT64 cb; - if (_GetSizeInBytes(arg + 1, cb) && (cb > 0)) + if (_GetSizeInBytes(arg + 1, cb, nullptr) && (cb > 0)) { for (auto i = vTargets.begin(); i != vTargets.end(); i++) { @@ -1149,33 +1704,34 @@ bool CmdLineParser::_ReadParametersFromCmdLine(const int argc, const char *argv[ } else { - fprintf(stderr, "Invalid offset passed to -T\n"); + fprintf(stderr, "ERROR: invalid offset passed to -T\n"); fError = true; } } break; - case 'v': //verbose mode - pProfile->SetVerbose(true); + case 'v': //verbose mode - handled in pass 1 break; case 'w': //write test [default=read] { - int c = -1; + int c = 0; + if (*(arg + 1) == '\0') { - c = _ulWriteRatio; + fprintf(stderr, "ERROR: no write ratio passed to -w\n"); + fError = true; } else { c = atoi(arg + 1); if (c < 0 || c > 100) { - c = -1; + fprintf(stderr, "ERROR: write ratio passed to -w must be between 0 and 100 (percent)\n"); fError = true; } } - if (c != -1) + if (!fError) { for (auto i = vTargets.begin(); i != vTargets.end(); i++) { @@ -1185,18 +1741,7 @@ bool CmdLineParser::_ReadParametersFromCmdLine(const int argc, const char *argv[ } break; - case 'W': //warm up time - { - int c = atoi(arg + 1); - if (c >= 0) - { - timeSpan.SetWarmup(c); - } - else - { - fError = true; - } - } + case 'W': //warm up time - pass 1 composable break; case 'x': //completion routines @@ -1266,23 +1811,7 @@ bool CmdLineParser::_ReadParametersFromCmdLine(const int argc, const char *argv[ fError = true; } - case 'z': //random seed - if (*(arg + 1) == '\0') - { - timeSpan.SetRandSeed((ULONG)GetTickCount64()); - } - else - { - int c = atoi(arg + 1); - if (c >= 0) - { - timeSpan.SetRandSeed(c); - } - else - { - fError = true; - } - } + case 'z': //random seed - pass 1 composable break; case 'Z': //zero write buffers @@ -1318,13 +1847,14 @@ bool CmdLineParser::_ReadParametersFromCmdLine(const int argc, const char *argv[ break; default: - fprintf(stderr, "ERROR: invalid option: '%s'\n", arg); + fprintf(stderr, "ERROR: invalid option: '%s'\n", carg); return false; } if (fError) { - fprintf(stderr, "ERROR: incorrectly provided option: '%s'\n", arg); + // note: original pointer to the cmdline argument, without parse movement + fprintf(stderr, "ERROR: incorrectly provided option: '%s'\n", carg); return false; } @@ -1378,10 +1908,11 @@ bool CmdLineParser::_ReadParametersFromCmdLine(const int argc, const char *argv[ return true; } -bool CmdLineParser::_ReadParametersFromXmlFile(const char *pszPath, Profile *pProfile) +bool CmdLineParser::_ReadParametersFromXmlFile(const char *pszPath, Profile *pProfile, vector *pvSubstTargets) { XmlProfileParser parser; - return parser.ParseFile(pszPath, pProfile, NULL); + + return parser.ParseFile(pszPath, pProfile, pvSubstTargets, NULL); } bool CmdLineParser::ParseCmdLine(const int argc, const char *argv[], Profile *pProfile, struct Synchronization *synch, SystemInformation *pSystem) @@ -1408,27 +1939,20 @@ bool CmdLineParser::ParseCmdLine(const int argc, const char *argv[], Profile *pP } pProfile->SetCmdLine(sCmdLine); - //check if parameters should be read from an xml file bool fOk = true; - bool fCmdLine; + 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 (argc == 2 && (argv[1][0] == '-' || argv[1][0] == '/') && argv[1][1] == 'X' && argv[1][2] != '\0') - { - fOk = _ReadParametersFromXmlFile(argv[1] + 2, pProfile); - fCmdLine = false; - } - else - { - fOk = _ReadParametersFromCmdLine(argc, argv, pProfile, synch); - fCmdLine = true; - } - - // check additional restrictions and conditions on the passed parameters. - // note that on the cmdline, all targets receive the same parameters so - // that their mutual consistency only needs to be checked once. if (fOk) { - fOk = pProfile->Validate(fCmdLine, pSystem); + fOk = pProfile->Validate(!fXMLProfile, pProfile->GetProfileOnly() ? nullptr : pSystem); } return fOk; diff --git a/CmdRequestCreator/CmdRequestCreator.cpp b/CmdRequestCreator/CmdRequestCreator.cpp index d9b3eb5..fd0a097 100644 --- a/CmdRequestCreator/CmdRequestCreator.cpp +++ b/CmdRequestCreator/CmdRequestCreator.cpp @@ -50,22 +50,6 @@ static HANDLE g_hAbortEvent = NULL; // handle to the 'abort' event static HANDLE g_hEventStarted = NULL; // event signalled to notify that the actual (measured) test is to be started static HANDLE g_hEventFinished = NULL; // event signalled to notify that the actual test has finished -/*****************************************************************************/ -// wrapper for printf. printf cannot be used directly, because IORequestGenerator.dll -// may be consumed by gui app which doesn't have stdout -void WINAPI PrintOut(const char *format, va_list args) -{ - vprintf(format, args); -} - -/*****************************************************************************/ -// wrapper for fprintf. fprintf cannot be used directly, because IORequestGenerator.dll -// may be consumed by gui app which doesn't have stdout -void WINAPI PrintError(const char *format, va_list args) -{ - vfprintf(stderr, format, args); -} - /*****************************************************************************/ BOOL WINAPI ctrlCRoutine(DWORD dwCtrlType) { @@ -123,6 +107,27 @@ int __cdecl main(int argc, const char* argv[]) return ERROR_PARSE_CMD_LINE; } + // Instantiate parsers + ResultParser resultParser; + XmlResultParser xmlResultParser; + IResultParser *pResultParser = nullptr; + if (profile.GetResultsFormat() == ResultsFormat::Xml) + { + pResultParser = &xmlResultParser; + } + else + { + pResultParser = &resultParser; + } + + // Profile only? If so, complete now. + if (profile.GetProfileOnly()) + { + string s = pResultParser->ParseProfile(profile); + printf("%s", s.c_str()); + return 0; + } + synch.pfnCallbackTestStarted = TestStarted; synch.pfnCallbackTestFinished = TestFinished; @@ -157,20 +162,9 @@ int __cdecl main(int argc, const char* argv[]) // // call IO request generator // - ResultParser resultParser; - XmlResultParser xmlResultParser; - IResultParser *pResultParser = nullptr; - if (profile.GetResultsFormat() == ResultsFormat::Xml) - { - pResultParser = &xmlResultParser; - } - else - { - pResultParser = &resultParser; - } IORequestGenerator ioGenerator; - if (!ioGenerator.GenerateRequests(profile, *pResultParser, (PRINTF)PrintOut, (PRINTF)PrintError, (PRINTF)PrintOut, &synch)) + if (!ioGenerator.GenerateRequests(profile, *pResultParser, &synch)) { if (profile.GetResultsFormat() == ResultsFormat::Xml) { diff --git a/Common/CmdLineParser.h b/Common/CmdLineParser.h index c970913..c1010d7 100644 --- a/Common/CmdLineParser.h +++ b/Common/CmdLineParser.h @@ -41,18 +41,26 @@ class CmdLineParser bool ParseCmdLine(const int argc, const char *argv[], Profile *pProfile, struct Synchronization *synch, SystemInformation *pSystem = nullptr); private: - bool _ReadParametersFromCmdLine(const int argc, const char *argv[], Profile *pProfile, struct Synchronization *synch); - bool _ReadParametersFromXmlFile(const char *pszPath, Profile *pProfile); + bool _ReadParametersFromCmdLine(const int argc, const char *argv[], Profile *pProfile, struct Synchronization *synch, bool& fXMLProfile); + bool _ReadParametersFromXmlFile(const char *pszPath, Profile *pProfile, vector *pvSubstTargets); bool _ParseETWParameter(const char *arg, Profile *pProfile); bool _ParseFlushParameter(const char *arg, MemoryMappedIoFlushMode *FlushMode ); bool _ParseAffinity(const char *arg, TimeSpan *pTimeSpan); + bool _ParseRandomDistribution(const char *arg, vector& vTargets); void _DisplayUsageInfo(const char *pszFilename) const; - bool _GetSizeInBytes(const char *pszSize, UINT64& ullSize) const; + bool _GetSizeInBytes(const char *pszSize, UINT64& ullSize, const char **pszRest) const; bool _GetRandomDataWriteBufferData(const string& sArg, UINT64& cb, string& sPath); - // variables that used to be global + static bool _IsSwitchChar(const char c) { return (c == '/' || c == '-'); } + enum class ParseState { + Unknown, + True, + False, + Bad + }; + DWORD _dwBlockSize; // block size; other parameters may be stated in blocks // so the block size is needed to process them diff --git a/Common/Common.cpp b/Common/Common.cpp index ab29d5b..e866bfc 100644 --- a/Common/Common.cpp +++ b/Common/Common.cpp @@ -237,50 +237,54 @@ string Util::DoubleToStringHelper(const double d) return string(szFloatBuffer); } -string ThreadTarget::GetXml() const +string ThreadTarget::GetXml(UINT32 indent) const { char buffer[4096]; - string sXml("\n"); + string sXml; + + AddXmlInc(sXml, "\n"); sprintf_s(buffer, _countof(buffer), "%u\n", _ulThread); - sXml += buffer; + AddXml(sXml, buffer); if (_ulWeight != 0) { sprintf_s(buffer, _countof(buffer), "%u\n", _ulWeight); - sXml += buffer; + AddXml(sXml, buffer); } - sXml += "\n"; + AddXmlDec(sXml, "\n"); return sXml; } -string Target::GetXml() const +string Target::GetXml(UINT32 indent) const { char buffer[4096]; - string sXml("\n"); - sXml += "" + _sPath + "\n"; + string sXml; + + AddXmlInc(sXml, "\n"); + AddXml(sXml, "" + _sPath + "\n"); sprintf_s(buffer, _countof(buffer), "%u\n", _dwBlockSize); - sXml += buffer; + AddXml(sXml, buffer); sprintf_s(buffer, _countof(buffer), "%I64u\n", _ullBaseFileOffset); - sXml += buffer; + AddXml(sXml, buffer); - sXml += _fSequentialScanHint ? "true\n" : "false\n"; - sXml += _fRandomAccessHint ? "true\n" : "false\n"; - sXml += _fTemporaryFileHint ? "true\n" : "false\n"; - sXml += _fUseLargePages ? "true\n" : "false\n"; + AddXml(sXml, _fSequentialScanHint ? "true\n" : "false\n"); + AddXml(sXml, _fRandomAccessHint ? "true\n" : "false\n"); + AddXml(sXml, _fTemporaryFileHint ? "true\n" : "false\n"); + AddXml(sXml, _fUseLargePages ? "true\n" : "false\n"); // TargetCacheMode::Cached is implied default switch (_cacheMode) { case TargetCacheMode::DisableLocalCache: - sXml += "true\n"; + AddXml(sXml, "true\n"); break; case TargetCacheMode::DisableOSCache: - sXml += "true\n"; + AddXml(sXml, "true\n"); break; } @@ -288,7 +292,7 @@ string Target::GetXml() const switch (_writeThroughMode) { case WriteThroughMode::On: - sXml += "true\n"; + AddXml(sXml, "true\n"); break; } @@ -296,7 +300,7 @@ string Target::GetXml() const switch (_memoryMappedIoMode) { case MemoryMappedIoMode::On: - sXml += "true\n"; + AddXml(sXml, "true\n"); break; } @@ -304,126 +308,180 @@ string Target::GetXml() const switch (_memoryMappedIoFlushMode) { case MemoryMappedIoFlushMode::ViewOfFile: - sXml += "ViewOfFile\n"; + AddXml(sXml, "ViewOfFile\n"); break; case MemoryMappedIoFlushMode::NonVolatileMemory: - sXml += "NonVolatileMemory\n"; + AddXml(sXml, "NonVolatileMemory\n") break; case MemoryMappedIoFlushMode::NonVolatileMemoryNoDrain: - sXml += "NonVolatileMemoryNoDrain\n"; + AddXml(sXml, "NonVolatileMemoryNoDrain\n"); break; } - sXml += "\n"; + AddXmlInc(sXml, "\n"); if (_fZeroWriteBuffers) { - sXml += "zero\n"; + AddXml(sXml, "zero\n"); } else if (_cbRandomDataWriteBuffer == 0) { - sXml += "sequential\n"; + AddXml(sXml, "sequential\n"); } else { - sXml += "random\n"; - sXml += "\n"; + AddXml(sXml, "random\n"); + AddXmlInc(sXml, "\n"); sprintf_s(buffer, _countof(buffer), "%I64u\n", _cbRandomDataWriteBuffer); - sXml += buffer; + AddXml(sXml, buffer); if (_sRandomDataWriteBufferSourcePath != "") { - sXml += "" + _sRandomDataWriteBufferSourcePath + "\n"; + AddXml(sXml, "" + _sRandomDataWriteBufferSourcePath + "\n"); } - sXml += "\n"; + AddXmlDec(sXml, "\n"); } - sXml += "\n"; + AddXmlDec(sXml, "\n"); - sXml += _fParallelAsyncIO ? "true\n" : "false\n"; + AddXml(sXml, _fParallelAsyncIO ? "true\n" : "false\n"); if (_fUseBurstSize) { sprintf_s(buffer, _countof(buffer), "%u\n", _dwBurstSize); - sXml += buffer; + AddXml(sXml, buffer); } if (_fThinkTime) { sprintf_s(buffer, _countof(buffer), "%u\n", _dwThinkTime); - sXml += buffer; + AddXml(sXml, buffer); } if (_fCreateFile) { sprintf_s(buffer, _countof(buffer), "%I64u\n", _ullFileSize); - sXml += buffer; + AddXml(sXml, buffer); } // If XML contains , is ignored - if (_fUseRandomAccessPattern) + if (_ulRandomRatio > 0) { sprintf_s(buffer, _countof(buffer), "%I64u\n", GetBlockAlignmentInBytes()); - sXml += buffer; + AddXml(sXml, buffer); + + // 100% random is alone + if (_ulRandomRatio != 100) + { + sprintf_s(buffer, _countof(buffer), "%u\n", GetRandomRatio()); + AddXml(sXml, buffer); + } + + // Distributions only occur in profiles with random IO. + + if (_vDistributionRange.size()) + { + char *type = nullptr; + + switch (_distributionType) + { + case DistributionType::Absolute: + type = "Absolute"; + break; + + case DistributionType::Percent: + type = "Percent"; + break; + + default: + assert(false); + } + + AddXmlInc(sXml, "\n"); + AddXmlInc(sXml, "<"); + sXml += type; + sXml += ">\n"; + + for (auto r : _vDistributionRange) + { + sprintf_s(buffer, _countof(buffer), "%I64u", r._span, r._dst.second); + AddXml(sXml, buffer); + sXml += "\n"; + } + + AddXmlDec(sXml, "\n"; + AddXmlDec(sXml, "\n"); + } } else { sprintf_s(buffer, _countof(buffer), "%I64u\n", GetBlockAlignmentInBytes()); - sXml += buffer; + AddXml(sXml, buffer); - sXml += _fInterlockedSequential ? + AddXml(sXml, _fInterlockedSequential ? "true\n" : - "false\n"; + "false\n"); } sprintf_s(buffer, _countof(buffer), "%I64u\n", _ullThreadStride); - sXml += buffer; + AddXml(sXml, buffer); sprintf_s(buffer, _countof(buffer), "%I64u\n", _ullMaxFileSize); - sXml += buffer; + AddXml(sXml, buffer); sprintf_s(buffer, _countof(buffer), "%u\n", _dwRequestCount); - sXml += buffer; + AddXml(sXml, buffer); sprintf_s(buffer, _countof(buffer), "%u\n", _ulWriteRatio); - sXml += buffer; + AddXml(sXml, buffer); - sprintf_s(buffer, _countof(buffer), "%u\n", _dwThroughputBytesPerMillisecond); - sXml += buffer; + // Preserve specified units + if (_dwThroughputIOPS) + { + sprintf_s(buffer, _countof(buffer), "%u\n", _dwThroughputIOPS); + AddXml(sXml, buffer); + } + else + { + sprintf_s(buffer, _countof(buffer), "%u\n", _dwThroughputBytesPerMillisecond); + AddXml(sXml, buffer); + } sprintf_s(buffer, _countof(buffer), "%u\n", _dwThreadsPerFile); - sXml += buffer; + AddXml(sXml, buffer); if (_ioPriorityHint == IoPriorityHintVeryLow) { - sXml += "1\n"; + AddXml(sXml, "1\n"); } else if (_ioPriorityHint == IoPriorityHintLow) { - sXml += "2\n"; + AddXml(sXml, "2\n"); } else if (_ioPriorityHint == IoPriorityHintNormal) { - sXml += "3\n"; + AddXml(sXml, "3\n"); } else { - sXml += "* UNSUPPORTED *\n"; + AddXml(sXml, "* UNSUPPORTED *\n"); } sprintf_s(buffer, _countof(buffer), "%u\n", _ulWeight); - sXml += buffer; + AddXml(sXml, buffer); if (_vThreadTargets.size() > 0) { - sXml += "\n"; + AddXmlInc(sXml, "\n"); for (const auto& threadTarget : _vThreadTargets) { - sXml += threadTarget.GetXml(); + sXml += threadTarget.GetXml(indent); } - sXml += "\n"; + AddXmlDec(sXml, "\n"); } - sXml += "\n"; + AddXmlDec(sXml, "\n"); return sXml; } @@ -537,55 +595,56 @@ BYTE* Target::GetRandomDataWriteBuffer(Random *pRand) return pBuffer; } -string TimeSpan::GetXml() const +string TimeSpan::GetXml(UINT32 indent) const { - string sXml("\n"); + string sXml; char buffer[4096]; - sXml += _fCompletionRoutines ? "true\n" : "false\n"; - sXml += _fMeasureLatency ? "true\n" : "false\n"; - sXml += _fCalculateIopsStdDev ? "true\n" : "false\n"; - sXml += _fDisableAffinity ? "true\n" : "false\n"; + AddXmlInc(sXml, "\n"); + AddXml(sXml, _fCompletionRoutines ? "true\n" : "false\n"); + AddXml(sXml,_fMeasureLatency ? "true\n" : "false\n"); + AddXml(sXml, _fCalculateIopsStdDev ? "true\n" : "false\n"); + AddXml(sXml, _fDisableAffinity ? "true\n" : "false\n"); sprintf_s(buffer, _countof(buffer), "%u\n", _ulDuration); - sXml += buffer; + AddXml(sXml, buffer); sprintf_s(buffer, _countof(buffer), "%u\n", _ulWarmUp); - sXml += buffer; + AddXml(sXml, buffer); sprintf_s(buffer, _countof(buffer), "%u\n", _ulCoolDown); - sXml += buffer; + AddXml(sXml, buffer); sprintf_s(buffer, _countof(buffer), "%u\n", _dwThreadCount); - sXml += buffer; + AddXml(sXml, buffer); sprintf_s(buffer, _countof(buffer), "%u\n", _dwRequestCount); - sXml += buffer; + AddXml(sXml, buffer); sprintf_s(buffer, _countof(buffer), "%u\n", _ulIoBucketDurationInMilliseconds); - sXml += buffer; + AddXml(sXml, buffer); sprintf_s(buffer, _countof(buffer), "%u\n", _ulRandSeed); - sXml += buffer; + AddXml(sXml, buffer); if (_vAffinity.size() > 0) { - sXml += "\n"; + AddXmlInc(sXml, "\n"); for (const auto& a : _vAffinity) { sprintf_s(buffer, _countof(buffer), "\n", a.wGroup, a.bProc); - sXml += buffer; + AddXml(sXml, buffer); } - sXml += "\n"; + AddXmlDec(sXml, "\n"); } - sXml += "\n"; + AddXmlInc(sXml, "\n"); for (const auto& target : _vTargets) { - sXml += target.GetXml(); + sXml += target.GetXml(indent); } - sXml += "\n"; - sXml += "\n"; + AddXmlDec(sXml, "\n"); + AddXmlDec(sXml, "\n"); return sXml; } @@ -603,66 +662,68 @@ void TimeSpan::MarkFilesAsPrecreated(const vector vFiles) } } -string Profile::GetXml() const +string Profile::GetXml(UINT32 indent) const { - string sXml("\n"); + string sXml; char buffer[4096]; + AddXmlInc(sXml, "\n"); + sprintf_s(buffer, _countof(buffer), "%u\n", _dwProgress); - sXml += buffer; + AddXml(sXml, buffer); if (_resultsFormat == ResultsFormat::Text) { - sXml += "text\n"; + AddXml(sXml, "text\n"); } else if (_resultsFormat == ResultsFormat::Xml) { - sXml += "xml\n"; + AddXml(sXml, "xml\n"); } else { - sXml += "* UNSUPPORTED *\n"; + AddXml(sXml, "* UNSUPPORTED *\n"); } - sXml += _fVerbose ? "true\n" : "false\n"; + AddXml(sXml, _fVerbose ? "true\n" : "false\n"); if (_precreateFiles == PrecreateFiles::UseMaxSize) { - sXml += "UseMaxSize\n"; + AddXml(sXml, "UseMaxSize\n"); } else if (_precreateFiles == PrecreateFiles::OnlyFilesWithConstantSizes) { - sXml += "CreateOnlyFilesWithConstantSizes\n"; + AddXml(sXml, "CreateOnlyFilesWithConstantSizes\n"); } else if (_precreateFiles == PrecreateFiles::OnlyFilesWithConstantOrZeroSizes) { - sXml += "CreateOnlyFilesWithConstantOrZeroSizes\n"; + AddXml(sXml, "CreateOnlyFilesWithConstantOrZeroSizes\n"); } if (_fEtwEnabled) { - sXml += "\n"; - sXml += _fEtwProcess ? "true\n" : "false\n"; - sXml += _fEtwThread ? "true\n" : "false\n"; - sXml += _fEtwImageLoad ? "true\n" : "false\n"; - sXml += _fEtwDiskIO ? "true\n" : "false\n"; - sXml += _fEtwMemoryPageFaults ? "true\n" : "false\n"; - sXml += _fEtwMemoryHardFaults ? "true\n" : "false\n"; - sXml += _fEtwNetwork ? "true\n" : "false\n"; - sXml += _fEtwRegistry ? "true\n" : "false\n"; - sXml += _fEtwUsePagedMemory ? "true\n" : "false\n"; - sXml += _fEtwUsePerfTimer ? "true\n" : "false\n"; - sXml += _fEtwUseSystemTimer ? "true\n" : "false\n"; - sXml += _fEtwUseCyclesCounter ? "true\n" : "false\n"; - sXml += "\n"; + AddXmlInc(sXml, "\n"); + AddXml(sXml, _fEtwProcess ? "true\n" : "false\n"); + AddXml(sXml, _fEtwThread ? "true\n" : "false\n"); + AddXml(sXml, _fEtwImageLoad ? "true\n" : "false\n"); + AddXml(sXml, _fEtwDiskIO ? "true\n" : "false\n"); + AddXml(sXml, _fEtwMemoryPageFaults ? "true\n" : "false\n"); + AddXml(sXml, _fEtwMemoryHardFaults ? "true\n" : "false\n"); + AddXml(sXml, _fEtwNetwork ? "true\n" : "false\n"); + AddXml(sXml, _fEtwRegistry ? "true\n" : "false\n"); + AddXml(sXml, _fEtwUsePagedMemory ? "true\n" : "false\n"); + AddXml(sXml, _fEtwUsePerfTimer ? "true\n" : "false\n"); + AddXml(sXml, _fEtwUseSystemTimer ? "true\n" : "false\n"); + AddXml(sXml, _fEtwUseCyclesCounter ? "true\n" : "false\n"); + AddXmlDec(sXml, "\n"); } - sXml += "\n"; + AddXmlInc(sXml, "\n"); for (const auto& timespan : _vTimeSpans) { - sXml += timespan.GetXml(); + sXml += timespan.GetXml(indent); } - sXml += "\n"; - sXml += "\n"; + AddXmlDec(sXml, "\n"); + AddXmlDec(sXml, "\n"); return sXml; } @@ -792,16 +853,7 @@ bool Profile::Validate(bool fSingleSpec, SystemInformation *pSystem) const fOk = false; } - // FIXME: we can no longer do this check, because the target no longer - // contains a property that uniquely identifies the case where "-s" or - // was passed. -#if 0 - if (target.GetUseRandomAccessPattern() && target.GetEnableCustomStrideSize()) - { - fprintf(stderr, "WARNING: -s is ignored if -r is provided\n"); - } -#endif - if (target.GetUseRandomAccessPattern()) + if (target.GetRandomRatio()) { if (target.GetThreadStrideInBytes() > 0) { @@ -824,15 +876,15 @@ bool Profile::Validate(bool fSingleSpec, SystemInformation *pSystem) const } else { - if (target.GetUseParallelAsyncIO() && target.GetRequestCount() == 1) + if (target.GetDistributionRange().size() != 0) { - fprintf(stderr, "WARNING: -p does not have effect unless outstanding I/O count (-o) is > 1\n"); + fprintf(stderr, "ERROR: random distribution ranges (-rd) do not apply to sequential-only IO patterns\n"); + fOk = false; } - - if (timeSpan.GetRandSeed() > 0) + + if (target.GetUseParallelAsyncIO() && target.GetRequestCount() == 1) { - fprintf(stderr, "WARNING: -z is ignored if -r is not provided\n"); - // although ulRandSeed==0 is a valid value, it's interpreted as "not provided" for this warning + fprintf(stderr, "WARNING: -p does not have effect unless outstanding I/O count (-o) is > 1\n"); } if (target.GetUseInterlockedSequential()) @@ -869,6 +921,78 @@ bool Profile::Validate(bool fSingleSpec, SystemInformation *pSystem) const } } + // Distribution ranges are only applied to random loads. Note validation failure in the sequential case. + // TBD this should be moved to a proper Distribution class. + { + UINT32 ioAcc = 0; + UINT64 targetAcc = 0; + bool absZero = false, absZeroLast = false; + for (const auto& r : target.GetDistributionRange()) + { + if (target.GetDistributionType() == DistributionType::Absolute) + { + // allow zero target span in last position + absZeroLast = false; + if (r._dst.second == 0 && !absZero) + { + // legal in last position + absZero = absZeroLast = true; + } + else if (r._dst.second < target.GetBlockSizeInBytes()) + { + fprintf(stderr, "ERROR: invalid random distribution target range %I64u - must be a minimum of the specified block size (%u bytes)\n", r._dst.second, target.GetBlockSizeInBytes()); + fOk = false; + break; + } + } + + // Validate accumulating IO% + if (ioAcc + r._span > 100) + { + fprintf(stderr, "ERROR: invalid random distribution IO%% %u: can be at most %u - total must be <= 100%%\n", r._span, 100 - ioAcc); + fOk = false; + break; + } + + // Validate accumulating Target% + // 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) + { + 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; + break; + } + + // Consuming the target before consuming the IO is invalid. + // No holes in IO. + if (targetAcc + r._dst.second == 100 && ioAcc + r._span < 100) + { + fprintf(stderr, "ERROR: invalid random distribution: the target is covered with %u%% IO left to distribute\n", 100 - (ioAcc + r._span)); + fOk = false; + break; + } + } + + ioAcc += r._span; + targetAcc += r._dst.second; + } + + // Percent dist Target% must sum to 100%. IO% underflow (either due to early Target% 100% or Target% overflow) is handled above. + if (target.GetDistributionType() == DistributionType::Percent && + targetAcc != 100) + { + fprintf(stderr, "ERROR: invalid random distribution span: Target%% (%I64u%%) must total 100%%\n", targetAcc); + fOk = false; + } + if (absZero && !absZeroLast) + { + fprintf(stderr, "ERROR: invalid zero target range in random distribution - must be a minimum of the specified block size (%u bytes)\n", target.GetBlockSizeInBytes()); + fOk = false; + } + } + if (target.GetRandomDataWriteBufferSize() > 0) { if (target.GetRandomDataWriteBufferSize() < target.GetBlockSizeInBytes()) @@ -898,6 +1022,18 @@ bool Profile::Validate(bool fSingleSpec, SystemInformation *pSystem) const target.GetMemoryMappedIoFlushMode() != MemoryMappedIoFlushMode::Undefined) { fprintf(stderr, "ERROR: memory mapped flush mode (-N) can only be specified with memory mapped IO (-Sm)\n"); + fOk = false; + } + + if (GetProfileOnly() == false) + { + auto sPath = target.GetPath(); + + if (sPath[0] == TEMPLATE_TARGET_PREFIX) + { + fprintf(stderr, "ERROR: template target '%s' was not substituted - all template targets must be substituted to run a profile\n", sPath.c_str()); + fOk = false; + } } // in the cases where there is only a single configuration specified for each target (e.g., cmdline), diff --git a/Common/Common.h b/Common/Common.h index 2120830..e827953 100644 --- a/Common/Common.h +++ b/Common/Common.h @@ -35,6 +35,7 @@ SOFTWARE. #include #include #include +#include #include //ntdll.dll #include #include "Histogram.h" @@ -54,12 +55,12 @@ TRACELOGGING_DECLARE_PROVIDER(g_hEtwProvider); // // Monday, June 16, 2014 12:00:00 AM -#define DISKSPD_RELEASE_TAG "" -#define DISKSPD_REVISION "a" +#define DISKSPD_RELEASE_TAG "-dev" +#define DISKSPD_REVISION "" #define DISKSPD_MAJOR 2 -#define DISKSPD_MINOR 0 -#define DISKSPD_BUILD 21 +#define DISKSPD_MINOR 1 +#define DISKSPD_BUILD 0 #define DISKSPD_QFE 0 #define DISKSPD_MAJORMINOR_VER_STR(x,y,z) #x "." #y "." #z @@ -67,7 +68,7 @@ TRACELOGGING_DECLARE_PROVIDER(g_hEtwProvider); #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 "2018/9/21" +#define DISKSPD_DATE_VERSION_STRING "2021/7/1" #define DISKSPD_TRACE_INFO 0x00000000 #define DISKSPD_TRACE_RESERVED 0x00000001 @@ -75,6 +76,17 @@ TRACELOGGING_DECLARE_PROVIDER(g_hEtwProvider); 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 @@ -150,6 +162,7 @@ namespace UnitTests class PerfTimerUnitTests; class ProfileUnitTests; class TargetUnitTests; + class IORequestGeneratorUnitTests; } class PerfTimer @@ -177,6 +190,78 @@ class PerfTimer 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 @@ -225,12 +310,79 @@ class Util { 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 { - ReadIO = 1, + Unknown = 0, + ReadIO, WriteIO }; @@ -325,6 +477,9 @@ class TargetResults IoBucketizer readBucketizer; IoBucketizer writeBucketizer; + + // Effective distribution after applying to target size (if specified/non-empty) + vector vDistributionRange; }; class ThreadResults @@ -635,6 +790,18 @@ class ProcessorTopology } }; +// +// 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 { @@ -698,24 +865,26 @@ class SystemInformation return sText; } - string SystemInformation::GetXml() const + string SystemInformation::GetXml(UINT32 indent) const { char szBuffer[64]; // enough for 64bit mask (17ch) and timestamp int nWritten; - string sXml("\n"); + string sXml; + + AddXmlInc(sXml, "\n"); // identify computer which ran the test - sXml += ""; + AddXml(sXml, ""); sXml += sComputerName; sXml += "\n"; // identify tool version which performed the test - sXml += "\n"; - sXml += "" DISKSPD_NUMERIC_VERSION_STRING "\n"; - sXml += "" DISKSPD_DATE_VERSION_STRING "\n"; - sXml += "\n"; - - sXml += ""; + 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), @@ -732,10 +901,10 @@ class SystemInformation sXml += "\n"; // processor topology - sXml += "\n"; + AddXmlInc(sXml, "\n"); for (const auto& g : processorTopology._vProcessorGroupInformation) { - sXml += "\n"); for (const auto& g : s._vProcessorMasks) { - sXml += "\n"; } - sXml += "\n"; + AddXmlDec(sXml, "\n"); } for (const auto& h : processorTopology._vProcessorHyperThreadInformation) { - sXml += "\n"; } - sXml += "\n"; - sXml += "\n"; + AddXmlDec(sXml, "\n"); + AddXmlDec(sXml, "\n"); return sXml; } @@ -848,6 +1017,16 @@ enum class MemoryMappedIoFlushMode { NonVolatileMemoryNoDrain, }; +enum class IOMode +{ + Unknown, + Random, + Sequential, + Mixed, + InterlockedSequential, + ParallelAsync +}; + class ThreadTarget { public: @@ -864,13 +1043,16 @@ class ThreadTarget void SetWeight(UINT32 ulWeight) { _ulWeight = ulWeight; } UINT32 GetWeight() const { return _ulWeight; } - string GetXml() const; + 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: @@ -878,9 +1060,9 @@ class Target Target() : _dwBlockSize(64 * 1024), _dwRequestCount(2), - _ullBlockAlignment(64 * 1024), - _fBlockAlignmentValid(false), - _fUseRandomAccessPattern(false), + _ullBlockAlignment(0), + _ulWriteRatio(0), + _ulRandomRatio(0), _ullBaseFileOffset(0), _fParallelAsyncIO(false), _fInterlockedSequential(false), @@ -896,7 +1078,6 @@ class Target _fPrecreated(false), _ullFileSize(0), _ullMaxFileSize(0), - _ulWriteRatio(0), _fUseBurstSize(false), _dwBurstSize(0), _dwThinkTime(0), @@ -910,14 +1091,41 @@ class Target _ioPriorityHint(IoPriorityHintNormal), _ulWeight(1), _dwThroughputBytesPerMillisecond(0), + _dwThroughputIOPS(0), _cbRandomDataWriteBuffer(0), _sRandomDataWriteBufferSourcePath(), - _pRandomDataWriteBuffer(nullptr) + _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(string sPath) { _sPath = sPath; } - string GetPath() const { return _sPath; } + 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; } @@ -925,20 +1133,25 @@ class Target void SetBlockAlignmentInBytes(UINT64 ullBlockAlignment) { _ullBlockAlignment = ullBlockAlignment; - _fBlockAlignmentValid = true; } - - UINT64 GetBlockAlignmentInBytes() const + // actual is used in validation to detect unclear/mis-specified intent + // like -rs -s + UINT64 GetBlockAlignmentInBytes(bool actual = false) const { - return _fBlockAlignmentValid ? _ullBlockAlignment : _dwBlockSize; + return _ullBlockAlignment ? _ullBlockAlignment : (actual ? 0 : _dwBlockSize); } - - void SetUseRandomAccessPattern(bool fBool) { _fUseRandomAccessPattern = fBool; } - bool GetUseRandomAccessPattern() const { return _fUseRandomAccessPattern; } + + 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 GetThreadBaseFileOffsetInBytes(UINT32 ulThreadNo) { return _ullBaseFileOffset + ulThreadNo * _ullThreadStride; } + 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; } @@ -959,10 +1172,10 @@ class Target TargetCacheMode GetCacheMode() const { return _cacheMode; } void SetWriteThroughMode(WriteThroughMode writeThroughMode ) { _writeThroughMode = writeThroughMode; } - WriteThroughMode GetWriteThroughMode( ) const { return _writeThroughMode; } + WriteThroughMode GetWriteThroughMode() const { return _writeThroughMode; } void SetMemoryMappedIoMode(MemoryMappedIoMode memoryMappedIoMode ) { _memoryMappedIoMode = memoryMappedIoMode; } - MemoryMappedIoMode GetMemoryMappedIoMode( ) const { return _memoryMappedIoMode; } + MemoryMappedIoMode GetMemoryMappedIoMode() const { return _memoryMappedIoMode; } void SetMemoryMappedIoNvToken(PVOID memoryMappedIoNvToken) { _memoryMappedIoNvToken = memoryMappedIoNvToken; } PVOID GetMemoryMappedIoNvToken() const { return _memoryMappedIoNvToken; } @@ -1003,9 +1216,6 @@ class Target void SetMaxFileSize(UINT64 ullMaxFileSize) { _ullMaxFileSize = ullMaxFileSize; } UINT64 GetMaxFileSize() const { return _ullMaxFileSize; } - void SetWriteRatio(UINT32 ulWriteRatio) { _ulWriteRatio = ulWriteRatio; } - UINT32 GetWriteRatio() const { return _ulWriteRatio; } - void SetUseParallelAsyncIO(bool fBool) { _fParallelAsyncIO = fBool; } bool GetUseParallelAsyncIO() const { return _fParallelAsyncIO; } @@ -1040,15 +1250,62 @@ class Target void SetPrecreated(bool fBool) { _fPrecreated = fBool; } bool GetPrecreated() const { return _fPrecreated; } - void SetThroughput(DWORD dwThroughputBytesPerMillisecond) { _dwThroughputBytesPerMillisecond = dwThroughputBytesPerMillisecond; } + // 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() const; + 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; @@ -1092,39 +1349,39 @@ class Target DWORD _dwRequestCount; // TODO: change the name to something more descriptive (OutstandingRequestCount?) UINT64 _ullBlockAlignment; - bool _fBlockAlignmentValid; - bool _fUseRandomAccessPattern; + UINT32 _ulWriteRatio; + UINT32 _ulRandomRatio; UINT64 _ullBaseFileOffset; - bool _fParallelAsyncIO; - bool _fInterlockedSequential; TargetCacheMode _cacheMode; WriteThroughMode _writeThroughMode; MemoryMappedIoMode _memoryMappedIoMode; MemoryMappedIoFlushMode _memoryMappedIoFlushMode; PVOID _memoryMappedIoNvToken; - bool _fZeroWriteBuffers; DWORD _dwThreadsPerFile; UINT64 _ullThreadStride; - bool _fCreateFile; - bool _fPrecreated; // used to track which files have been created before the first timespan and which have to be created later UINT64 _ullFileSize; UINT64 _ullMaxFileSize; - UINT32 _ulWriteRatio; - bool _fUseBurstSize; // TODO: "use" or "enable"?; since burst size must be specified with the think time, one variable should be sufficient DWORD _dwBurstSize; // number of IOs in a burst DWORD _dwThinkTime; // time to pause before issuing the next burst of IOs - // TODO: could this be removed by using _dwThinkTime==0? - bool _fThinkTime; //variable to decide whether to think between IOs (default is false) - DWORD _dwThroughputBytesPerMillisecond; // set to 0 to disable throttling - bool _fSequentialScanHint; // open file with the FILE_FLAG_SEQUENTIAL_SCAN hint - bool _fRandomAccessHint; // open file with the FILE_FLAG_RANDOM_ACCESS hint - bool _fTemporaryFileHint; // open file with the FILE_ATTRIBUTE_TEMPORARY hint - bool _fUseLargePages; // Use large pages for IO buffers + 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) @@ -1137,6 +1394,9 @@ class Target UINT32 _ulWeight; vector _vThreadTargets; + + vector _vDistributionRange; + DistributionType _distributionType; bool _FillRandomDataWriteBuffer(Random *pRand); @@ -1230,7 +1490,7 @@ class TimeSpan void SetIoBucketDurationInMilliseconds(UINT32 ulIoBucketDurationInMilliseconds) { _ulIoBucketDurationInMilliseconds = ulIoBucketDurationInMilliseconds; } UINT32 GetIoBucketDurationInMilliseconds() const { return _ulIoBucketDurationInMilliseconds; } - string GetXml() const; + string GetXml(UINT32 indent) const; void MarkFilesAsPrecreated(const vector vFiles); private: @@ -1270,6 +1530,7 @@ class Profile { public: Profile() : + _fProfileOnly(false), _fVerbose(false), _dwProgress(0), _fEtwEnabled(false), @@ -1302,6 +1563,9 @@ class Profile 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; } @@ -1346,7 +1610,7 @@ class Profile bool GetEtwUseSystemTimer() const { return _fEtwUseSystemTimer; } bool GetEtwUseCyclesCounter() const { return _fEtwUseCyclesCounter; } - string GetXml() const; + string GetXml(UINT32 indent) const; bool Validate(bool fSingleSpec, SystemInformation *pSystem = nullptr) const; void MarkFilesAsPrecreated(const vector vFiles); @@ -1355,6 +1619,7 @@ class Profile vector_vTimeSpans; bool _fVerbose; + bool _fProfileOnly; DWORD _dwProgress; string _sCmdLine; ResultsFormat _resultsFormat; @@ -1392,8 +1657,6 @@ class IORequest _ActivityId() { memset(&_overlapped, 0, sizeof(OVERLAPPED)); - _overlapped.Offset = 0xFFFFFFFF; - _overlapped.OffsetHigh = 0xFFFFFFFF; } static IORequest *OverlappedToIORequest(OVERLAPPED *pOverlapped) @@ -1476,6 +1739,9 @@ typedef struct _ACTIVITY_ID { C_ASSERT(sizeof(ACTIVITY_ID) == sizeof(GUID)); +// Forward declaration +class ThreadTargetState; + class ThreadParameters { public: @@ -1493,16 +1759,13 @@ class ThreadParameters const TimeSpan *pTimeSpan; vector vTargets; + vector vTargetStates; vector vhTargets; - vector vullFileSizes; + vector vulReadBufferSize; vector vpDataBuffers; vector vIORequest; vector vThroughputMeters; - - // For vanilla sequential access (-s): - // Private per-thread offsets, incremented directly, indexed to number of targets - vector vullPrivateSequentialOffsets; // For interlocked sequential access (-si): // Pointers to offsets shared between threads, incremented with an interlocked op @@ -1555,10 +1818,582 @@ class ThreadParameters 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(Profile& profile, const SystemInformation& system, vector vResults) = 0; + virtual string ParseResults(const Profile& profile, const SystemInformation& system, vector vResults) = 0; + virtual string ParseProfile(const Profile& profile) = 0; }; class EtwResultParser diff --git a/Common/IORequestGenerator.h b/Common/IORequestGenerator.h index 874fae3..7d02bb1 100644 --- a/Common/IORequestGenerator.h +++ b/Common/IORequestGenerator.h @@ -34,14 +34,11 @@ SOFTWARE. void PrintError(const char *format, ...); -void *ManagedMalloc(size_t size); namespace UnitTests { class IORequestGeneratorUnitTests; } -#define FIRST_OFFSET 0xFFFFFFFFFFFFFFFFULL - class IORequestGenerator { public: @@ -51,8 +48,7 @@ class IORequestGenerator } - bool GenerateRequests(Profile& profile, IResultParser& resultParser, PRINTF pPrintOut, PRINTF pPrintError, PRINTF pPrintVerbose, struct Synchronization *pSynch); - static UINT64 GetNextFileOffset(ThreadParameters& tp, size_t targetNum, UINT64 prevOffset); + bool GenerateRequests(Profile& profile, IResultParser& resultParser, struct Synchronization *pSynch); private: @@ -68,7 +64,6 @@ class IORequestGenerator void _CloseOpenFiles(vector& vhFiles) const; DWORD _CreateDirectoryPath(const char *path) const; bool _CreateFile(UINT64 ullFileSize, const char *pszFilename, bool fZeroBuffers, bool fVerbose) const; - void _DisplayFileSizeVerbose(bool fVerbose, UINT64 fsize) const; bool _GetActiveGroupsAndProcs() const; struct ETWSessionInfo _GetResultETWSession(const EVENT_TRACE_PROPERTIES *pTraceProperties) const; bool _GetSystemPerfInfo(SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION *pInfo, UINT32 uCpuCount) const; diff --git a/Common/IoBucketizer.cpp b/Common/IoBucketizer.cpp index d15e9f5..672eb54 100644 --- a/Common/IoBucketizer.cpp +++ b/Common/IoBucketizer.cpp @@ -28,6 +28,7 @@ SOFTWARE. */ #include "IoBucketizer.h" +#include /* Calculating stddev using an online algorithm: diff --git a/Common/ResultParser.h b/Common/ResultParser.h index 560448e..7858cbd 100644 --- a/Common/ResultParser.h +++ b/Common/ResultParser.h @@ -38,10 +38,11 @@ namespace UnitTests class ResultParser : public IResultParser { public: - string ParseResults(Profile& profile, const SystemInformation& system, vector vResults); + string ParseResults(const Profile& profile, const SystemInformation& system, vector vResults); + string ParseProfile(const Profile& profile); private: - void _DisplayFileSize(UINT64 fsize); + void _DisplayFileSize(UINT64 fsize, UINT32 align = 0); void _DisplayETWSessionInfo(struct ETWSessionInfo sessionInfo); void _DisplayETW(struct ETWMask ETWMask, struct ETWEventCounters EtwEventCounters); void _Print(const char *format, ...); @@ -58,6 +59,8 @@ class ResultParser : public IResultParser const Histogram& totalLatencyHistogram); void _PrintTimeSpan(const TimeSpan &timeSpan); 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); string _sResult; diff --git a/Common/XmlProfileParser.h b/Common/XmlProfileParser.h index 6c1004d..0d1ab3e 100644 --- a/Common/XmlProfileParser.h +++ b/Common/XmlProfileParser.h @@ -34,20 +34,23 @@ SOFTWARE. class XmlProfileParser { public: - bool ParseFile(const char *pszPath, Profile *pProfile, HMODULE hModule); + bool ParseFile(const char *pszPath, Profile *pProfile, vector *pvSubstTargets, HMODULE hModule); private: HRESULT _ParseEtw(IXMLDOMDocument2 *pXmlDoc, Profile *pProfile); - HRESULT _ParseTimeSpans(IXMLDOMDocument2 *pXmlDoc, Profile *pProfile); - HRESULT _ParseTimeSpan(IXMLDOMNode *pXmlNode, TimeSpan *pTimeSpan); - HRESULT _ParseTargets(IXMLDOMNode *pXmlNode, TimeSpan *pTimeSpan); + HRESULT _ParseTimeSpans(IXMLDOMDocument2 *pXmlDoc, Profile *pProfile, vector>& vSubsts); + HRESULT _ParseTimeSpan(IXMLDOMNode *pXmlNode, TimeSpan *pTimeSpan, vector>& vSubsts); + HRESULT _ParseTargets(IXMLDOMNode *pXmlNode, TimeSpan *pTimeSpan, vector>& vSubsts); HRESULT _ParseRandomDataSource(IXMLDOMNode *pXmlNode, Target *pTarget); HRESULT _ParseWriteBufferContent(IXMLDOMNode *pXmlNode, Target *pTarget); HRESULT _ParseTarget(IXMLDOMNode *pXmlNode, Target *pTarget); HRESULT _ParseThreadTargets(IXMLDOMNode *pXmlNode, Target *pTarget); + HRESULT _ParseThroughput(IXMLDOMNode *pXmlNode, Target *pTarget); HRESULT _ParseThreadTarget(IXMLDOMNode *pXmlNode, ThreadTarget *pThreadTarget); HRESULT _ParseAffinityAssignment(IXMLDOMNode *pXmlNode, TimeSpan *pTimeSpan); HRESULT _ParseAffinityGroupAssignment(IXMLDOMNode *pXmlNode, TimeSpan *pTimeSpan); + HRESULT _ParseDistribution(IXMLDOMNode *pXmlNode, Target *pTarget); + HRESULT _SubstTarget(Target *pTarget, vector>& vSubsts); HRESULT _GetString(IXMLDOMNode *pXmlNode, const char *pszQuery, string *psValue) const; HRESULT _GetUINT32(IXMLDOMNode *pXmlNode, const char *pszQuery, UINT32 *pulValue) const; @@ -59,4 +62,4 @@ class XmlProfileParser HRESULT _GetVerbose(IXMLDOMDocument2 *pXmlDoc, bool *pfVerbose); HRESULT _GetProgress(IXMLDOMDocument2 *pXmlDoc, DWORD *pdwProgress); -}; +}; \ No newline at end of file diff --git a/Common/XmlResultParser.h b/Common/XmlResultParser.h index 1a9df0c..79e37b1 100644 --- a/Common/XmlResultParser.h +++ b/Common/XmlResultParser.h @@ -33,7 +33,8 @@ SOFTWARE. class XmlResultParser: public IResultParser { public: - string ParseResults(Profile& profile, const SystemInformation& system, vector vResults); + string ParseResults(const Profile& profile, const SystemInformation& system, vector vResults); + string ParseProfile(const Profile& profile); private: void _PrintCpuUtilization(const Results& results, const SystemInformation& system); @@ -45,7 +46,11 @@ class XmlResultParser: public IResultParser 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; }; \ No newline at end of file diff --git a/IORequestGenerator/IORequestGenerator.cpp b/IORequestGenerator/IORequestGenerator.cpp index abecb6a..120aaa1 100644 --- a/IORequestGenerator/IORequestGenerator.cpp +++ b/IORequestGenerator/IORequestGenerator.cpp @@ -333,7 +333,7 @@ bool SetPrivilege(LPCSTR pszPrivilege, LPCSTR pszErrorPrefix = "ERROR:") { CloseHandle(hToken); } - + return fOk; } @@ -429,10 +429,6 @@ static RtlGetNvToken g_pfnRtlGetNonVolatileToken; typedef NTSTATUS(__stdcall *RtlFreeNvToken)(PVOID); static RtlFreeNvToken g_pfnRtlFreeNonVolatileToken; -static PRINTF g_pfnPrintOut = nullptr; -static PRINTF g_pfnPrintError = nullptr; -static PRINTF g_pfnPrintVerbose = nullptr; - 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 @@ -467,50 +463,29 @@ void IORequestGenerator::_CloseOpenFiles(vector& vhFiles) const } /*****************************************************************************/ -// wrapper for pfnPrintOut. printf cannot be used directly, because IORequestGenerator.dll -// may be consumed by gui app which doesn't have stdout -static void print(const char *format, ...) -{ - assert(NULL != format); - - if( NULL != g_pfnPrintOut ) - { - va_list listArg; - va_start(listArg, format); - g_pfnPrintOut(format, listArg); - va_end(listArg); - } -} - -/*****************************************************************************/ -// wrapper for pfnPrintError. fprintf(stderr) cannot be used directly, because IORequestGenerator.dll -// may be consumed by gui app which doesn't have stdout +// wrapper for stderr void PrintError(const char *format, ...) { assert(NULL != format); - if( NULL != g_pfnPrintError ) - { - va_list listArg; - - va_start(listArg, format); - g_pfnPrintError(format, listArg); - va_end(listArg); - } + 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 printfv(bool fVerbose, const char *format, ...) +static void PrintVerbose(bool fVerbose, const char *format, ...) { assert(NULL != format); - if( NULL != g_pfnPrintVerbose && fVerbose ) + if(fVerbose ) { va_list argList; va_start(argList, format); - g_pfnPrintVerbose(format, argList); + vprintf(format, argList); va_end(argList); } } @@ -529,29 +504,6 @@ DWORD WINAPI etwThreadFunc(LPVOID cookie) return result ? 0 : 1; } -/*****************************************************************************/ -// display file size in a user-friendly form using 'verbose' stream -// -void IORequestGenerator::_DisplayFileSizeVerbose(bool fVerbose, UINT64 fsize) const -{ - if( fsize > (UINT64)10*1024*1024*1024 ) // > 10GB - { - printfv(fVerbose, "%I64uGB", fsize >> 30); - } - else if( fsize > (UINT64)10*1024*1024 ) // > 10MB - { - printfv(fVerbose, "%I64uMB", fsize >> 20); - } - else if( fsize > 10*1024 ) // > 10KB - { - printfv(fVerbose, "%I64uKB", fsize >> 10); - } - else - { - printfv(fVerbose, "%I64uB", fsize); - } -} - /*****************************************************************************/ bool IORequestGenerator::_LoadDLLs() { @@ -593,15 +545,15 @@ bool IORequestGenerator::_GetSystemPerfInfo(SYSTEM_PROCESSOR_PERFORMANCE_INFORMA 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++) @@ -632,103 +584,13 @@ bool IORequestGenerator::_GetSystemPerfInfo(SYSTEM_PROCESSOR_PERFORMANCE_INFORMA break; } } - + uCpuCtr += pGroup->_maximumProcessorCount; } return fOk; } -/*****************************************************************************/ -// calculate the offset of the next I/O operation -// - -__inline UINT64 IORequestGenerator::GetNextFileOffset(ThreadParameters& tp, size_t targetNum, UINT64 prevOffset) -{ - Target &target = tp.vTargets[targetNum]; - - UINT64 blockAlignment = target.GetBlockAlignmentInBytes(); - UINT64 baseFileOffset = target.GetBaseFileOffsetInBytes(); - UINT64 baseThreadOffset = target.GetThreadBaseFileOffsetInBytes(tp.ulRelativeThreadNo); - UINT64 blockSize = target.GetBlockSizeInBytes(); - UINT64 nextBlockOffset; - - // now apply bounds for IO offset - // aligned target size is the closed interval of byte offsets at which it is legal to issue IO - // ISSUE IMPROVEMENT: much of this should be precalculated. It belongs within Target, which will - // need discovery of target sizing moved from its current just-in-time at thread launch. - UINT64 alignedTargetSize = tp.vullFileSizes[targetNum] - baseFileOffset - blockSize; - - if (target.GetUseRandomAccessPattern() || - target.GetUseInterlockedSequential()) - { - // convert aligned target size to the open interval - alignedTargetSize = ((alignedTargetSize / blockAlignment) + 1) * blockAlignment; - - // increment/produce - note, logically relative to base offset - if (target.GetUseRandomAccessPattern()) - { - nextBlockOffset = tp.pRand->Rand64(); - nextBlockOffset -= (nextBlockOffset % blockAlignment); - nextBlockOffset %= alignedTargetSize; - } - else - { - nextBlockOffset = InterlockedAdd64((PLONGLONG) &tp.pullSharedSequentialOffsets[targetNum], blockAlignment) - blockAlignment; - nextBlockOffset %= alignedTargetSize; - } - } - else - { - if (prevOffset == FIRST_OFFSET) - { - nextBlockOffset = baseThreadOffset - baseFileOffset; - } - else - { - if (target.GetUseParallelAsyncIO()) - { - nextBlockOffset = prevOffset - baseFileOffset + blockAlignment; - } - else // normal sequential access pattern - { - nextBlockOffset = tp.vullPrivateSequentialOffsets[targetNum] + blockAlignment; - } - } - - // parasync and seq bases are potentially modified by threadstride and loop back to the - // file base offset + increment which will return them to their initial base offset. - if (nextBlockOffset > alignedTargetSize) { - nextBlockOffset = (baseThreadOffset - baseFileOffset) % blockAlignment; - - } - - if (!target.GetUseParallelAsyncIO()) - { - tp.vullPrivateSequentialOffsets[targetNum] = nextBlockOffset; - } - } - - // Convert into the next full offset - nextBlockOffset += baseFileOffset; - -#ifndef NDEBUG - // Don't overrun the end of the file - UINT64 fileSize = tp.vullFileSizes[targetNum]; - assert(nextBlockOffset + blockSize <= fileSize); -#endif - - return nextBlockOffset; -} - -/*****************************************************************************/ -// Decide the kind of IO to issue during a mix test -// Future Work: Add more types of distribution in addition to random -__inline static IOOperation DecideIo(Random *pRand, UINT32 ulWriteRatio) -{ - return ((pRand->Rand32() % 100 + 1) > ulWriteRatio) ? IOOperation::ReadIO : IOOperation::WriteIO; -} - VOID CALLBACK fileIOCompletionRoutine(DWORD dwErrorCode, DWORD dwBytesTransferred, LPOVERLAPPED pOverlapped); static bool issueNextIO(ThreadParameters *p, IORequest *pIORequest, DWORD *pdwBytesTransferred, bool useCompletionRoutines) @@ -740,24 +602,22 @@ static bool issueNextIO(ThreadParameters *p, IORequest *pIORequest, DWORD *pdwBy LARGE_INTEGER li; BOOL rslt = true; - li.LowPart = pOverlapped->Offset; - li.HighPart = pOverlapped->OffsetHigh; - - li.QuadPart = IORequestGenerator::GetNextFileOffset(*p, iTarget, li.QuadPart); - - pOverlapped->Offset = li.LowPart; - pOverlapped->OffsetHigh = li.HighPart; - - IOOperation readOrWrite = DecideIo(p->pRand, pTarget->GetWriteRatio()); - pIORequest->SetIoType(readOrWrite); - + // + // 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, @@ -766,18 +626,25 @@ static bool issueNextIO(ThreadParameters *p, IORequest *pIORequest, DWORD *pdwBy TraceLoggingOpcode(EVENT_TRACE_TYPE_START), TraceLoggingLevel(TRACE_LEVEL_VERBOSE), TraceLoggingUInt32(p->ulThreadNo, "Thread"), - TraceLoggingString(readOrWrite == IOOperation::ReadIO ? "Read" : "Write", "IO Type"), + 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 (readOrWrite == IOOperation::ReadIO) + + if (pIORequest->GetIoType() == IOOperation::ReadIO) { if (pTarget->GetMemoryMappedIoMode() == MemoryMappedIoMode::On) { @@ -896,7 +763,7 @@ static void completeIO(ThreadParameters *p, IORequest *pIORequest, DWORD dwBytes DWORD dwIOCnt = ++p->dwIOCnt; if (dwIOCnt % p->pProfile->GetProgress() == 0) { - print("."); + printf("."); } } } @@ -1107,7 +974,7 @@ static bool doWorkUsingCompletionRoutines(ThreadParameters *p) assert(NULL != p); bool fOk = true; BOOL rslt = FALSE; - + //start IO operations UINT32 cIORequests = (UINT32)p->vIORequest.size(); @@ -1204,9 +1071,9 @@ DWORD WINAPI threadFunc(LPVOID cookie) // A single file can be specified in multiple targets, so only open one // handle for each unique file. // - + vector vhUniqueHandles; - map< UniqueTarget, UINT32 > mHandleMap; + map mHandleMap; bool fCalculateIopsStdDev = p->pTimeSpan->GetCalculateIopsStdDev(); UINT64 ioBucketDuration = 0; @@ -1223,7 +1090,7 @@ DWORD WINAPI threadFunc(LPVOID cookie) { GROUP_AFFINITY GroupAffinity; - printfv(p->pProfile->GetVerbose(), "affinitizing thread %u to Group %u / CPU %u\n", p->ulThreadNo, p->wGroupNum, p->bProcNum); + 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(); @@ -1251,7 +1118,6 @@ DWORD WINAPI threadFunc(LPVOID cookie) UINT32 cIORequests = p->GetTotalRequestCount(); - // TODO: open files size_t iTarget = 0; for (auto pTarget = p->vTargets.begin(); pTarget != p->vTargets.end(); pTarget++) { @@ -1380,7 +1246,7 @@ DWORD WINAPI threadFunc(LPVOID cookie) goto cleanup; } } - + mHandleMap[ut] = (UINT32)vhUniqueHandles.size(); vhUniqueHandles.push_back(hFile); } @@ -1426,58 +1292,55 @@ DWORD WINAPI threadFunc(LPVOID cookie) if (0 == fsize) { // TODO: error out - PrintError("The file is too small or there has been an error during getting file size\n"); + PrintError("ERROR: target size could not be determined\n"); fOk = false; goto cleanup; } if (fsize < pTarget->GetMaxFileSize()) { - PrintError("Warning - file size is less than MaxFileSize\n"); + PrintError("WARNING: file size %I64u is less than MaxFileSize %I64u\n", fsize, pTarget->GetMaxFileSize()); } - if (pTarget->GetMaxFileSize() > 0) - { - // user wants to use only a part of the target - // if smaller, of course use the entire content - p->vullFileSizes.push_back(pTarget->GetMaxFileSize() > fsize ? fsize : pTarget->GetMaxFileSize()); - } - else - { - // the whole file will be used - p->vullFileSizes.push_back(fsize); - } + // + // Build target state. + // - UINT64 startingFileOffset = pTarget->GetThreadBaseFileOffsetInBytes(p->ulRelativeThreadNo); + p->vTargetStates.emplace_back( + p, + iTarget, + fsize); - // test whether the file is large enough for this thread to do work - if (startingFileOffset + pTarget->GetBlockSizeInBytes() >= p->vullFileSizes[iTarget]) + // + // 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 size: %I64u, base offset: %I64u block size: %u\n", + 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; } - if (pTarget->GetUseRandomAccessPattern()) + PrintVerbose(p->pProfile->GetVerbose(), "thread %u starting: file '%s' relative thread %u", + p->ulThreadNo, + pTarget->GetPath().c_str(), + p->ulRelativeThreadNo); + + if (pTarget->GetRandomRatio() > 0) { - printfv(p->pProfile->GetVerbose(), "thread %u starting: file '%s' relative thread %u random pattern\n", - p->ulThreadNo, - pTarget->GetPath().c_str(), - p->ulRelativeThreadNo); + PrintVerbose(p->pProfile->GetVerbose(), ", %u% random pattern\n", + pTarget->GetRandomRatio()); } else { - printfv(p->pProfile->GetVerbose(), "thread %u starting: file '%s' relative thread %u file offset: %I64u (starting in block: %I64u)\n", - p->ulThreadNo, - pTarget->GetPath().c_str(), - p->ulRelativeThreadNo, - startingFileOffset, - startingFileOffset / pTarget->GetBlockSizeInBytes()); + PrintVerbose(p->pProfile->GetVerbose(), ", %ssequential file offset\n", pTarget->GetUseInterlockedSequential() ? "interlocked ":""); } } @@ -1535,34 +1398,39 @@ DWORD WINAPI threadFunc(LPVOID cookie) iTarget++; } - - // TODO: copy parameters for better memory locality? + + // TODO: copy parameters for better memory locality? // TODO: tell the main thread we're ready - // TODO: wait for a signal to start - printfv(p->pProfile->GetVerbose(), "thread %u started (random seed: %u)\n", p->ulThreadNo, p->ulRandSeed); - - p->vullPrivateSequentialOffsets.clear(); - p->vullPrivateSequentialOffsets.resize(p->vTargets.size()); + 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->vullFileSizes.size(); i++) + + 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->vullFileSizes[i]; - if(fCalculateIopsStdDev) + 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) { @@ -1590,6 +1458,16 @@ DWORD WINAPI threadFunc(LPVOID cookie) } } + // + // 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); } } @@ -1599,12 +1477,17 @@ DWORD WINAPI threadFunc(LPVOID cookie) 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); } } @@ -1649,7 +1532,7 @@ DWORD WINAPI threadFunc(LPVOID cookie) { 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) { @@ -1686,14 +1569,14 @@ DWORD WINAPI threadFunc(LPVOID cookie) // // wait for a signal to start // - printfv(p->pProfile->GetVerbose(), "thread %u: waiting for a signal to start\n", p->ulThreadNo); + 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; } - printfv(p->pProfile->GetVerbose(), "thread %u: received signal to start\n", p->ulThreadNo); + PrintVerbose(p->pProfile->GetVerbose(), "thread %u: received signal to start\n", p->ulThreadNo); //check if everything is ok if (g_bError) @@ -1813,12 +1696,12 @@ DWORD IORequestGenerator::_CreateDirectoryPath(const char *pszPath) const { return ERROR_NOT_SUPPORTED; } - + if (strcpy_s(dirPath, _countof(dirPath), pszPath) != 0) { return ERROR_BUFFER_OVERFLOW; } - + c = dirPath; while('\0' != *c) { @@ -1839,7 +1722,7 @@ DWORD IORequestGenerator::_CreateDirectoryPath(const char *pszPath) const *c = L'\\'; } } - + c++; } @@ -1852,7 +1735,7 @@ DWORD IORequestGenerator::_CreateDirectoryPath(const char *pszPath) const bool IORequestGenerator::_CreateFile(UINT64 ullFileSize, const char *pszFilename, bool fZeroBuffers, bool fVerbose) const { bool fSlowWrites = false; - printfv(fVerbose, "Creating file '%s' of size %I64u.\n", pszFilename, ullFileSize); + 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:")) @@ -2097,12 +1980,8 @@ bool IORequestGenerator::_PrecreateFiles(Profile& profile) const return fOk; } -bool IORequestGenerator::GenerateRequests(Profile& profile, IResultParser& resultParser, PRINTF pPrintOut, PRINTF pPrintError, PRINTF pPrintVerbose, struct Synchronization *pSynch) +bool IORequestGenerator::GenerateRequests(Profile& profile, IResultParser& resultParser, struct Synchronization *pSynch) { - g_pfnPrintOut = pPrintOut; - g_pfnPrintError = pPrintError; - g_pfnPrintVerbose = pPrintVerbose; - bool fOk = _PrecreateFiles(profile); if (fOk) { @@ -2110,7 +1989,7 @@ bool IORequestGenerator::GenerateRequests(Profile& profile, IResultParser& resul vector vResults(vTimeSpans.size()); for (size_t i = 0; fOk && (i < vTimeSpans.size()); i++) { - printfv(profile.GetVerbose(), "Generating requests for timespan %u.\n", i + 1); + PrintVerbose(profile.GetVerbose(), "Generating requests for timespan %u.\n", i + 1); fOk = _GenerateRequestsForTimeSpan(profile, vTimeSpans[i], vResults[i], pSynch); } @@ -2118,7 +1997,8 @@ bool IORequestGenerator::GenerateRequests(Profile& profile, IResultParser& resul SystemInformation system; EtwResultParser::ParseResults(vResults); string sResults = resultParser.ParseResults(profile, system, vResults); - print("%s", sResults.c_str()); + printf("%s", sResults.c_str()); + fflush(stdout); } return fOk; @@ -2269,7 +2149,7 @@ bool IORequestGenerator::_GenerateRequestsForTimeSpan(const Profile& profile, co results.vThreadResults.resize(cThreads); for (UINT32 iThread = 0; iThread < cThreads; ++iThread) { - printfv(profile.GetVerbose(), "creating thread %u\n", iThread); + PrintVerbose(profile.GetVerbose(), "creating thread %u\n", iThread); ThreadParameters *cookie = new ThreadParameters(); // threadFunc is going to free the memory if (nullptr == cookie) { @@ -2315,6 +2195,7 @@ bool IORequestGenerator::_GenerateRequestsForTimeSpan(const Profile& profile, co { if (vThreadTargets[iThreadTarget].GetThread() == iThread) { + // confirm copy constructor? cookie->vTargets.push_back(*i); break; } @@ -2346,11 +2227,12 @@ bool IORequestGenerator::_GenerateRequestsForTimeSpan(const Profile& profile, co cAssignedThreads += i->GetThreadsPerFile(); if (iThread < cAssignedThreads) { + // confirm copy constructor? cookie->vTargets.push_back(*i); cookie->pullSharedSequentialOffsets = &(*psi); ulRelativeThreadNo = (iThread - cBaseThread) % i->GetThreadsPerFile(); - printfv(profile.GetVerbose(), "thread %u is relative thread %u for %s\n", iThread, ulRelativeThreadNo, i->GetPath().c_str()); + PrintVerbose(profile.GetVerbose(), "thread %u is relative thread %u for %s\n", iThread, ulRelativeThreadNo, i->GetPath().c_str()); break; } cBaseThread += i->GetThreadsPerFile(); @@ -2431,7 +2313,7 @@ bool IORequestGenerator::_GenerateRequestsForTimeSpan(const Profile& profile, co BOOL bBreak = FALSE; PEVENT_TRACE_PROPERTIES pETWSession = NULL; - printfv(profile.GetVerbose(), "starting warm up...\n"); + PrintVerbose(profile.GetVerbose(), "starting warm up...\n"); // // send start signal // @@ -2482,7 +2364,7 @@ bool IORequestGenerator::_GenerateRequestsForTimeSpan(const Profile& profile, co TRACEHANDLE hTraceSession = NULL; if (fUseETW) { - printfv(profile.GetVerbose(), "starting trace session\n"); + PrintVerbose(profile.GetVerbose(), "starting trace session\n"); hTraceSession = StartETWSession(profile); if (NULL == hTraceSession) { @@ -2497,10 +2379,10 @@ bool IORequestGenerator::_GenerateRequestsForTimeSpan(const Profile& profile, co _TerminateWorkerThreads(vhThreads); return false; } - printfv(profile.GetVerbose(), "tracing events\n"); + PrintVerbose(profile.GetVerbose(), "tracing events\n"); } - printfv(profile.GetVerbose(), "starting measurements...\n"); + PrintVerbose(profile.GetVerbose(), "starting measurements...\n"); // // notify the front-end that the test is about to start; @@ -2581,7 +2463,7 @@ bool IORequestGenerator::_GenerateRequestsForTimeSpan(const Profile& profile, co // if (fUseETW) { - printfv(profile.GetVerbose(), "stopping ETW session\n"); + PrintVerbose(profile.GetVerbose(), "stopping ETW session\n"); pETWSession = StopETWSession(hTraceSession); if (NULL == pETWSession) { @@ -2595,7 +2477,7 @@ bool IORequestGenerator::_GenerateRequestsForTimeSpan(const Profile& profile, co ullTimeDiff = 0; // mark that no test was run } - printfv(profile.GetVerbose(), "starting cool down...\n"); + PrintVerbose(profile.GetVerbose(), "starting cool down...\n"); if ((timeSpan.GetCooldown() > 0) && !bBreak) { TraceLoggingActivity CoolActivity; @@ -2620,7 +2502,7 @@ bool IORequestGenerator::_GenerateRequestsForTimeSpan(const Profile& profile, co TraceLoggingWriteStop(CoolActivity, "Cool Down"); } - printfv(profile.GetVerbose(), "finished test...\n"); + PrintVerbose(profile.GetVerbose(), "finished test...\n"); // // signal the threads to finish @@ -2810,5 +2692,4 @@ vector IORequestGenerator::_Get } return vFilesToCreate; -} - +} \ No newline at end of file diff --git a/README.md b/README.md index 9ecf6c4..f9d6e0a 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,23 @@ What's New? ## DISKSPD ## +DISKSPD 2.1 7/1/2021 + +* New `-gi` form allowing throughput limit specification in units of IOPS (per specified blocksize) +* New `-rs` to specify mixed random/sequential operation (pct random); geometric distribution of run lengths +* New `-rd` to specify non-uniform IO distributions across target + * `pct` by target percentage + * `abs` by absolute offset +* New `-Rp` to show specified parameter set in indicated profile output form; works with -X XML profiles and conventional command line +* XML results/profiles are now indented for ease of review +* Text result output updates + * now shows values in size units (K/M/G, and now TiB) to two decimals + * thread stride no longer shown unless specified + * -F/-O threadpool parameters shown +* XML profiles can now be built more generically + * XML profiles can be stated in terms of templated target names (*1, *2), replaced in order from command line invocation + * the command line now allows options alongside -X: -v, -z, -R and -W/-d/-C along with template target specs + DISKSPD 2.0.21a 9/21/2018 * Added support for memory mapped I/O: diff --git a/ResultParser/ResultParser.cpp b/ResultParser/ResultParser.cpp index b1872d1..a33d46a 100644 --- a/ResultParser/ResultParser.cpp +++ b/ResultParser/ResultParser.cpp @@ -32,6 +32,7 @@ SOFTWARE. #include "ResultParser.h" #include "common.h" +#include #include #include @@ -43,43 +44,85 @@ SOFTWARE. #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); - char buffer[4096] = {}; - vsprintf_s(buffer, _countof(buffer), format, listArg); + vsprintf_s(printBuffer, _countof(printBuffer), format, listArg); va_end(listArg); - _sResult += buffer; + _sResult += printBuffer; } /*****************************************************************************/ // display file size in a user-friendly form // -void ResultParser::_DisplayFileSize(UINT64 fsize) +struct { + UINT32 sizeShift; + PCHAR name; +} sizeUnits[] = { + { 40, "TiB" }, + { 30, "GiB" }, + { 20, "MiB" }, + { 10, "KiB" } +}; + +void ResultParser::_DisplayFileSize(UINT64 fsize, UINT32 align) { - if( fsize > (UINT64)10*1024*1024*1024 ) // > 10GB - { - _Print("%uGiB", fsize >> 30); - } - else if( fsize > (UINT64)10*1024*1024 ) // > 10MB - { - _Print("%uMiB", fsize >> 20); - } - else if( fsize > 10*1024 ) // > 10KB - { - _Print("%uKiB", fsize >> 10); - } - else + char fmtbuf[16]; + + for (auto& s : sizeUnits) { - _Print("%I64uB", fsize); + 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) +void ResultParser::_DisplayETWSessionInfo(struct ETWSessionInfo sessionInfo) { _Print("\n\n"); _Print(" ETW Buffer Settings & Statistics\n"); @@ -109,9 +152,8 @@ void ResultParser::_DisplayETWSessionInfo(struct ETWSessionInfo sessionInfo) sessionInfo.ulRealTimeBuffersLost); } - /*****************************************************************************/ -void ResultParser::_DisplayETW(struct ETWMask ETWMask, struct ETWEventCounters EtwEventCounters) +void ResultParser::_DisplayETW(struct ETWMask ETWMask, struct ETWEventCounters EtwEventCounters) { _Print("\n\n\nETW:\n"); _Print("----\n\n"); @@ -169,7 +211,7 @@ void ResultParser::_DisplayETW(struct ETWMask ETWMask, struct ETWEventCounters E { _Print("\tRegistry\n"); - _Print("\t\tNtCreateKey: %I64u\n", + _Print("\t\tNtCreateKey: %I64u\n", EtwEventCounters.ullRegCreate); _Print("\t\tNtDeleteKey: %I64u\n", @@ -214,9 +256,212 @@ void ResultParser::_DisplayETW(struct ETWMask ETWMask, struct ETWEventCounters E } } +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) { - _Print("\tpath: '%s'\n", target.GetPath().c_str()); + 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 @@ -279,12 +524,19 @@ void ResultParser::_PrintTarget(const Target &target, bool fUseThreadsPerFile, b if (target.GetRandomDataWriteBufferSize() > 0) { - _Print("\t\twrite buffer size: %I64u\n", target.GetRandomDataWriteBufferSize()); + _Print("\t\twrite buffer size: "); + _DisplayFileSize(target.GetRandomDataWriteBufferSize()); + _Print("\n"); + string sWriteBufferSourcePath = target.GetRandomDataWriteBufferSourcePath(); - if (sWriteBufferSourcePath != "") + 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()) @@ -304,40 +556,58 @@ void ResultParser::_PrintTarget(const Target &target, bool fUseThreadsPerFile, b { _Print("\t\tperforming mix test (read/write ratio: %d/%d)\n", 100 - target.GetWriteRatio(), target.GetWriteRatio()); } - _Print("\t\tblock size: %d\n", target.GetBlockSizeInBytes()); - if (target.GetUseRandomAccessPattern()) + + _Print("\t\tblock size: "); + _DisplayFileSize(target.GetBlockSizeInBytes()); + _Print("\n"); + + if (target.GetRandomRatio() == 100) { _Print("\t\tusing random I/O (alignment: "); } else { - if (target.GetUseInterlockedSequential()) + if (target.GetRandomRatio() > 0) { - _Print("\t\tusing interlocked sequential I/O (stride: "); + _Print("\t\tusing mixed random/sequential I/O (%u%% random) (alignment/stride: ", target.GetRandomRatio()); } else { - _Print("\t\tusing sequential I/O (stride: "); + _Print("\t\tusing%s sequential I/O (stride: ", target.GetUseInterlockedSequential() ? " interlocked":""); } } - _Print("%I64u)\n", target.GetBlockAlignmentInBytes()); + _DisplayFileSize(target.GetBlockAlignmentInBytes()); + _Print(")\n"); if (fUseRequestsPerFile) { - _Print("\t\tnumber of outstanding I/O operations: %d\n", target.GetRequestCount()); + _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: %I64u\n", target.GetBaseFileOffsetInBytes()); + _Print("\t\tbase file offset: "); + _DisplayFileSize(target.GetBaseFileOffsetInBytes()); + _Print("\n"); } if (0 != target.GetMaxFileSize()) { - _Print("\t\tmax file size: %I64u\n", target.GetMaxFileSize()); + _Print("\t\tmax file size: "); + _DisplayFileSize(target.GetMaxFileSize()); + _Print("\n"); } - _Print("\t\tthread stride size: %I64u\n", target.GetThreadStrideInBytes()); + if (0 != target.GetThreadStrideInBytes()) + { + _Print("\t\tthread stride size: "); + _DisplayFileSize(target.GetThreadStrideInBytes()); + _Print("\n"); + } if (target.GetSequentialScanHint()) { @@ -386,6 +656,21 @@ void ResultParser::_PrintTarget(const Target &target, bool fUseThreadsPerFile, b { _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) @@ -406,6 +691,11 @@ void ResultParser::_PrintTimeSpan(const TimeSpan& timeSpan) _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) @@ -491,7 +781,7 @@ void ResultParser::_PrintCpuUtilization(const Results& results, const SystemInfo if (ulBaseProc >= ulProcCount) { break; } - + for (unsigned int ulProcessor = 0; ulProcessor < pGroup->_maximumProcessorCount; ulProcessor++) { double idleTime; double userTime; @@ -544,11 +834,11 @@ void ResultParser::_PrintCpuUtilization(const Results& results, const SystemInfo if (ulActiveProcCount == 0) { ulActiveProcCount = 1; } - + _Print("-------------------------------------------\n"); - sprintf_s(szFloatBuffer, sizeof(szFloatBuffer), - ulNumGroups == 1 ? + 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, @@ -796,12 +1086,12 @@ void ResultParser::_PrintLatencyChart(const Histogram& readLatencyHistogr Util::DoubleToStringHelper(writeLatencyHistogram.GetMin() / 1000) : "N/A"; - _Print(" min | %10s | %10s | %10.3lf\n", + _Print(" min | %10s | %10s | %10.3lf\n", readMin.c_str(), writeMin.c_str(), totalLatencyHistogram.GetMin()/1000); PercentileDescriptor percentiles[] = { - { 0.25, "25th" }, + { 0.25, "25th" }, { 0.50, "50th" }, { 0.75, "75th" }, { 0.90, "90th" }, @@ -838,13 +1128,20 @@ void ResultParser::_PrintLatencyChart(const Histogram& readLatencyHistogr string readMax = Util::DoubleToStringHelper(readLatencyHistogram.GetMax() / 1000); string writeMax = Util::DoubleToStringHelper(writeLatencyHistogram.GetMax() / 1000); - _Print(" max | %10s | %10s | %10.3lf\n", + _Print(" max | %10s | %10s | %10.3lf\n", fHasReads ? readMax.c_str() : "N/A", fHasWrites ? writeMax.c_str() : "N/A", totalLatencyHistogram.GetMax()/1000); } -string ResultParser::ParseResults(Profile& profile, const SystemInformation& system, vector vResults) +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(); @@ -888,6 +1185,8 @@ string ResultParser::ParseResults(Profile& profile, const SystemInformation& sys _Print("proc count:\t\t%u\n", ulProcCount); _PrintCpuUtilization(results, system); + _PrintEffectiveDistributions(results); + _Print("\nTotal IO\n"); _PrintSection(_SectionEnum::TOTAL, timeSpan, results); @@ -927,7 +1226,7 @@ string ResultParser::ParseResults(Profile& profile, const SystemInformation& sys UINT64 cTotalTicks = 0; for (auto pResults = vResults.begin(); pResults != vResults.end(); pResults++) { - double time = PerfTimer::PerfTimeToSeconds(pResults->ullTimeCount); + double time = PerfTimer::PerfTimeToSeconds(pResults->ullTimeCount); if (time >= 0.0000001) // skip timespans that were interrupted { cTotalTicks += pResults->ullTimeCount; diff --git a/UnitTests/CmdLineParser/CmdLineParser.UnitTests.cpp b/UnitTests/CmdLineParser/CmdLineParser.UnitTests.cpp index b1d3a7e..a555cf4 100644 --- a/UnitTests/CmdLineParser/CmdLineParser.UnitTests.cpp +++ b/UnitTests/CmdLineParser/CmdLineParser.UnitTests.cpp @@ -71,116 +71,128 @@ namespace UnitTests { CmdLineParser p; UINT64 ullResult = 0; - VERIFY_IS_TRUE(p._GetSizeInBytes("0", ullResult)); + VERIFY_IS_TRUE(p._GetSizeInBytes("0", ullResult, nullptr)); VERIFY_IS_TRUE(ullResult == 0); - VERIFY_IS_TRUE(p._GetSizeInBytes("1", ullResult)); + VERIFY_IS_TRUE(p._GetSizeInBytes("1", ullResult, nullptr)); VERIFY_IS_TRUE(ullResult == 1); - VERIFY_IS_TRUE(p._GetSizeInBytes("2", ullResult)); + VERIFY_IS_TRUE(p._GetSizeInBytes("2", ullResult, nullptr)); VERIFY_IS_TRUE(ullResult == 2); - VERIFY_IS_TRUE(p._GetSizeInBytes("10", ullResult)); + VERIFY_IS_TRUE(p._GetSizeInBytes("10", ullResult, nullptr)); VERIFY_IS_TRUE(ullResult == 10); - VERIFY_IS_TRUE(p._GetSizeInBytes("4096", ullResult)); + VERIFY_IS_TRUE(p._GetSizeInBytes("4096", ullResult, nullptr)); VERIFY_IS_TRUE(ullResult == 4096); - VERIFY_IS_TRUE(p._GetSizeInBytes("123908798324", ullResult)); + VERIFY_IS_TRUE(p._GetSizeInBytes("123908798324", ullResult, nullptr)); VERIFY_IS_TRUE(ullResult == 123908798324); // _GetSizeInBytes shouldn't modify ullResult on if the input string is incorrect ullResult = 9; - VERIFY_IS_TRUE(p._GetSizeInBytes("10a", ullResult) == false); + VERIFY_IS_TRUE(p._GetSizeInBytes("10a", ullResult, nullptr) == false); VERIFY_IS_TRUE(ullResult == 9); // block - VERIFY_IS_TRUE(p._GetSizeInBytes("1b", ullResult)); + VERIFY_IS_TRUE(p._GetSizeInBytes("1b", ullResult, nullptr)); VERIFY_IS_TRUE(ullResult == p._dwBlockSize); - VERIFY_IS_TRUE(p._GetSizeInBytes("3B", ullResult)); + VERIFY_IS_TRUE(p._GetSizeInBytes("3B", ullResult, nullptr)); VERIFY_IS_TRUE(ullResult == 3 * p._dwBlockSize); - VERIFY_IS_TRUE(p._GetSizeInBytes("30b", ullResult)); + VERIFY_IS_TRUE(p._GetSizeInBytes("30b", ullResult, nullptr)); VERIFY_IS_TRUE(ullResult == 30 * p._dwBlockSize); - VERIFY_IS_TRUE(p._GetSizeInBytes("30 b", ullResult) == false); - VERIFY_IS_TRUE(p._GetSizeInBytes("30 B", ullResult) == false); + VERIFY_IS_TRUE(p._GetSizeInBytes("30 b", ullResult, nullptr) == false); + VERIFY_IS_TRUE(p._GetSizeInBytes("30 B", ullResult, nullptr) == false); // KB - VERIFY_IS_TRUE(p._GetSizeInBytes("1K", ullResult)); + VERIFY_IS_TRUE(p._GetSizeInBytes("1K", ullResult, nullptr)); VERIFY_IS_TRUE(ullResult == 1024); - VERIFY_IS_TRUE(p._GetSizeInBytes("3K", ullResult)); + VERIFY_IS_TRUE(p._GetSizeInBytes("3K", ullResult, nullptr)); VERIFY_IS_TRUE(ullResult == 3 * 1024); - VERIFY_IS_TRUE(p._GetSizeInBytes("30K", ullResult)); + VERIFY_IS_TRUE(p._GetSizeInBytes("30K", ullResult, nullptr)); VERIFY_IS_TRUE(ullResult == 30 * 1024); - VERIFY_IS_TRUE(p._GetSizeInBytes("30 K", ullResult) == false); - VERIFY_IS_TRUE(p._GetSizeInBytes("30KB", ullResult) == false); + VERIFY_IS_TRUE(p._GetSizeInBytes("30 K", ullResult, nullptr) == false); + VERIFY_IS_TRUE(p._GetSizeInBytes("30KB", ullResult, nullptr) == false); // MB - VERIFY_IS_TRUE(p._GetSizeInBytes("1M", ullResult)); + VERIFY_IS_TRUE(p._GetSizeInBytes("1M", ullResult, nullptr)); VERIFY_IS_TRUE(ullResult == 1024 * 1024); - VERIFY_IS_TRUE(p._GetSizeInBytes("4M", ullResult)); + VERIFY_IS_TRUE(p._GetSizeInBytes("4M", ullResult, nullptr)); VERIFY_IS_TRUE(ullResult == 4 * 1024 * 1024); - VERIFY_IS_TRUE(p._GetSizeInBytes("50M", ullResult)); + VERIFY_IS_TRUE(p._GetSizeInBytes("50M", ullResult, nullptr)); VERIFY_IS_TRUE(ullResult == 50 * 1024 * 1024); - VERIFY_IS_TRUE(p._GetSizeInBytes("40 M", ullResult) == false); - VERIFY_IS_TRUE(p._GetSizeInBytes("40MB", ullResult) == false); + VERIFY_IS_TRUE(p._GetSizeInBytes("40 M", ullResult, nullptr) == false); + VERIFY_IS_TRUE(p._GetSizeInBytes("40MB", ullResult, nullptr) == false); // GB - VERIFY_IS_TRUE(p._GetSizeInBytes("1G", ullResult)); + VERIFY_IS_TRUE(p._GetSizeInBytes("1G", ullResult, nullptr)); VERIFY_IS_TRUE(ullResult == 1024 * 1024 * 1024); - VERIFY_IS_TRUE(p._GetSizeInBytes("6G", ullResult)); + VERIFY_IS_TRUE(p._GetSizeInBytes("6G", ullResult, nullptr)); VERIFY_IS_TRUE(ullResult == (UINT64)6 * 1024 * 1024 * 1024); - VERIFY_IS_TRUE(p._GetSizeInBytes("70G", ullResult)); + VERIFY_IS_TRUE(p._GetSizeInBytes("70G", ullResult, nullptr)); VERIFY_IS_TRUE(ullResult == (UINT64)70 * 1024 * 1024 * 1024); - VERIFY_IS_TRUE(p._GetSizeInBytes("70 G", ullResult) == false); - VERIFY_IS_TRUE(p._GetSizeInBytes("70GB", ullResult) == false); + VERIFY_IS_TRUE(p._GetSizeInBytes("70 G", ullResult, nullptr) == false); + VERIFY_IS_TRUE(p._GetSizeInBytes("70GB", ullResult, nullptr) == false); + // TB + VERIFY_IS_TRUE(p._GetSizeInBytes("1T", ullResult, nullptr)); + VERIFY_IS_TRUE(ullResult == (UINT64)1024 * 1024 * 1024 * 1024); + + VERIFY_IS_TRUE(p._GetSizeInBytes("6T", ullResult, nullptr)); + VERIFY_IS_TRUE(ullResult == (UINT64)6 * 1024 * 1024 * 1024 * 1024); + + VERIFY_IS_TRUE(p._GetSizeInBytes("70T", ullResult, nullptr)); + VERIFY_IS_TRUE(ullResult == (UINT64)70 * 1024 * 1024 * 1024 * 1024); + + VERIFY_IS_TRUE(p._GetSizeInBytes("70 T", ullResult, nullptr) == false); + VERIFY_IS_TRUE(p._GetSizeInBytes("70TB", ullResult, nullptr) == false); // check overflows // MAXUINT64 == 18446744073709551615 ullResult = 0; - VERIFY_IS_TRUE(p._GetSizeInBytes("18446744073709551615", ullResult)); + VERIFY_IS_TRUE(p._GetSizeInBytes("18446744073709551615", ullResult, nullptr)); VERIFY_IS_TRUE(ullResult == MAXUINT64); // MAXUINT64 + 1 - VERIFY_IS_TRUE(p._GetSizeInBytes("18446744073709551616", ullResult) == false); + VERIFY_IS_TRUE(p._GetSizeInBytes("18446744073709551616", ullResult, nullptr) == false); // MAXUINT64 / 1024 = 18014398509481983 ullResult = 0; - VERIFY_IS_TRUE(p._GetSizeInBytes("18014398509481983K", ullResult)); + VERIFY_IS_TRUE(p._GetSizeInBytes("18014398509481983K", ullResult, nullptr)); VERIFY_IS_TRUE(ullResult == (MAXUINT64 >> 10) << 10); - VERIFY_IS_TRUE(p._GetSizeInBytes("18014398509481984K", ullResult) == false); + VERIFY_IS_TRUE(p._GetSizeInBytes("18014398509481984K", ullResult, nullptr) == false); // MAXUINT64 / 1024^2 = 17592186044415 ullResult = 0; - VERIFY_IS_TRUE(p._GetSizeInBytes("17592186044415M", ullResult)); + VERIFY_IS_TRUE(p._GetSizeInBytes("17592186044415M", ullResult, nullptr)); VERIFY_IS_TRUE(ullResult == (MAXUINT64 >> 20) << 20); - VERIFY_IS_TRUE(p._GetSizeInBytes("17592186044416M", ullResult) == false); + VERIFY_IS_TRUE(p._GetSizeInBytes("17592186044416M", ullResult, nullptr) == false); // MAXUINT64 / 1024^3 = 17179869183 ullResult = 0; - VERIFY_IS_TRUE(p._GetSizeInBytes("17179869183G", ullResult)); + VERIFY_IS_TRUE(p._GetSizeInBytes("17179869183G", ullResult, nullptr)); VERIFY_IS_TRUE(ullResult == (MAXUINT64 >> 30) << 30); - VERIFY_IS_TRUE(p._GetSizeInBytes("17179869184G", ullResult) == false); + VERIFY_IS_TRUE(p._GetSizeInBytes("17179869184G", ullResult, nullptr) == false); // block p._dwBlockSize = 1024; ullResult = 0; - VERIFY_IS_TRUE(p._GetSizeInBytes("18014398509481983b", ullResult)); + VERIFY_IS_TRUE(p._GetSizeInBytes("18014398509481983b", ullResult, nullptr)); VERIFY_IS_TRUE(ullResult == (MAXUINT64 >> 10) << 10); - VERIFY_IS_TRUE(p._GetSizeInBytes("18014398509481984b", ullResult) == false); + VERIFY_IS_TRUE(p._GetSizeInBytes("18014398509481984b", ullResult, nullptr) == false); } - + void CmdLineParserUnitTests::TestParseCmdLineAssignAffinity() { CmdLineParser p; @@ -431,7 +443,7 @@ namespace UnitTests VERIFY_IS_TRUE(t.GetPath().compare("testfile.dat") == 0); VERIFY_ARE_EQUAL(t.GetBlockSizeInBytes(), (DWORD)(4 * 1024)); VERIFY_ARE_EQUAL(t.GetRequestCount(), (DWORD)2); - VERIFY_IS_TRUE(t.GetUseRandomAccessPattern() == false); + VERIFY_IS_TRUE(t.GetRandomRatio() == 0); VERIFY_IS_TRUE(t.GetUseInterlockedSequential() == false); VERIFY_ARE_EQUAL(t.GetBlockAlignmentInBytes(), t.GetBlockSizeInBytes()); VERIFY_ARE_EQUAL(t.GetBaseFileOffsetInBytes(), 0); @@ -460,7 +472,7 @@ namespace UnitTests VERIFY_IS_TRUE(t.GetPath().compare("testfile2.dat") == 0); VERIFY_ARE_EQUAL(t.GetBlockSizeInBytes(), (DWORD)(4 * 1024)); VERIFY_ARE_EQUAL(t.GetRequestCount(), (DWORD)2); - VERIFY_IS_TRUE(t.GetUseRandomAccessPattern() == false); + VERIFY_IS_TRUE(t.GetRandomRatio() == 0); VERIFY_IS_TRUE(t.GetUseInterlockedSequential() == false); VERIFY_ARE_EQUAL(t.GetBlockAlignmentInBytes(), t.GetBlockSizeInBytes()); VERIFY_ARE_EQUAL(t.GetBaseFileOffsetInBytes(), 0); @@ -540,7 +552,7 @@ namespace UnitTests VERIFY_IS_TRUE(t.GetPath().compare("testfile.dat") == 0); VERIFY_ARE_EQUAL(t.GetBlockSizeInBytes(), (DWORD)(128 * 1024)); VERIFY_ARE_EQUAL(t.GetRequestCount(), (DWORD)2); - VERIFY_IS_TRUE(t.GetUseRandomAccessPattern() == false); + VERIFY_IS_TRUE(t.GetRandomRatio() == 0); VERIFY_IS_TRUE(t.GetUseInterlockedSequential() == false); VERIFY_ARE_EQUAL(t.GetBlockAlignmentInBytes(), t.GetBlockSizeInBytes()); VERIFY_ARE_EQUAL(t.GetBaseFileOffsetInBytes(), 0); @@ -615,7 +627,7 @@ namespace UnitTests VERIFY_IS_TRUE(t.GetPath().compare("testfile.dat") == 0); VERIFY_ARE_EQUAL(t.GetBlockSizeInBytes(), (DWORD)(128 * 1024)); VERIFY_ARE_EQUAL(t.GetRequestCount(), (DWORD)2); - VERIFY_IS_TRUE(t.GetUseRandomAccessPattern() == false); + VERIFY_IS_TRUE(t.GetRandomRatio() == 0); VERIFY_IS_TRUE(t.GetUseInterlockedSequential() == false); VERIFY_ARE_EQUAL(t.GetBlockAlignmentInBytes(), t.GetBlockSizeInBytes()); VERIFY_ARE_EQUAL(t.GetBaseFileOffsetInBytes(), 0); @@ -721,7 +733,7 @@ namespace UnitTests VERIFY_IS_TRUE(t.GetPath().compare("testfile.dat") == 0); VERIFY_ARE_EQUAL(t.GetBlockSizeInBytes(), (DWORD)(128 * 1024)); VERIFY_ARE_EQUAL(t.GetRequestCount(), (DWORD)2); - VERIFY_IS_TRUE(t.GetUseRandomAccessPattern() == false); + VERIFY_IS_TRUE(t.GetRandomRatio() == 0); VERIFY_IS_TRUE(t.GetUseInterlockedSequential() == false); VERIFY_ARE_EQUAL(t.GetBlockAlignmentInBytes(), t.GetBlockSizeInBytes()); VERIFY_ARE_EQUAL(t.GetBaseFileOffsetInBytes(), 0); @@ -776,7 +788,7 @@ namespace UnitTests { VERIFY_IS_TRUE(profile.GetVerbose() == false); VERIFY_IS_TRUE(profile.GetProgress() == 0); - + VERIFY_IS_TRUE(profile.GetEtwEnabled() == false); VERIFY_IS_TRUE(profile.GetEtwProcess() == false); VERIFY_IS_TRUE(profile.GetEtwThread() == false); @@ -814,7 +826,7 @@ namespace UnitTests VERIFY_IS_TRUE(t.GetPath().compare("testfile.dat") == 0); VERIFY_ARE_EQUAL(t.GetBlockSizeInBytes(), (DWORD)(128 * 1024)); VERIFY_ARE_EQUAL(t.GetRequestCount(), (DWORD)2); - VERIFY_IS_TRUE(t.GetUseRandomAccessPattern() == false); + VERIFY_IS_TRUE(t.GetRandomRatio() == 0); VERIFY_IS_TRUE(t.GetUseInterlockedSequential() == false); VERIFY_ARE_EQUAL(t.GetBlockAlignmentInBytes(), t.GetBlockSizeInBytes()); VERIFY_ARE_EQUAL(t.GetBaseFileOffsetInBytes(), 0); @@ -982,7 +994,7 @@ namespace UnitTests VERIFY_IS_TRUE(t.GetPath().compare("testfile.dat") == 0); VERIFY_ARE_EQUAL(t.GetBlockSizeInBytes(), (DWORD)(128 * 1024)); VERIFY_ARE_EQUAL(t.GetRequestCount(), (DWORD)2); - VERIFY_IS_TRUE(t.GetUseRandomAccessPattern() == false); + VERIFY_IS_TRUE(t.GetRandomRatio() == 0); VERIFY_IS_TRUE(t.GetUseInterlockedSequential() == false); VERIFY_ARE_EQUAL(t.GetBlockAlignmentInBytes(), t.GetBlockSizeInBytes()); VERIFY_ARE_EQUAL(t.GetBaseFileOffsetInBytes(), 0); @@ -1067,7 +1079,7 @@ namespace UnitTests VERIFY_IS_TRUE(t.GetPath().compare("testfile.dat") == 0); VERIFY_ARE_EQUAL(t.GetBlockSizeInBytes(), (DWORD)(128 * 1024)); VERIFY_ARE_EQUAL(t.GetRequestCount(), (DWORD)2); - VERIFY_IS_TRUE(t.GetUseRandomAccessPattern() == false); + VERIFY_IS_TRUE(t.GetRandomRatio() == 0); VERIFY_IS_TRUE(t.GetUseInterlockedSequential() == false); VERIFY_ARE_EQUAL(t.GetBlockAlignmentInBytes(), t.GetBlockSizeInBytes()); VERIFY_ARE_EQUAL(t.GetBaseFileOffsetInBytes(), 0); @@ -1141,7 +1153,7 @@ namespace UnitTests VERIFY_IS_TRUE(t.GetPath().compare("testfile.dat") == 0); VERIFY_ARE_EQUAL(t.GetBlockSizeInBytes(), (DWORD)(128 * 1024)); VERIFY_ARE_EQUAL(t.GetRequestCount(), (DWORD)2); - VERIFY_IS_TRUE(t.GetUseRandomAccessPattern() == false); + VERIFY_IS_TRUE(t.GetRandomRatio() == 0); VERIFY_IS_TRUE(t.GetUseInterlockedSequential() == false); VERIFY_ARE_EQUAL(t.GetBlockAlignmentInBytes(), t.GetBlockSizeInBytes()); VERIFY_ARE_EQUAL(t.GetBaseFileOffsetInBytes(), 0); @@ -1215,7 +1227,7 @@ namespace UnitTests VERIFY_IS_TRUE(t.GetPath().compare("testfile.dat") == 0); VERIFY_ARE_EQUAL(t.GetBlockSizeInBytes(), (DWORD)(128 * 1024)); VERIFY_ARE_EQUAL(t.GetRequestCount(), (DWORD)2); - VERIFY_IS_TRUE(t.GetUseRandomAccessPattern() == false); + VERIFY_IS_TRUE(t.GetRandomRatio() == 0); VERIFY_IS_TRUE(t.GetUseInterlockedSequential() == false); VERIFY_ARE_EQUAL(t.GetBlockAlignmentInBytes(), t.GetBlockSizeInBytes()); VERIFY_ARE_EQUAL(t.GetBaseFileOffsetInBytes(), 0); @@ -1289,7 +1301,7 @@ namespace UnitTests VERIFY_IS_TRUE(t.GetPath().compare("testfile.dat") == 0); VERIFY_ARE_EQUAL(t.GetBlockSizeInBytes(), (DWORD)(128 * 1024)); VERIFY_ARE_EQUAL(t.GetRequestCount(), (DWORD)2); - VERIFY_IS_TRUE(t.GetUseRandomAccessPattern() == false); + VERIFY_IS_TRUE(t.GetRandomRatio() == 0); VERIFY_IS_TRUE(t.GetUseInterlockedSequential() == false); VERIFY_ARE_EQUAL(t.GetBlockAlignmentInBytes(), t.GetBlockSizeInBytes()); VERIFY_ARE_EQUAL(t.GetBaseFileOffsetInBytes(), 0); @@ -1358,7 +1370,7 @@ namespace UnitTests VERIFY_IS_TRUE(t.GetPath().compare("testfile.dat") == 0); VERIFY_ARE_EQUAL(t.GetBlockSizeInBytes(), (DWORD)(128 * 1024)); VERIFY_ARE_EQUAL(t.GetRequestCount(), (DWORD)2); - VERIFY_IS_TRUE(t.GetUseRandomAccessPattern() == false); + VERIFY_IS_TRUE(t.GetRandomRatio() == 0); VERIFY_IS_TRUE(t.GetUseInterlockedSequential() == false); VERIFY_ARE_EQUAL(t.GetBlockAlignmentInBytes(), t.GetBlockSizeInBytes()); VERIFY_ARE_EQUAL(t.GetBaseFileOffsetInBytes(), 0); @@ -1416,7 +1428,7 @@ namespace UnitTests VERIFY_IS_TRUE(profile.GetCmdLine().compare("foo -b128K -w84 -Sm -Nn testfile.dat") == 0); VerifyParseCmdLineMappedIO(profile, MemoryMappedIoFlushMode::NonVolatileMemory); } - + { CmdLineParser p; Profile profile; @@ -1475,7 +1487,7 @@ namespace UnitTests VERIFY_IS_TRUE(t.GetPath().compare("testfile.dat") == 0); VERIFY_ARE_EQUAL(t.GetBlockSizeInBytes(), (DWORD)(128 * 1024)); VERIFY_ARE_EQUAL(t.GetRequestCount(), (DWORD)2); - VERIFY_IS_TRUE(t.GetUseRandomAccessPattern() == false); + VERIFY_IS_TRUE(t.GetRandomRatio() == 0); VERIFY_IS_TRUE(t.GetUseInterlockedSequential() == false); VERIFY_ARE_EQUAL(t.GetBlockAlignmentInBytes(), t.GetBlockSizeInBytes()); VERIFY_ARE_EQUAL(t.GetBaseFileOffsetInBytes(), 0); @@ -1549,7 +1561,7 @@ namespace UnitTests VERIFY_IS_TRUE(t.GetPath().compare("testfile.dat") == 0); VERIFY_ARE_EQUAL(t.GetBlockSizeInBytes(), (DWORD)(128 * 1024)); VERIFY_ARE_EQUAL(t.GetRequestCount(), (DWORD)2); - VERIFY_IS_TRUE(t.GetUseRandomAccessPattern() == false); + VERIFY_IS_TRUE(t.GetRandomRatio() == 0); VERIFY_IS_TRUE(t.GetUseInterlockedSequential() == false); VERIFY_ARE_EQUAL(t.GetBlockAlignmentInBytes(), t.GetBlockSizeInBytes()); VERIFY_ARE_EQUAL(t.GetBaseFileOffsetInBytes(), 0); @@ -1623,7 +1635,7 @@ namespace UnitTests VERIFY_IS_TRUE(t.GetPath().compare("testfile.dat") == 0); VERIFY_ARE_EQUAL(t.GetBlockSizeInBytes(), (DWORD)(128 * 1024)); VERIFY_ARE_EQUAL(t.GetRequestCount(), (DWORD)2); - VERIFY_IS_TRUE(t.GetUseRandomAccessPattern() == false); + VERIFY_IS_TRUE(t.GetRandomRatio() == 0); VERIFY_IS_TRUE(t.GetUseInterlockedSequential() == false); VERIFY_ARE_EQUAL(t.GetBlockAlignmentInBytes(), t.GetBlockSizeInBytes()); VERIFY_ARE_EQUAL(t.GetBaseFileOffsetInBytes(), 0); @@ -1697,7 +1709,7 @@ namespace UnitTests VERIFY_IS_TRUE(t.GetPath().compare("testfile.dat") == 0); VERIFY_ARE_EQUAL(t.GetBlockSizeInBytes(), (DWORD)(128 * 1024)); VERIFY_ARE_EQUAL(t.GetRequestCount(), (DWORD)2); - VERIFY_IS_TRUE(t.GetUseRandomAccessPattern() == false); + VERIFY_IS_TRUE(t.GetRandomRatio() == 0); VERIFY_IS_TRUE(t.GetUseInterlockedSequential() == false); VERIFY_ARE_EQUAL(t.GetBlockAlignmentInBytes(), t.GetBlockSizeInBytes()); VERIFY_ARE_EQUAL(t.GetBaseFileOffsetInBytes(), 0); @@ -1771,7 +1783,7 @@ namespace UnitTests VERIFY_IS_TRUE(t.GetPath().compare("testfile.dat") == 0); VERIFY_ARE_EQUAL(t.GetBlockSizeInBytes(), (DWORD)(128 * 1024)); VERIFY_ARE_EQUAL(t.GetRequestCount(), (DWORD)2); - VERIFY_IS_TRUE(t.GetUseRandomAccessPattern() == false); + VERIFY_IS_TRUE(t.GetRandomRatio() == 0); VERIFY_IS_TRUE(t.GetUseInterlockedSequential() == false); VERIFY_ARE_EQUAL(t.GetBlockAlignmentInBytes(), t.GetBlockSizeInBytes()); VERIFY_ARE_EQUAL(t.GetBaseFileOffsetInBytes(), 0); @@ -1845,7 +1857,7 @@ namespace UnitTests VERIFY_IS_TRUE(t.GetPath().compare("testfile.dat") == 0); VERIFY_ARE_EQUAL(t.GetBlockSizeInBytes(), (DWORD)(128 * 1024)); VERIFY_ARE_EQUAL(t.GetRequestCount(), (DWORD)2); - VERIFY_IS_TRUE(t.GetUseRandomAccessPattern() == false); + VERIFY_IS_TRUE(t.GetRandomRatio() == 0); VERIFY_IS_TRUE(t.GetUseInterlockedSequential() == false); VERIFY_ARE_EQUAL(t.GetBlockAlignmentInBytes(), t.GetBlockSizeInBytes()); VERIFY_ARE_EQUAL(t.GetBaseFileOffsetInBytes(), 0); @@ -1919,7 +1931,7 @@ namespace UnitTests VERIFY_IS_TRUE(t.GetPath().compare("testfile.dat") == 0); VERIFY_ARE_EQUAL(t.GetBlockSizeInBytes(), (DWORD)(128 * 1024)); VERIFY_ARE_EQUAL(t.GetRequestCount(), (DWORD)2); - VERIFY_IS_TRUE(t.GetUseRandomAccessPattern() == false); + VERIFY_IS_TRUE(t.GetRandomRatio() == 0); VERIFY_IS_TRUE(t.GetUseInterlockedSequential() == false); VERIFY_ARE_EQUAL(t.GetBlockAlignmentInBytes(), t.GetBlockSizeInBytes()); VERIFY_ARE_EQUAL(t.GetBaseFileOffsetInBytes(), 0); @@ -1951,11 +1963,11 @@ namespace UnitTests CmdLineParser p; Profile profile; struct Synchronization s = {}; - const char *argv[] = { "foo", "-b128K", "-w84", "-o123", "-B456", "testfile.dat" }; + const char *argv[] = { "foo", "-b128K", "-w84", "-o123", "-B512k", "testfile.dat" }; VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == true); VERIFY_IS_TRUE(profile.GetVerbose() == false); VERIFY_IS_TRUE(profile.GetProgress() == 0); - VERIFY_IS_TRUE(profile.GetCmdLine().compare("foo -b128K -w84 -o123 -B456 testfile.dat") == 0); + VERIFY_IS_TRUE(profile.GetCmdLine().compare("foo -b128K -w84 -o123 -B512k testfile.dat") == 0); VERIFY_IS_TRUE(profile.GetEtwEnabled() == false); VERIFY_IS_TRUE(profile.GetEtwProcess() == false); VERIFY_IS_TRUE(profile.GetEtwThread() == false); @@ -1993,10 +2005,10 @@ namespace UnitTests VERIFY_IS_TRUE(t.GetPath().compare("testfile.dat") == 0); VERIFY_ARE_EQUAL(t.GetBlockSizeInBytes(), (DWORD)(128 * 1024)); VERIFY_ARE_EQUAL(t.GetRequestCount(), (DWORD)123); - VERIFY_IS_TRUE(t.GetUseRandomAccessPattern() == false); + VERIFY_IS_TRUE(t.GetRandomRatio() == 0); VERIFY_IS_TRUE(t.GetUseInterlockedSequential() == false); VERIFY_ARE_EQUAL(t.GetBlockAlignmentInBytes(), t.GetBlockSizeInBytes()); - VERIFY_ARE_EQUAL(t.GetBaseFileOffsetInBytes(), 456); + VERIFY_ARE_EQUAL(t.GetBaseFileOffsetInBytes(), 512 * 1024); VERIFY_IS_TRUE(t.GetUseParallelAsyncIO() == false); VERIFY_IS_TRUE(t.GetCacheMode() == TargetCacheMode::Cached); VERIFY_IS_TRUE(t.GetWriteThroughMode() == WriteThroughMode::Off); @@ -2067,7 +2079,7 @@ namespace UnitTests VERIFY_IS_TRUE(t.GetPath().compare("testfile.dat") == 0); VERIFY_ARE_EQUAL(t.GetBlockSizeInBytes(), (DWORD)(128 * 1024)); VERIFY_ARE_EQUAL(t.GetRequestCount(), (DWORD)2); - VERIFY_IS_TRUE(t.GetUseRandomAccessPattern() == false); + VERIFY_IS_TRUE(t.GetRandomRatio() == 0); VERIFY_IS_TRUE(t.GetUseInterlockedSequential() == false); VERIFY_ARE_EQUAL(t.GetBlockAlignmentInBytes(), t.GetBlockSizeInBytes()); VERIFY_ARE_EQUAL(t.GetBaseFileOffsetInBytes(), 0); @@ -2141,7 +2153,7 @@ namespace UnitTests VERIFY_IS_TRUE(t.GetPath().compare("testfile.dat") == 0); VERIFY_ARE_EQUAL(t.GetBlockSizeInBytes(), (DWORD)(128 * 1024)); VERIFY_ARE_EQUAL(t.GetRequestCount(), (DWORD)2); - VERIFY_IS_TRUE(t.GetUseRandomAccessPattern() == false); + VERIFY_IS_TRUE(t.GetRandomRatio() == 0); VERIFY_IS_TRUE(t.GetUseInterlockedSequential() == false); VERIFY_ARE_EQUAL(t.GetBlockAlignmentInBytes(), t.GetBlockSizeInBytes()); VERIFY_ARE_EQUAL(t.GetBaseFileOffsetInBytes(), 0); @@ -2215,7 +2227,7 @@ namespace UnitTests VERIFY_IS_TRUE(t.GetPath().compare("testfile.dat") == 0); VERIFY_ARE_EQUAL(t.GetBlockSizeInBytes(), (DWORD)(128 * 1024)); VERIFY_ARE_EQUAL(t.GetRequestCount(), (DWORD)2); - VERIFY_IS_TRUE(t.GetUseRandomAccessPattern() == false); + VERIFY_IS_TRUE(t.GetRandomRatio() == 0); VERIFY_IS_TRUE(t.GetUseInterlockedSequential() == false); VERIFY_ARE_EQUAL(t.GetBlockAlignmentInBytes(), t.GetBlockSizeInBytes()); VERIFY_ARE_EQUAL(t.GetBaseFileOffsetInBytes(), 0); @@ -2241,7 +2253,7 @@ namespace UnitTests VERIFY_IS_TRUE(t.GetUseLargePages() == false); VERIFY_ARE_EQUAL(t.GetThroughputInBytesPerMillisecond(), (DWORD)567); } - + void CmdLineParserUnitTests::TestParseCmdLineRandomIOAlignment() { CmdLineParser p; @@ -2289,7 +2301,7 @@ namespace UnitTests VERIFY_IS_TRUE(t.GetPath().compare("testfile.dat") == 0); VERIFY_ARE_EQUAL(t.GetBlockSizeInBytes(), (DWORD)(128 * 1024)); VERIFY_ARE_EQUAL(t.GetRequestCount(), (DWORD)2); - VERIFY_IS_TRUE(t.GetUseRandomAccessPattern() == true); + VERIFY_IS_TRUE(t.GetRandomRatio() == 100); VERIFY_IS_TRUE(t.GetUseInterlockedSequential() == false); VERIFY_ARE_EQUAL(t.GetBlockAlignmentInBytes(), (23 * 1024 * 1024)); VERIFY_ARE_EQUAL(t.GetBaseFileOffsetInBytes(), 0); @@ -2363,7 +2375,7 @@ namespace UnitTests VERIFY_IS_TRUE(t.GetPath().compare("testfile.dat") == 0); VERIFY_ARE_EQUAL(t.GetBlockSizeInBytes(), (DWORD)(128 * 1024)); VERIFY_ARE_EQUAL(t.GetRequestCount(), (DWORD)2); - VERIFY_IS_TRUE(t.GetUseRandomAccessPattern() == false); + VERIFY_IS_TRUE(t.GetRandomRatio() == 0); VERIFY_IS_TRUE(t.GetUseInterlockedSequential() == false); VERIFY_ARE_EQUAL(t.GetBlockAlignmentInBytes(), (567 * 1024)); VERIFY_ARE_EQUAL(t.GetBaseFileOffsetInBytes(), 0); @@ -2395,11 +2407,11 @@ namespace UnitTests CmdLineParser p; Profile profile; struct Synchronization s = {}; - const char *argv[] = { "foo", "-b128K", "-w84", "-t23", "-T567K", "testfile.dat" }; + const char *argv[] = { "foo", "-b128K", "-w84", "-t23", "-T512K", "testfile.dat" }; VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == true); VERIFY_IS_TRUE(profile.GetVerbose() == false); VERIFY_IS_TRUE(profile.GetProgress() == 0); - VERIFY_IS_TRUE(profile.GetCmdLine().compare("foo -b128K -w84 -t23 -T567K testfile.dat") == 0); + VERIFY_IS_TRUE(profile.GetCmdLine().compare("foo -b128K -w84 -t23 -T512K testfile.dat") == 0); VERIFY_IS_TRUE(profile.GetEtwEnabled() == false); VERIFY_IS_TRUE(profile.GetEtwProcess() == false); VERIFY_IS_TRUE(profile.GetEtwThread() == false); @@ -2437,7 +2449,7 @@ namespace UnitTests VERIFY_IS_TRUE(t.GetPath().compare("testfile.dat") == 0); VERIFY_ARE_EQUAL(t.GetBlockSizeInBytes(), (DWORD)(128 * 1024)); VERIFY_ARE_EQUAL(t.GetRequestCount(), (DWORD)2); - VERIFY_IS_TRUE(t.GetUseRandomAccessPattern() == false); + VERIFY_IS_TRUE(t.GetRandomRatio() == 0); VERIFY_IS_TRUE(t.GetUseInterlockedSequential() == false); VERIFY_ARE_EQUAL(t.GetBlockAlignmentInBytes(), t.GetBlockSizeInBytes()); VERIFY_ARE_EQUAL(t.GetBaseFileOffsetInBytes(), 0); @@ -2448,7 +2460,7 @@ namespace UnitTests VERIFY_IS_TRUE(t.GetMemoryMappedIoFlushMode() == MemoryMappedIoFlushMode::Undefined); VERIFY_IS_TRUE(t.GetZeroWriteBuffers() == false); VERIFY_ARE_EQUAL(t.GetThreadsPerFile(), (DWORD)23); - VERIFY_ARE_EQUAL(t.GetThreadStrideInBytes(), (567 * 1024)); + VERIFY_ARE_EQUAL(t.GetThreadStrideInBytes(), (512 * 1024)); VERIFY_IS_TRUE(t.GetCreateFile() == false); VERIFY_ARE_EQUAL(t.GetFileSize(), 0); VERIFY_ARE_EQUAL(t.GetMaxFileSize(), 0); @@ -2511,7 +2523,7 @@ namespace UnitTests VERIFY_IS_TRUE(t.GetPath().compare("testfile.dat") == 0); VERIFY_ARE_EQUAL(t.GetBlockSizeInBytes(), (DWORD)(128 * 1024)); VERIFY_ARE_EQUAL(t.GetRequestCount(), (DWORD)2); - VERIFY_IS_TRUE(t.GetUseRandomAccessPattern() == false); + VERIFY_IS_TRUE(t.GetRandomRatio() == 0); VERIFY_IS_TRUE(t.GetUseInterlockedSequential() == false); VERIFY_ARE_EQUAL(t.GetBlockAlignmentInBytes(), t.GetBlockSizeInBytes()); VERIFY_ARE_EQUAL(t.GetBaseFileOffsetInBytes(), 0); @@ -2585,7 +2597,7 @@ namespace UnitTests VERIFY_IS_TRUE(t.GetPath().compare("testfile.dat") == 0); VERIFY_ARE_EQUAL(t.GetBlockSizeInBytes(), (DWORD)(128 * 1024)); VERIFY_ARE_EQUAL(t.GetRequestCount(), (DWORD)2); - VERIFY_IS_TRUE(t.GetUseRandomAccessPattern() == false); + VERIFY_IS_TRUE(t.GetRandomRatio() == 0); VERIFY_IS_TRUE(t.GetUseInterlockedSequential() == false); VERIFY_ARE_EQUAL(t.GetBlockAlignmentInBytes(), t.GetBlockSizeInBytes()); VERIFY_ARE_EQUAL(t.GetBaseFileOffsetInBytes(), 0); @@ -2659,7 +2671,7 @@ namespace UnitTests VERIFY_IS_TRUE(t.GetPath().compare("testfile.dat") == 0); VERIFY_ARE_EQUAL(t.GetBlockSizeInBytes(), (DWORD)(128 * 1024)); VERIFY_ARE_EQUAL(t.GetRequestCount(), (DWORD)2); - VERIFY_IS_TRUE(t.GetUseRandomAccessPattern() == false); + VERIFY_IS_TRUE(t.GetRandomRatio() == 0); VERIFY_IS_TRUE(t.GetUseInterlockedSequential() == false); VERIFY_ARE_EQUAL(t.GetBlockAlignmentInBytes(), t.GetBlockSizeInBytes()); VERIFY_ARE_EQUAL(t.GetBaseFileOffsetInBytes(), 0); @@ -2733,7 +2745,7 @@ namespace UnitTests VERIFY_IS_TRUE(t.GetPath().compare("testfile.dat") == 0); VERIFY_ARE_EQUAL(t.GetBlockSizeInBytes(), (DWORD)(128 * 1024)); VERIFY_ARE_EQUAL(t.GetRequestCount(), (DWORD)2); - VERIFY_IS_TRUE(t.GetUseRandomAccessPattern() == false); + VERIFY_IS_TRUE(t.GetRandomRatio() == 0); VERIFY_IS_TRUE(t.GetUseInterlockedSequential() == false); VERIFY_ARE_EQUAL(t.GetBlockAlignmentInBytes(), t.GetBlockSizeInBytes()); VERIFY_ARE_EQUAL(t.GetBaseFileOffsetInBytes(), 0); @@ -2807,7 +2819,7 @@ namespace UnitTests VERIFY_IS_TRUE(t.GetPath().compare("testfile.dat") == 0); VERIFY_ARE_EQUAL(t.GetBlockSizeInBytes(), (DWORD)(128 * 1024)); VERIFY_ARE_EQUAL(t.GetRequestCount(), (DWORD)2); - VERIFY_IS_TRUE(t.GetUseRandomAccessPattern() == false); + VERIFY_IS_TRUE(t.GetRandomRatio() == 0); VERIFY_IS_TRUE(t.GetUseInterlockedSequential() == false); VERIFY_ARE_EQUAL(t.GetBlockAlignmentInBytes(), t.GetBlockSizeInBytes()); VERIFY_ARE_EQUAL(t.GetBaseFileOffsetInBytes(), 0); @@ -2881,7 +2893,7 @@ namespace UnitTests VERIFY_IS_TRUE(t.GetPath().compare("testfile.dat") == 0); VERIFY_ARE_EQUAL(t.GetBlockSizeInBytes(), (DWORD)(128 * 1024)); VERIFY_ARE_EQUAL(t.GetRequestCount(), (DWORD)2); - VERIFY_IS_TRUE(t.GetUseRandomAccessPattern() == false); + VERIFY_IS_TRUE(t.GetRandomRatio() == 0); VERIFY_IS_TRUE(t.GetUseInterlockedSequential() == false); VERIFY_ARE_EQUAL(t.GetBlockAlignmentInBytes(), t.GetBlockSizeInBytes()); VERIFY_ARE_EQUAL(t.GetBaseFileOffsetInBytes(), 0); @@ -2955,7 +2967,7 @@ namespace UnitTests VERIFY_IS_TRUE(t.GetPath().compare("testfile.dat") == 0); VERIFY_ARE_EQUAL(t.GetBlockSizeInBytes(), (DWORD)(128 * 1024)); VERIFY_ARE_EQUAL(t.GetRequestCount(), (DWORD)2); - VERIFY_IS_TRUE(t.GetUseRandomAccessPattern() == false); + VERIFY_IS_TRUE(t.GetRandomRatio() == 0); VERIFY_IS_TRUE(t.GetUseInterlockedSequential() == false); VERIFY_ARE_EQUAL(t.GetBlockAlignmentInBytes(), t.GetBlockSizeInBytes()); VERIFY_ARE_EQUAL(t.GetBaseFileOffsetInBytes(), 0); @@ -3029,7 +3041,7 @@ namespace UnitTests VERIFY_IS_TRUE(t.GetPath().compare("testfile.dat") == 0); VERIFY_ARE_EQUAL(t.GetBlockSizeInBytes(), (DWORD)(128 * 1024)); VERIFY_ARE_EQUAL(t.GetRequestCount(), (DWORD)2); - VERIFY_IS_TRUE(t.GetUseRandomAccessPattern() == false); + VERIFY_IS_TRUE(t.GetRandomRatio() == 0); VERIFY_IS_TRUE(t.GetUseInterlockedSequential() == false); VERIFY_ARE_EQUAL(t.GetBlockAlignmentInBytes(), t.GetBlockSizeInBytes()); VERIFY_ARE_EQUAL(t.GetBaseFileOffsetInBytes(), 0); @@ -3103,7 +3115,7 @@ namespace UnitTests VERIFY_IS_TRUE(t.GetPath().compare("testfile.dat") == 0); VERIFY_ARE_EQUAL(t.GetBlockSizeInBytes(), (DWORD)(128 * 1024)); VERIFY_ARE_EQUAL(t.GetRequestCount(), (DWORD)2); - VERIFY_IS_TRUE(t.GetUseRandomAccessPattern() == false); + VERIFY_IS_TRUE(t.GetRandomRatio() == 0); VERIFY_IS_TRUE(t.GetUseInterlockedSequential() == false); VERIFY_ARE_EQUAL(t.GetBlockAlignmentInBytes(), t.GetBlockSizeInBytes()); VERIFY_ARE_EQUAL(t.GetBaseFileOffsetInBytes(), 0); @@ -3177,7 +3189,7 @@ namespace UnitTests VERIFY_IS_TRUE(t.GetPath().compare("testfile.dat") == 0); VERIFY_ARE_EQUAL(t.GetBlockSizeInBytes(), (DWORD)(128 * 1024)); VERIFY_ARE_EQUAL(t.GetRequestCount(), (DWORD)2); - VERIFY_IS_TRUE(t.GetUseRandomAccessPattern() == false); + VERIFY_IS_TRUE(t.GetRandomRatio() == 0); VERIFY_IS_TRUE(t.GetUseInterlockedSequential() == false); VERIFY_ARE_EQUAL(t.GetBlockAlignmentInBytes(), t.GetBlockSizeInBytes()); VERIFY_ARE_EQUAL(t.GetBaseFileOffsetInBytes(), 0); @@ -3251,7 +3263,7 @@ namespace UnitTests VERIFY_IS_TRUE(t.GetPath().compare("testfile.dat") == 0); VERIFY_ARE_EQUAL(t.GetBlockSizeInBytes(), (DWORD)(128 * 1024)); VERIFY_ARE_EQUAL(t.GetRequestCount(), (DWORD)2); - VERIFY_IS_TRUE(t.GetUseRandomAccessPattern() == false); + VERIFY_IS_TRUE(t.GetRandomRatio() == 0); VERIFY_IS_TRUE(t.GetUseInterlockedSequential() == false); VERIFY_ARE_EQUAL(t.GetBlockAlignmentInBytes(), t.GetBlockSizeInBytes()); VERIFY_ARE_EQUAL(t.GetBaseFileOffsetInBytes(), 0); @@ -3325,7 +3337,7 @@ namespace UnitTests VERIFY_IS_TRUE(t.GetPath().compare("testfile.dat") == 0); VERIFY_ARE_EQUAL(t.GetBlockSizeInBytes(), (DWORD)(128 * 1024)); VERIFY_ARE_EQUAL(t.GetRequestCount(), (DWORD)2); - VERIFY_IS_TRUE(t.GetUseRandomAccessPattern() == false); + VERIFY_IS_TRUE(t.GetRandomRatio() == 0); VERIFY_IS_TRUE(t.GetUseInterlockedSequential() == false); VERIFY_ARE_EQUAL(t.GetBlockAlignmentInBytes(), t.GetBlockSizeInBytes()); VERIFY_ARE_EQUAL(t.GetBaseFileOffsetInBytes(), 0); @@ -3399,7 +3411,7 @@ namespace UnitTests VERIFY_IS_TRUE(t.GetPath().compare("testfile.dat") == 0); VERIFY_ARE_EQUAL(t.GetBlockSizeInBytes(), (DWORD)(128 * 1024)); VERIFY_ARE_EQUAL(t.GetRequestCount(), (DWORD)2); - VERIFY_IS_TRUE(t.GetUseRandomAccessPattern() == false); + VERIFY_IS_TRUE(t.GetRandomRatio() == 0); VERIFY_IS_TRUE(t.GetUseInterlockedSequential() == false); VERIFY_ARE_EQUAL(t.GetBlockAlignmentInBytes(), t.GetBlockSizeInBytes()); VERIFY_ARE_EQUAL(t.GetBaseFileOffsetInBytes(), 0); @@ -3426,7 +3438,7 @@ namespace UnitTests VERIFY_IS_TRUE(t.GetIOPriorityHint() == IoPriorityHintLow); VERIFY_ARE_EQUAL(t.GetThroughputInBytesPerMillisecond(), (DWORD)0); } - + void CmdLineParserUnitTests::TestParseCmdLineMeasureLatency() { CmdLineParser p; @@ -3474,7 +3486,7 @@ namespace UnitTests VERIFY_IS_TRUE(t.GetPath().compare("testfile.dat") == 0); VERIFY_ARE_EQUAL(t.GetBlockSizeInBytes(), (DWORD)(128 * 1024)); VERIFY_ARE_EQUAL(t.GetRequestCount(), (DWORD)2); - VERIFY_IS_TRUE(t.GetUseRandomAccessPattern() == false); + VERIFY_IS_TRUE(t.GetRandomRatio() == 0); VERIFY_IS_TRUE(t.GetUseInterlockedSequential() == false); VERIFY_ARE_EQUAL(t.GetBlockAlignmentInBytes(), t.GetBlockSizeInBytes()); VERIFY_ARE_EQUAL(t.GetBaseFileOffsetInBytes(), 0); @@ -3548,7 +3560,7 @@ namespace UnitTests VERIFY_IS_TRUE(t.GetPath().compare("testfile.dat") == 0); VERIFY_ARE_EQUAL(t.GetBlockSizeInBytes(), (DWORD)(128 * 1024)); VERIFY_ARE_EQUAL(t.GetRequestCount(), (DWORD)2); - VERIFY_IS_TRUE(t.GetUseRandomAccessPattern() == false); + VERIFY_IS_TRUE(t.GetRandomRatio() == 0); VERIFY_IS_TRUE(t.GetUseInterlockedSequential() == false); VERIFY_ARE_EQUAL(t.GetBlockAlignmentInBytes(), t.GetBlockSizeInBytes()); VERIFY_ARE_EQUAL(t.GetBaseFileOffsetInBytes(), 0); @@ -3624,7 +3636,7 @@ namespace UnitTests VERIFY_IS_TRUE(t.GetPath().compare("testfile.dat") == 0); VERIFY_ARE_EQUAL(t.GetBlockSizeInBytes(), (DWORD)(128 * 1024)); VERIFY_ARE_EQUAL(t.GetRequestCount(), (DWORD)2); - VERIFY_IS_TRUE(t.GetUseRandomAccessPattern() == false); + VERIFY_IS_TRUE(t.GetRandomRatio() == 0); VERIFY_IS_TRUE(t.GetUseInterlockedSequential() == false); VERIFY_ARE_EQUAL(t.GetBlockAlignmentInBytes(), t.GetBlockSizeInBytes()); VERIFY_ARE_EQUAL(t.GetBaseFileOffsetInBytes(), 0); @@ -3658,9 +3670,6 @@ namespace UnitTests CmdLineParser p; UINT64 cb; string sPath; - p._GetRandomDataWriteBufferData("", cb, sPath); - VERIFY_ARE_EQUAL(cb, 0); - VERIFY_IS_TRUE(sPath == ""); p._GetRandomDataWriteBufferData("11332", cb, sPath); VERIFY_ARE_EQUAL(cb, 11332); @@ -3726,7 +3735,7 @@ namespace UnitTests VERIFY_ARE_EQUAL(t.GetRandomDataWriteBufferSize(), (size_t)(3 * 1024 * 1024)); VERIFY_IS_TRUE(t.GetRandomDataWriteBufferSourcePath() == "x:\\foo\\bar.baz"); } - + void CmdLineParserUnitTests::TestParseCmdLineInterlockedSequential() { CmdLineParser p; @@ -3774,7 +3783,7 @@ namespace UnitTests VERIFY_IS_TRUE(t.GetPath().compare("testfile.dat") == 0); VERIFY_ARE_EQUAL(t.GetBlockSizeInBytes(), (DWORD)(128 * 1024)); VERIFY_ARE_EQUAL(t.GetRequestCount(), (DWORD)2); - VERIFY_IS_TRUE(t.GetUseRandomAccessPattern() == false); + VERIFY_IS_TRUE(t.GetRandomRatio() == 0); VERIFY_IS_TRUE(t.GetUseInterlockedSequential() == true); VERIFY_ARE_EQUAL(t.GetBlockAlignmentInBytes(), t.GetBlockSizeInBytes()); VERIFY_ARE_EQUAL(t.GetBaseFileOffsetInBytes(), 0); @@ -3800,7 +3809,7 @@ namespace UnitTests VERIFY_IS_TRUE(t.GetUseLargePages() == false); VERIFY_ARE_EQUAL(t.GetThroughputInBytesPerMillisecond(), (DWORD)0); } - + void CmdLineParserUnitTests::TestParseCmdLineInterlockedSequentialWithStride() { CmdLineParser p; @@ -3848,7 +3857,7 @@ namespace UnitTests VERIFY_IS_TRUE(t.GetPath().compare("testfile.dat") == 0); VERIFY_ARE_EQUAL(t.GetBlockSizeInBytes(), (DWORD)(128 * 1024)); VERIFY_ARE_EQUAL(t.GetRequestCount(), (DWORD)2); - VERIFY_IS_TRUE(t.GetUseRandomAccessPattern() == false); + VERIFY_IS_TRUE(t.GetRandomRatio() == 0); VERIFY_IS_TRUE(t.GetUseInterlockedSequential() == true); VERIFY_ARE_EQUAL(t.GetBlockAlignmentInBytes(), (567 * 1024)); VERIFY_ARE_EQUAL(t.GetBaseFileOffsetInBytes(), 0); @@ -3922,7 +3931,7 @@ namespace UnitTests VERIFY_IS_TRUE(t.GetPath().compare("testfile.dat") == 0); VERIFY_ARE_EQUAL(t.GetBlockSizeInBytes(), (DWORD)(128 * 1024)); VERIFY_ARE_EQUAL(t.GetRequestCount(), (DWORD)2); - VERIFY_IS_TRUE(t.GetUseRandomAccessPattern() == false); + VERIFY_IS_TRUE(t.GetRandomRatio() == 0); VERIFY_IS_TRUE(t.GetUseInterlockedSequential() == false); VERIFY_ARE_EQUAL(t.GetBlockAlignmentInBytes(), t.GetBlockSizeInBytes()); VERIFY_ARE_EQUAL(t.GetBaseFileOffsetInBytes(), 0); @@ -3948,4 +3957,660 @@ namespace UnitTests VERIFY_IS_TRUE(t.GetUseLargePages() == false); VERIFY_ARE_EQUAL(t.GetThroughputInBytesPerMillisecond(), (DWORD)0); } -} + + void CmdLineParserUnitTests::TestParseCmdLineThroughput() + { + CmdLineParser p; + struct Synchronization s = {}; + + { + Profile profile; + const char *argv[] = { "foo", "-b128K", "-g10i", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == true); + VERIFY_ARE_EQUAL(profile.GetTimeSpans()[0].GetTargets()[0].GetThroughputInBytesPerMillisecond(), (DWORD)((128*1024*10)/1000)); + VERIFY_IS_TRUE(profile.GetTimeSpans()[0].GetTargets()[0].GetThroughputIOPS() == 10); + } + { + Profile profile; + const char *argv[] = { "foo", "-b128K", "-g1024", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == true); + VERIFY_IS_TRUE(profile.GetTimeSpans()[0].GetTargets()[0].GetThroughputInBytesPerMillisecond() == 1024); + VERIFY_IS_TRUE(profile.GetTimeSpans()[0].GetTargets()[0].GetThroughputIOPS() == 0); + } + + // Invalid cases: valid unit on wrong side, no digits, zeroes, bad unit + + { + Profile profile; + const char *argv[] = { "foo", "-b128K", "-gi100", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == false); + } + { + Profile profile; + const char *argv[] = { "foo", "-b128K", "-g0", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == false); + } + { + Profile profile; + const char *argv[] = { "foo", "-b128K", "-g0i", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == false); + } + { + Profile profile; + const char *argv[] = { "foo", "-b128K", "-g", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == false); + } + { + Profile profile; + const char *argv[] = { "foo", "-b128K", "-gi", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == false); + } + { + Profile profile; + const char *argv[] = { "foo", "-b128K", "-g100x", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == false); + } + } + + void CmdLineParserUnitTests::TestParseCmdLineRandomSequentialMixed() + { + // Coverage for -rs and combinations of conflicts with -r/-s/-rs + + CmdLineParser p; + struct Synchronization s = {}; + + // + // -rs cases + // + + // Isolated + + { + Profile profile; + const char *argv[] = { "foo", "-rs50", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == true); + VERIFY_ARE_EQUAL(profile.GetTimeSpans()[0].GetTargets()[0].GetRandomRatio(), (UINT32) 50); + } + + // Combined with -r, in any order + + { + Profile profile; + const char *argv[] = { "foo", "-rs50", "-r8k", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == true); + VERIFY_ARE_EQUAL(profile.GetTimeSpans()[0].GetTargets()[0].GetRandomRatio(), (UINT32) 50); + VERIFY_ARE_EQUAL(profile.GetTimeSpans()[0].GetTargets()[0].GetBlockAlignmentInBytes(), 8*1024); + } + { + Profile profile; + const char *argv[] = { "foo", "-r8k", "-rs50", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == true); + VERIFY_ARE_EQUAL(profile.GetTimeSpans()[0].GetTargets()[0].GetRandomRatio(), (UINT32) 50); + VERIFY_ARE_EQUAL(profile.GetTimeSpans()[0].GetTargets()[0].GetBlockAlignmentInBytes(), 8*1024); + } + + // Combined with -r, in any order (don't care block size) + // While the -r in this order has no effect and could be flagged as an error, we don't currently parse to this level + + { + Profile profile; + const char *argv[] = { "foo", "-rs50", "-r", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == true); + VERIFY_ARE_EQUAL(profile.GetTimeSpans()[0].GetTargets()[0].GetRandomRatio(), (UINT32) 50); + } + { + Profile profile; + const char *argv[] = { "foo", "-r", "-rs50", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == true); + VERIFY_ARE_EQUAL(profile.GetTimeSpans()[0].GetTargets()[0].GetRandomRatio(), (UINT32) 50); + } + + // Now for conflict cases + + // Combined with -s in any order + + { + Profile profile; + const char *argv[] = { "foo", "-rs50", "-s8k", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == false); + } + { + Profile profile; + const char *argv[] = { "foo", "-s8k", "-rs50", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == false); + } + + // Combined with -s in any order + + { + Profile profile; + const char *argv[] = { "foo", "-rs50", "-s", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == false); + } + { + Profile profile; + const char *argv[] = { "foo", "-s", "-rs50", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == false); + } + + // -r/-s conflict + + { + Profile profile; + const char *argv[] = { "foo", "-r", "-s", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == false); + } + { + Profile profile; + const char *argv[] = { "foo", "-s", "-r", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == false); + } + + // 3-way conflict + // If it were more important, we could/should enumerate the orderings + { + Profile profile; + const char *argv[] = { "foo", "-rs50", "-s", "-r", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == false); + } + { + Profile profile; + const char *argv[] = { "foo", "-s", "-rs50", "-r", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == false); + } + { + Profile profile; + const char *argv[] = { "foo", "-s", "-r", "-rs50", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == false); + } + + // 3-way with -s + { + Profile profile; + const char *argv[] = { "foo", "-rs50", "-s8k", "-r", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == false); + } + { + Profile profile; + const char *argv[] = { "foo", "-s8k", "-rs50", "-r", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == false); + } + { + Profile profile; + const char *argv[] = { "foo", "-s8k", "-r", "-rs50", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == false); + } + + // 3-way with -r + { + Profile profile; + const char *argv[] = { "foo", "-rs50", "-s", "-r8k", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == false); + } + { + Profile profile; + const char *argv[] = { "foo", "-s", "-rs50", "-r8k", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == false); + } + { + Profile profile; + const char *argv[] = { "foo", "-s", "-r8k", "-rs50", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == false); + } + + // Duplicated specs + { + Profile profile; + const char *argv[] = { "foo", "-s", "-s8k", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == false); + } + { + Profile profile; + const char *argv[] = { "foo", "-s8k", "-s", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == false); + } + { + Profile profile; + const char *argv[] = { "foo", "-s", "-s", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == false); + } + { + Profile profile; + const char *argv[] = { "foo", "-s8k", "-s8k", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == false); + } + { + Profile profile; + const char *argv[] = { "foo", "-r", "-r8k", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == false); + } + { + Profile profile; + const char *argv[] = { "foo", "-r8k", "-r", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == false); + } + { + Profile profile; + const char *argv[] = { "foo", "-r", "-r", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == false); + } + { + Profile profile; + const char *argv[] = { "foo", "-r8k", "-r8k", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == false); + } + { + Profile profile; + const char *argv[] = { "foo", "-r8k", "-r4k", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == false); + } + + // Bounds checks: -rs100 is OK w/wo random alignment and -rs0 is rejected + { + Profile profile; + const char *argv[] = { "foo", "-rs100", "-r", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == true); + } + { + Profile profile; + const char *argv[] = { "foo", "-rs100", "-r8k", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == true); + } + { + Profile profile; + const char *argv[] = { "foo", "-r", "-rs100", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == true); + } + { + Profile profile; + const char *argv[] = { "foo", "-r8k", "-rs100", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == true); + } + { + Profile profile; + const char *argv[] = { "foo", "-rs0", "-r", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == false); + } + { + Profile profile; + const char *argv[] = { "foo", "-rs0", "-r8k", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == false); + } + { + Profile profile; + const char *argv[] = { "foo", "-rs0", "-s", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == false); + } + { + Profile profile; + const char *argv[] = { "foo", "-rs0", "-s8k", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s) == false); + } + } + + void CmdLineParserUnitTests::TestParseCmdLineTargetDistribution() + { + // coverage for parsing of cmdline target distributions (percent/absolute) + + CmdLineParser p; + struct Synchronization s = {}; + + // + // Positive cases - these match the ResultParser UTs + // + + { + Profile profile; + const char *argv[] = { "foo", "-rdpct10/10:10/10:0/10", "-r", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s)); + + auto t = profile.GetTimeSpans()[0].GetTargets()[0]; // manage object lifetime so target is not destroyed + auto dt = t.GetDistributionType(); + auto& v = t.GetDistributionRange(); + + VERIFY_ARE_EQUAL(dt, DistributionType::Percent); + VERIFY_ARE_EQUAL(v[0]._src, (UINT64) 0); + VERIFY_ARE_EQUAL(v[0]._span, (UINT64) 10); + VERIFY_ARE_EQUAL(v[0]._dst.first, (UINT64) 0); + VERIFY_ARE_EQUAL(v[0]._dst.second, (UINT64) 10); + VERIFY_ARE_EQUAL(v[1]._src, (UINT64) 10); /// + VERIFY_ARE_EQUAL(v[1]._span, (UINT64) 10); + VERIFY_ARE_EQUAL(v[1]._dst.first, (UINT64) 10); + VERIFY_ARE_EQUAL(v[1]._dst.second, (UINT64) 10); + VERIFY_ARE_EQUAL(v[2]._src, (UINT64) 20); /// + VERIFY_ARE_EQUAL(v[2]._span, (UINT64) 0); + VERIFY_ARE_EQUAL(v[2]._dst.first, (UINT64) 20); + VERIFY_ARE_EQUAL(v[2]._dst.second, (UINT64) 10); + VERIFY_ARE_EQUAL(v[3]._src, (UINT64) 20); /// + VERIFY_ARE_EQUAL(v[3]._span, (UINT64) 80); + VERIFY_ARE_EQUAL(v[3]._dst.first, (UINT64) 30); + VERIFY_ARE_EQUAL(v[3]._dst.second, (UINT64) 70); + } + { + Profile profile; + const char *argv[] = { "foo", "-rdabs10/1G:10/1G:0/100G", "-r", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s)); + + auto t = profile.GetTimeSpans()[0].GetTargets()[0]; + auto dt = t.GetDistributionType(); + auto& v = t.GetDistributionRange(); + + VERIFY_ARE_EQUAL(dt, DistributionType::Absolute); + VERIFY_ARE_EQUAL(v[0]._src, (UINT64) 0); + VERIFY_ARE_EQUAL(v[0]._span, (UINT64) 10); + VERIFY_ARE_EQUAL(v[0]._dst.first, (UINT64) 0); + VERIFY_ARE_EQUAL(v[0]._dst.second, (UINT64) 1*GB); + VERIFY_ARE_EQUAL(v[1]._src, (UINT64) 10); /// + VERIFY_ARE_EQUAL(v[1]._span, (UINT64) 10); + VERIFY_ARE_EQUAL(v[1]._dst.first, (UINT64) 1*GB); + VERIFY_ARE_EQUAL(v[1]._dst.second, (UINT64) 1*GB); + VERIFY_ARE_EQUAL(v[2]._src, (UINT64) 20); /// + VERIFY_ARE_EQUAL(v[2]._span, (UINT64) 0); + VERIFY_ARE_EQUAL(v[2]._dst.first, (UINT64) 2*GB); + VERIFY_ARE_EQUAL(v[2]._dst.second, (UINT64) 100*GB); + VERIFY_ARE_EQUAL(v[3]._src, (UINT64) 20); /// + VERIFY_ARE_EQUAL(v[3]._span, (UINT64) 80); + VERIFY_ARE_EQUAL(v[3]._dst.first, (UINT64) 102*GB); + VERIFY_ARE_EQUAL(v[3]._dst.second, (UINT64) 0); + } + + // valid with mixed load + { + Profile profile; + const char *argv[] = { "foo", "-rdpct90/10", "-rs50", "-r8k", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s)); + } + + // + // Negative cases + // + + // not valid with sequential load + { + Profile profile; + const char *argv[] = { "foo", "-rdpct90/10", "-s", "testfile.dat" }; + VERIFY_IS_FALSE(p.ParseCmdLine(_countof(argv), argv, &profile, &s)); + } + { + Profile profile; + const char *argv[] = { "foo", "-rdpct90/10", "testfile.dat" }; + VERIFY_IS_FALSE(p.ParseCmdLine(_countof(argv), argv, &profile, &s)); + } + + // no/invalid distribution + { + Profile profile; + const char *argv[] = { "foo", "-rd", "-r", "testfile.dat" }; + VERIFY_IS_FALSE(p.ParseCmdLine(_countof(argv), argv, &profile, &s)); + } + { + Profile profile; + const char *argv[] = { "foo", "-rdfoo", "-r", "testfile.dat" }; + VERIFY_IS_FALSE(p.ParseCmdLine(_countof(argv), argv, &profile, &s)); + } + { + Profile profile; + const char *argv[] = { "foo", "-rdfoo10/1G:10/1G:0/100G", "-r", "testfile.dat" }; + VERIFY_IS_FALSE(p.ParseCmdLine(_countof(argv), argv, &profile, &s)); + } + + // bad ints in first pos + { + Profile profile; + const char *argv[] = { "foo", "-rdpctBAD", "-r", "testfile.dat" }; + VERIFY_IS_FALSE(p.ParseCmdLine(_countof(argv), argv, &profile, &s)); + } + { + Profile profile; + const char *argv[] = { "foo", "-rdabsBAD", "-r", "testfile.dat" }; + VERIFY_IS_FALSE(p.ParseCmdLine(_countof(argv), argv, &profile, &s)); + } + + // good int, no sep + { + Profile profile; + const char *argv[] = { "foo", "-rdpct10", "-r", "testfile.dat" }; + VERIFY_IS_FALSE(p.ParseCmdLine(_countof(argv), argv, &profile, &s)); + } + { + Profile profile; + const char *argv[] = { "foo", "-rdabs10", "-r", "testfile.dat" }; + VERIFY_IS_FALSE(p.ParseCmdLine(_countof(argv), argv, &profile, &s)); + } + + // good int, bad sep + { + Profile profile; + const char *argv[] = { "foo", "-rdpct10[", "-r", "testfile.dat" }; + VERIFY_IS_FALSE(p.ParseCmdLine(_countof(argv), argv, &profile, &s)); + } + { + Profile profile; + const char *argv[] = { "foo", "-rdabs10[", "-r", "testfile.dat" }; + VERIFY_IS_FALSE(p.ParseCmdLine(_countof(argv), argv, &profile, &s)); + } + + // good int, good sep, no int + { + Profile profile; + const char *argv[] = { "foo", "-rdpct10/", "-r", "testfile.dat" }; + VERIFY_IS_FALSE(p.ParseCmdLine(_countof(argv), argv, &profile, &s)); + } + { + Profile profile; + const char *argv[] = { "foo", "-rdabs10/", "-r", "testfile.dat" }; + VERIFY_IS_FALSE(p.ParseCmdLine(_countof(argv), argv, &profile, &s)); + } + + // good int, good sep, bad int + { + Profile profile; + const char *argv[] = { "foo", "-rdpct10/BAD", "-r", "testfile.dat" }; + VERIFY_IS_FALSE(p.ParseCmdLine(_countof(argv), argv, &profile, &s)); + } + { + Profile profile; + const char *argv[] = { "foo", "-rdabs10/BAD", "-r", "testfile.dat" }; + VERIFY_IS_FALSE(p.ParseCmdLine(_countof(argv), argv, &profile, &s)); + } + + // good int, good sep, good int, bad sep + { + Profile profile; + const char *argv[] = { "foo", "-rdpct10/10[", "-r", "testfile.dat" }; + VERIFY_IS_FALSE(p.ParseCmdLine(_countof(argv), argv, &profile, &s)); + } + { + Profile profile; + const char *argv[] = { "foo", "-rdabs10/10g[", "-r", "testfile.dat" }; // detail - abs range > blocksize in order to be OK + VERIFY_IS_FALSE(p.ParseCmdLine(_countof(argv), argv, &profile, &s)); + } + + // good int, good sep, good int, good sep, no int + { + Profile profile; + const char *argv[] = { "foo", "-rdpct10/10:", "-r", "testfile.dat" }; + VERIFY_IS_FALSE(p.ParseCmdLine(_countof(argv), argv, &profile, &s)); + } + { + Profile profile; + const char *argv[] = { "foo", "-rdabs10/10g:", "-r", "testfile.dat" }; // detail - abs range > blocksize in order to be OK + VERIFY_IS_FALSE(p.ParseCmdLine(_countof(argv), argv, &profile, &s)); + } + + // good int, good sep, good int, good sep, bad int + { + Profile profile; + const char *argv[] = { "foo", "-rdpct10/10:BAD", "-r", "testfile.dat" }; + VERIFY_IS_FALSE(p.ParseCmdLine(_countof(argv), argv, &profile, &s)); + } + { + Profile profile; + const char *argv[] = { "foo", "-rdabs10/10g:BAD", "-r", "testfile.dat" }; // detail - abs range > blocksize in order to be OK + VERIFY_IS_FALSE(p.ParseCmdLine(_countof(argv), argv, &profile, &s)); + } + + // + // Negative cases for IO% + // + + // a single pct cannot be > 100, and cannot sum > 100 + { + Profile profile; + const char *argv[] = { "foo", "-rdpct101/10", "-r", "testfile.dat" }; + VERIFY_IS_FALSE(p.ParseCmdLine(_countof(argv), argv, &profile, &s)); + } + { + Profile profile; + const char *argv[] = { "foo", "-rdpct60/10:60/10", "-r", "testfile.dat" }; + VERIFY_IS_FALSE(p.ParseCmdLine(_countof(argv), argv, &profile, &s)); + } + { + Profile profile; + const char *argv[] = { "foo", "-rdabs101/10g", "-r", "testfile.dat" }; + VERIFY_IS_FALSE(p.ParseCmdLine(_countof(argv), argv, &profile, &s)); + } + { + Profile profile; + const char *argv[] = { "foo", "-rdabs60/10g:60/10g", "-r", "testfile.dat" }; + VERIFY_IS_FALSE(p.ParseCmdLine(_countof(argv), argv, &profile, &s)); + } + + // target% cannot be covered before IO% is covered + { + Profile profile; + const char *argv[] = { "foo", "-rdpct10/50:10/50", "-r", "testfile.dat" }; + VERIFY_IS_FALSE(p.ParseCmdLine(_countof(argv), argv, &profile, &s)); + } + + // target% cannot be > 100 + { + Profile profile; + const char *argv[] = { "foo", "-rdpct10/50:10/60", "-r", "testfile.dat" }; + VERIFY_IS_FALSE(p.ParseCmdLine(_countof(argv), argv, &profile, &s)); + } + + // + // Negative cases for Target%/Size + // + + // a target%/size cannot be zero (first/second positions) + { + Profile profile; + const char *argv[] = { "foo", "-rdpct10/0", "-r", "testfile.dat" }; + VERIFY_IS_FALSE(p.ParseCmdLine(_countof(argv), argv, &profile, &s)); + } + { + Profile profile; + const char *argv[] = { "foo", "-rdpct60/10:10/0", "-r", "testfile.dat" }; + VERIFY_IS_FALSE(p.ParseCmdLine(_countof(argv), argv, &profile, &s)); + } + { + Profile profile; + const char *argv[] = { "foo", "-rdabs10/0", "-r", "testfile.dat" }; + VERIFY_IS_FALSE(p.ParseCmdLine(_countof(argv), argv, &profile, &s)); + } + { + Profile profile; + const char *argv[] = { "foo", "-rdabs60/10g:10/0", "-r", "testfile.dat" }; + VERIFY_IS_FALSE(p.ParseCmdLine(_countof(argv), argv, &profile, &s)); + } + + // + // Negative cases for abs + // + + // abs range must be >= blocksize + { + Profile profile; + const char *argv[] = { "foo", "-rdabs10/10", "-r", "testfile.dat" }; + VERIFY_IS_FALSE(p.ParseCmdLine(_countof(argv), argv, &profile, &s)); + } + } + + void CmdLineParserUnitTests::TestParseCmdLineResultOutput() + { + // Cases for combinations of -R[p][text|xml] + + CmdLineParser p; + struct Synchronization s = {}; + + char *aType[] = { "text", "xml" }; + vector vType(aType, &aType[0] + _countof(aType)); + char *aProfile[] = { "-R", "-Rp" }; + vector vProfile(aProfile, &aProfile[0] + _countof(aProfile)); + + // combinations of p/text|xml OK + for (auto ty : vType) + { + for (auto pr : vProfile) + { + string str = pr; + str += ty; + + Profile profile; + const char *argv[] = { "foo", str.c_str(), "testfile.dat" }; + fprintf(stderr, "case: %s\n", str.c_str()); + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s)); + + if (pr[2] == 'p') + { + VERIFY_IS_TRUE(profile.GetProfileOnly()); + } + else + { + VERIFY_IS_FALSE(profile.GetProfileOnly()); + } + + if (*ty == 't') + { + VERIFY_ARE_EQUAL(ResultsFormat::Text, profile.GetResultsFormat()); + } + else + { + VERIFY_ARE_EQUAL(ResultsFormat::Xml, profile.GetResultsFormat()); + } + } + } + } + + void CmdLineParserUnitTests::TestParseCmdLineTargetPosition() + { + // coverage for positioning of targets and parameters + + CmdLineParser p; + struct Synchronization s = {}; + + // in order - this obviously duplicates all the other normal cases but, to + // document it in place against the negative cases, repeat. + { + Profile profile; + const char *argv[] = { "foo", "-r", "testfile.dat" }; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s)); + } + { + Profile profile; + const char *argv[] = { "foo", "-r", "testfile.dat" , "testfile2.dat"}; + VERIFY_IS_TRUE(p.ParseCmdLine(_countof(argv), argv, &profile, &s)); + } + + // out of order - parameter follows one or more targets + { + Profile profile; + const char *argv[] = { "foo", "testfile.dat" , "-r"}; + VERIFY_IS_FALSE(p.ParseCmdLine(_countof(argv), argv, &profile, &s)); + } + { + Profile profile; + const char *argv[] = { "foo", "testfile.dat" , "testfile2.dat", "-r"}; + VERIFY_IS_FALSE(p.ParseCmdLine(_countof(argv), argv, &profile, &s)); + } + + // out of order - in between + { + Profile profile; + const char *argv[] = { "foo", "testfile.dat" , "-r", "testfile2.dat"}; + VERIFY_IS_FALSE(p.ParseCmdLine(_countof(argv), argv, &profile, &s)); + } + } +} \ No newline at end of file diff --git a/UnitTests/CmdLineParser/CmdLineParser.UnitTests.h b/UnitTests/CmdLineParser/CmdLineParser.UnitTests.h index 87516f0..dba9812 100644 --- a/UnitTests/CmdLineParser/CmdLineParser.UnitTests.h +++ b/UnitTests/CmdLineParser/CmdLineParser.UnitTests.h @@ -56,56 +56,61 @@ namespace UnitTests TEST_METHOD_CLEANUP(MethodCleanup); TEST_METHOD(Test_GetSizeInBytes); + TEST_METHOD(TestGetRandomDataWriteBufferData); TEST_METHOD(TestParseCmdLine); - TEST_METHOD(TestParseCmdLineBlockSize); - TEST_METHOD(TestParseCmdLineGroupAffinity); TEST_METHOD(TestParseCmdLineAssignAffinity); - TEST_METHOD(TestParseCmdLineHintFlag); - TEST_METHOD(TestParseCmdLineDisableAllCacheMode1); - TEST_METHOD(TestParseCmdLineDisableAllCacheMode2); + TEST_METHOD(TestParseCmdLineBlockSize); + TEST_METHOD(TestParseCmdLineBufferedWriteThrough); + TEST_METHOD(TestParseCmdLineBurstSizeAndThinkTime); + TEST_METHOD(TestParseCmdLineConflictingCacheModes); + TEST_METHOD(TestParseCmdLineCreateFileAndMaxFileSize); TEST_METHOD(TestParseCmdLineDisableAffinity); TEST_METHOD(TestParseCmdLineDisableAffinityConflict); - TEST_METHOD(TestParseCmdLineVerbose); - TEST_METHOD(TestParseCmdLineDisableOSCache); + TEST_METHOD(TestParseCmdLineDisableAllCacheMode1); + TEST_METHOD(TestParseCmdLineDisableAllCacheMode2); TEST_METHOD(TestParseCmdLineDisableLocalCache); - TEST_METHOD(TestParseCmdLineBufferedWriteThrough); - TEST_METHOD(TestParseCmdLineMappedIO); - TEST_METHOD(TestParseCmdLineConflictingCacheModes); - TEST_METHOD(TestParseCmdLineUseCompletionRoutines); - TEST_METHOD(TestParseCmdLineRandSeed); - TEST_METHOD(TestParseCmdLineRandSeedGetTickCount); - TEST_METHOD(TestParseCmdLineWarmupAndCooldown); + TEST_METHOD(TestParseCmdLineDisableOSCache); TEST_METHOD(TestParseCmdLineDurationAndProgress); - TEST_METHOD(TestParseCmdLineUseParallelAsyncIO); - TEST_METHOD(TestParseCmdLineUseLargePages); - TEST_METHOD(TestParseCmdLineOverlappedCountAndBaseOffset); - TEST_METHOD(TestParseCmdLineCreateFileAndMaxFileSize); - TEST_METHOD(TestParseCmdLineBurstSizeAndThinkTime); - TEST_METHOD(TestParseCmdLineTotalThreadCountAndThroughput); - TEST_METHOD(TestParseCmdLineRandomIOAlignment); - TEST_METHOD(TestParseCmdLineStrideSize); - TEST_METHOD(TestParseCmdLineThreadsPerFileAndThreadStride); - TEST_METHOD(TestParseCmdLineEtwUsePagedMemory); - TEST_METHOD(TestParseCmdLineEtwPROCESS); - TEST_METHOD(TestParseCmdLineEtwTHREAD); - TEST_METHOD(TestParseCmdLineEtwIMAGE_LOAD); TEST_METHOD(TestParseCmdLineEtwDISK_IO); + TEST_METHOD(TestParseCmdLineEtwIMAGE_LOAD); + TEST_METHOD(TestParseCmdLineEtwMEMORY_HARD_FAULTS); + TEST_METHOD(TestParseCmdLineEtwMEMORY_PAGE_FAULTS); TEST_METHOD(TestParseCmdLineEtwNETWORK); + TEST_METHOD(TestParseCmdLineEtwPROCESS); TEST_METHOD(TestParseCmdLineEtwREGISTRY); - TEST_METHOD(TestParseCmdLineEtwMEMORY_PAGE_FAULTS); - TEST_METHOD(TestParseCmdLineEtwMEMORY_HARD_FAULTS); + TEST_METHOD(TestParseCmdLineEtwTHREAD); + TEST_METHOD(TestParseCmdLineEtwUseCycleCount); + TEST_METHOD(TestParseCmdLineEtwUsePagedMemory); TEST_METHOD(TestParseCmdLineEtwUsePerfTimer); TEST_METHOD(TestParseCmdLineEtwUseSystemTimer); - TEST_METHOD(TestParseCmdLineEtwUseCycleCount); + TEST_METHOD(TestParseCmdLineGroupAffinity); + TEST_METHOD(TestParseCmdLineHintFlag); + TEST_METHOD(TestParseCmdLineInterlockedSequential); + TEST_METHOD(TestParseCmdLineInterlockedSequentialWithStride); TEST_METHOD(TestParseCmdLineIOPriority); + TEST_METHOD(TestParseCmdLineMappedIO); TEST_METHOD(TestParseCmdLineMeasureLatency); - TEST_METHOD(TestParseCmdLineZeroWriteBuffers); + TEST_METHOD(TestParseCmdLineOverlappedCountAndBaseOffset); + TEST_METHOD(TestParseCmdLineRandomIOAlignment); + TEST_METHOD(TestParseCmdLineRandomSequentialMixed); TEST_METHOD(TestParseCmdLineRandomWriteBuffers); - TEST_METHOD(TestGetRandomDataWriteBufferData); + TEST_METHOD(TestParseCmdLineRandSeed); + TEST_METHOD(TestParseCmdLineRandSeedGetTickCount); + TEST_METHOD(TestParseCmdLineResultOutput); + TEST_METHOD(TestParseCmdLineStrideSize); + TEST_METHOD(TestParseCmdLineTargetDistribution); + TEST_METHOD(TestParseCmdLineTargetPosition); + TEST_METHOD(TestParseCmdLineThreadsPerFileAndThreadStride); + TEST_METHOD(TestParseCmdLineThroughput); + TEST_METHOD(TestParseCmdLineTotalThreadCountAndThroughput); + TEST_METHOD(TestParseCmdLineTotalThreadCountAndTotalRequestCount); + TEST_METHOD(TestParseCmdLineUseCompletionRoutines); + TEST_METHOD(TestParseCmdLineUseLargePages); + TEST_METHOD(TestParseCmdLineUseParallelAsyncIO); + TEST_METHOD(TestParseCmdLineVerbose); + TEST_METHOD(TestParseCmdLineWarmupAndCooldown); TEST_METHOD(TestParseCmdLineWriteBufferContentRandomNoFilePath); TEST_METHOD(TestParseCmdLineWriteBufferContentRandomWithFilePath); - TEST_METHOD(TestParseCmdLineInterlockedSequential); - TEST_METHOD(TestParseCmdLineInterlockedSequentialWithStride); - TEST_METHOD(TestParseCmdLineTotalThreadCountAndTotalRequestCount); + TEST_METHOD(TestParseCmdLineZeroWriteBuffers); }; } diff --git a/UnitTests/Common/Common.UnitTests.cpp b/UnitTests/Common/Common.UnitTests.cpp index 9469aea..6aae922 100644 --- a/UnitTests/Common/Common.UnitTests.cpp +++ b/UnitTests/Common/Common.UnitTests.cpp @@ -266,14 +266,14 @@ namespace UnitTests void ProfileUnitTests::Test_GetXmlEmptyProfile() { Profile profile; - string sXml = profile.GetXml(); + string sXml = profile.GetXml(0); //printf("'%s'\n", sXml.c_str()); VERIFY_IS_TRUE(sXml == "\n" - "0\n" - "text\n" - "false\n" - "\n" - "\n" + " 0\n" + " text\n" + " false\n" + " \n" + " \n" "\n"); } @@ -281,15 +281,15 @@ namespace UnitTests { Profile profile; profile.SetPrecreateFiles(PrecreateFiles::UseMaxSize); - string sXml = profile.GetXml(); + 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" + " 0\n" + " text\n" + " false\n" + " UseMaxSize\n" + " \n" + " \n" "\n"); } @@ -297,15 +297,15 @@ namespace UnitTests { Profile profile; profile.SetPrecreateFiles(PrecreateFiles::OnlyFilesWithConstantSizes); - string sXml = profile.GetXml(); + 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" + " 0\n" + " text\n" + " false\n" + " CreateOnlyFilesWithConstantSizes\n" + " \n" + " \n" "\n"); } @@ -313,15 +313,15 @@ namespace UnitTests { Profile profile; profile.SetPrecreateFiles(PrecreateFiles::OnlyFilesWithConstantOrZeroSizes); - string sXml = profile.GetXml(); + 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" + " 0\n" + " text\n" + " false\n" + " CreateOnlyFilesWithConstantOrZeroSizes\n" + " \n" + " \n" "\n"); } @@ -499,29 +499,29 @@ namespace UnitTests void TargetUnitTests::Test_TargetGetXmlWriteBufferContentSequential() { Target target; - string sXml = target.GetXml(); + 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" + " 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"); } @@ -529,29 +529,29 @@ namespace UnitTests { Target target; target.SetZeroWriteBuffers(true); - string sXml = target.GetXml(); + 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" + " 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"); } @@ -559,32 +559,32 @@ namespace UnitTests { Target target; target.SetRandomDataWriteBufferSize(224433); - string sXml = target.GetXml(); + 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" + " 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"); } @@ -593,33 +593,33 @@ namespace UnitTests Target target; target.SetRandomDataWriteBufferSize(224433); target.SetRandomDataWriteBufferSourcePath("x:\\foo\\bar.baz"); - string sXml = target.GetXml(); + 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" + " 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"); } @@ -628,31 +628,31 @@ namespace UnitTests Target target; target.SetCacheMode(TargetCacheMode::DisableOSCache); target.SetWriteThroughMode(WriteThroughMode::On); - string sXml = target.GetXml(); + 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" + " 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"); } @@ -660,30 +660,30 @@ namespace UnitTests { Target target; target.SetCacheMode(TargetCacheMode::DisableLocalCache); - string sXml = target.GetXml(); + 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" + " 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"); } @@ -691,30 +691,30 @@ namespace UnitTests { Target target; target.SetCacheMode(TargetCacheMode::DisableOSCache); - string sXml = target.GetXml(); + 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" + " 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"); } @@ -722,30 +722,30 @@ namespace UnitTests { Target target; target.SetWriteThroughMode(WriteThroughMode::On); - string sXml = target.GetXml(); + 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" + " 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"); } @@ -753,30 +753,30 @@ namespace UnitTests { Target target; target.SetMemoryMappedIoMode(MemoryMappedIoMode::On); - string sXml = target.GetXml(); + 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" + " 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"); } @@ -785,31 +785,31 @@ namespace UnitTests Target target; target.SetMemoryMappedIoMode(MemoryMappedIoMode::On); target.SetMemoryMappedIoFlushMode(MemoryMappedIoFlushMode::ViewOfFile); - string sXml = target.GetXml(); + 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" + " 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"); } @@ -818,31 +818,31 @@ namespace UnitTests Target target; target.SetMemoryMappedIoMode(MemoryMappedIoMode::On); target.SetMemoryMappedIoFlushMode(MemoryMappedIoFlushMode::NonVolatileMemory); - string sXml = target.GetXml(); + 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" + " 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"); } @@ -851,31 +851,31 @@ namespace UnitTests Target target; target.SetMemoryMappedIoMode(MemoryMappedIoMode::On); target.SetMemoryMappedIoFlushMode(MemoryMappedIoFlushMode::NonVolatileMemoryNoDrain); - string sXml = target.GetXml(); + 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" + " 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"); } @@ -883,29 +883,29 @@ namespace UnitTests { Target target; target.SetRandomAccessHint(true); - string sXml = target.GetXml(); + 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" + " 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"); } @@ -913,29 +913,29 @@ namespace UnitTests { Target target; target.SetSequentialScanHint(true); - string sXml = target.GetXml(); + 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" + " 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"); } @@ -944,29 +944,29 @@ namespace UnitTests Target target; target.SetSequentialScanHint(true); target.SetTemporaryFileHint(true); - string sXml = target.GetXml(); + 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" + " 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"); } diff --git a/UnitTests/IORequestGenerator/IORequestGenerator.UnitTests.cpp b/UnitTests/IORequestGenerator/IORequestGenerator.UnitTests.cpp index 28bcae9..0fe43ac 100644 --- a/UnitTests/IORequestGenerator/IORequestGenerator.UnitTests.cpp +++ b/UnitTests/IORequestGenerator/IORequestGenerator.UnitTests.cpp @@ -249,14 +249,14 @@ namespace UnitTests vector v = io._GetFilesToPrecreate(profile); VERIFY_ARE_EQUAL(v.size(), (size_t)0); } - + void IORequestGeneratorUnitTests::Test_GetNextFileOffsetRandom() { - Target target; + Target target; target.SetBaseFileOffsetInBytes(1000); target.SetBlockAlignmentInBytes(500); target.SetBlockSizeInBytes(1000); - target.SetUseRandomAccessPattern(true); + target.SetRandomRatio(100); Random r; ThreadParameters tp; @@ -266,21 +266,26 @@ namespace UnitTests TimeSpan timespan; tp.pTimeSpan = ×pan; - tp.vullPrivateSequentialOffsets.push_back(0); - tp.vullFileSizes.push_back(3000); + ThreadTargetState tts(&tp, 0, 3000); + IORequest ior(tp.pRand); + + ULARGE_INTEGER nextOffset; for( int i = 0; i < 10; ++i ) { - UINT64 nextOffset = IORequestGenerator::GetNextFileOffset(tp, 0, 0); - VERIFY_IS_GREATER_THAN_OR_EQUAL(nextOffset, 1000); - VERIFY_IS_LESS_THAN_OR_EQUAL(nextOffset, 2000); - VERIFY_ARE_EQUAL(nextOffset % 500, 0); + tts.NextIORequest(ior); + nextOffset.LowPart = ior.GetOverlapped()->Offset; + nextOffset.HighPart = ior.GetOverlapped()->OffsetHigh; + + VERIFY_IS_GREATER_THAN_OR_EQUAL(nextOffset.QuadPart, 1000); + VERIFY_IS_LESS_THAN_OR_EQUAL(nextOffset.QuadPart, 2000); + VERIFY_ARE_EQUAL(nextOffset.QuadPart % 500, 0); } } - + void IORequestGeneratorUnitTests::Test_GetNextFileOffsetSequential() { - Target target; + Target target; target.SetBaseFileOffsetInBytes(1000); target.SetBlockAlignmentInBytes(500); target.SetBlockSizeInBytes(1000); @@ -293,24 +298,32 @@ namespace UnitTests TimeSpan timespan; tp.pTimeSpan = ×pan; - tp.vullPrivateSequentialOffsets.push_back(0); - tp.vullFileSizes.push_back(3000); - - UINT64 nextOffset; - - nextOffset = IORequestGenerator::GetNextFileOffset(tp, 0, FIRST_OFFSET); - VERIFY_ARE_EQUAL(nextOffset, 1000); - nextOffset = IORequestGenerator::GetNextFileOffset(tp, 0, 0); - VERIFY_ARE_EQUAL(nextOffset, 1500); - nextOffset = IORequestGenerator::GetNextFileOffset(tp, 0, 0); - VERIFY_ARE_EQUAL(nextOffset, 2000); - nextOffset = IORequestGenerator::GetNextFileOffset(tp, 0, 0); - VERIFY_ARE_EQUAL(nextOffset, 1000); + ThreadTargetState tts(&tp, 0, 3000); + IORequest ior(tp.pRand); + + ULARGE_INTEGER nextOffset; + + tts.NextIORequest(ior); + nextOffset.LowPart = ior.GetOverlapped()->Offset; + nextOffset.HighPart = ior.GetOverlapped()->OffsetHigh; + VERIFY_ARE_EQUAL(nextOffset.QuadPart, 1000); + tts.NextIORequest(ior); + nextOffset.LowPart = ior.GetOverlapped()->Offset; + nextOffset.HighPart = ior.GetOverlapped()->OffsetHigh; + VERIFY_ARE_EQUAL(nextOffset.QuadPart, 1500); + tts.NextIORequest(ior); + nextOffset.LowPart = ior.GetOverlapped()->Offset; + nextOffset.HighPart = ior.GetOverlapped()->OffsetHigh; + VERIFY_ARE_EQUAL(nextOffset.QuadPart, 2000); + tts.NextIORequest(ior); + nextOffset.LowPart = ior.GetOverlapped()->Offset; + nextOffset.HighPart = ior.GetOverlapped()->OffsetHigh; + VERIFY_ARE_EQUAL(nextOffset.QuadPart, 1000); } - + void IORequestGeneratorUnitTests::Test_GetNextFileOffsetInterlockedSequential() { - Target target; + Target target; target.SetBaseFileOffsetInBytes(1000); target.SetBlockAlignmentInBytes(500); target.SetBlockSizeInBytes(1000); @@ -331,31 +344,44 @@ namespace UnitTests TimeSpan timespan; timespan.SetThreadCount(2); - + tp1.pTimeSpan = ×pan; tp2.pTimeSpan = ×pan; - tp1.vullFileSizes.push_back(3000); - tp2.vullFileSizes.push_back(3000); + ThreadTargetState tts1(&tp1, 0, 3000); + ThreadTargetState tts2(&tp2, 0, 3000); + IORequest ior(tp1.pRand); - UINT64 nextOffset; + ULARGE_INTEGER nextOffset; // begin at base - nextOffset = IORequestGenerator::GetNextFileOffset(tp1, 0, FIRST_OFFSET); - VERIFY_ARE_EQUAL(nextOffset, 1000); - nextOffset = IORequestGenerator::GetNextFileOffset(tp1, 0, 0); - VERIFY_ARE_EQUAL(nextOffset, 1500); - nextOffset = IORequestGenerator::GetNextFileOffset(tp2, 0, FIRST_OFFSET); - VERIFY_ARE_EQUAL(nextOffset, 2000); - nextOffset = IORequestGenerator::GetNextFileOffset(tp1, 0, 0); - VERIFY_ARE_EQUAL(nextOffset, 1000); - nextOffset = IORequestGenerator::GetNextFileOffset(tp2, 0, 0); - VERIFY_ARE_EQUAL(nextOffset, 1500); + // thread 2 jumps in and continues the pattern (despite FIRST_OFFSET) + // note that blocksize is 1000, so we loop back at 2000 + tts1.NextIORequest(ior); + nextOffset.LowPart = ior.GetOverlapped()->Offset; + nextOffset.HighPart = ior.GetOverlapped()->OffsetHigh; + VERIFY_ARE_EQUAL(nextOffset.QuadPart, 1000); + tts1.NextIORequest(ior); + nextOffset.LowPart = ior.GetOverlapped()->Offset; + nextOffset.HighPart = ior.GetOverlapped()->OffsetHigh; + VERIFY_ARE_EQUAL(nextOffset.QuadPart, 1500); + tts2.NextIORequest(ior); + nextOffset.LowPart = ior.GetOverlapped()->Offset; + nextOffset.HighPart = ior.GetOverlapped()->OffsetHigh; + VERIFY_ARE_EQUAL(nextOffset.QuadPart, 2000); + tts1.NextIORequest(ior); + nextOffset.LowPart = ior.GetOverlapped()->Offset; + nextOffset.HighPart = ior.GetOverlapped()->OffsetHigh; + VERIFY_ARE_EQUAL(nextOffset.QuadPart, 1000); + tts2.NextIORequest(ior); + nextOffset.LowPart = ior.GetOverlapped()->Offset; + nextOffset.HighPart = ior.GetOverlapped()->OffsetHigh; + VERIFY_ARE_EQUAL(nextOffset.QuadPart, 1500); } void IORequestGeneratorUnitTests::Test_GetNextFileOffsetParallelAsyncIO() { - Target target; + Target target; target.SetBaseFileOffsetInBytes(1000); target.SetBlockAlignmentInBytes(500); target.SetBlockSizeInBytes(1000); @@ -369,20 +395,23 @@ namespace UnitTests TimeSpan timespan; tp.pTimeSpan = ×pan; - tp.vullPrivateSequentialOffsets.push_back(0); - tp.vullFileSizes.push_back(3000); + ThreadTargetState tts(&tp, 0, 3000); + IORequest ior(tp.pRand); - UINT64 nextOffset; + ULARGE_INTEGER nextOffset; { UINT64 aOff[] = { 1000, 1500, 2000, 1000, 1500 }; vector vOff(aOff, aOff + _countof(aOff)); - nextOffset = IORequestGenerator::GetNextFileOffset(tp, 0, FIRST_OFFSET); + tts.InitializeParallelAsyncIORequest(ior); + tts.NextIORequest(ior); for (auto off : vOff) { - VERIFY_ARE_EQUAL(nextOffset, off, L"case 1"); - nextOffset = IORequestGenerator::GetNextFileOffset(tp, 0, nextOffset); + nextOffset.LowPart = ior.GetOverlapped()->Offset; + nextOffset.HighPart = ior.GetOverlapped()->OffsetHigh; + VERIFY_ARE_EQUAL(nextOffset.QuadPart, off, L"case 1"); + tts.NextIORequest(ior); } } } @@ -390,9 +419,9 @@ namespace UnitTests void IORequestGeneratorUnitTests::Test_GetThreadBaseFileOffset() { Random r; - ThreadParameters tp; + ThreadParameters tp; Target target; - + tp.pRand = &r; target.SetBaseFileOffsetInBytes(1000); target.SetBlockAlignmentInBytes(500); @@ -403,8 +432,8 @@ namespace UnitTests UINT64 startingOffset; // normal sequential - both threads, each file at base - tp.vTargets[0].SetUseRandomAccessPattern(false); - tp.vTargets[1].SetUseRandomAccessPattern(false); + tp.vTargets[0].SetRandomRatio(0); + tp.vTargets[1].SetRandomRatio(0); tp.ulThreadNo = 0; tp.ulRelativeThreadNo = 0; @@ -478,8 +507,8 @@ namespace UnitTests UINT64 startingOffset; // normal sequential - first at base, second with stride - tp.vTargets[0].SetUseRandomAccessPattern(false); - tp.vTargets[1].SetUseRandomAccessPattern(false); + tp.vTargets[0].SetRandomRatio(0); + tp.vTargets[1].SetRandomRatio(0); tp.ulThreadNo = 0; tp.ulRelativeThreadNo = 0; @@ -564,12 +593,13 @@ namespace UnitTests ThreadParameters tp; tp.pRand = &r; tp.vTargets.push_back(target); - tp.vullPrivateSequentialOffsets.push_back(0); - tp.vullFileSizes.push_back(3000); - // this is equivalent to -c2000 -T250 -s500 -b1000 + // this is equivalent to -c3000 -T250 -s500 -b1000 + + ThreadTargetState tts(&tp, 0, 3000); + IORequest ior(tp.pRand); - UINT64 nextOffset; + ULARGE_INTEGER nextOffset; // relative thread zero should loop back to base tp.ulThreadNo = 0; @@ -578,11 +608,13 @@ namespace UnitTests UINT64 aOff[] = { 0, 500, 1000, 1500, 2000, 0, 500 }; vector vOff(aOff, aOff + _countof(aOff)); - nextOffset = IORequestGenerator::GetNextFileOffset(tp, 0, FIRST_OFFSET); + tts.NextIORequest(ior); for (auto off : vOff) { - VERIFY_ARE_EQUAL(nextOffset, off, L"case 1"); - nextOffset = IORequestGenerator::GetNextFileOffset(tp, 0, nextOffset); + nextOffset.LowPart = ior.GetOverlapped()->Offset; + nextOffset.HighPart = ior.GetOverlapped()->OffsetHigh; + VERIFY_ARE_EQUAL(nextOffset.QuadPart, off, L"case 1"); + tts.NextIORequest(ior); } } @@ -590,35 +622,41 @@ namespace UnitTests // it will also detect and handle not issuing an IO spanning eof. tp.ulThreadNo = 1; tp.ulRelativeThreadNo = 1; + tts.Reset(); { UINT64 aOff[] = { 250, 750, 1250, 1750, 250, 750 }; vector vOff(aOff, aOff + _countof(aOff)); - nextOffset = IORequestGenerator::GetNextFileOffset(tp, 0, FIRST_OFFSET); + tts.NextIORequest(ior); for (auto off : vOff) { - VERIFY_ARE_EQUAL(nextOffset, off, L"case 2"); - nextOffset = IORequestGenerator::GetNextFileOffset(tp, 0, nextOffset); + nextOffset.LowPart = ior.GetOverlapped()->Offset; + nextOffset.HighPart = ior.GetOverlapped()->OffsetHigh; + VERIFY_ARE_EQUAL(nextOffset.QuadPart, off, L"case 2"); + tts.NextIORequest(ior); } } // increasing the stride, relative thread one will loop back to an earlier offset // before returning to its initial offset tp.vTargets[0].SetThreadStrideInBytes(750); + tts.Reset(); { UINT64 aOff[] = { 750, 1250, 1750, 250, 750, 1250 }; vector vOff(aOff, aOff + _countof(aOff)); - nextOffset = IORequestGenerator::GetNextFileOffset(tp, 0, FIRST_OFFSET); + tts.NextIORequest(ior); for (auto off : vOff) { - VERIFY_ARE_EQUAL(nextOffset, off, L"case 3"); - nextOffset = IORequestGenerator::GetNextFileOffset(tp, 0, nextOffset); + nextOffset.LowPart = ior.GetOverlapped()->Offset; + nextOffset.HighPart = ior.GetOverlapped()->OffsetHigh; + VERIFY_ARE_EQUAL(nextOffset.QuadPart, off, L"case 3"); + tts.NextIORequest(ior); } } } - void IORequestGeneratorUnitTests::Test_SequentialWithStrideNonInterleaved() + void IORequestGeneratorUnitTests::Test_SequentialWithStride() { // this ut handles the case where -T > -s @@ -631,72 +669,659 @@ namespace UnitTests ThreadParameters tp; tp.pRand = &r; tp.vTargets.push_back(target); - tp.vullPrivateSequentialOffsets.push_back(0); - tp.vullFileSizes.push_back(3000); - // this is equivalent to -c2000 -T250 -s500 -b1000 + // this is equivalent to -c3000 -T500 -s250 -b1000 - UINT64 nextOffset; + ThreadTargetState tts(&tp, 0, 3000); + IORequest ior(tp.pRand); + + ULARGE_INTEGER nextOffset; // relative thread zero should loop back to base tp.ulThreadNo = 0; tp.ulRelativeThreadNo = 0; { - UINT64 aOff[] = { 0, 250, 500, 750, 1000, 1250, 1500, 1750, 2000, 0, 250 }; + UINT64 aOff[] = { 0, 250, 500, 750, 1000, 1250, 1500, 1750, 2000, 0, 250, 500, 750, 1000, 1250, 1500, 1750, 2000 }; vector vOff(aOff, aOff + _countof(aOff)); - nextOffset = IORequestGenerator::GetNextFileOffset(tp, 0, FIRST_OFFSET); + tts.NextIORequest(ior); for (auto off : vOff) { - VERIFY_ARE_EQUAL(nextOffset, off, L"case 1"); - nextOffset = IORequestGenerator::GetNextFileOffset(tp, 0, nextOffset); + nextOffset.LowPart = ior.GetOverlapped()->Offset; + nextOffset.HighPart = ior.GetOverlapped()->OffsetHigh; + VERIFY_ARE_EQUAL(nextOffset.QuadPart, off, L"case 1"); + tts.NextIORequest(ior); } } // relative thread one should also loop back to base tp.ulThreadNo = 1; tp.ulRelativeThreadNo = 1; + tts.Reset(); { - UINT64 aOff[] = { 500, 750, 1000, 1250, 1500, 1750, 2000, 0, 250, 500, 750 }; + UINT64 aOff[] = { 500, 750, 1000, 1250, 1500, 1750, 2000, 0, 250, 500, 750, 1000, 1250, 1500, 1750, 2000 }; vector vOff(aOff, aOff + _countof(aOff)); - nextOffset = IORequestGenerator::GetNextFileOffset(tp, 0, FIRST_OFFSET); + tts.NextIORequest(ior); for (auto off : vOff) { - VERIFY_ARE_EQUAL(nextOffset, off, L"case 2"); - nextOffset = IORequestGenerator::GetNextFileOffset(tp, 0, nextOffset); + nextOffset.LowPart = ior.GetOverlapped()->Offset; + nextOffset.HighPart = ior.GetOverlapped()->OffsetHigh; + VERIFY_ARE_EQUAL(nextOffset.QuadPart, off, L"case 2"); + tts.NextIORequest(ior); } } + } + + void IORequestGeneratorUnitTests::Test_SequentialWithStrideUneven() + { + // this ut handles the case where -T > -s and -T is not a multiple of -s + // threads io offsets are disjoint + + Target target; + target.SetThreadStrideInBytes(500); + target.SetBlockAlignmentInBytes(200); + target.SetBlockSizeInBytes(200); + + Random r; + ThreadParameters tp; + tp.pRand = &r; + tp.vTargets.push_back(target); + + // this is equivalent to -c3000 -T500 -s200 -b200 + + ThreadTargetState tts(&tp, 0, 3000); + IORequest ior(tp.pRand); + + ULARGE_INTEGER nextOffset; - // making the stride uneven w.r.t the alignment ... thread zero is the same - tp.vTargets[0].SetThreadStrideInBytes(800); + // relative thread zero should loop back to base tp.ulThreadNo = 0; tp.ulRelativeThreadNo = 0; { - UINT64 aOff[] = { 0, 250, 500, 750, 1000, 1250, 1500, 1750, 2000, 0, 250 }; + UINT64 aOff[] = { 0, 200, 400, 600, 800, 1000, 1200, 1400, 1600, 1800, 2000, 2200, 2400, 2600, 2800, 0, 200, 400 }; vector vOff(aOff, aOff + _countof(aOff)); - nextOffset = IORequestGenerator::GetNextFileOffset(tp, 0, FIRST_OFFSET); + tts.NextIORequest(ior); for (auto off : vOff) { - VERIFY_ARE_EQUAL(nextOffset, off, L"case 3"); - nextOffset = IORequestGenerator::GetNextFileOffset(tp, 0, nextOffset); + nextOffset.LowPart = ior.GetOverlapped()->Offset; + nextOffset.HighPart = ior.GetOverlapped()->OffsetHigh; + VERIFY_ARE_EQUAL(nextOffset.QuadPart, off, L"case 1"); + tts.NextIORequest(ior); } } - // while thread one loops back to an early point in the file, but not base + // relative thread one should also loop back to base tp.ulThreadNo = 1; tp.ulRelativeThreadNo = 1; + tts.Reset(); { - UINT64 aOff[] = { 800, 1050, 1300, 1550, 1800, 50, 300, 550, 800, 1050 }; + UINT64 aOff[] = { 500, 700, 900, 1100, 1300, 1500, 1700, 1900, 2100, 2300, 2500, 2700, 100, 300, 500, 700, 900 }; vector vOff(aOff, aOff + _countof(aOff)); - nextOffset = IORequestGenerator::GetNextFileOffset(tp, 0, FIRST_OFFSET); + tts.NextIORequest(ior); for (auto off : vOff) { - VERIFY_ARE_EQUAL(nextOffset, off, L"case 4"); - nextOffset = IORequestGenerator::GetNextFileOffset(tp, 0, nextOffset); + nextOffset.LowPart = ior.GetOverlapped()->Offset; + nextOffset.HighPart = ior.GetOverlapped()->OffsetHigh; + VERIFY_ARE_EQUAL(nextOffset.QuadPart, off, L"case 2"); + tts.NextIORequest(ior); + } + } + } + + void IORequestGeneratorUnitTests::Test_ThreadTargetStateInit() + { + // this ut validates that a constructed ThreadTargetState + // has an initialized IO type, and that it will then agree + // with the first generated IO's type for homegenous/mixed + // write mixes. + + Target target; + target.SetThreadStrideInBytes(500); + target.SetBlockAlignmentInBytes(200); + target.SetBlockSizeInBytes(200); + + Random r; + ThreadParameters tp; + tp.pRand = &r; + tp.vTargets.push_back(target); + + UINT32 writeMix[] = { 0, 50, 100 }; + vector vwriteMix(writeMix, writeMix + _countof(writeMix)); + + for (auto w : vwriteMix) + { + target.SetWriteRatio(w); + + // Validate that ThreadTargetState defines last IO type in all running modes + + VERIFY_ARE_EQUAL(target.GetIOMode(), IOMode::Sequential); + { + ThreadTargetState tts(&tp, 0, 3000); + VERIFY_ARE_NOT_EQUAL(tts._lastIO, IOOperation::Unknown); + IORequest ior(tp.pRand); + VERIFY_ARE_EQUAL(tts._lastIO, ior.GetIoType()); + } + // nothing to undo + + target.SetUseInterlockedSequential(true); + VERIFY_ARE_EQUAL(target.GetIOMode(), IOMode::InterlockedSequential); + { + ThreadTargetState tts(&tp, 0, 3000); + VERIFY_ARE_NOT_EQUAL(tts._lastIO, IOOperation::Unknown); + IORequest ior(tp.pRand); + VERIFY_ARE_EQUAL(tts._lastIO, ior.GetIoType()); + } + target.SetUseInterlockedSequential(false); + + target.SetRandomRatio(50); + VERIFY_ARE_EQUAL(target.GetIOMode(), IOMode::Mixed); + { + ThreadTargetState tts(&tp, 0, 3000); + VERIFY_ARE_NOT_EQUAL(tts._lastIO, IOOperation::Unknown); + IORequest ior(tp.pRand); + VERIFY_ARE_EQUAL(tts._lastIO, ior.GetIoType()); + } + target.SetRandomRatio(0); + + target.SetRandomRatio(100); + VERIFY_ARE_EQUAL(target.GetIOMode(), IOMode::Random); + { + ThreadTargetState tts(&tp, 0, 3000); + VERIFY_ARE_NOT_EQUAL(tts._lastIO, IOOperation::Unknown); + IORequest ior(tp.pRand); + VERIFY_ARE_EQUAL(tts._lastIO, ior.GetIoType()); + } + target.SetRandomRatio(0); + + target.SetUseParallelAsyncIO(true); + VERIFY_ARE_EQUAL(target.GetIOMode(), IOMode::ParallelAsync); + { + ThreadTargetState tts(&tp, 0, 3000); + VERIFY_ARE_NOT_EQUAL(tts._lastIO, IOOperation::Unknown); + IORequest ior(tp.pRand); + VERIFY_ARE_EQUAL(tts._lastIO, ior.GetIoType()); + } + target.SetUseParallelAsyncIO(false); + } + } + + void IORequestGeneratorUnitTests::Test_ThreadTargetStateEffectiveDistPct() + { + // this ut validates that a constructed ThreadTargetState + // has a properly laid out effective distribution given + // the specified percent distribution on the target. + // + // basic cases: + // hole + // degenerate span (covers no offsets) + // rollover to next (degenerate is not last in dist) + // apply to last (degenerate is last in dist) + + Target target; + target.SetBlockAlignmentInBytes(4*KB); + target.SetBlockSizeInBytes(4*KB); + + Random r; + ThreadParameters tp; + tp.pRand = &r; + + vector v; + + // -rdpct10/10:10/10:0/10 + tail + // this is the same distribution in the cmdlineparser UT + v.emplace_back(0, 10, make_pair(0, 10)); + v.emplace_back(10, 10, make_pair(10, 10)); + v.emplace_back(20, 0, make_pair(20, 10)); // zero IO% length hole + v.emplace_back(20, 80, make_pair(30, 70)); + target.SetDistributionRange(v, DistributionType::Percent); + tp.vTargets.push_back(target); + + { + ThreadTargetState tts(&tp, 0, 100*KB); + vector& ev = tts._vDistributionRange; + + VERIFY_ARE_EQUAL(tts._ioDistributionSpan, (UINT32) 100); + VERIFY_ARE_EQUAL(tts._vDistributionRange.size(), (size_t) 3); + VERIFY_ARE_EQUAL(ev[0]._src, (UINT64) 0); + VERIFY_ARE_EQUAL(ev[0]._span, (UINT64) 10); + VERIFY_ARE_EQUAL(ev[0]._dst.first, (UINT64) 0); + VERIFY_ARE_EQUAL(ev[0]._dst.second, (UINT64) 8*KB); + VERIFY_ARE_EQUAL(ev[1]._src, (UINT64) 10); /// + VERIFY_ARE_EQUAL(ev[1]._span, (UINT64) 10); + VERIFY_ARE_EQUAL(ev[1]._dst.first, (UINT64) 8*KB); + VERIFY_ARE_EQUAL(ev[1]._dst.second, (UINT64) 12*KB); // note length + // note hole removed + VERIFY_ARE_EQUAL(ev[2]._src, (UINT64) 20); /// + VERIFY_ARE_EQUAL(ev[2]._span, (UINT64) 80); + VERIFY_ARE_EQUAL(ev[2]._dst.first, (UINT64) 28*KB); + VERIFY_ARE_EQUAL(ev[2]._dst.second, (UINT64) 72*KB); + tp.vTargets.clear(); + v.clear(); + } + + // + // Degenerate span cases + // + + // -rdpct10/10:10/10:10/1 + tail + // this creates the degenerate - non-degenerate case where + // the non-degenerate must round up. + v.emplace_back(0, 10, make_pair(0, 10)); + v.emplace_back(10, 10, make_pair(10, 10)); + v.emplace_back(20, 10, make_pair(20, 1)); // degenerate < alignment + v.emplace_back(30, 70, make_pair(21, 79)); + target.SetDistributionRange(v, DistributionType::Percent); + tp.vTargets.push_back(target); + + { + ThreadTargetState tts(&tp, 0, 100*KB); + vector& ev = tts._vDistributionRange; + + VERIFY_ARE_EQUAL(tts._ioDistributionSpan, (UINT32) 100); + VERIFY_ARE_EQUAL(tts._vDistributionRange.size(), (size_t) 4); + VERIFY_ARE_EQUAL(ev[0]._src, (UINT64) 0); + VERIFY_ARE_EQUAL(ev[0]._span, (UINT64) 10); + VERIFY_ARE_EQUAL(ev[0]._dst.first, (UINT64) 0); + VERIFY_ARE_EQUAL(ev[0]._dst.second, (UINT64) 8*KB); + VERIFY_ARE_EQUAL(ev[1]._src, (UINT64) 10); /// + VERIFY_ARE_EQUAL(ev[1]._span, (UINT64) 10); + VERIFY_ARE_EQUAL(ev[1]._dst.first, (UINT64) 8*KB); + VERIFY_ARE_EQUAL(ev[1]._dst.second, (UINT64) 12*KB); + VERIFY_ARE_EQUAL(ev[2]._src, (UINT64) 20); /// degenerate + VERIFY_ARE_EQUAL(ev[2]._span, (UINT64) 10); + VERIFY_ARE_EQUAL(ev[2]._dst.first, (UINT64) 20*KB); + VERIFY_ARE_EQUAL(ev[2]._dst.second, (UINT64) 4*KB); + VERIFY_ARE_EQUAL(ev[3]._src, (UINT64) 30); /// + VERIFY_ARE_EQUAL(ev[3]._span, (UINT64) 70); + VERIFY_ARE_EQUAL(ev[3]._dst.first, (UINT64) 24*KB); + VERIFY_ARE_EQUAL(ev[3]._dst.second, (UINT64) 76*KB); + tp.vTargets.clear(); + v.clear(); + } + + // -rdpct10/96:10/3:80/1 + // tail is degenerate and needs to roll to last + v.emplace_back(0, 10, make_pair(0, 96)); + v.emplace_back(10, 10, make_pair(96, 3)); + v.emplace_back(20, 80, make_pair(99, 1)); // degenerate tail + target.SetDistributionRange(v, DistributionType::Percent); + tp.vTargets.push_back(target); + + { + ThreadTargetState tts(&tp, 0, 100*KB); + vector& ev = tts._vDistributionRange; + + VERIFY_ARE_EQUAL(tts._ioDistributionSpan, (UINT32) 100); + VERIFY_ARE_EQUAL(tts._vDistributionRange.size(), (size_t) 2); + VERIFY_ARE_EQUAL(ev[0]._src, (UINT64) 0); + VERIFY_ARE_EQUAL(ev[0]._span, (UINT64) 10); + VERIFY_ARE_EQUAL(ev[0]._dst.first, (UINT64) 0); + VERIFY_ARE_EQUAL(ev[0]._dst.second, (UINT64) 96*KB); + VERIFY_ARE_EQUAL(ev[1]._src, (UINT64) 10); /// + VERIFY_ARE_EQUAL(ev[1]._span, (UINT64) 90); + VERIFY_ARE_EQUAL(ev[1]._dst.first, (UINT64) 96*KB); + VERIFY_ARE_EQUAL(ev[1]._dst.second, (UINT64) 4*KB); + tp.vTargets.clear(); + v.clear(); + } + + // -rdpct10/5:10/1:80/94 + // the degenerate cannot immediately combine with its non-degenerate predecessor, so rolls over + // however, since the predecessor is smaller, it prefers to combine in that direction + v.emplace_back(0, 10, make_pair(0, 5)); + v.emplace_back(10, 10, make_pair(5, 1)); + v.emplace_back(20, 80, make_pair(6, 94)); // non-degenerate tail + target.SetDistributionRange(v, DistributionType::Percent); + tp.vTargets.push_back(target); + + { + ThreadTargetState tts(&tp, 0, 100*KB); + vector& ev = tts._vDistributionRange; + + VERIFY_ARE_EQUAL(tts._ioDistributionSpan, (UINT32) 100); + VERIFY_ARE_EQUAL(tts._vDistributionRange.size(), (size_t) 2); + VERIFY_ARE_EQUAL(ev[0]._src, (UINT64) 0); + VERIFY_ARE_EQUAL(ev[0]._span, (UINT64) 20); + VERIFY_ARE_EQUAL(ev[0]._dst.first, (UINT64) 0); + VERIFY_ARE_EQUAL(ev[0]._dst.second, (UINT64) 4*KB); + VERIFY_ARE_EQUAL(ev[1]._src, (UINT64) 20); /// + VERIFY_ARE_EQUAL(ev[1]._span, (UINT64) 80); + VERIFY_ARE_EQUAL(ev[1]._dst.first, (UINT64) 4*KB); + VERIFY_ARE_EQUAL(ev[1]._dst.second, (UINT64) 96*KB); + tp.vTargets.clear(); + v.clear(); + } + + // -rdpct10/1:10/1 + // first two are degenerate and combine, tail rounds up and consumes rest + v.emplace_back(0, 10, make_pair(0, 1)); + v.emplace_back(10, 10, make_pair(1, 1)); + v.emplace_back(20, 80, make_pair(2, 98)); // non-degenerate tail + target.SetDistributionRange(v, DistributionType::Percent); + tp.vTargets.push_back(target); + + { + ThreadTargetState tts(&tp, 0, 100*KB); + vector& ev = tts._vDistributionRange; + + VERIFY_ARE_EQUAL(tts._ioDistributionSpan, (UINT32) 100); + VERIFY_ARE_EQUAL(tts._vDistributionRange.size(), (size_t) 2); + VERIFY_ARE_EQUAL(ev[0]._src, (UINT64) 0); + VERIFY_ARE_EQUAL(ev[0]._span, (UINT64) 20); + VERIFY_ARE_EQUAL(ev[0]._dst.first, (UINT64) 0); + VERIFY_ARE_EQUAL(ev[0]._dst.second, (UINT64) 4*KB); + VERIFY_ARE_EQUAL(ev[1]._src, (UINT64) 20); /// + VERIFY_ARE_EQUAL(ev[1]._span, (UINT64) 80); + VERIFY_ARE_EQUAL(ev[1]._dst.first, (UINT64) 4*KB); + VERIFY_ARE_EQUAL(ev[1]._dst.second, (UINT64) 96*KB); + tp.vTargets.clear(); + v.clear(); + } + + // repeated 10/1 + // This stresses the combination logic + // The first four should successively combine in a [0, 4KiB) range, the fifth + // will land in a new [4KiB,8KiB) range. + // + for (int n = 1; n <= 5; ++n) + { + int m; + for (m = 0; m < n; ++m) + { + v.emplace_back(m*10, 10, make_pair(m*1, 1)); + } + v.emplace_back(m*10, 100 - m*10, make_pair(m, 100 - m)); // non-degenerate tail + target.SetDistributionRange(v, DistributionType::Percent); + tp.vTargets.push_back(target); + + // Fifth case is three ranges; handle seperately. May be worth wrestling down + // how to combine these to allow us to sweep n further, but for now this is fine. + if (n == 5) + { + ThreadTargetState tts(&tp, 0, 100*KB); + vector& ev = tts._vDistributionRange; + + VERIFY_ARE_EQUAL(tts._ioDistributionSpan, (UINT32) 100); + VERIFY_ARE_EQUAL(tts._vDistributionRange.size(), (size_t) 3); + VERIFY_ARE_EQUAL(ev[0]._src, (UINT64) 0); + VERIFY_ARE_EQUAL(ev[0]._span, (UINT64) 40); + VERIFY_ARE_EQUAL(ev[0]._dst.first, (UINT64) 0); + VERIFY_ARE_EQUAL(ev[0]._dst.second, (UINT64) 4*KB); + VERIFY_ARE_EQUAL(ev[1]._src, (UINT64) 40); /// + VERIFY_ARE_EQUAL(ev[1]._span, (UINT64) 10); + VERIFY_ARE_EQUAL(ev[1]._dst.first, (UINT64) 4*KB); + VERIFY_ARE_EQUAL(ev[1]._dst.second, (UINT64) 4*KB); + VERIFY_ARE_EQUAL(ev[2]._src, (UINT64) 50); /// + VERIFY_ARE_EQUAL(ev[2]._span, (UINT64) 50); + VERIFY_ARE_EQUAL(ev[2]._dst.first, (UINT64) 8*KB); + VERIFY_ARE_EQUAL(ev[2]._dst.second, (UINT64) 92*KB); + tp.vTargets.clear(); + v.clear(); + break; } + + { + ThreadTargetState tts(&tp, 0, 100*KB); + vector& ev = tts._vDistributionRange; + + VERIFY_ARE_EQUAL(tts._ioDistributionSpan, (UINT32) 100); + VERIFY_ARE_EQUAL(tts._vDistributionRange.size(), (size_t) 2); + VERIFY_ARE_EQUAL(ev[0]._src, (UINT64) 0); + VERIFY_ARE_EQUAL(ev[0]._span, (UINT64) m*10); + VERIFY_ARE_EQUAL(ev[0]._dst.first, (UINT64) 0); + VERIFY_ARE_EQUAL(ev[0]._dst.second, (UINT64) 4*KB); + VERIFY_ARE_EQUAL(ev[1]._src, (UINT64) m*10); /// + VERIFY_ARE_EQUAL(ev[1]._span, (UINT64) 100 - m*10); + VERIFY_ARE_EQUAL(ev[1]._dst.first, (UINT64) 4*KB); + VERIFY_ARE_EQUAL(ev[1]._dst.second, (UINT64) 96*KB); + tp.vTargets.clear(); + v.clear(); + } + } + } + + void IORequestGeneratorUnitTests::Test_ThreadTargetStateEffectiveDistAbs() + { + // this ut validates that a constructed ThreadTargetState + // has a properly laid out effective distribution given + // the specified absolute distribution on the target. + + Target target; + target.SetBlockAlignmentInBytes(4*KB); + target.SetBlockSizeInBytes(4*KB); + + Random r; + ThreadParameters tp; + + vector v; + + // -rdabs10/1G:10/1G:0/100G, again producing tail - with autoscale (0) + // this is the same distribution in the cmdlineparser UT + // aligned tail range + v.emplace_back(0,10, make_pair(0, 1*GB)); + v.emplace_back(10,10, make_pair(1*GB, 1*GB)); + v.emplace_back(20,0, make_pair(2*GB, 100*GB)); + v.emplace_back(20,80, make_pair(102*GB, 0)); + target.SetDistributionRange(v, DistributionType::Absolute); + tp.vTargets.push_back(target); + + { + ThreadTargetState tts(&tp, 0, 200*GB); + vector& ev = tts._vDistributionRange; + + VERIFY_ARE_EQUAL(tts._ioDistributionSpan, (UINT32) 100); + VERIFY_ARE_EQUAL(tts._vDistributionRange.size(), (size_t) 3); + VERIFY_ARE_EQUAL(ev[0]._src, (UINT64) 0); + VERIFY_ARE_EQUAL(ev[0]._span, (UINT64) 10); + VERIFY_ARE_EQUAL(ev[0]._dst.first, (UINT64) 0); + VERIFY_ARE_EQUAL(ev[0]._dst.second, (UINT64) 1*GB); + VERIFY_ARE_EQUAL(ev[1]._src, (UINT64) 10); /// + VERIFY_ARE_EQUAL(ev[1]._span, (UINT64) 10); + VERIFY_ARE_EQUAL(ev[1]._dst.first, (UINT64) 1*GB); + VERIFY_ARE_EQUAL(ev[1]._dst.second, (UINT64) 1*GB); // note length + // note hole removed + VERIFY_ARE_EQUAL(ev[2]._src, (UINT64) 20); /// + VERIFY_ARE_EQUAL(ev[2]._span, (UINT64) 80); + VERIFY_ARE_EQUAL(ev[2]._dst.first, (UINT64) 102*GB); + VERIFY_ARE_EQUAL(ev[2]._dst.second, (UINT64) 98*GB); + tp.vTargets.clear(); + v.clear(); + } + + // -rdabs10/1G:10/1G:0/100G:20/1T + // trim - distribution exceeds target size, end is in last range so no IO% trim + // note same distribution aside from hardening the tail (no autoscale) to a large value + v.emplace_back(0,10, make_pair(0, 1*GB)); + v.emplace_back(10,10, make_pair(1*GB, 1*GB)); + v.emplace_back(20,0, make_pair(2*GB, 100*GB)); + v.emplace_back(20,80, make_pair(102*GB, 1*TB)); + target.SetDistributionRange(v, DistributionType::Absolute); + tp.vTargets.push_back(target); + + { + ThreadTargetState tts(&tp, 0, 200*GB); + vector& ev = tts._vDistributionRange; + + VERIFY_ARE_EQUAL(tts._ioDistributionSpan, (UINT32) 100); + VERIFY_ARE_EQUAL(tts._vDistributionRange.size(), (size_t) 3); + VERIFY_ARE_EQUAL(ev[0]._src, (UINT64) 0); + VERIFY_ARE_EQUAL(ev[0]._span, (UINT64) 10); + VERIFY_ARE_EQUAL(ev[0]._dst.first, (UINT64) 0); + VERIFY_ARE_EQUAL(ev[0]._dst.second, (UINT64) 1*GB); + VERIFY_ARE_EQUAL(ev[1]._src, (UINT64) 10); /// + VERIFY_ARE_EQUAL(ev[1]._span, (UINT64) 10); + VERIFY_ARE_EQUAL(ev[1]._dst.first, (UINT64) 1*GB); + VERIFY_ARE_EQUAL(ev[1]._dst.second, (UINT64) 1*GB); // note length + // note hole removed + VERIFY_ARE_EQUAL(ev[2]._src, (UINT64) 20); /// + VERIFY_ARE_EQUAL(ev[2]._span, (UINT64) 80); + VERIFY_ARE_EQUAL(ev[2]._dst.first, (UINT64) 102*GB); + VERIFY_ARE_EQUAL(ev[2]._dst.second, (UINT64) 98*GB); + tp.vTargets.clear(); + v.clear(); + } + + // -rdabs10/100G:10/100G + // trim - distribution exceeds target size (autoscale tail); target end aligned to last range start + // drop the IO% of the trailing range + v.emplace_back(0,10, make_pair(0, 100*GB)); + v.emplace_back(10,10, make_pair(100*GB, 100*GB)); + v.emplace_back(20,80, make_pair(200*GB, 0)); + target.SetDistributionRange(v, DistributionType::Absolute); + tp.vTargets.push_back(target); + + { + ThreadTargetState tts(&tp, 0, 200*GB); + vector& ev = tts._vDistributionRange; + + VERIFY_ARE_EQUAL(tts._vDistributionRange.size(), (size_t) 2); + VERIFY_ARE_EQUAL(tts._ioDistributionSpan, (UINT32) 20); + VERIFY_ARE_EQUAL(ev[0]._src, (UINT64) 0); + VERIFY_ARE_EQUAL(ev[0]._span, (UINT64) 10); + VERIFY_ARE_EQUAL(ev[0]._dst.first, (UINT64) 0); + VERIFY_ARE_EQUAL(ev[0]._dst.second, (UINT64) 100*GB); + VERIFY_ARE_EQUAL(ev[1]._src, (UINT64) 10); /// + VERIFY_ARE_EQUAL(ev[1]._span, (UINT64) 10); + VERIFY_ARE_EQUAL(ev[1]._dst.first, (UINT64) 100*GB); + VERIFY_ARE_EQUAL(ev[1]._dst.second, (UINT64) 100*GB); // note length + tp.vTargets.clear(); + v.clear(); + } + + // -rdabs10/100G:10/200G + // trim - distribution exceeds target size (autoscale tail); last range beyond target end + // drop the IO% of the trailing range + v.emplace_back(0,10, make_pair(0, 100*GB)); + v.emplace_back(10,10, make_pair(100*GB, 200*GB)); + v.emplace_back(20,80, make_pair(300*GB, 0)); + target.SetDistributionRange(v, DistributionType::Absolute); + tp.vTargets.push_back(target); + + { + ThreadTargetState tts(&tp, 0, 200*GB); + vector& ev = tts._vDistributionRange; + + VERIFY_ARE_EQUAL(tts._vDistributionRange.size(), (size_t) 2); + VERIFY_ARE_EQUAL(tts._ioDistributionSpan, (UINT32) 20); + VERIFY_ARE_EQUAL(ev[0]._src, (UINT64) 0); + VERIFY_ARE_EQUAL(ev[0]._span, (UINT64) 10); + VERIFY_ARE_EQUAL(ev[0]._dst.first, (UINT64) 0); + VERIFY_ARE_EQUAL(ev[0]._dst.second, (UINT64) 100*GB); + VERIFY_ARE_EQUAL(ev[1]._src, (UINT64) 10); /// + VERIFY_ARE_EQUAL(ev[1]._span, (UINT64) 10); + VERIFY_ARE_EQUAL(ev[1]._dst.first, (UINT64) 100*GB); + VERIFY_ARE_EQUAL(ev[1]._dst.second, (UINT64) 100*GB); // note length + tp.vTargets.clear(); + v.clear(); + } + + // + // Unaligned intervals + // + + target.SetBlockAlignmentInBytes(3*KB); + target.SetBlockSizeInBytes(3*KB); + + // -rdabs10/10K:10/10K + // not naturally aligned to target, but is naturally aligned to interval (!) + // 0-10k and 10k-90k - IO all the way to 100K (97K + 3K) is OK + v.emplace_back(0,10, make_pair(0, 10*KB)); + v.emplace_back(10,10, make_pair(10*KB, 90*KB)); + v.emplace_back(20,80, make_pair(300*GB, 0)); + target.SetDistributionRange(v, DistributionType::Absolute); + tp.vTargets.push_back(target); + + { + ThreadTargetState tts(&tp, 0, 100*KB); + vector& ev = tts._vDistributionRange; + + VERIFY_ARE_EQUAL(tts._vDistributionRange.size(), (size_t) 2); + VERIFY_ARE_EQUAL(tts._ioDistributionSpan, (UINT32) 20); + VERIFY_ARE_EQUAL(ev[0]._src, (UINT64) 0); + VERIFY_ARE_EQUAL(ev[0]._span, (UINT64) 10); + VERIFY_ARE_EQUAL(ev[0]._dst.first, (UINT64) 0); + VERIFY_ARE_EQUAL(ev[0]._dst.second, (UINT64) 10*KB); + VERIFY_ARE_EQUAL(ev[1]._src, (UINT64) 10); /// + VERIFY_ARE_EQUAL(ev[1]._span, (UINT64) 10); + VERIFY_ARE_EQUAL(ev[1]._dst.first, (UINT64) 10*KB); + VERIFY_ARE_EQUAL(ev[1]._dst.second, (UINT64) 90*KB); // note length + tp.vTargets.clear(); + v.clear(); + } + + // -rdabs10/10K:10/1G + // previous distribution, target ends in last range (same result, trimmed) + v.emplace_back(0,10, make_pair(0, 10*KB)); + v.emplace_back(10,10, make_pair(10*KB, 90*KB)); + v.emplace_back(20,80, make_pair(300*GB, 0)); + target.SetDistributionRange(v, DistributionType::Absolute); + tp.vTargets.push_back(target); + + { + ThreadTargetState tts(&tp, 0, 100*KB); + vector& ev = tts._vDistributionRange; + + VERIFY_ARE_EQUAL(tts._vDistributionRange.size(), (size_t) 2); + VERIFY_ARE_EQUAL(tts._ioDistributionSpan, (UINT32) 20); + VERIFY_ARE_EQUAL(ev[0]._src, (UINT64) 0); + VERIFY_ARE_EQUAL(ev[0]._span, (UINT64) 10); + VERIFY_ARE_EQUAL(ev[0]._dst.first, (UINT64) 0); + VERIFY_ARE_EQUAL(ev[0]._dst.second, (UINT64) 10*KB); + VERIFY_ARE_EQUAL(ev[1]._src, (UINT64) 10); /// + VERIFY_ARE_EQUAL(ev[1]._span, (UINT64) 10); + VERIFY_ARE_EQUAL(ev[1]._dst.first, (UINT64) 10*KB); + VERIFY_ARE_EQUAL(ev[1]._dst.second, (UINT64) 90*KB); // note length + tp.vTargets.clear(); + v.clear(); + } + + // -rdabs10/98k + // autoscale tail cannot issue IO, so distribution is not extended + v.emplace_back(0,10, make_pair(0, 98*KB)); + v.emplace_back(10,90, make_pair(98*KB, 0)); + target.SetDistributionRange(v, DistributionType::Absolute); + tp.vTargets.push_back(target); + + { + ThreadTargetState tts(&tp, 0, 100*KB); + vector& ev = tts._vDistributionRange; + + VERIFY_ARE_EQUAL(tts._vDistributionRange.size(), (size_t) 1); + VERIFY_ARE_EQUAL(tts._ioDistributionSpan, (UINT32) 10); + VERIFY_ARE_EQUAL(ev[0]._src, (UINT64) 0); + VERIFY_ARE_EQUAL(ev[0]._span, (UINT64) 10); + VERIFY_ARE_EQUAL(ev[0]._dst.first, (UINT64) 0); + VERIFY_ARE_EQUAL(ev[0]._dst.second, (UINT64) 98*KB); + tp.vTargets.clear(); + v.clear(); + } + + // -rdabs10/96k + // autoscale tail can issue single IO @ 96K + v.emplace_back(0,10, make_pair(0, 96*KB)); + v.emplace_back(10,90, make_pair(96*KB, 0)); + target.SetDistributionRange(v, DistributionType::Absolute); + tp.vTargets.push_back(target); + + { + ThreadTargetState tts(&tp, 0, 100*KB); + vector& ev = tts._vDistributionRange; + + VERIFY_ARE_EQUAL(tts._vDistributionRange.size(), (size_t) 2); + VERIFY_ARE_EQUAL(tts._ioDistributionSpan, (UINT32) 100); + VERIFY_ARE_EQUAL(ev[0]._src, (UINT64) 0); + VERIFY_ARE_EQUAL(ev[0]._span, (UINT64) 10); + VERIFY_ARE_EQUAL(ev[0]._dst.first, (UINT64) 0); + VERIFY_ARE_EQUAL(ev[0]._dst.second, (UINT64) 96*KB); + VERIFY_ARE_EQUAL(ev[1]._src, (UINT64) 10); + VERIFY_ARE_EQUAL(ev[1]._span, (UINT64) 90); + VERIFY_ARE_EQUAL(ev[1]._dst.first, (UINT64) 96*KB); + VERIFY_ARE_EQUAL(ev[1]._dst.second, (UINT64) 4*KB); + tp.vTargets.clear(); + v.clear(); } } -} +} \ No newline at end of file diff --git a/UnitTests/IORequestGenerator/IORequestGenerator.UnitTests.h b/UnitTests/IORequestGenerator/IORequestGenerator.UnitTests.h index 0589a9e..abfe1a3 100644 --- a/UnitTests/IORequestGenerator/IORequestGenerator.UnitTests.h +++ b/UnitTests/IORequestGenerator/IORequestGenerator.UnitTests.h @@ -45,14 +45,20 @@ namespace UnitTests TEST_METHOD(Test_GetFilesToPrecreateConstantSizes); TEST_METHOD(Test_GetFilesToPrecreateConstantOrZeroSizes); TEST_METHOD(Test_GetFilesToPrecreateUseMaxSize); + TEST_METHOD(Test_GetNextFileOffsetRandom); TEST_METHOD(Test_GetNextFileOffsetSequential); TEST_METHOD(Test_GetNextFileOffsetInterlockedSequential); TEST_METHOD(Test_GetNextFileOffsetParallelAsyncIO); + TEST_METHOD(Test_SequentialWithStride); + TEST_METHOD(Test_SequentialWithStrideInterleaved); + TEST_METHOD(Test_SequentialWithStrideUneven); + TEST_METHOD(Test_GetThreadBaseFileOffset); TEST_METHOD(Test_GetThreadBaseFileOffsetWithStride); - TEST_METHOD(Test_SequentialWithStrideInterleaved); - TEST_METHOD(Test_SequentialWithStrideNonInterleaved); - }; -} + TEST_METHOD(Test_ThreadTargetStateInit); + TEST_METHOD(Test_ThreadTargetStateEffectiveDistPct); + TEST_METHOD(Test_ThreadTargetStateEffectiveDistAbs); + }; +} \ No newline at end of file diff --git a/UnitTests/ResultParser/ResultParser.UnitTests.cpp b/UnitTests/ResultParser/ResultParser.UnitTests.cpp index a646679..fd5dca3 100644 --- a/UnitTests/ResultParser/ResultParser.UnitTests.cpp +++ b/UnitTests/ResultParser/ResultParser.UnitTests.cpp @@ -138,27 +138,62 @@ namespace UnitTests "Total IO\n" "thread | bytes | I/Os | MiB/s | I/O per s | IopsStdDev | file\n" "-------------------------------------------------------------------------------------------\n" - " 0 | 6291456 | 16 | 0.05 | 0.13 | 0.00 | testfile1.dat (10240KiB)\n" + " 0 | 6291456 | 16 | 0.05 | 0.13 | 0.00 | testfile1.dat (10MiB)\n" "-------------------------------------------------------------------------------------------\n" "total: 6291456 | 16 | 0.05 | 0.13 | 0.00\n" "\n" "Read IO\n" "thread | bytes | I/Os | MiB/s | I/O per s | IopsStdDev | file\n" "-------------------------------------------------------------------------------------------\n" - " 0 | 4194304 | 6 | 0.03 | 0.05 | 0.00 | testfile1.dat (10240KiB)\n" + " 0 | 4194304 | 6 | 0.03 | 0.05 | 0.00 | testfile1.dat (10MiB)\n" "-------------------------------------------------------------------------------------------\n" "total: 4194304 | 6 | 0.03 | 0.05 | 0.00\n" "\n" "Write IO\n" "thread | bytes | I/Os | MiB/s | I/O per s | IopsStdDev | file\n" "-------------------------------------------------------------------------------------------\n" - " 0 | 2097152 | 10 | 0.02 | 0.08 | 0.00 | testfile1.dat (10240KiB)\n" + " 0 | 2097152 | 10 | 0.02 | 0.08 | 0.00 | testfile1.dat (10MiB)\n" "-------------------------------------------------------------------------------------------\n" "total: 2097152 | 10 | 0.02 | 0.08 | 0.00\n"; + VERIFY_ARE_EQUAL(sResults, pcszExpectedOutput); + } + + void ResultParserUnitTests::Test_ParseProfile() + { + Profile profile; + ResultParser parser; + TimeSpan timeSpan; + Target target; + + timeSpan.AddTarget(target); + profile.AddTimeSpan(timeSpan); - VERIFY_ARE_EQUAL(sResults.length(), strlen(pcszExpectedOutput)); - VERIFY_ARE_EQUAL(memcmp(sResults.c_str(), pcszExpectedOutput, sResults.length()), 0); - VERIFY_IS_TRUE(strcmp(sResults.c_str(), pcszExpectedOutput) == 0); + string s = parser.ParseProfile(profile); + const char *pszExpectedResult = "\nCommand Line: \n" + "\n" + "Input parameters:\n" + "\n" + "\ttimespan: 1\n" + "\t-------------\n" + "\tduration: 10s\n" + "\twarm up time: 5s\n" + "\tcool down time: 0s\n" + "\trandom seed: 0\n" + "\tpath: ''\n" + "\t\tthink time: 0ms\n" + "\t\tburst size: 0\n" + "\t\tusing software cache\n" + "\t\tusing hardware write cache, writethrough off\n" + "\t\tperforming read test\n" + "\t\tblock size: 64KiB\n" + "\t\tusing sequential I/O (stride: 64KiB)\n" + "\t\tnumber of outstanding I/O operations per thread: 2\n" + "\t\tthreads per file: 1\n" + "\t\tusing I/O Completion Ports\n" + "\t\tIO priority: normal\n\n"; + + VERIFY_ARE_EQUAL(strlen(pszExpectedResult), s.length()); + VERIFY_IS_TRUE(!strcmp(pszExpectedResult, s.c_str())); } void ResultParserUnitTests::Test_PrintTarget() @@ -173,14 +208,98 @@ namespace UnitTests "\t\tusing software cache\n" "\t\tusing hardware write cache, writethrough off\n" "\t\tperforming read test\n" - "\t\tblock size: 65536\n" - "\t\tusing sequential I/O (stride: 65536)\n" - "\t\tnumber of outstanding I/O operations: 2\n" - "\t\tthread stride size: 0\n" + "\t\tblock size: 64KiB\n" + "\t\tusing sequential I/O (stride: 64KiB)\n" + "\t\tnumber of outstanding I/O operations per thread: 2\n" "\t\tIO priority: normal\n"; - VERIFY_IS_TRUE(parser._sResult == pszExpectedResult); + VERIFY_ARE_EQUAL(parser._sResult, pszExpectedResult); - parser._sResult = ""; + parser._sResult.clear(); + target.SetThreadStrideInBytes(100 * 1024); + parser._PrintTarget(target, false, true, false); + pszExpectedResult = "\tpath: ''\n" \ + "\t\tthink time: 0ms\n" + "\t\tburst size: 0\n" + "\t\tusing software cache\n" + "\t\tusing hardware write cache, writethrough off\n" + "\t\tperforming read test\n" + "\t\tblock size: 64KiB\n" + "\t\tusing sequential I/O (stride: 64KiB)\n" + "\t\tnumber of outstanding I/O operations per thread: 2\n" + "\t\tthread stride size: 100KiB\n" + "\t\tIO priority: normal\n"; + VERIFY_ARE_EQUAL(parser._sResult, pszExpectedResult); + target.SetThreadStrideInBytes(0); + + parser._sResult.clear(); + target.SetMaxFileSize(2000 * 1024); + parser._PrintTarget(target, false, true, false); + pszExpectedResult = "\tpath: ''\n" \ + "\t\tthink time: 0ms\n" + "\t\tburst size: 0\n" + "\t\tusing software cache\n" + "\t\tusing hardware write cache, writethrough off\n" + "\t\tperforming read test\n" + "\t\tblock size: 64KiB\n" + "\t\tusing sequential I/O (stride: 64KiB)\n" + "\t\tnumber of outstanding I/O operations per thread: 2\n" + "\t\tmax file size: 1.95MiB\n" + "\t\tIO priority: normal\n"; + VERIFY_ARE_EQUAL(parser._sResult, pszExpectedResult); + target.SetMaxFileSize(0); + + parser._sResult.clear(); + target.SetBaseFileOffsetInBytes(2 * 1024 * 1024); + parser._PrintTarget(target, false, true, false); + pszExpectedResult = "\tpath: ''\n" \ + "\t\tthink time: 0ms\n" + "\t\tburst size: 0\n" + "\t\tusing software cache\n" + "\t\tusing hardware write cache, writethrough off\n" + "\t\tperforming read test\n" + "\t\tblock size: 64KiB\n" + "\t\tusing sequential I/O (stride: 64KiB)\n" + "\t\tnumber of outstanding I/O operations per thread: 2\n" + "\t\tbase file offset: 2MiB\n" + "\t\tIO priority: normal\n"; + VERIFY_ARE_EQUAL(parser._sResult, pszExpectedResult); + target.SetBaseFileOffsetInBytes(0); + + parser._sResult.clear(); + target.SetThroughput(1000); + parser._PrintTarget(target, false, true, false); + pszExpectedResult = "\tpath: ''\n" \ + "\t\tthink time: 0ms\n" + "\t\tburst size: 0\n" + "\t\tusing software cache\n" + "\t\tusing hardware write cache, writethrough off\n" + "\t\tperforming read test\n" + "\t\tblock size: 64KiB\n" + "\t\tusing sequential I/O (stride: 64KiB)\n" + "\t\tnumber of outstanding I/O operations per thread: 2\n" + "\t\tIO priority: normal\n" + "\t\tthroughput rate-limited to 1000 B/ms\n"; + VERIFY_ARE_EQUAL(parser._sResult, pszExpectedResult); + target.SetThroughput(0); + + parser._sResult.clear(); + target.SetThroughputIOPS(1000); + parser._PrintTarget(target, false, true, false); + pszExpectedResult = "\tpath: ''\n" \ + "\t\tthink time: 0ms\n" + "\t\tburst size: 0\n" + "\t\tusing software cache\n" + "\t\tusing hardware write cache, writethrough off\n" + "\t\tperforming read test\n" + "\t\tblock size: 64KiB\n" + "\t\tusing sequential I/O (stride: 64KiB)\n" + "\t\tnumber of outstanding I/O operations per thread: 2\n" + "\t\tIO priority: normal\n" + "\t\tthroughput rate-limited to 1000 IOPS\n"; + VERIFY_ARE_EQUAL(parser._sResult, pszExpectedResult); + target.SetThroughputIOPS(0); + + parser._sResult.clear(); target.SetWriteRatio(30); parser._PrintTarget(target, false, true, false); pszExpectedResult = "\tpath: ''\n" \ @@ -189,15 +308,14 @@ namespace UnitTests "\t\tusing software cache\n" "\t\tusing hardware write cache, writethrough off\n" "\t\tperforming mix test (read/write ratio: 70/30)\n" - "\t\tblock size: 65536\n" - "\t\tusing sequential I/O (stride: 65536)\n" - "\t\tnumber of outstanding I/O operations: 2\n" - "\t\tthread stride size: 0\n" + "\t\tblock size: 64KiB\n" + "\t\tusing sequential I/O (stride: 64KiB)\n" + "\t\tnumber of outstanding I/O operations per thread: 2\n" "\t\tIO priority: normal\n"; - VERIFY_IS_TRUE(parser._sResult == pszExpectedResult); + VERIFY_ARE_EQUAL(parser._sResult, pszExpectedResult); target.SetWriteRatio(0); - parser._sResult = ""; + parser._sResult.clear(); target.SetRandomDataWriteBufferSize(12341234); parser._PrintTarget(target, false, true, false); pszExpectedResult = "\tpath: ''\n" \ @@ -205,35 +323,36 @@ namespace UnitTests "\t\tburst size: 0\n" "\t\tusing software cache\n" "\t\tusing hardware write cache, writethrough off\n" - "\t\twrite buffer size: 12341234\n" + "\t\twrite buffer size: 11.77MiB\n" + "\t\twrite buffer source: random fill\n" "\t\tperforming read test\n" - "\t\tblock size: 65536\n" - "\t\tusing sequential I/O (stride: 65536)\n" - "\t\tnumber of outstanding I/O operations: 2\n" - "\t\tthread stride size: 0\n" + "\t\tblock size: 64KiB\n" + "\t\tusing sequential I/O (stride: 64KiB)\n" + "\t\tnumber of outstanding I/O operations per thread: 2\n" "\t\tIO priority: normal\n"; - VERIFY_IS_TRUE(parser._sResult == pszExpectedResult); + VERIFY_ARE_EQUAL(parser._sResult, pszExpectedResult); - parser._sResult = ""; + parser._sResult.clear(); target.SetRandomDataWriteBufferSourcePath("x:\\foo\\bar.dat"); - target.SetUseRandomAccessPattern(true); + target.SetRandomRatio(100); parser._PrintTarget(target, false, true, false); pszExpectedResult = "\tpath: ''\n" \ "\t\tthink time: 0ms\n" "\t\tburst size: 0\n" "\t\tusing software cache\n" "\t\tusing hardware write cache, writethrough off\n" - "\t\twrite buffer size: 12341234\n" + "\t\twrite buffer size: 11.77MiB\n" "\t\twrite buffer source: 'x:\\foo\\bar.dat'\n" "\t\tperforming read test\n" - "\t\tblock size: 65536\n" - "\t\tusing random I/O (alignment: 65536)\n" - "\t\tnumber of outstanding I/O operations: 2\n" - "\t\tthread stride size: 0\n" + "\t\tblock size: 64KiB\n" + "\t\tusing random I/O (alignment: 64KiB)\n" + "\t\tnumber of outstanding I/O operations per thread: 2\n" "\t\tIO priority: normal\n"; - VERIFY_IS_TRUE(parser._sResult == pszExpectedResult); + VERIFY_ARE_EQUAL(parser._sResult, pszExpectedResult); + target.SetRandomDataWriteBufferSize(0); + target.SetRandomDataWriteBufferSourcePath(""); - parser._sResult = ""; + parser._sResult.clear(); target.SetCacheMode(TargetCacheMode::DisableOSCache); parser._PrintTarget(target, false, true, false); pszExpectedResult = "\tpath: ''\n" \ @@ -241,17 +360,14 @@ namespace UnitTests "\t\tburst size: 0\n" "\t\tsoftware cache disabled\n" "\t\tusing hardware write cache, writethrough off\n" - "\t\twrite buffer size: 12341234\n" - "\t\twrite buffer source: 'x:\\foo\\bar.dat'\n" "\t\tperforming read test\n" - "\t\tblock size: 65536\n" - "\t\tusing random I/O (alignment: 65536)\n" - "\t\tnumber of outstanding I/O operations: 2\n" - "\t\tthread stride size: 0\n" + "\t\tblock size: 64KiB\n" + "\t\tusing random I/O (alignment: 64KiB)\n" + "\t\tnumber of outstanding I/O operations per thread: 2\n" "\t\tIO priority: normal\n"; - VERIFY_IS_TRUE(parser._sResult == pszExpectedResult); + VERIFY_ARE_EQUAL(parser._sResult, pszExpectedResult); - parser._sResult = ""; + parser._sResult.clear(); target.SetCacheMode(TargetCacheMode::DisableOSCache); target.SetWriteThroughMode(WriteThroughMode::On); parser._PrintTarget(target, false, true, false); @@ -260,17 +376,14 @@ namespace UnitTests "\t\tburst size: 0\n" "\t\tsoftware cache disabled\n" "\t\thardware write cache disabled, writethrough on\n" - "\t\twrite buffer size: 12341234\n" - "\t\twrite buffer source: 'x:\\foo\\bar.dat'\n" "\t\tperforming read test\n" - "\t\tblock size: 65536\n" - "\t\tusing random I/O (alignment: 65536)\n" - "\t\tnumber of outstanding I/O operations: 2\n" - "\t\tthread stride size: 0\n" + "\t\tblock size: 64KiB\n" + "\t\tusing random I/O (alignment: 64KiB)\n" + "\t\tnumber of outstanding I/O operations per thread: 2\n" "\t\tIO priority: normal\n"; - VERIFY_IS_TRUE(parser._sResult == pszExpectedResult); + VERIFY_ARE_EQUAL(parser._sResult, pszExpectedResult); - parser._sResult = ""; + parser._sResult.clear(); target.SetCacheMode(TargetCacheMode::Cached); target.SetWriteThroughMode(WriteThroughMode::On); parser._PrintTarget(target, false, true, false); @@ -279,18 +392,15 @@ namespace UnitTests "\t\tburst size: 0\n" "\t\tusing software cache\n" "\t\thardware and software write caches disabled, writethrough on\n" - "\t\twrite buffer size: 12341234\n" - "\t\twrite buffer source: 'x:\\foo\\bar.dat'\n" "\t\tperforming read test\n" - "\t\tblock size: 65536\n" - "\t\tusing random I/O (alignment: 65536)\n" - "\t\tnumber of outstanding I/O operations: 2\n" - "\t\tthread stride size: 0\n" + "\t\tblock size: 64KiB\n" + "\t\tusing random I/O (alignment: 64KiB)\n" + "\t\tnumber of outstanding I/O operations per thread: 2\n" "\t\tIO priority: normal\n"; - VERIFY_IS_TRUE(parser._sResult == pszExpectedResult); + VERIFY_ARE_EQUAL(parser._sResult, pszExpectedResult); target.SetWriteThroughMode(WriteThroughMode::Undefined); - parser._sResult = ""; + parser._sResult.clear(); target.SetCacheMode(TargetCacheMode::DisableLocalCache); parser._PrintTarget(target, false, true, false); pszExpectedResult = "\tpath: ''\n" \ @@ -298,17 +408,14 @@ namespace UnitTests "\t\tburst size: 0\n" "\t\tlocal software cache disabled, remote cache enabled\n" "\t\tusing hardware write cache, writethrough off\n" - "\t\twrite buffer size: 12341234\n" - "\t\twrite buffer source: 'x:\\foo\\bar.dat'\n" "\t\tperforming read test\n" - "\t\tblock size: 65536\n" - "\t\tusing random I/O (alignment: 65536)\n" - "\t\tnumber of outstanding I/O operations: 2\n" - "\t\tthread stride size: 0\n" + "\t\tblock size: 64KiB\n" + "\t\tusing random I/O (alignment: 64KiB)\n" + "\t\tnumber of outstanding I/O operations per thread: 2\n" "\t\tIO priority: normal\n"; - VERIFY_IS_TRUE(parser._sResult == pszExpectedResult); + VERIFY_ARE_EQUAL(parser._sResult, pszExpectedResult); - parser._sResult = ""; + parser._sResult.clear(); target.SetCacheMode(TargetCacheMode::Cached); target.SetMemoryMappedIoMode(MemoryMappedIoMode::On); parser._PrintTarget(target, false, true, false); @@ -318,17 +425,14 @@ namespace UnitTests "\t\tusing software cache\n" "\t\tusing hardware write cache, writethrough off\n" "\t\tmemory mapped I/O enabled\n" - "\t\twrite buffer size: 12341234\n" - "\t\twrite buffer source: 'x:\\foo\\bar.dat'\n" "\t\tperforming read test\n" - "\t\tblock size: 65536\n" - "\t\tusing random I/O (alignment: 65536)\n" - "\t\tnumber of outstanding I/O operations: 2\n" - "\t\tthread stride size: 0\n" + "\t\tblock size: 64KiB\n" + "\t\tusing random I/O (alignment: 64KiB)\n" + "\t\tnumber of outstanding I/O operations per thread: 2\n" "\t\tIO priority: normal\n"; - VERIFY_IS_TRUE(parser._sResult == pszExpectedResult); + VERIFY_ARE_EQUAL(parser._sResult, pszExpectedResult); - parser._sResult = ""; + parser._sResult.clear(); target.SetMemoryMappedIoFlushMode(MemoryMappedIoFlushMode::ViewOfFile); parser._PrintTarget(target, false, true, false); pszExpectedResult = "\tpath: ''\n" \ @@ -337,17 +441,14 @@ namespace UnitTests "\t\tusing software cache\n" "\t\tusing hardware write cache, writethrough off\n" "\t\tmemory mapped I/O enabled, flush mode: FlushViewOfFile\n" - "\t\twrite buffer size: 12341234\n" - "\t\twrite buffer source: 'x:\\foo\\bar.dat'\n" "\t\tperforming read test\n" - "\t\tblock size: 65536\n" - "\t\tusing random I/O (alignment: 65536)\n" - "\t\tnumber of outstanding I/O operations: 2\n" - "\t\tthread stride size: 0\n" + "\t\tblock size: 64KiB\n" + "\t\tusing random I/O (alignment: 64KiB)\n" + "\t\tnumber of outstanding I/O operations per thread: 2\n" "\t\tIO priority: normal\n"; - VERIFY_IS_TRUE(parser._sResult == pszExpectedResult); + VERIFY_ARE_EQUAL(parser._sResult, pszExpectedResult); - parser._sResult = ""; + parser._sResult.clear(); target.SetMemoryMappedIoFlushMode(MemoryMappedIoFlushMode::NonVolatileMemory); parser._PrintTarget(target, false, true, false); pszExpectedResult = "\tpath: ''\n" \ @@ -356,17 +457,14 @@ namespace UnitTests "\t\tusing software cache\n" "\t\tusing hardware write cache, writethrough off\n" "\t\tmemory mapped I/O enabled, flush mode: FlushNonVolatileMemory\n" - "\t\twrite buffer size: 12341234\n" - "\t\twrite buffer source: 'x:\\foo\\bar.dat'\n" "\t\tperforming read test\n" - "\t\tblock size: 65536\n" - "\t\tusing random I/O (alignment: 65536)\n" - "\t\tnumber of outstanding I/O operations: 2\n" - "\t\tthread stride size: 0\n" + "\t\tblock size: 64KiB\n" + "\t\tusing random I/O (alignment: 64KiB)\n" + "\t\tnumber of outstanding I/O operations per thread: 2\n" "\t\tIO priority: normal\n"; - VERIFY_IS_TRUE(parser._sResult == pszExpectedResult); + VERIFY_ARE_EQUAL(parser._sResult, pszExpectedResult); - parser._sResult = ""; + parser._sResult.clear(); target.SetMemoryMappedIoFlushMode(MemoryMappedIoFlushMode::NonVolatileMemoryNoDrain); parser._PrintTarget(target, false, true, false); pszExpectedResult = "\tpath: ''\n" \ @@ -375,17 +473,14 @@ namespace UnitTests "\t\tusing software cache\n" "\t\tusing hardware write cache, writethrough off\n" "\t\tmemory mapped I/O enabled, flush mode: FlushNonVolatileMemory with no drain\n" - "\t\twrite buffer size: 12341234\n" - "\t\twrite buffer source: 'x:\\foo\\bar.dat'\n" "\t\tperforming read test\n" - "\t\tblock size: 65536\n" - "\t\tusing random I/O (alignment: 65536)\n" - "\t\tnumber of outstanding I/O operations: 2\n" - "\t\tthread stride size: 0\n" + "\t\tblock size: 64KiB\n" + "\t\tusing random I/O (alignment: 64KiB)\n" + "\t\tnumber of outstanding I/O operations per thread: 2\n" "\t\tIO priority: normal\n"; - VERIFY_IS_TRUE(parser._sResult == pszExpectedResult); + VERIFY_ARE_EQUAL(parser._sResult, pszExpectedResult); - parser._sResult = ""; + parser._sResult.clear(); target.SetMemoryMappedIoMode(MemoryMappedIoMode::Off); target.SetMemoryMappedIoFlushMode(MemoryMappedIoFlushMode::Undefined); target.SetCacheMode(TargetCacheMode::DisableLocalCache); @@ -396,18 +491,15 @@ namespace UnitTests "\t\tburst size: 0\n" "\t\tlocal software cache disabled, remote cache enabled\n" "\t\tusing hardware write cache, writethrough off\n" - "\t\twrite buffer size: 12341234\n" - "\t\twrite buffer source: 'x:\\foo\\bar.dat'\n" "\t\tperforming read test\n" - "\t\tblock size: 65536\n" - "\t\tusing random I/O (alignment: 65536)\n" - "\t\tnumber of outstanding I/O operations: 2\n" - "\t\tthread stride size: 0\n" + "\t\tblock size: 64KiB\n" + "\t\tusing random I/O (alignment: 64KiB)\n" + "\t\tnumber of outstanding I/O operations per thread: 2\n" "\t\tusing FILE_ATTRIBUTE_TEMPORARY hint\n" "\t\tIO priority: normal\n"; - VERIFY_IS_TRUE(parser._sResult == pszExpectedResult); + VERIFY_ARE_EQUAL(parser._sResult, pszExpectedResult); - parser._sResult = ""; + parser._sResult.clear(); target.SetRandomAccessHint(true); parser._PrintTarget(target, false, true, false); pszExpectedResult = "\tpath: ''\n" \ @@ -415,19 +507,16 @@ namespace UnitTests "\t\tburst size: 0\n" "\t\tlocal software cache disabled, remote cache enabled\n" "\t\tusing hardware write cache, writethrough off\n" - "\t\twrite buffer size: 12341234\n" - "\t\twrite buffer source: 'x:\\foo\\bar.dat'\n" "\t\tperforming read test\n" - "\t\tblock size: 65536\n" - "\t\tusing random I/O (alignment: 65536)\n" - "\t\tnumber of outstanding I/O operations: 2\n" - "\t\tthread stride size: 0\n" + "\t\tblock size: 64KiB\n" + "\t\tusing random I/O (alignment: 64KiB)\n" + "\t\tnumber of outstanding I/O operations per thread: 2\n" "\t\tusing FILE_FLAG_RANDOM_ACCESS hint\n" "\t\tusing FILE_ATTRIBUTE_TEMPORARY hint\n" "\t\tIO priority: normal\n"; - VERIFY_IS_TRUE(parser._sResult == pszExpectedResult); + VERIFY_ARE_EQUAL(parser._sResult, pszExpectedResult); - parser._sResult = ""; + parser._sResult.clear(); target.SetRandomAccessHint(false); target.SetTemporaryFileHint(false); target.SetSequentialScanHint(true); @@ -437,15 +526,411 @@ namespace UnitTests "\t\tburst size: 0\n" "\t\tlocal software cache disabled, remote cache enabled\n" "\t\tusing hardware write cache, writethrough off\n" - "\t\twrite buffer size: 12341234\n" - "\t\twrite buffer source: 'x:\\foo\\bar.dat'\n" "\t\tperforming read test\n" - "\t\tblock size: 65536\n" - "\t\tusing random I/O (alignment: 65536)\n" - "\t\tnumber of outstanding I/O operations: 2\n" - "\t\tthread stride size: 0\n" + "\t\tblock size: 64KiB\n" + "\t\tusing random I/O (alignment: 64KiB)\n" + "\t\tnumber of outstanding I/O operations per thread: 2\n" "\t\tusing FILE_FLAG_SEQUENTIAL_SCAN hint\n" "\t\tIO priority: normal\n"; - VERIFY_IS_TRUE(parser._sResult == pszExpectedResult); + VERIFY_ARE_EQUAL(parser._sResult, pszExpectedResult); + } + + void ResultParserUnitTests::Test_PrintTargetDistributionPct() + { + ResultParser parser; + Target target; + + vector v; + + // these match the CmdLineParser UTs + + // -rdpct10/10:10/10:0/10, though we need to produce the tail here + v.emplace_back(0, 10, make_pair(0, 10)); + v.emplace_back(10, 10, make_pair(10, 10)); + v.emplace_back(20, 0, make_pair(20, 10)); // zero IO% length hole + v.emplace_back(20, 80, make_pair(30, 70)); + target.SetDistributionRange(v, DistributionType::Percent); + + parser._PrintTarget(target, false, true, false); + const char *pszExpectedResult = "\tpath: ''\n" \ + "\t\tthink time: 0ms\n" + "\t\tburst size: 0\n" + "\t\tusing software cache\n" + "\t\tusing hardware write cache, writethrough off\n" + "\t\tperforming read test\n" + "\t\tblock size: 64KiB\n" + "\t\tusing sequential I/O (stride: 64KiB)\n" + "\t\tnumber of outstanding I/O operations per thread: 2\n" + "\t\tIO priority: normal\n" + "\t\tIO Distribution:\n" + "\t\t 10% of IO => [ 0% - 10%) of target\n" + "\t\t 10% of IO => [10% - 20%) of target\n" + "\t\t 0% of IO => [20% - 30%) of target\n" + "\t\t 80% of IO => [30% - 100%) of target\n"; + VERIFY_ARE_EQUAL(parser._sResult, pszExpectedResult); + v.clear(); + parser._sResult.clear(); + } + + void ResultParserUnitTests::Test_PrintTargetDistributionAbs() + { + ResultParser parser; + Target target; + + vector v; + + // these match the CmdLineParser UTs + + // -rdabs10/1G:10/1G:0/100G, again producing tail + v.emplace_back(0,10, make_pair(0, 1*GB)); + v.emplace_back(10,10, make_pair(1*GB, 1*GB)); + v.emplace_back(20,0, make_pair(2*GB, 100*GB)); + v.emplace_back(20,80, make_pair(102*GB, 0)); + target.SetDistributionRange(v, DistributionType::Absolute); + + parser._PrintTarget(target, false, true, false); + const char *pszExpectedResult = "\tpath: ''\n" \ + "\t\tthink time: 0ms\n" + "\t\tburst size: 0\n" + "\t\tusing software cache\n" + "\t\tusing hardware write cache, writethrough off\n" + "\t\tperforming read test\n" + "\t\tblock size: 64KiB\n" + "\t\tusing sequential I/O (stride: 64KiB)\n" + "\t\tnumber of outstanding I/O operations per thread: 2\n" + "\t\tIO priority: normal\n" + "\t\tIO Distribution:\n" + "\t\t 10% of IO => [ 0 - 1GiB)\n" + "\t\t 10% of IO => [ 1GiB - 2GiB)\n" + "\t\t 0% of IO => [ 2GiB - 102GiB)\n" + "\t\t 80% of IO => [ 102GiB - end)\n"; + VERIFY_ARE_EQUAL(parser._sResult, pszExpectedResult); + v.clear(); + parser._sResult.clear(); + } + + void ResultParserUnitTests::Test_PrintEffectiveDistributionPct() + { + // the first matches the corresponding IORequestGenerator effdist UT + ResultParser parser; + + Target target; + target.SetBlockAlignmentInBytes(4*KB); + target.SetBlockSizeInBytes(4*KB); + + Random r; + ThreadParameters tp; + tp.pRand = &r; + + vector v; + + // -rdpct10/10:10/10:0/10 + tail + // this is the same distribution in the cmdlineparser UT + v.emplace_back(0, 10, make_pair(0, 10)); + v.emplace_back(10, 10, make_pair(10, 10)); + v.emplace_back(20, 0, make_pair(20, 10)); // zero IO% length hole + v.emplace_back(20, 80, make_pair(30, 70)); + target.SetDistributionRange(v, DistributionType::Percent); + tp.vTargets.push_back(target); + + { + ThreadTargetState tts(&tp, 0, 100*KB); + + Results results; + ThreadResults threadResults; + TargetResults targetResults; + + targetResults.sPath = "testfile.dat"; + targetResults.vDistributionRange = tts._vDistributionRange; + + threadResults.vTargetResults.push_back(targetResults); + results.vThreadResults.push_back(threadResults); + + parser._PrintEffectiveDistributions(results); + + const char* pcszResults = "\nEffective IO Distributions\n" \ + "--------------------------\n" + "target: testfile.dat [thread: 0]\n" + " 10% of IO => [ 0 - 8KiB)\n" + " 10% of IO => [ 8KiB - 20KiB)\n" + " 80% of IO => [ 28KiB - 100KiB)\n"; + VERIFY_ARE_EQUAL(pcszResults, parser._sResult); + + parser._sResult.clear(); + } + + // + // Tests of distribution deduplication. + // + + // now repeat, duplicating the thread result for a second thread + + { + ThreadTargetState tts(&tp, 0, 100*KB); + + Results results; + ThreadResults threadResults; + TargetResults targetResults; + + targetResults.sPath = "testfile.dat"; + targetResults.vDistributionRange = tts._vDistributionRange; + + threadResults.vTargetResults.push_back(targetResults); + results.vThreadResults.push_back(threadResults); + results.vThreadResults.push_back(threadResults); + + parser._PrintEffectiveDistributions(results); + + const char* pcszResults = "\nEffective IO Distributions\n" \ + "--------------------------\n" + "target: testfile.dat [thread: 0 1]\n" + " 10% of IO => [ 0 - 8KiB)\n" + " 10% of IO => [ 8KiB - 20KiB)\n" + " 80% of IO => [ 28KiB - 100KiB)\n"; + VERIFY_ARE_EQUAL(pcszResults, parser._sResult); + + parser._sResult.clear(); + } + + // now repeat, for a third thread - ellision + + { + ThreadTargetState tts(&tp, 0, 100*KB); + + Results results; + ThreadResults threadResults; + TargetResults targetResults; + + targetResults.sPath = "testfile.dat"; + targetResults.vDistributionRange = tts._vDistributionRange; + + threadResults.vTargetResults.push_back(targetResults); + results.vThreadResults.push_back(threadResults); + results.vThreadResults.push_back(threadResults); + results.vThreadResults.push_back(threadResults); + + parser._PrintEffectiveDistributions(results); + + const char* pcszResults = "\nEffective IO Distributions\n" \ + "--------------------------\n" + "target: testfile.dat [thread: 0 - 2]\n" + " 10% of IO => [ 0 - 8KiB)\n" + " 10% of IO => [ 8KiB - 20KiB)\n" + " 80% of IO => [ 28KiB - 100KiB)\n"; + VERIFY_ARE_EQUAL(pcszResults, parser._sResult); + + parser._sResult.clear(); + } + + // now repeat, moving the third thread to a different target + + { + ThreadTargetState tts(&tp, 0, 100*KB); + + Results results; + ThreadResults threadResults; + TargetResults targetResults; + + targetResults.vDistributionRange = tts._vDistributionRange; + + targetResults.sPath = "testfile.dat"; + threadResults.vTargetResults.push_back(targetResults); + results.vThreadResults.push_back(threadResults); + results.vThreadResults.push_back(threadResults); + + targetResults.sPath = "testfile2.dat"; + threadResults.vTargetResults.clear(); + threadResults.vTargetResults.push_back(targetResults); + results.vThreadResults.push_back(threadResults); + + parser._PrintEffectiveDistributions(results); + + const char* pcszResults = "\nEffective IO Distributions\n" \ + "--------------------------\n" + "target: testfile.dat [thread: 0 1]\n" + "target: testfile2.dat [thread: 2]\n" + " 10% of IO => [ 0 - 8KiB)\n" + " 10% of IO => [ 8KiB - 20KiB)\n" + " 80% of IO => [ 28KiB - 100KiB)\n"; + VERIFY_ARE_EQUAL(pcszResults, parser._sResult); + + parser._sResult.clear(); + } + + // now repeat, four threads on the first target, three contiguous and one not + // the thread on the second target is used to create the gap - ellision will occur + // and the fourth thread will stand alone + + { + ThreadTargetState tts(&tp, 0, 100*KB); + + Results results; + ThreadResults threadResults; + TargetResults targetResults; + + targetResults.vDistributionRange = tts._vDistributionRange; + + targetResults.sPath = "testfile.dat"; + threadResults.vTargetResults.push_back(targetResults); + results.vThreadResults.push_back(threadResults); + results.vThreadResults.push_back(threadResults); + results.vThreadResults.push_back(threadResults); + + threadResults.vTargetResults.clear(); + targetResults.sPath = "testfile2.dat"; + threadResults.vTargetResults.push_back(targetResults); + results.vThreadResults.push_back(threadResults); + + threadResults.vTargetResults.clear(); + targetResults.sPath = "testfile.dat"; + threadResults.vTargetResults.push_back(targetResults); + results.vThreadResults.push_back(threadResults); + + parser._PrintEffectiveDistributions(results); + + const char* pcszResults = "\nEffective IO Distributions\n" \ + "--------------------------\n" + "target: testfile.dat [thread: 0 - 2 4]\n" + "target: testfile2.dat [thread: 3]\n" + " 10% of IO => [ 0 - 8KiB)\n" + " 10% of IO => [ 8KiB - 20KiB)\n" + " 80% of IO => [ 28KiB - 100KiB)\n"; + VERIFY_ARE_EQUAL(pcszResults, parser._sResult); + + parser._sResult.clear(); + } + + // two distinct distributions which share the same IO% + + { + ThreadTargetState tts1(&tp, 0, 100*KB); + ThreadTargetState tts2(&tp, 0, 1*MB); + + Results results; + ThreadResults threadResults; + TargetResults targetResults; + + targetResults.vDistributionRange = tts1._vDistributionRange; + targetResults.sPath = "testfile.dat"; + threadResults.vTargetResults.push_back(targetResults); + results.vThreadResults.push_back(threadResults); + + threadResults.vTargetResults.clear(); + + targetResults.vDistributionRange = tts2._vDistributionRange; + targetResults.sPath = "testfile2.dat"; + threadResults.vTargetResults.push_back(targetResults); + results.vThreadResults.push_back(threadResults); + + parser._PrintEffectiveDistributions(results); + + const char* pcszResults = "\nEffective IO Distributions\n" \ + "--------------------------\n" + "target: testfile.dat [thread: 0]\n" + " 10% of IO => [ 0 - 8KiB)\n" + " 10% of IO => [ 8KiB - 20KiB)\n" + " 80% of IO => [ 28KiB - 100KiB)\n" + "target: testfile2.dat [thread: 1]\n" + " 10% of IO => [ 0 - 100KiB)\n" + " 10% of IO => [ 100KiB - 204KiB)\n" + " 80% of IO => [ 304KiB - 1MiB)\n"; + VERIFY_ARE_EQUAL(pcszResults, parser._sResult); + + parser._sResult.clear(); + } + + tp.vTargets.clear(); + v.clear(); + } + + void ResultParserUnitTests::Test_PrintEffectiveDistributionAbs() + { + // the first matches the corresponding IORequestGenerator effdist UT + ResultParser parser; + + Target target; + target.SetBlockAlignmentInBytes(4*KB); + target.SetBlockSizeInBytes(4*KB); + + Random r; + ThreadParameters tp; + + vector v; + + // -rdabs10/1G:10/1G:0/100G, again producing tail - with autoscale (0) + // this is the same distribution in the cmdlineparser UT + // aligned tail range + v.emplace_back(0,10, make_pair(0, 1*GB)); + v.emplace_back(10,10, make_pair(1*GB, 1*GB)); + v.emplace_back(20,0, make_pair(2*GB, 100*GB)); + v.emplace_back(20,80, make_pair(102*GB, 0)); + target.SetDistributionRange(v, DistributionType::Absolute); + tp.vTargets.push_back(target); + + { + ThreadTargetState tts(&tp, 0, 200*GB); + + Results results; + ThreadResults threadResults; + TargetResults targetResults; + + targetResults.sPath = "testfile.dat"; + targetResults.vDistributionRange = tts._vDistributionRange; + + threadResults.vTargetResults.push_back(targetResults); + results.vThreadResults.push_back(threadResults); + + parser._PrintEffectiveDistributions(results); + + const char* pcszResults = "\nEffective IO Distributions\n" \ + "--------------------------\n" + "target: testfile.dat [thread: 0]\n" + " 10% of IO => [ 0 - 1GiB)\n" + " 10% of IO => [ 1GiB - 2GiB)\n" + " 80% of IO => [ 102GiB - 200GiB)\n"; + VERIFY_ARE_EQUAL(pcszResults, parser._sResult); + + tp.vTargets.clear(); + v.clear(); + parser._sResult.clear(); + } + + // -rdabs10/50k:20/10k:30/1G on 100KiB target - autoscale tail, but trimmed on last spec'd range + // this results in logical truncation since the covered ranges are only 60% of IO%, a case which + // is specific to absolute distributions. + v.emplace_back(0, 10, make_pair(0, 50*KB)); + v.emplace_back(10, 20, make_pair(50*KB, 10*KB)); + v.emplace_back(30, 30, make_pair(60*KB, 1*GB)); + v.emplace_back(60, 40, make_pair(1*GB + 60*KB, 0)); + target.SetDistributionRange(v, DistributionType::Absolute); + tp.vTargets.push_back(target); + + { + ThreadTargetState tts(&tp, 0, 100*KB); + + Results results; + ThreadResults threadResults; + TargetResults targetResults; + + targetResults.sPath = "testfile.dat"; + targetResults.vDistributionRange = tts._vDistributionRange; + + threadResults.vTargetResults.push_back(targetResults); + results.vThreadResults.push_back(threadResults); + + parser._PrintEffectiveDistributions(results); + + const char* pcszResults = "\nEffective IO Distributions\n" \ + "--------------------------\n" + "target: testfile.dat [thread: 0]\n" + " 16.7% of IO => [ 0 - 50KiB)\n" + " 33.3% of IO => [ 50KiB - 60KiB)\n" + " 50.0% of IO => [ 60KiB - 100KiB)\n"; + VERIFY_ARE_EQUAL(pcszResults, parser._sResult); + + tp.vTargets.clear(); + v.clear(); + parser._sResult.clear(); + } } -} +} \ No newline at end of file diff --git a/UnitTests/ResultParser/ResultParser.UnitTests.h b/UnitTests/ResultParser/ResultParser.UnitTests.h index 5b13f36..d909d56 100644 --- a/UnitTests/ResultParser/ResultParser.UnitTests.h +++ b/UnitTests/ResultParser/ResultParser.UnitTests.h @@ -41,7 +41,12 @@ namespace UnitTests public: TEST_CLASS(ResultParserUnitTests); TEST_METHOD(Test_ParseResults); + TEST_METHOD(Test_ParseProfile); TEST_METHOD(Test_PrintTarget); + TEST_METHOD(Test_PrintTargetDistributionPct); + TEST_METHOD(Test_PrintTargetDistributionAbs); + TEST_METHOD(Test_PrintEffectiveDistributionPct); + TEST_METHOD(Test_PrintEffectiveDistributionAbs); }; } diff --git a/UnitTests/XmlProfileParser/XmlProfileParser.UnitTests.cpp b/UnitTests/XmlProfileParser/XmlProfileParser.UnitTests.cpp index dcbdc64..116de8d 100644 --- a/UnitTests/XmlProfileParser/XmlProfileParser.UnitTests.cpp +++ b/UnitTests/XmlProfileParser/XmlProfileParser.UnitTests.cpp @@ -29,7 +29,6 @@ SOFTWARE. #include "StdAfx.h" #include "XmlProfileParser.UnitTests.h" -#include "XmlProfileParser.h" #include using namespace WEX::TestExecution; @@ -203,7 +202,9 @@ namespace UnitTests XmlProfileParser p; Profile profile; - VERIFY_IS_TRUE(p.ParseFile(_sTempFilePath.c_str(), &profile, _hModule)); + VERIFY_IS_TRUE(p.ParseFile(_sTempFilePath.c_str(), &profile, nullptr, _hModule)); + // note: many conflicts, this ut is a broad blend of specific element parsing, NOT a valid profile + VERIFY_IS_FALSE(profile.Validate(false)); VERIFY_IS_TRUE(profile.GetVerbose() == true); VERIFY_IS_TRUE(profile.GetProgress() == 15); // TODO: VERIFY_IS_TRUE(profile.GetCmdLine().compare("foo -b4K -w84 -a1,4,6 testfile.dat testfile2.dat") == 0); @@ -240,7 +241,7 @@ namespace UnitTests VERIFY_IS_TRUE(t.GetPath().compare("testfile.dat") == 0); VERIFY_ARE_EQUAL(t.GetBlockSizeInBytes(), (DWORD)(100)); VERIFY_ARE_EQUAL(t.GetRequestCount(), (DWORD)123); - VERIFY_IS_TRUE(t.GetUseRandomAccessPattern() == true); + VERIFY_IS_TRUE(t.GetRandomRatio() == 100); VERIFY_IS_TRUE(t.GetUseInterlockedSequential() == false); VERIFY_ARE_EQUAL(t.GetBlockAlignmentInBytes(), 234); VERIFY_ARE_EQUAL(t.GetBaseFileOffsetInBytes(), 333); @@ -270,7 +271,7 @@ namespace UnitTests VERIFY_IS_TRUE(t.GetPath().compare("testfile2.dat") == 0); VERIFY_ARE_EQUAL(t.GetBlockSizeInBytes(), (DWORD)(200)); VERIFY_ARE_EQUAL(t.GetRequestCount(), (DWORD)2); - VERIFY_IS_TRUE(t.GetUseRandomAccessPattern() == false); + VERIFY_IS_TRUE(t.GetRandomRatio() == 0); VERIFY_IS_TRUE(t.GetUseInterlockedSequential() == true); VERIFY_ARE_EQUAL(t.GetBlockAlignmentInBytes(), 2222); VERIFY_ARE_EQUAL(t.GetBaseFileOffsetInBytes(), 0); @@ -301,7 +302,7 @@ namespace UnitTests VERIFY_IS_TRUE(t.GetPath().compare("testfile3.dat") == 0); VERIFY_ARE_EQUAL(t.GetBlockSizeInBytes(), (DWORD)(200)); VERIFY_ARE_EQUAL(t.GetRequestCount(), (DWORD)2); - VERIFY_IS_TRUE(t.GetUseRandomAccessPattern() == false); + VERIFY_IS_TRUE(t.GetRandomRatio() == 0); VERIFY_IS_TRUE(t.GetUseInterlockedSequential() == true); VERIFY_ARE_EQUAL(t.GetBlockAlignmentInBytes(), 2222); VERIFY_ARE_EQUAL(t.GetBaseFileOffsetInBytes(), 0); @@ -332,7 +333,7 @@ namespace UnitTests VERIFY_IS_TRUE(t.GetPath().compare("testfile4.dat") == 0); VERIFY_ARE_EQUAL(t.GetBlockSizeInBytes(), (DWORD)(200)); VERIFY_ARE_EQUAL(t.GetRequestCount(), (DWORD)2); - VERIFY_IS_TRUE(t.GetUseRandomAccessPattern() == false); + VERIFY_IS_TRUE(t.GetRandomRatio() == 0); VERIFY_IS_TRUE(t.GetUseInterlockedSequential() == true); VERIFY_ARE_EQUAL(t.GetBlockAlignmentInBytes(), 2222); VERIFY_ARE_EQUAL(t.GetBaseFileOffsetInBytes(), 0); @@ -423,7 +424,8 @@ namespace UnitTests XmlProfileParser p; Profile profile; - VERIFY_IS_TRUE(p.ParseFile(_sTempFilePath.c_str(), &profile, _hModule)); + VERIFY_IS_TRUE(p.ParseFile(_sTempFilePath.c_str(), &profile, nullptr, _hModule)); + VERIFY_IS_TRUE(profile.Validate(false)); VERIFY_IS_TRUE(profile.GetPrecreateFiles() == PrecreateFiles::UseMaxSize); } @@ -449,7 +451,8 @@ namespace UnitTests XmlProfileParser p; Profile profile; - VERIFY_IS_TRUE(p.ParseFile(_sTempFilePath.c_str(), &profile, _hModule)); + VERIFY_IS_TRUE(p.ParseFile(_sTempFilePath.c_str(), &profile, nullptr, _hModule)); + VERIFY_IS_TRUE(profile.Validate(false)); VERIFY_IS_TRUE(profile.GetPrecreateFiles() == PrecreateFiles::OnlyFilesWithConstantSizes); } @@ -475,7 +478,8 @@ namespace UnitTests XmlProfileParser p; Profile profile; - VERIFY_IS_TRUE(p.ParseFile(_sTempFilePath.c_str(), &profile, _hModule)); + VERIFY_IS_TRUE(p.ParseFile(_sTempFilePath.c_str(), &profile, nullptr, _hModule)); + VERIFY_IS_TRUE(profile.Validate(false)); VERIFY_IS_TRUE(profile.GetPrecreateFiles() == PrecreateFiles::OnlyFilesWithConstantOrZeroSizes); } @@ -503,7 +507,8 @@ namespace UnitTests XmlProfileParser p; Profile profile; - VERIFY_IS_TRUE(p.ParseFile(_sTempFilePath.c_str(), &profile, _hModule)); + VERIFY_IS_TRUE(p.ParseFile(_sTempFilePath.c_str(), &profile, nullptr, _hModule)); + VERIFY_IS_TRUE(profile.Validate(false)); vector vTimespans(profile.GetTimeSpans()); VERIFY_ARE_EQUAL(vTimespans.size(), (size_t)1); vector vTargets(vTimespans[0].GetTargets()); @@ -541,7 +546,8 @@ namespace UnitTests XmlProfileParser p; Profile profile; - VERIFY_IS_TRUE(p.ParseFile(_sTempFilePath.c_str(), &profile, _hModule)); + VERIFY_IS_TRUE(p.ParseFile(_sTempFilePath.c_str(), &profile, nullptr, _hModule)); + VERIFY_IS_TRUE(profile.Validate(false)); vector vTimespans(profile.GetTimeSpans()); VERIFY_ARE_EQUAL(vTimespans.size(), (size_t)1); @@ -578,7 +584,7 @@ namespace UnitTests XmlProfileParser p; Profile profile; - VERIFY_IS_FALSE(p.ParseFile(_sTempFilePath.c_str(), &profile, _hModule)); + VERIFY_IS_FALSE(p.ParseFile(_sTempFilePath.c_str(), &profile, nullptr, _hModule)); } // out-of-range group index (WORD) @@ -606,7 +612,7 @@ namespace UnitTests XmlProfileParser p; Profile profile; - VERIFY_IS_FALSE(p.ParseFile(_sTempFilePath.c_str(), &profile, _hModule)); + VERIFY_IS_FALSE(p.ParseFile(_sTempFilePath.c_str(), &profile, nullptr, _hModule)); } } @@ -635,7 +641,8 @@ namespace UnitTests XmlProfileParser p; Profile profile; - VERIFY_IS_TRUE(p.ParseFile(_sTempFilePath.c_str(), &profile, _hModule)); + VERIFY_IS_TRUE(p.ParseFile(_sTempFilePath.c_str(), &profile, nullptr, _hModule)); + VERIFY_IS_TRUE(profile.Validate(false)); vector vTimespans(profile.GetTimeSpans()); VERIFY_ARE_EQUAL(vTimespans.size(), (size_t)1); @@ -672,7 +679,8 @@ namespace UnitTests XmlProfileParser p; Profile profile; - VERIFY_IS_TRUE(p.ParseFile(_sTempFilePath.c_str(), &profile, _hModule)); + VERIFY_IS_TRUE(p.ParseFile(_sTempFilePath.c_str(), &profile, nullptr, _hModule)); + VERIFY_IS_TRUE(profile.Validate(false)); vector vTimespans(profile.GetTimeSpans()); VERIFY_ARE_EQUAL(vTimespans.size(), (size_t)1); vector vTargets(vTimespans[0].GetTargets()); @@ -705,7 +713,8 @@ namespace UnitTests XmlProfileParser p; Profile profile; - VERIFY_IS_TRUE(p.ParseFile(_sTempFilePath.c_str(), &profile, _hModule)); + VERIFY_IS_TRUE(p.ParseFile(_sTempFilePath.c_str(), &profile, nullptr, _hModule)); + VERIFY_IS_TRUE(profile.Validate(false)); vector vTimespans(profile.GetTimeSpans()); VERIFY_ARE_EQUAL(vTimespans.size(), (size_t)1); VERIFY_IS_TRUE(vTimespans[0].GetRandomWriteData() == true); @@ -744,7 +753,8 @@ namespace UnitTests XmlProfileParser p; Profile profile; - VERIFY_IS_TRUE(p.ParseFile(_sTempFilePath.c_str(), &profile, _hModule)); + VERIFY_IS_TRUE(p.ParseFile(_sTempFilePath.c_str(), &profile, nullptr, _hModule)); + VERIFY_IS_TRUE(profile.Validate(false)); vector vTimespans(profile.GetTimeSpans()); VERIFY_ARE_EQUAL(vTimespans.size(), (size_t)1); vector vTargets(vTimespans[0].GetTargets()); @@ -783,7 +793,8 @@ namespace UnitTests XmlProfileParser p; Profile profile; - VERIFY_IS_TRUE(p.ParseFile(_sTempFilePath.c_str(), &profile, _hModule)); + VERIFY_IS_TRUE(p.ParseFile(_sTempFilePath.c_str(), &profile, nullptr, _hModule)); + VERIFY_IS_TRUE(profile.Validate(false)); vector vTimespans(profile.GetTimeSpans()); VERIFY_ARE_EQUAL(vTimespans.size(), (size_t)1); vector vTargets(vTimespans[0].GetTargets()); @@ -835,7 +846,8 @@ namespace UnitTests XmlProfileParser p; Profile profile; - VERIFY_IS_TRUE(p.ParseFile(_sTempFilePath.c_str(), &profile, _hModule)); + VERIFY_IS_TRUE(p.ParseFile(_sTempFilePath.c_str(), &profile, nullptr, _hModule)); + VERIFY_IS_TRUE(profile.Validate(false)); vector vTimespans(profile.GetTimeSpans()); VERIFY_ARE_EQUAL(vTimespans.size(), (size_t)1); VERIFY_ARE_EQUAL(vTimespans[0].GetThreadCount(), (DWORD)4); @@ -854,4 +866,807 @@ namespace UnitTests VERIFY_ARE_EQUAL(vTargets[1].GetWeight(), (UINT32)100); VERIFY_ARE_EQUAL(vTargets[1].GetThreadTargets().size(), (size_t)0); } -} + + void XmlProfileParserUnitTests::Test_ParseThroughput() + { + const PCHAR xmlDoc = "\n" + "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " %s\n" + " %s\n" + " \n" + " \n" + " \n" + " \n" + "\n"; + XmlProfileParser p; + FILE *pFile = nullptr; + + // + // Positive varations - units & default + // + + { + Profile profile; + fopen_s(&pFile, _sTempFilePath.c_str(), "wb"); + VERIFY_IS_TRUE(pFile != nullptr); + fprintf(pFile, xmlDoc, "131072", " unit=\"IOPS\"", "10"); + fclose(pFile); + pFile = nullptr; + + VERIFY_IS_TRUE(p.ParseFile(_sTempFilePath.c_str(), &profile, nullptr, _hModule)); + VERIFY_IS_TRUE(profile.Validate(false)); + VERIFY_ARE_EQUAL(profile.GetTimeSpans()[0].GetTargets()[0].GetThroughputInBytesPerMillisecond(), (DWORD)((128*1024*10)/1000)); + } + + { + Profile profile; + fopen_s(&pFile, _sTempFilePath.c_str(), "wb"); + VERIFY_IS_TRUE(pFile != nullptr); + fprintf(pFile, xmlDoc, "131072", " unit=\"BPMS\"", "1500"); + fclose(pFile); + pFile = nullptr; + + VERIFY_IS_TRUE(p.ParseFile(_sTempFilePath.c_str(), &profile, nullptr, _hModule)); + VERIFY_IS_TRUE(profile.Validate(false)); + VERIFY_ARE_EQUAL(profile.GetTimeSpans()[0].GetTargets()[0].GetThroughputInBytesPerMillisecond(), (DWORD)1500); + } + + { + Profile profile; + fopen_s(&pFile, _sTempFilePath.c_str(), "wb"); + VERIFY_IS_TRUE(pFile != nullptr); + fprintf(pFile, xmlDoc, "131072", "", "1024"); + fclose(pFile); + pFile = nullptr; + + VERIFY_IS_TRUE(p.ParseFile(_sTempFilePath.c_str(), &profile, nullptr, _hModule)); + VERIFY_IS_TRUE(profile.Validate(false)); + VERIFY_ARE_EQUAL(profile.GetTimeSpans()[0].GetTargets()[0].GetThroughputInBytesPerMillisecond(), (DWORD)1024); + } + + // + // Negative variations - bad units / good units with bad data + // + + { + Profile profile; + fopen_s(&pFile, _sTempFilePath.c_str(), "wb"); + VERIFY_IS_TRUE(pFile != nullptr); + fprintf(pFile, xmlDoc, "131072", " unit=\"FRUIT\"", "1500"); + fclose(pFile); + pFile = nullptr; + + VERIFY_IS_FALSE(p.ParseFile(_sTempFilePath.c_str(), &profile, nullptr, _hModule)); + } + + { + Profile profile; + fopen_s(&pFile, _sTempFilePath.c_str(), "wb"); + VERIFY_IS_TRUE(pFile != nullptr); + fprintf(pFile, xmlDoc, "131072", " unit=\"IOPS\"", "FRUIT"); + fclose(pFile); + pFile = nullptr; + + VERIFY_IS_FALSE(p.ParseFile(_sTempFilePath.c_str(), &profile, nullptr, _hModule)); + } + + { + Profile profile; + fopen_s(&pFile, _sTempFilePath.c_str(), "wb"); + VERIFY_IS_TRUE(pFile != nullptr); + fprintf(pFile, xmlDoc, "131072", " unit=\"BPMS\"", "FRUIT"); + fclose(pFile); + pFile = nullptr; + + VERIFY_IS_FALSE(p.ParseFile(_sTempFilePath.c_str(), &profile, nullptr, _hModule)); + } + + { + Profile profile; + fopen_s(&pFile, _sTempFilePath.c_str(), "wb"); + VERIFY_IS_TRUE(pFile != nullptr); + fprintf(pFile, xmlDoc, "131072", "", "FRUIT"); + fclose(pFile); + pFile = nullptr; + + VERIFY_IS_FALSE(p.ParseFile(_sTempFilePath.c_str(), &profile, nullptr, _hModule)); + } + } + + void XmlProfileParserUnitTests::Test_ParseRandomSequentialMixed() + { + const PCHAR xmlDoc = "\n" + "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + "%s" // insert conflicts + " %s" // Random/RandomRatio + " \n" + " \n" + " \n" + " \n" + "\n"; + XmlProfileParser p; + FILE *pFile = nullptr; + + // Positive cases + + // Isolated + + { + Profile profile; + fopen_s(&pFile, _sTempFilePath.c_str(), "wb"); + VERIFY_IS_TRUE(pFile != nullptr); + fprintf(pFile, xmlDoc, "", "Ratio", "50", "Ratio"); + fclose(pFile); + pFile = nullptr; + + VERIFY_IS_TRUE(p.ParseFile(_sTempFilePath.c_str(), &profile, nullptr, _hModule)); + VERIFY_IS_TRUE(profile.Validate(false)); + VERIFY_ARE_EQUAL(profile.GetTimeSpans()[0].GetTargets()[0].GetRandomRatio(), (UINT32) 50); + } + + // With + + { + Profile profile; + fopen_s(&pFile, _sTempFilePath.c_str(), "wb"); + VERIFY_IS_TRUE(pFile != nullptr); + fprintf(pFile, xmlDoc, "8192", "Ratio", "50", "Ratio"); + fclose(pFile); + pFile = nullptr; + + VERIFY_IS_TRUE(p.ParseFile(_sTempFilePath.c_str(), &profile, nullptr, _hModule)); + VERIFY_IS_TRUE(profile.Validate(false)); + VERIFY_ARE_EQUAL(profile.GetTimeSpans()[0].GetTargets()[0].GetRandomRatio(), (UINT32) 50); + } + + // Negative, bounds validation + + { + Profile profile; + fopen_s(&pFile, _sTempFilePath.c_str(), "wb"); + VERIFY_IS_TRUE(pFile != nullptr); + fprintf(pFile, xmlDoc, "", "Ratio", "0", "Ratio"); + fclose(pFile); + pFile = nullptr; + + VERIFY_IS_FALSE(p.ParseFile(_sTempFilePath.c_str(), &profile, nullptr, _hModule)); + } + { + Profile profile; + fopen_s(&pFile, _sTempFilePath.c_str(), "wb"); + VERIFY_IS_TRUE(pFile != nullptr); + fprintf(pFile, xmlDoc, "", "Ratio", "100", "Ratio"); + fclose(pFile); + pFile = nullptr; + + VERIFY_IS_FALSE(p.ParseFile(_sTempFilePath.c_str(), &profile, nullptr, _hModule)); + } + { + Profile profile; + fopen_s(&pFile, _sTempFilePath.c_str(), "wb"); + VERIFY_IS_TRUE(pFile != nullptr); + fprintf(pFile, xmlDoc, "", "Ratio", "-100", "Ratio"); + fclose(pFile); + pFile = nullptr; + + VERIFY_IS_FALSE(p.ParseFile(_sTempFilePath.c_str(), &profile, nullptr, _hModule)); + } + { + Profile profile; + fopen_s(&pFile, _sTempFilePath.c_str(), "wb"); + VERIFY_IS_TRUE(pFile != nullptr); + fprintf(pFile, xmlDoc, "", "Ratio", "200", "Ratio"); + fclose(pFile); + pFile = nullptr; + + VERIFY_IS_FALSE(p.ParseFile(_sTempFilePath.c_str(), &profile, nullptr, _hModule)); + } + { + Profile profile; + fopen_s(&pFile, _sTempFilePath.c_str(), "wb"); + VERIFY_IS_TRUE(pFile != nullptr); + fprintf(pFile, xmlDoc, "", "Ratio", "junk", "Ratio"); + fclose(pFile); + pFile = nullptr; + + VERIFY_IS_FALSE(p.ParseFile(_sTempFilePath.c_str(), &profile, nullptr, _hModule)); + } + + // Negative, sequential conflict with RandomRatio + { + Profile profile; + fopen_s(&pFile, _sTempFilePath.c_str(), "wb"); + VERIFY_IS_TRUE(pFile != nullptr); + fprintf(pFile, xmlDoc, "8192", "Ratio", "50", "Ratio"); + fclose(pFile); + pFile = nullptr; + + VERIFY_IS_FALSE(p.ParseFile(_sTempFilePath.c_str(), &profile, nullptr, _hModule)); + } + + // Negative, sequential conflict with Random + { + Profile profile; + fopen_s(&pFile, _sTempFilePath.c_str(), "wb"); + VERIFY_IS_TRUE(pFile != nullptr); + fprintf(pFile, xmlDoc, "8192", "", "8192", ""); + fclose(pFile); + pFile = nullptr; + + VERIFY_IS_FALSE(p.ParseFile(_sTempFilePath.c_str(), &profile, nullptr, _hModule)); + } + } + + // Template document for running distribution tests - type tags bracketing a type-specific insert + static const PCHAR xmlDistDoc = "\n" + "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " 65536\n" + " \n" + " <%s>\n" // type + " %s" // ranges + " \n" // type + " " + " \n" + " \n" + " \n" + " \n" + "\n"; + + void XmlProfileParserUnitTests::ValidateDistribution( + const PWCHAR desc, + boolean expectedParse, + boolean expectedValidate, + DistributionType type, + PCHAR xmlDoc, + const RangePair *insert, + const DistQuad *dist + ) + { + Profile profile; + XmlProfileParser p; + FILE *pFile = nullptr; + string xmlInsert; + + // Construct XML range list + for (UINT32 i = 0; i < insert->size; i++) + { + xmlInsert += "range[i].io); + xmlInsert += "\">"; + xmlInsert += to_string(insert->range[i].target); + xmlInsert += "\n"; + } + + // Bracketing typename - xmlDoc is assumed to be of the form ...[insert here]... + PCHAR typeName = nullptr; + + switch (type) + { + case DistributionType::Absolute: + typeName = "Absolute"; + break; + + case DistributionType::Percent: + typeName = "Percent"; + break; + + default: + assert(false); + break; + } + + // Generate content into temporary file and parse/validate + // Emit the description at parse validation for documentation in the output + fopen_s(&pFile, _sTempFilePath.c_str(), "wb"); + VERIFY_IS_TRUE(pFile != nullptr); + fprintf(pFile, xmlDoc, typeName, xmlInsert.c_str(), typeName); + fclose(pFile); + + if (!expectedParse) + { + VERIFY_IS_FALSE(p.ParseFile(_sTempFilePath.c_str(), &profile, nullptr, _hModule), desc); + return; // done + } + + VERIFY_IS_TRUE(p.ParseFile(_sTempFilePath.c_str(), &profile, nullptr, _hModule), desc); + + if (!expectedValidate) + { + VERIFY_IS_FALSE(profile.Validate(false)); + return; // done + } + + VERIFY_IS_TRUE(profile.Validate(false)); + auto t = profile.GetTimeSpans()[0].GetTargets()[0]; + auto dt = t.GetDistributionType(); + auto& v = t.GetDistributionRange(); + + VERIFY_ARE_EQUAL(type, dt); + VERIFY_ARE_EQUAL(dist->size, v.size()); + for (size_t i = 0; i < v.size(); ++i) + { + VERIFY_ARE_EQUAL(v[i]._src, dist->range[i].ioBase); + VERIFY_ARE_EQUAL(v[i]._span, dist->range[i].ioSpan); + VERIFY_ARE_EQUAL(v[i]._dst.first, dist->range[i].targetBase); + VERIFY_ARE_EQUAL(v[i]._dst.second, dist->range[i].targetSpan); + } + } + + void XmlProfileParserUnitTests::Test_ParseDistributionAbsolute() + { + // + // Positive cases - first matches the ResultParser UT + // + + { + // -rdabs10/1g:10/1g:0/100g + // hole + tail + const RangePair insert = { + 3, + {{10, 1*GB}, + {10, 1*GB}, + {0, 100*GB}} + }; + const DistQuad dist = { + 4, + {{0, 10, 0, 1*GB}, + {10, 10, 1*GB, 1*GB}, + {20, 0, 2*GB, 100*GB}, + {20, 80, 102*GB, 0}} + }; + + ValidateDistribution(L"Case 1", true, true, DistributionType::Absolute, xmlDistDoc, &insert, &dist); + } + + // + // Negative cases + // + + { + // -rdabs10/10 (note lack of units/small) + // same negative case in cmdlineparser + const RangePair insert = { + 1, + {{10, 10}} + }; + + ValidateDistribution(L"Case 2", true, false, DistributionType::Absolute, xmlDistDoc, &insert, nullptr); + } + { + // zero target not at end + const RangePair insert = { + 3, + {{10, 1*GB}, + {10, 0}, + {80, 1*GB}} + }; + + ValidateDistribution(L"Case 3", true, false, DistributionType::Absolute, xmlDistDoc, &insert, nullptr); + } + { + // -rdabs10/10G:80/10G:20:10G + // IO% overflow + const RangePair insert = { + 3, + {{10, 10*GB}, + {80, 10*GB}, + {20, 10*GB}} + }; + + ValidateDistribution(L"Case 4", true, false, DistributionType::Absolute, xmlDistDoc, &insert, nullptr); + } + } + + void XmlProfileParserUnitTests::Test_ParseDistributionPercent() + { + // + // Positive cases - first matches the ResultParser UT + // + + { + // -rdpct10/10:10/10:0/10 + // hole + tail + const RangePair insert = { + 3, + {{10, 10}, + {10, 10}, + {0, 10}} + }; + const DistQuad dist = { + 4, + {{0, 10, 0, 10}, + {10, 10, 10, 10}, + {20, 0, 20, 10}, + {20, 80, 30, 70}} + }; + + ValidateDistribution(L"Case 1", true, true, DistributionType::Percent, xmlDistDoc, &insert, &dist); + } + + // + // Negative cases + // + + { + // zero target not at end - fails parse (before validate) + const RangePair insert = { + 3, + {{10, 10}, + {10, 0}, + {80, 10}} + }; + + ValidateDistribution(L"Case 2", false, false, DistributionType::Percent, xmlDistDoc, &insert, nullptr); + } + { + // zero target at end - fails parse (before validate) + const RangePair insert = { + 2, + {{10, 10}, + {90, 0}} + }; + + ValidateDistribution(L"Case 3", false, false, DistributionType::Percent, xmlDistDoc, &insert, nullptr); + } + { + // IO% overflow / Target% OK + const RangePair insert = { + 3, + {{10, 10}, + {80, 10}, + {20, 80}} + }; + + ValidateDistribution(L"Case 4", true, false, DistributionType::Percent, xmlDistDoc, &insert, nullptr); + } + { + // IO% 100% / Target% incomplete + const RangePair insert = { + 3, + {{10, 10}, + {80, 10}, + {10, 10}} + }; + + ValidateDistribution(L"Case 5", true, false, DistributionType::Percent, xmlDistDoc, &insert, nullptr); + } + { + // IO% incomplete / Target% 100% + const RangePair insert = { + 3, + {{10, 10}, + {10, 80}, + {10, 10}} + }; + + ValidateDistribution(L"Case 6", true, false, DistributionType::Percent, xmlDistDoc, &insert, nullptr); + } + } + + void XmlProfileParserUnitTests::Test_ParseTemplateTargets() + { + const PCHAR xmlDocPre = \ + "\n" + "\n" + " \n"; + + const PCHAR xmlDocPost = \ + " \n" + "\n"; + + const PCHAR xmlTimeSpanPre = \ + "\n" + " \n"; + + const PCHAR xmlTimeSpanPost = \ + " \n" + "\n"; + + const PCHAR xmlTarget = \ + "\n" + " %s\n" + "\n"; + + XmlProfileParser p; + FILE *pFile = nullptr; + + // Non template baseline - null ptr pass for subst + + { + Profile profile; + fopen_s(&pFile, _sTempFilePath.c_str(), "wb"); + VERIFY_IS_TRUE(pFile != nullptr); + fprintf(pFile, xmlDocPre); + fprintf(pFile, xmlTimeSpanPre); + fprintf(pFile, xmlTarget, "foo.bin"); + fprintf(pFile, xmlTimeSpanPost); + fprintf(pFile, xmlDocPost); + fclose(pFile); + pFile = nullptr; + + VERIFY_IS_TRUE(p.ParseFile(_sTempFilePath.c_str(), &profile, nullptr, _hModule)); + VERIFY_IS_TRUE(profile.Validate(false)); + VERIFY_IS_TRUE(!strcmp(profile.GetTimeSpans()[0].GetTargets()[0].GetPath().c_str(), "foo.bin")); + } + + // Non template baseline - empty pass for subst + + { + Profile profile; + fopen_s(&pFile, _sTempFilePath.c_str(), "wb"); + VERIFY_IS_TRUE(pFile != nullptr); + fprintf(pFile, xmlDocPre); + fprintf(pFile, xmlTimeSpanPre); + fprintf(pFile, xmlTarget, "foo.bin"); + fprintf(pFile, xmlTimeSpanPost); + fprintf(pFile, xmlDocPost); + fclose(pFile); + pFile = nullptr; + + vector vTargets; + + VERIFY_IS_TRUE(p.ParseFile(_sTempFilePath.c_str(), &profile, &vTargets, _hModule)); + VERIFY_IS_TRUE(profile.Validate(false)); + VERIFY_IS_TRUE(!strcmp(profile.GetTimeSpans()[0].GetTargets()[0].GetPath().c_str(), "foo.bin")); + } + + // Template - 1 template, bad formats which will not parse (and will parse independent of subst) + + { + + char *cStrs[] = { "*", "*foo", "*1foo", "**1", "*-1" }; + + for (auto s : cStrs) + { + Profile profile; + fopen_s(&pFile, _sTempFilePath.c_str(), "wb"); + VERIFY_IS_TRUE(pFile != nullptr); + fprintf(pFile, xmlDocPre); + fprintf(pFile, xmlTimeSpanPre); + fprintf(pFile, xmlTarget, s); + fprintf(pFile, xmlTimeSpanPost); + fprintf(pFile, xmlDocPost); + fclose(pFile); + pFile = nullptr; + + vector vTargets; + + VERIFY_IS_FALSE(p.ParseFile(_sTempFilePath.c_str(), &profile, &vTargets, _hModule)); + } + } + + // Template - 1 template, unsubst, will fail execution validation since !profile only + + { + Profile profile; + fopen_s(&pFile, _sTempFilePath.c_str(), "wb"); + VERIFY_IS_TRUE(pFile != nullptr); + fprintf(pFile, xmlDocPre); + fprintf(pFile, xmlTimeSpanPre); + fprintf(pFile, xmlTarget, "*1"); + fprintf(pFile, xmlTimeSpanPost); + fprintf(pFile, xmlDocPost); + fclose(pFile); + pFile = nullptr; + + vector vTargets; + + VERIFY_IS_TRUE(p.ParseFile(_sTempFilePath.c_str(), &profile, &vTargets, _hModule)); + VERIFY_IS_FALSE(profile.Validate(false)); + VERIFY_IS_TRUE(!strcmp(profile.GetTimeSpans()[0].GetTargets()[0].GetPath().c_str(), "*1")); + } + + // Template - 1 timespan 1 template, unsubst, will pass execution validation since profile only + + { + Profile profile; + fopen_s(&pFile, _sTempFilePath.c_str(), "wb"); + VERIFY_IS_TRUE(pFile != nullptr); + fprintf(pFile, xmlDocPre); + fprintf(pFile, xmlTimeSpanPre); + fprintf(pFile, xmlTarget, "*1"); + fprintf(pFile, xmlTimeSpanPost); + fprintf(pFile, xmlDocPost); + fclose(pFile); + pFile = nullptr; + + vector vTargets; + + VERIFY_IS_TRUE(p.ParseFile(_sTempFilePath.c_str(), &profile, &vTargets, _hModule)); + profile.SetProfileOnly(true); + VERIFY_IS_TRUE(profile.Validate(false)); + VERIFY_IS_TRUE(!strcmp(profile.GetTimeSpans()[0].GetTargets()[0].GetPath().c_str(), "*1")); + } + + // Template - 1 timespan 1 template 1 subst - will pass execution validation + + { + Profile profile; + fopen_s(&pFile, _sTempFilePath.c_str(), "wb"); + VERIFY_IS_TRUE(pFile != nullptr); + fprintf(pFile, xmlDocPre); + fprintf(pFile, xmlTimeSpanPre); + fprintf(pFile, xmlTarget, "*1"); + fprintf(pFile, xmlTimeSpanPost); + fprintf(pFile, xmlDocPost); + fclose(pFile); + pFile = nullptr; + + vector vTargets; + vTargets.emplace_back(); + vTargets[0].SetPath("foo.bin"); + + VERIFY_IS_TRUE(p.ParseFile(_sTempFilePath.c_str(), &profile, &vTargets, _hModule)); + VERIFY_IS_TRUE(profile.Validate(false)); + VERIFY_IS_TRUE(!strcmp(profile.GetTimeSpans()[0].GetTargets()[0].GetPath().c_str(), "foo.bin")); + } + + // Template - 1 timespan 1 template 2 subst - will fail parse due to unused subst + + { + Profile profile; + fopen_s(&pFile, _sTempFilePath.c_str(), "wb"); + VERIFY_IS_TRUE(pFile != nullptr); + fprintf(pFile, xmlDocPre); + fprintf(pFile, xmlTimeSpanPre); + fprintf(pFile, xmlTarget, "*1"); + fprintf(pFile, xmlTimeSpanPost); + fprintf(pFile, xmlDocPost); + fclose(pFile); + pFile = nullptr; + + vector vTargets; + vTargets.emplace_back(); + vTargets[0].SetPath("foo.bin"); + vTargets.emplace_back(); + vTargets[1].SetPath("bar.bin"); + + VERIFY_IS_FALSE(p.ParseFile(_sTempFilePath.c_str(), &profile, &vTargets, _hModule)); + } + + // 1 timespan 2 same template 1 subst + + { + Profile profile; + fopen_s(&pFile, _sTempFilePath.c_str(), "wb"); + VERIFY_IS_TRUE(pFile != nullptr); + fprintf(pFile, xmlDocPre); + fprintf(pFile, xmlTimeSpanPre); + fprintf(pFile, xmlTarget, "*1"); + fprintf(pFile, xmlTarget, "*1"); + fprintf(pFile, xmlTimeSpanPost); + fprintf(pFile, xmlDocPost); + fclose(pFile); + pFile = nullptr; + + vector vTargets; + vTargets.emplace_back(); + vTargets[0].SetPath("foo.bin"); + + VERIFY_IS_TRUE(p.ParseFile(_sTempFilePath.c_str(), &profile, &vTargets, _hModule)); + VERIFY_IS_TRUE(profile.Validate(false)); + VERIFY_IS_TRUE(!strcmp(profile.GetTimeSpans()[0].GetTargets()[0].GetPath().c_str(), "foo.bin")); + VERIFY_IS_TRUE(!strcmp(profile.GetTimeSpans()[0].GetTargets()[1].GetPath().c_str(), "foo.bin")); + } + + // 1 timespan 2 template 2 subst + + { + Profile profile; + fopen_s(&pFile, _sTempFilePath.c_str(), "wb"); + VERIFY_IS_TRUE(pFile != nullptr); + fprintf(pFile, xmlDocPre); + fprintf(pFile, xmlTimeSpanPre); + fprintf(pFile, xmlTarget, "*1"); + fprintf(pFile, xmlTarget, "*2"); + fprintf(pFile, xmlTimeSpanPost); + fprintf(pFile, xmlDocPost); + fclose(pFile); + pFile = nullptr; + + vector vTargets; + vTargets.emplace_back(); + vTargets[0].SetPath("foo.bin"); + vTargets.emplace_back(); + vTargets[1].SetPath("bar.bin"); + + VERIFY_IS_TRUE(p.ParseFile(_sTempFilePath.c_str(), &profile, &vTargets, _hModule)); + VERIFY_IS_TRUE(profile.Validate(false)); + VERIFY_IS_TRUE(!strcmp(profile.GetTimeSpans()[0].GetTargets()[0].GetPath().c_str(), "foo.bin")); + VERIFY_IS_TRUE(!strcmp(profile.GetTimeSpans()[0].GetTargets()[1].GetPath().c_str(), "bar.bin")); + } + + // 1 timespan 2 template 1 subst - will fail since second template does not have a subst + + { + Profile profile; + fopen_s(&pFile, _sTempFilePath.c_str(), "wb"); + VERIFY_IS_TRUE(pFile != nullptr); + fprintf(pFile, xmlDocPre); + fprintf(pFile, xmlTimeSpanPre); + fprintf(pFile, xmlTarget, "*1"); + fprintf(pFile, xmlTarget, "*2"); + fprintf(pFile, xmlTimeSpanPost); + fprintf(pFile, xmlDocPost); + fclose(pFile); + pFile = nullptr; + + vector vTargets; + vTargets.emplace_back(); + vTargets[0].SetPath("foo.bin"); + + VERIFY_IS_FALSE(p.ParseFile(_sTempFilePath.c_str(), &profile, &vTargets, _hModule)); + } + + // 2 timespan same template 1 subst + + { + Profile profile; + fopen_s(&pFile, _sTempFilePath.c_str(), "wb"); + VERIFY_IS_TRUE(pFile != nullptr); + fprintf(pFile, xmlDocPre); + fprintf(pFile, xmlTimeSpanPre); + fprintf(pFile, xmlTarget, "*1"); + fprintf(pFile, xmlTimeSpanPost); + fprintf(pFile, xmlTimeSpanPre); + fprintf(pFile, xmlTarget, "*1"); + fprintf(pFile, xmlTimeSpanPost); + fprintf(pFile, xmlDocPost); + fclose(pFile); + pFile = nullptr; + + vector vTargets; + vTargets.emplace_back(); + vTargets[0].SetPath("foo.bin"); + + VERIFY_IS_TRUE(p.ParseFile(_sTempFilePath.c_str(), &profile, &vTargets, _hModule)); + VERIFY_IS_TRUE(profile.Validate(false)); + VERIFY_IS_TRUE(!strcmp(profile.GetTimeSpans()[0].GetTargets()[0].GetPath().c_str(), "foo.bin")); + VERIFY_IS_TRUE(!strcmp(profile.GetTimeSpans()[1].GetTargets()[0].GetPath().c_str(), "foo.bin")); + } + + // 2 timespan 1 template/ea 2 subst + + { + Profile profile; + fopen_s(&pFile, _sTempFilePath.c_str(), "wb"); + VERIFY_IS_TRUE(pFile != nullptr); + fprintf(pFile, xmlDocPre); + fprintf(pFile, xmlTimeSpanPre); + fprintf(pFile, xmlTarget, "*1"); + fprintf(pFile, xmlTimeSpanPost); + fprintf(pFile, xmlTimeSpanPre); + fprintf(pFile, xmlTarget, "*2"); + fprintf(pFile, xmlTimeSpanPost); + fprintf(pFile, xmlDocPost); + fclose(pFile); + pFile = nullptr; + + vector vTargets; + vTargets.emplace_back(); + vTargets[0].SetPath("foo.bin"); + vTargets.emplace_back(); + vTargets[1].SetPath("bar.bin"); + + VERIFY_IS_TRUE(p.ParseFile(_sTempFilePath.c_str(), &profile, &vTargets, _hModule)); + VERIFY_IS_TRUE(profile.Validate(false)); + VERIFY_IS_TRUE(!strcmp(profile.GetTimeSpans()[0].GetTargets()[0].GetPath().c_str(), "foo.bin")); + VERIFY_IS_TRUE(!strcmp(profile.GetTimeSpans()[1].GetTargets()[0].GetPath().c_str(), "bar.bin")); + } + } +} \ No newline at end of file diff --git a/UnitTests/XmlProfileParser/XmlProfileParser.UnitTests.h b/UnitTests/XmlProfileParser/XmlProfileParser.UnitTests.h index 036ff54..31eaafc 100644 --- a/UnitTests/XmlProfileParser/XmlProfileParser.UnitTests.h +++ b/UnitTests/XmlProfileParser/XmlProfileParser.UnitTests.h @@ -29,6 +29,7 @@ SOFTWARE. #pragma once #include "WexTestClass.h" +#include "XmlProfileParser.h" #include using namespace std; @@ -52,23 +53,64 @@ namespace UnitTests TEST_METHOD_SETUP(MethodSetup); TEST_METHOD_CLEANUP(MethodCleanup); + TEST_METHOD(Test_ParseDistributionAbsolute); + TEST_METHOD(Test_ParseDistributionPercent); TEST_METHOD(Test_ParseFile); - TEST_METHOD(Test_ParseGroupAffinity); - TEST_METHOD(Test_ParseNonGroupAffinity); - TEST_METHOD(Test_ParseFilePrecreateFilesUseMaxSize); - TEST_METHOD(Test_ParseFilePrecreateFilesOnlyFilesWithConstantSizes); + TEST_METHOD(Test_ParseFileGlobalRequestCount); TEST_METHOD(Test_ParseFilePrecreateFilesOnlyFilesWithConstantOrZeroSizes); - TEST_METHOD(Test_ParseFileWriteBufferContentSequential); - TEST_METHOD(Test_ParseFileWriteBufferContentZero); + TEST_METHOD(Test_ParseFilePrecreateFilesOnlyFilesWithConstantSizes); + TEST_METHOD(Test_ParseFilePrecreateFilesUseMaxSize); TEST_METHOD(Test_ParseFileWriteBufferContentRandom); TEST_METHOD(Test_ParseFileWriteBufferContentRandomNoFilePath); TEST_METHOD(Test_ParseFileWriteBufferContentRandomWithFilePath); - TEST_METHOD(Test_ParseFileGlobalRequestCount); + TEST_METHOD(Test_ParseFileWriteBufferContentSequential); + TEST_METHOD(Test_ParseFileWriteBufferContentZero); + TEST_METHOD(Test_ParseGroupAffinity); + TEST_METHOD(Test_ParseNonGroupAffinity); + TEST_METHOD(Test_ParseRandomSequentialMixed); + TEST_METHOD(Test_ParseTemplateTargets); + TEST_METHOD(Test_ParseThroughput); + + // + // Utility wrapping the specification and validation of a given distribution. + // + // Note that % and abs distributions are represented in the same way, only + // differing in the relative scale of the target spans. + // + + #pragma warning(push) + #pragma warning(disable:4200) // zero length array + + typedef struct { + UINT32 size; + struct { + UINT32 io; + UINT64 target; + } range[]; + } RangePair; + + typedef struct { + UINT32 size; + struct{ + UINT32 ioBase, ioSpan; + UINT64 targetBase, targetSpan; + } range[]; + } DistQuad; + + #pragma warning(pop) + + void ValidateDistribution( + const PWCHAR desc, + boolean expectedParseResult, + boolean expectedValidate, + DistributionType type, + PCHAR xmlDoc, + const RangePair *insert, + const DistQuad *dist + ); - // TODO: test what happens when parameters have suffixes (e.g. 1M) private: string _sTempFilePath; HMODULE _hModule; }; } - diff --git a/UnitTests/XmlResultParser/XmlResultParser.UnitTests.cpp b/UnitTests/XmlResultParser/XmlResultParser.UnitTests.cpp index 7a1bf11..a12a903 100644 --- a/UnitTests/XmlResultParser/XmlResultParser.UnitTests.cpp +++ b/UnitTests/XmlResultParser/XmlResultParser.UnitTests.cpp @@ -69,10 +69,12 @@ namespace UnitTests results.vSystemProcessorPerfInfo.push_back(zeroSystemProcessorInfo); results.vSystemProcessorPerfInfo.push_back(zeroSystemProcessorInfo); - // TODO: many file cases (see WINPERF bug 2407) + // 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); @@ -139,169 +141,301 @@ namespace UnitTests // stringify random text, quoting "'s and adding newline/preserving tabs // gc some.txt |% { write-host $("`"{0}\n`"" -f $($_ -replace "`"","\`"" -replace "`t","\t")) } - const char *pcszExpectedOutput = "\n" - "\n" - "\n" - "\n" - "" DISKSPD_NUMERIC_VERSION_STRING "\n" - "" DISKSPD_DATE_VERSION_STRING "\n" - "\n" - "\n" - "\n" - "\n" - "\n" - "\n" - "\n" - "\n" - "\n" - "\n" - "\n" - "\n" - "\n" - "\n" - "\n" - "\n" - "0\n" - "text\n" - "false\n" - "\n" - "\n" - "false\n" - "false\n" - "true\n" - "false\n" - "10\n" - "5\n" - "0\n" - "0\n" - "0\n" - "1000\n" - "0\n" - "\n" - "\n" - "testfile1.dat\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" - "\n" - "\n" - "\n" - "\n" - "\n" - "120.00\n" - "1\n" - "0\n" - "3\n" - "\n" - "\n" - "0\n" - "0\n" - "55.00\n" - "30.00\n" - "25.00\n" - "45.00\n" - "\n" - "\n" - "1\n" - "1\n" - "0.00\n" - "0.00\n" - "0.00\n" - "100.00\n" - "\n" - "\n" - "1\n" - "2\n" - "0.00\n" - "0.00\n" - "0.00\n" - "100.00\n" - "\n" - "\n" - "18.33\n" - "10.00\n" - "8.33\n" - "81.67\n" - "\n" - "\n" - "\n" - "0.000\n" - "0.000\n" - "\n" - "\n" - "\n" - "\n" - "\n" - "\n" - "\n" - "\n" - "\n" - "\n" - "\n" - "\n" - "0\n" - "\n" - "testfile1.dat\n" - "6291456\n" - "10485760\n" - "16\n" - "4194304\n" - "6\n" - "2097152\n" - "10\n" - "\n" - "0.000\n" - "0.000\n" - "\n" - "\n" - "\n" - "\n" - "\n" - "\n" - "\n" - "\n" - "\n" - "\n" - "\n" - "\n" - "\n" - "\n" + const char *pcszExpectedOutput = \ + "\n" + " \n" + " \n" + " \n" + " " DISKSPD_NUMERIC_VERSION_STRING "\n" + " " DISKSPD_DATE_VERSION_STRING "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " 0\n" + " text\n" + " false\n" + " \n" + " \n" + " false\n" + " false\n" + " true\n" + " false\n" + " 10\n" + " 5\n" + " 0\n" + " 0\n" + " 0\n" + " 1000\n" + " 0\n" + " \n" + " \n" + " testfile1.dat\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" + " 1000\n" + " 1\n" + " 3\n" + " 1\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " 120.00\n" + " 1\n" + " 0\n" + " 3\n" + " \n" + " \n" + " 0\n" + " 0\n" + " 55.00\n" + " 30.00\n" + " 25.00\n" + " 45.00\n" + " \n" + " \n" + " 1\n" + " 1\n" + " 0.00\n" + " 0.00\n" + " 0.00\n" + " 100.00\n" + " \n" + " \n" + " 1\n" + " 2\n" + " 0.00\n" + " 0.00\n" + " 0.00\n" + " 100.00\n" + " \n" + " \n" + " 18.33\n" + " 10.00\n" + " 8.33\n" + " 81.67\n" + " \n" + " \n" + " \n" + " 0.000\n" + " 0.000\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " 0\n" + " \n" + " testfile1.dat\n" + " 6291456\n" + " 10485760\n" + " 16\n" + " 4194304\n" + " 6\n" + " 2097152\n" + " 10\n" + " \n" + " 0.000\n" + " 0.000\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" ""; #if 0 HANDLE h; DWORD written; - h = CreateFileW(L"z:\\foo.txt", GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + h = CreateFileW(L"g:\\xmlresult-received.txt", GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); WriteFile(h, sResults.c_str(), (DWORD)sResults.length(), &written, NULL); + VERIFY_ARE_EQUAL(sResults.length(), written); + CloseHandle(h); + + h = CreateFileW(L"g:\\xmlresult-expected.txt", GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + WriteFile(h, pcszExpectedOutput, (DWORD)strlen(pcszExpectedOutput), &written, NULL); + VERIFY_ARE_EQUAL((DWORD)strlen(pcszExpectedOutput), written); CloseHandle(h); - printf("'%s'\n", sResults.c_str()); + printf("--\n%s\n", sResults.c_str()); printf("-------------------------------------------------\n"); - printf("'%s'\n", pcszExpectedOutput); + printf("--\n%s\n", pcszExpectedOutput); #endif - VERIFY_ARE_EQUAL(sResults.length(), strlen(pcszExpectedOutput)); - VERIFY_ARE_EQUAL(memcmp(sResults.c_str(), pcszExpectedOutput, sResults.length()), 0); - VERIFY_IS_TRUE(strcmp(sResults.c_str(), pcszExpectedOutput) == 0); + VERIFY_ARE_EQUAL(0, strcmp(sResults.c_str(), pcszExpectedOutput)); + } + + void XmlResultParserUnitTests::Test_ParseProfile() + { + Profile profile; + XmlResultParser parser; + TimeSpan timeSpan; + Target target; + + timeSpan.AddTarget(target); + profile.AddTimeSpan(timeSpan); + + string s = parser.ParseProfile(profile); + const char *pcszExpectedOutput = "\n" + " 0\n" + " text\n" + " false\n" + " \n" + " \n" + " false\n" + " false\n" + " false\n" + " false\n" + " 10\n" + " 5\n" + " 0\n" + " 0\n" + " 0\n" + " 1000\n" + " 0\n" + " \n" + " \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" + " \n" + " \n" + " \n" + "\n"; + + //VERIFY_ARE_EQUAL(pcszExpectedOutput, s.c_str()); + VERIFY_ARE_EQUAL(strlen(pcszExpectedOutput), s.length()); + VERIFY_IS_TRUE(!strcmp(pcszExpectedOutput, s.c_str())); + } + + void XmlResultParserUnitTests::Test_ParseTargetProfile() + { + Target target; + string sResults; + char pszExpectedOutput[4096]; + int nWritten; + + const char *pcszOutputTemplate = \ + "\n" + " testfile1.dat\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" + " %s\n" // 2 param + " 1\n" + " 3\n" + " 1\n" + "\n"; + + 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); + sResults = target.GetXml(0); + VERIFY_ARE_EQUAL(sResults, pszExpectedOutput); + + // IOPS - with units + + target.SetThroughputIOPS(1000); + nWritten = sprintf_s(pszExpectedOutput, sizeof(pszExpectedOutput), + pcszOutputTemplate, " unit=\"IOPS\"", "1000"); + 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); + nWritten = sprintf_s(pszExpectedOutput, sizeof(pszExpectedOutput), + pcszOutputTemplate, "", "1000"); + VERIFY_IS_GREATER_THAN(nWritten, 0); + sResults = target.GetXml(0); + VERIFY_ARE_EQUAL(sResults, pszExpectedOutput); } -} +} \ No newline at end of file diff --git a/UnitTests/XmlResultParser/XmlResultParser.UnitTests.h b/UnitTests/XmlResultParser/XmlResultParser.UnitTests.h index a4fa55b..acebf22 100644 --- a/UnitTests/XmlResultParser/XmlResultParser.UnitTests.h +++ b/UnitTests/XmlResultParser/XmlResultParser.UnitTests.h @@ -41,5 +41,7 @@ namespace UnitTests public: TEST_CLASS(XmlResultParserUnitTests); TEST_METHOD(Test_ParseResults); + TEST_METHOD(Test_ParseProfile); + TEST_METHOD(Test_ParseTargetProfile); }; } \ No newline at end of file diff --git a/XmlProfileParser/XmlProfileParser.cpp b/XmlProfileParser/XmlProfileParser.cpp index 376e717..53ca937 100644 --- a/XmlProfileParser/XmlProfileParser.cpp +++ b/XmlProfileParser/XmlProfileParser.cpp @@ -73,7 +73,7 @@ HRESULT ReportXmlError( return errorCode; } -bool XmlProfileParser::ParseFile(const char *pszPath, Profile *pProfile, HMODULE hModule) +bool XmlProfileParser::ParseFile(const char *pszPath, Profile *pProfile, vector *pvSubstTargets, HMODULE hModule) { assert(pszPath != nullptr); assert(pProfile != nullptr); @@ -149,7 +149,7 @@ bool XmlProfileParser::ParseFile(const char *pszPath, Profile *pProfile, HMODULE hr = E_FAIL; } } - if (SUCCEEDED(hr)) + if (SUCCEEDED(hr)) { CComVariant vXmlSchema(spXmlSchema); CComBSTR bNull(""); @@ -176,72 +176,108 @@ bool XmlProfileParser::ParseFile(const char *pszPath, Profile *pProfile, HMODULE } } - // now parse the specification, if correct + // + // XML has now passed basic schema validation. Bulld the target substitutions and parse the profile. + // + + vector> vSubsts; + + if (pvSubstTargets) + { + for (auto target : *pvSubstTargets) + { + vSubsts.emplace_back(make_pair(target.GetPath(), false)); + } + } + if (SUCCEEDED(hr)) { - bool fVerbose; - hr = _GetVerbose(spXmlDoc, &fVerbose); + bool b; + hr = _GetBool(spXmlDoc, "//Profile/Verbose", &b); if (SUCCEEDED(hr) && (hr != S_FALSE)) { - pProfile->SetVerbose(fVerbose); + pProfile->SetVerbose(b); } + } - if (SUCCEEDED(hr)) + if (SUCCEEDED(hr)) + { + DWORD i; + hr = _GetDWORD(spXmlDoc, "//Profile/Progress", &i); + if (SUCCEEDED(hr) && (hr != S_FALSE)) { - DWORD dwProgress; - hr = _GetProgress(spXmlDoc, &dwProgress); - if (SUCCEEDED(hr) && (hr != S_FALSE)) - { - pProfile->SetProgress(dwProgress); - } + pProfile->SetProgress(i); } + } - if (SUCCEEDED(hr)) + if (SUCCEEDED(hr)) + { + string sResultFormat; + hr = _GetString(spXmlDoc, "//Profile/ResultFormat", &sResultFormat); + if (SUCCEEDED(hr) && (hr != S_FALSE) && sResultFormat == "xml") { - string sResultFormat; - hr = _GetString(spXmlDoc, "//Profile/ResultFormat", &sResultFormat); - if (SUCCEEDED(hr) && (hr != S_FALSE) && sResultFormat == "xml") - { - pProfile->SetResultsFormat(ResultsFormat::Xml); - } + pProfile->SetResultsFormat(ResultsFormat::Xml); } + } - if (SUCCEEDED(hr)) + if (SUCCEEDED(hr)) + { + string sCreateFiles; + hr = _GetString(spXmlDoc, "//Profile/PrecreateFiles", &sCreateFiles); + if (SUCCEEDED(hr) && (hr != S_FALSE)) { - string sCreateFiles; - hr = _GetString(spXmlDoc, "//Profile/PrecreateFiles", &sCreateFiles); - if (SUCCEEDED(hr) && (hr != S_FALSE)) + if (sCreateFiles == "UseMaxSize") { - if (sCreateFiles == "UseMaxSize") - { - pProfile->SetPrecreateFiles(PrecreateFiles::UseMaxSize); - } - else if (sCreateFiles == "CreateOnlyFilesWithConstantSizes") - { - pProfile->SetPrecreateFiles(PrecreateFiles::OnlyFilesWithConstantSizes); - } - else if (sCreateFiles == "CreateOnlyFilesWithConstantOrZeroSizes") - { - pProfile->SetPrecreateFiles(PrecreateFiles::OnlyFilesWithConstantOrZeroSizes); - } - else - { - hr = E_INVALIDARG; - } + pProfile->SetPrecreateFiles(PrecreateFiles::UseMaxSize); + } + else if (sCreateFiles == "CreateOnlyFilesWithConstantSizes") + { + pProfile->SetPrecreateFiles(PrecreateFiles::OnlyFilesWithConstantSizes); + } + else if (sCreateFiles == "CreateOnlyFilesWithConstantOrZeroSizes") + { + pProfile->SetPrecreateFiles(PrecreateFiles::OnlyFilesWithConstantOrZeroSizes); + } + else + { + hr = E_INVALIDARG; } } + } - if (SUCCEEDED(hr)) - { - hr = _ParseEtw(spXmlDoc, pProfile); - } + if (SUCCEEDED(hr)) + { + hr = _ParseEtw(spXmlDoc, pProfile); + } - if (SUCCEEDED(hr)) + if (SUCCEEDED(hr)) + { + hr = _ParseTimeSpans(spXmlDoc, pProfile, vSubsts); + } + + // + // Error on unused substitutions - user should ensure these match up. + // + // Note that no (zero) substitutions are OK at the point of parsing, which allows + // for -Rp forms on template profiles. Validation for executed profiles will occur + // later during common validation. + // + // Generate an error for each unused substitution. + // + + if (SUCCEEDED(hr)) + { + for (size_t i = 1; i <= vSubsts.size(); ++i) { - hr = _ParseTimeSpans(spXmlDoc, pProfile); + if (!vSubsts[i - 1].second) + { + fprintf(stderr, "ERROR: unused template target substitution _%u -> %s - check profile\n", (int) i, vSubsts[i - 1].first.c_str()); + hr = E_INVALIDARG; + } } } } + if (fComInitialized) { CoUninitialize(); @@ -384,7 +420,7 @@ HRESULT XmlProfileParser::_ParseEtw(IXMLDOMDocument2 *pXmlDoc, Profile *pProfile return hr; } -HRESULT XmlProfileParser::_ParseTimeSpans(IXMLDOMDocument2 *pXmlDoc, Profile *pProfile) +HRESULT XmlProfileParser::_ParseTimeSpans(IXMLDOMDocument2 *pXmlDoc, Profile *pProfile, vector>& vSubsts) { CComPtr spNodeList = nullptr; CComVariant query("//Profile/TimeSpans/TimeSpan"); @@ -402,7 +438,7 @@ HRESULT XmlProfileParser::_ParseTimeSpans(IXMLDOMDocument2 *pXmlDoc, Profile *pP if (SUCCEEDED(hr)) { TimeSpan timeSpan; - hr = _ParseTimeSpan(spNode, &timeSpan); + hr = _ParseTimeSpan(spNode, &timeSpan, vSubsts); if (SUCCEEDED(hr)) { pProfile->AddTimeSpan(timeSpan); @@ -415,7 +451,7 @@ HRESULT XmlProfileParser::_ParseTimeSpans(IXMLDOMDocument2 *pXmlDoc, Profile *pP return hr; } -HRESULT XmlProfileParser::_ParseTimeSpan(IXMLDOMNode *pXmlNode, TimeSpan *pTimeSpan) +HRESULT XmlProfileParser::_ParseTimeSpan(IXMLDOMNode *pXmlNode, TimeSpan *pTimeSpan, vector>& vSubsts) { UINT32 ulDuration; HRESULT hr = _GetUINT32(pXmlNode, "Duration", &ulDuration); @@ -548,12 +584,59 @@ HRESULT XmlProfileParser::_ParseTimeSpan(IXMLDOMNode *pXmlNode, TimeSpan *pTimeS if (SUCCEEDED(hr)) { - hr = _ParseTargets(pXmlNode, pTimeSpan); + hr = _ParseTargets(pXmlNode, pTimeSpan, vSubsts); } + return hr; } -HRESULT XmlProfileParser::_ParseTargets(IXMLDOMNode *pXmlNode, TimeSpan *pTimeSpan) +HRESULT XmlProfileParser::_SubstTarget(Target *pTarget, vector>& vSubsts) +{ + auto& sPath = pTarget->GetPath(); + + if (sPath.length() >= 1 && sPath[0] == TEMPLATE_TARGET_PREFIX) + { + auto str = sPath.c_str(); + char *strend; + ULONG i; + + // + // Note that we will parse all template targets for correctness but allow empty substuttion lists + // to pass through. If this profile will be executed, validation of paths will catch unsubst template targets + // + // strtoul will accept signed integers (e.g., -1), so we need to add our explicit assertion that this is indeed a digit. + // + + i = strtoul(str + 1, &strend, 10); + + if (i == 0 || *strend != '\0' || !isdigit(*(str + 1))) + { + fprintf(stderr, "ERROR: template path '%s' is not a valid path reference - must be %c - check profile\n", str, TEMPLATE_TARGET_PREFIX); + return E_INVALIDARG; + } + + if (vSubsts.size() != 0) + { + if (i > vSubsts.size()) + { + fprintf(stderr, "ERROR: template path '%s' does not have a specified substitution - check profile\n", str); + return E_INVALIDARG; + } + + // + // Substitute this target, indicating this substitution was used (for validation). + // Note 1 based count and 0 based index. + // + + pTarget->SetPath(vSubsts[i - 1].first); + vSubsts[i - 1].second = true; + } + } + + return S_OK; +} + +HRESULT XmlProfileParser::_ParseTargets(IXMLDOMNode *pXmlNode, TimeSpan *pTimeSpan, vector>& vSubsts) { CComVariant query("Targets/Target"); CComPtr spNodeList = nullptr; @@ -571,8 +654,20 @@ HRESULT XmlProfileParser::_ParseTargets(IXMLDOMNode *pXmlNode, TimeSpan *pTimeSp if (SUCCEEDED(hr)) { Target target; - _ParseTarget(spNode, &target); - pTimeSpan->AddTarget(target); + hr = _ParseTarget(spNode, &target); + if (SUCCEEDED(hr)) + { + hr = _SubstTarget(&target, vSubsts); + } + if (SUCCEEDED(hr)) + { + pTimeSpan->AddTarget(target); + } + } + + if (!SUCCEEDED(hr)) + { + break; } } } @@ -658,6 +753,10 @@ HRESULT XmlProfileParser::_ParseWriteBufferContent(IXMLDOMNode *pXmlNode, Target HRESULT XmlProfileParser::_ParseTarget(IXMLDOMNode *pXmlNode, Target *pTarget) { + // For enforcement of sequential/random conflicts. + // This is simplified for the XML since we control parse order. + bool fSequential = false; + string sPath; HRESULT hr = _GetString(pXmlNode, "Path", &sPath); if (SUCCEEDED(hr) && (hr != S_FALSE)) @@ -674,16 +773,6 @@ HRESULT XmlProfileParser::_ParseTarget(IXMLDOMNode *pXmlNode, Target *pTarget) pTarget->SetBlockSizeInBytes(dwBlockSize); } } - - if (SUCCEEDED(hr)) - { - UINT64 ullStrideSize; - hr = _GetUINT64(pXmlNode, "StrideSize", &ullStrideSize); - if (SUCCEEDED(hr) && (hr != S_FALSE)) - { - pTarget->SetBlockAlignmentInBytes(ullStrideSize); - } - } if (SUCCEEDED(hr)) { @@ -755,14 +844,54 @@ HRESULT XmlProfileParser::_ParseTarget(IXMLDOMNode *pXmlNode, Target *pTarget) } } + if (SUCCEEDED(hr)) + { + UINT64 ullStrideSize; + hr = _GetUINT64(pXmlNode, "StrideSize", &ullStrideSize); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + pTarget->SetBlockAlignmentInBytes(ullStrideSize); + fSequential = true; + } + } + if (SUCCEEDED(hr)) { UINT64 ullRandom; hr = _GetUINT64(pXmlNode, "Random", &ullRandom); if (SUCCEEDED(hr) && (hr != S_FALSE)) { - pTarget->SetUseRandomAccessPattern(true); - pTarget->SetBlockAlignmentInBytes(ullRandom); + // conflict with sequential + if (fSequential) + { + fprintf(stderr, "sequential conflicts with \n"); + hr = HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER); + } + else + { + pTarget->SetRandomRatio(100); + pTarget->SetBlockAlignmentInBytes(ullRandom); + } + } + } + + // now override default of 100% random? + if (SUCCEEDED(hr)) + { + UINT32 ulRandomRatio; + hr = _GetUINT32(pXmlNode, "RandomRatio", &ulRandomRatio); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + // conflict with sequential + if (fSequential) + { + fprintf(stderr, "sequential conflicts with \n"); + hr = HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER); + } + else + { + pTarget->SetRandomRatio(ulRandomRatio); + } } } @@ -871,12 +1000,7 @@ HRESULT XmlProfileParser::_ParseTarget(IXMLDOMNode *pXmlNode, Target *pTarget) if (SUCCEEDED(hr)) { - DWORD dwThroughput; - hr = _GetDWORD(pXmlNode, "Throughput", &dwThroughput); - if (SUCCEEDED(hr) && (hr != S_FALSE)) - { - pTarget->SetThroughput(dwThroughput); - } + hr = _ParseThroughput(pXmlNode, pTarget); } if (SUCCEEDED(hr)) @@ -961,6 +1085,16 @@ HRESULT XmlProfileParser::_ParseTarget(IXMLDOMNode *pXmlNode, Target *pTarget) } } + // + // Note: XSD validation ensures only one type of distribution is specified, but it as simple + // here to probe for each. + // + + if (SUCCEEDED(hr)) + { + hr = _ParseDistribution(pXmlNode, pTarget); + } + if (SUCCEEDED(hr)) { hr = _ParseThreadTargets(pXmlNode, pTarget); @@ -968,6 +1102,66 @@ HRESULT XmlProfileParser::_ParseTarget(IXMLDOMNode *pXmlNode, Target *pTarget) return hr; } +HRESULT XmlProfileParser::_ParseThroughput(IXMLDOMNode *pXmlNode, Target *pTarget) +{ + CComPtr spNode = nullptr; + CComVariant query("Throughput"); + HRESULT hr = pXmlNode->selectSingleNode(query.bstrVal, &spNode); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + // get value + UINT32 value = 0; + + BSTR bstrText; + hr = spNode->get_text(&bstrText); + if (SUCCEEDED(hr)) + { + value = (UINT32) _wtoi64((wchar_t *)bstrText); // XSD constrains s.t. cast is safe + SysFreeString(bstrText); + } + else + { + return hr; + } + + // get unit - bpms default + bool isBpms = true; + + CComPtr spNamedNodeMap = nullptr; + CComBSTR attr("unit"); + hr = spNode->get_attributes(&spNamedNodeMap); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + CComPtr spAttrNode = nullptr; + HRESULT hr = spNamedNodeMap->getNamedItem(attr, &spAttrNode); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + BSTR bstrText; + hr = spAttrNode->get_text(&bstrText); + if (SUCCEEDED(hr)) + { + isBpms = wcscmp((wchar_t *)bstrText, L"IOPS"); + SysFreeString(bstrText); + } + } + } + + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + if (isBpms) + { + pTarget->SetThroughput(value); + } + else + { + // NOTE: this depends on parse order s.t. blocksize is available + pTarget->SetThroughputIOPS(value); + } + } + } + return hr; +} + HRESULT XmlProfileParser::_ParseThreadTargets(IXMLDOMNode *pXmlNode, Target *pTarget) { CComVariant query("ThreadTargets/ThreadTarget"); @@ -1016,6 +1210,108 @@ HRESULT XmlProfileParser::_ParseThreadTarget(IXMLDOMNode *pXmlNode, ThreadTarget return hr; } +struct { + char* xPath; + DistributionType t; +} distributionTypes[] = { + { "Distribution/Absolute/Range", DistributionType::Absolute }, + { "Distribution/Percent/Range", DistributionType::Percent } +}; + +HRESULT XmlProfileParser::_ParseDistribution(IXMLDOMNode *pXmlNode, Target *pTarget) +{ + HRESULT hr = S_OK; + + for (auto& type : distributionTypes) + { + CComPtr spNodeList = nullptr; + CComVariant query(type.xPath); + hr = pXmlNode->selectNodes(query.bstrVal, &spNodeList); + if (SUCCEEDED(hr)) + { + long cNodes; + hr = spNodeList->get_length(&cNodes); + if (SUCCEEDED(hr) && cNodes != 0) + { + UINT64 targetBase = 0, targetSpan; + UINT32 ioBase = 0, ioSpan; + vector v; + + for (int i = 0; i < cNodes; i++) + { + // target span from the element + // note that this is the same 64bit int for both distribution types, + // it is the interpretation at the time the effective is calculated + // that makes the distinction. XSD covers range validations. + CComPtr spNode = nullptr; + hr = spNodeList->get_item(i, &spNode); + if (SUCCEEDED(hr)) + { + BSTR bstrText; + hr = spNode->get_text(&bstrText); + if (SUCCEEDED(hr)) + { + targetSpan = _wtoi64((wchar_t *)bstrText); + SysFreeString(bstrText); + } + } + + if (SUCCEEDED(hr)) + { + // io span from the attribute + CComPtr spNamedNodeMap = nullptr; + CComBSTR attr("IO"); + hr = spNode->get_attributes(&spNamedNodeMap); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + CComPtr spAttrNode = nullptr; + HRESULT hr = spNamedNodeMap->getNamedItem(attr, &spAttrNode); + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + BSTR bstrText; + hr = spAttrNode->get_text(&bstrText); + if (SUCCEEDED(hr)) + { + ioSpan = _wtoi((wchar_t *)bstrText); + SysFreeString(bstrText); + } + } + } + } + + if (SUCCEEDED(hr) && (hr != S_FALSE)) + { + v.emplace_back(ioBase, ioSpan, + make_pair(targetBase, targetSpan)); + ioBase += ioSpan; + targetBase += targetSpan; + } + // failed during parse + else + { + break; + } + + // + // Note that we are aware here whether we got to 100% IO specification. + // This validation is delayed to the common path for XML/cmdline. + // + } + + 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; + } + } + } + + return hr; +} + // Compatibility with the old, non-group aware affinity assignment. Preserved to allow downlevel XML profiles // to run without modification. // Any assignment done through this method will only assign within group 0, and is equivalent to the non-group @@ -1179,7 +1475,7 @@ HRESULT XmlProfileParser::_GetUINT64(IXMLDOMNode *pXmlNode, const char *pszQuery hr = spNode->get_text(&bstrText); if (SUCCEEDED(hr)) { - *pullValue = _wtoi64((wchar_t *)bstrText); // TODO: make sure it works on large unsigned ints + *pullValue = _wtoi64((wchar_t *)bstrText); } SysFreeString(bstrText); } @@ -1214,14 +1510,4 @@ HRESULT XmlProfileParser::_GetBool(IXMLDOMNode *pXmlNode, const char *pszQuery, } } return hr; -} - -HRESULT XmlProfileParser::_GetVerbose(IXMLDOMDocument2 *pXmlDoc, bool *pfVerbose) -{ - return _GetBool(pXmlDoc, "//Profile/Verbose", pfVerbose); -} - -HRESULT XmlProfileParser::_GetProgress(IXMLDOMDocument2 *pXmlDoc, DWORD *pdwProgress) -{ - return _GetDWORD(pXmlDoc, "//Profile/Progress", pdwProgress); -} +} \ No newline at end of file diff --git a/XmlResultParser/XmlResultParser.cpp b/XmlResultParser/XmlResultParser.cpp index 7ed6aa7..441e112 100644 --- a/XmlResultParser/XmlResultParser.cpp +++ b/XmlResultParser/XmlResultParser.cpp @@ -30,17 +30,45 @@ 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); - char buffer[4096] = {}; - vsprintf_s(buffer, _countof(buffer), format, listArg); + _sResult.append(_indent, ' '); + vsprintf_s(printBuffer, _countof(printBuffer), format, listArg); va_end(listArg); - _sResult += buffer; + _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) { @@ -55,6 +83,32 @@ void XmlResultParser::_PrintTargetResults(const TargetResults& results) _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) @@ -81,7 +135,7 @@ void XmlResultParser::_PrintTargetLatency(const TargetResults& results) void XmlResultParser::_PrintTargetIops(const IoBucketizer& readBucketizer, const IoBucketizer& writeBucketizer, UINT32 bucketTimeInMs) { - _Print("\n"); + _PrintInc("\n"); IoBucketizer totalIoBucketizer; totalIoBucketizer.Merge(readBucketizer); @@ -100,12 +154,12 @@ void XmlResultParser::_PrintTargetIops(const IoBucketizer& readBucketizer, const _Print("%.3f\n", totalIoBucketizer.GetStandardDeviationIOPS() / (bucketTimeInMs / 1000.0)); } _PrintIops(readBucketizer, writeBucketizer, bucketTimeInMs); - _Print("\n"); + _PrintDec("\n"); } void XmlResultParser::_PrintETWSessionInfo(struct ETWSessionInfo sessionInfo) { - _Print("\n"); + _PrintInc("\n"); _Print("%lu\n", sessionInfo.ulBufferSize); _Print("%lu\n", sessionInfo.ulMinimumBuffers); _Print("%lu\n", sessionInfo.ulMaximumBuffers); @@ -118,18 +172,18 @@ void XmlResultParser::_PrintETWSessionInfo(struct ETWSessionInfo sessionInfo) _Print("%lu\n", sessionInfo.ulEventsLost); _Print("%lu\n", sessionInfo.ulLogBuffersLost); _Print("%lu\n", sessionInfo.ulRealTimeBuffersLost); - _Print("\n"); + _PrintDec("\n"); } void XmlResultParser::_PrintETW(struct ETWMask ETWMask, struct ETWEventCounters EtwEventCounters) { - _Print("\n"); + _PrintInc("\n"); if (ETWMask.bDiskIO) { - _Print("\n"); + _PrintInc("\n"); _Print("%I64u\n", EtwEventCounters.ullIORead); _Print("%I64u\n", EtwEventCounters.ullIOWrite); - _Print("\n"); + _PrintDec("\n"); } if (ETWMask.bImageLoad) { @@ -137,13 +191,13 @@ void XmlResultParser::_PrintETW(struct ETWMask ETWMask, struct ETWEventCounters } if (ETWMask.bMemoryPageFaults) { - _Print("\n"); + _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); - _Print("\n"); + _PrintDec("\n"); } if (ETWMask.bMemoryHardFaults && !ETWMask.bMemoryPageFaults) { @@ -151,7 +205,7 @@ void XmlResultParser::_PrintETW(struct ETWMask ETWMask, struct ETWEventCounters } if (ETWMask.bNetwork) { - _Print("\n"); + _PrintInc("\n"); _Print("%I64u\n", EtwEventCounters.ullNetAccept); _Print("%I64u\n", EtwEventCounters.ullNetConnect); _Print("%I64u\n", EtwEventCounters.ullNetDisconnect); @@ -161,18 +215,18 @@ void XmlResultParser::_PrintETW(struct ETWMask ETWMask, struct ETWEventCounters _Print("%I64u\n", EtwEventCounters.ullNetTcpReceive); _Print("%I64u\n", EtwEventCounters.ullNetUdpSend); _Print("%I64u\n", EtwEventCounters.ullNetUdpReceive); - _Print("\n"); + _PrintDec("\n"); } if (ETWMask.bProcess) { - _Print("\n"); + _PrintInc("\n"); _Print("%I64u\n", EtwEventCounters.ullProcessStart); _Print("%I64u\n", EtwEventCounters.ullProcessEnd); - _Print("\n"); + _PrintDec("\n"); } if (ETWMask.bRegistry) { - _Print("\n"); + _PrintInc("\n"); _Print("%I64u\n", EtwEventCounters.ullRegCreate); _Print("%I64u\n", EtwEventCounters.ullRegDelete); _Print("%I64u\n", EtwEventCounters.ullRegDeleteValue); @@ -185,16 +239,16 @@ void XmlResultParser::_PrintETW(struct ETWMask ETWMask, struct ETWEventCounters _Print("%I64u\n", EtwEventCounters.ullRegQueryValue); _Print("%I64u\n", EtwEventCounters.ullRegSetInformation); _Print("%I64u\n", EtwEventCounters.ullRegSetValue); - _Print("\n"); + _PrintDec("\n"); } if (ETWMask.bThread) { - _Print("\n"); + _PrintInc("\n"); _Print("%I64u\n", EtwEventCounters.ullThreadStart); _Print("%I64u\n", EtwEventCounters.ullThreadEnd); - _Print("\n"); + _PrintDec("\n"); } - _Print("\n"); + _PrintDec("\n"); } void XmlResultParser::_PrintCpuUtilization(const Results& results, const SystemInformation& system) @@ -204,7 +258,7 @@ void XmlResultParser::_PrintCpuUtilization(const Results& results, const SystemI size_t ulActiveProcCount = 0; size_t ulNumGroups = system.processorTopology._vProcessorGroupInformation.size(); - _Print("\n"); + _PrintInc("\n"); double busyTime = 0; double totalIdleTime = 0; @@ -238,14 +292,14 @@ void XmlResultParser::_PrintCpuUtilization(const Results& results, const SystemI thisTime = (krnlTime + userTime) - idleTime; - _Print("\n"); + _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); - _Print("\n"); + _PrintDec("\n"); busyTime += thisTime; totalIdleTime += idleTime; @@ -262,14 +316,14 @@ void XmlResultParser::_PrintCpuUtilization(const Results& results, const SystemI ulActiveProcCount = 1; } - _Print("\n"); + _PrintInc("\n"); _Print("%.2f\n", busyTime / ulActiveProcCount); _Print("%.2f\n", totalUserTime / ulActiveProcCount); _Print("%.2f\n", (totalKrnlTime - totalIdleTime) / ulActiveProcCount); _Print("%.2f\n", totalIdleTime / ulActiveProcCount); - _Print("\n"); + _PrintDec("\n"); - _Print("\n"); + _PrintDec("\n"); } // emit the iops time series (this obviates needing perfmon counters, in common cases, and provides file level data) @@ -358,7 +412,7 @@ void XmlResultParser::_PrintLatencyPercentiles(const Results& results) } } - _Print("\n"); + _PrintInc("\n"); if (readLatencyHistogram.GetSampleSize() > 0) { _Print("%.3f\n", readLatencyHistogram.GetAvg() / 1000); @@ -375,7 +429,7 @@ void XmlResultParser::_PrintLatencyPercentiles(const Results& results) _Print("%.3f\n", totalLatencyHistogram.GetStandardDeviation() / 1000); } - _Print("\n"); + _PrintInc("\n"); _Print("0\n"); if (readLatencyHistogram.GetSampleSize() > 0) { @@ -389,7 +443,7 @@ void XmlResultParser::_PrintLatencyPercentiles(const Results& results) { _Print("%.3f\n", totalLatencyHistogram.GetMin() / 1000); } - _Print("\n"); + _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.) @@ -410,7 +464,7 @@ void XmlResultParser::_PrintLatencyPercentiles(const Results& results) for (auto p : vPercentiles) { - _Print("\n"); + _PrintInc("\n"); _Print("%.*f\n", p.first, p.second); if (readLatencyHistogram.GetSampleSize() > 0) { @@ -424,10 +478,10 @@ void XmlResultParser::_PrintLatencyPercentiles(const Results& results) { _Print("%.3f\n", totalLatencyHistogram.GetPercentile(p.second / 100) / 1000); } - _Print("\n"); + _PrintDec("\n"); } - _Print("\n"); + _PrintInc("\n"); _Print("100\n"); if (readLatencyHistogram.GetSampleSize() > 0) { @@ -441,23 +495,31 @@ void XmlResultParser::_PrintLatencyPercentiles(const Results& results) { _Print("%.3f\n", totalLatencyHistogram.GetMax() / 1000); } - _Print("\n"); - _Print("\n"); + _PrintDec("\n"); + _PrintDec("\n"); +} + +string XmlResultParser::ParseProfile(const Profile& profile) +{ + _sResult = profile.GetXml(0); + return _sResult; } -string XmlResultParser::ParseResults(Profile& profile, const SystemInformation& system, vector vResults) +string XmlResultParser::ParseResults(const Profile& profile, const SystemInformation& system, vector vResults) { _sResult.clear(); - _Print("\n"); - _sResult += system.GetXml(); - _sResult += profile.GetXml(); + _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]; - _Print("\n"); + _PrintInc("\n"); + double fTime = PerfTimer::PerfTimeToSeconds(results.ullTimeCount); //test duration if (fTime >= 0.0000001) { @@ -492,11 +554,11 @@ string XmlResultParser::ParseResults(Profile& profile, const SystemInformation& for (size_t iThread = 0; iThread < results.vThreadResults.size(); iThread++) { const ThreadResults& threadResults = results.vThreadResults[iThread]; - _Print("\n"); + _PrintInc("\n"); _Print("%u\n", iThread); for (const auto& targetResults : threadResults.vTargetResults) { - _Print("\n"); + _PrintInc("\n"); _PrintTargetResults(targetResults); if (timeSpan.GetMeasureLatency()) { @@ -506,17 +568,19 @@ string XmlResultParser::ParseResults(Profile& profile, const SystemInformation& { _PrintTargetIops(targetResults.readBucketizer, targetResults.writeBucketizer, timeSpan.GetIoBucketDurationInMilliseconds()); } - _Print("\n"); + _PrintDec("\n"); } - _Print("\n"); + _PrintDec("\n"); } } else { _Print("The test was interrupted before the measurements began. No results are displayed.\n"); } - _Print("\n"); + + _PrintDec("\n"); } - _Print(""); + + _PrintDec(""); return _sResult; } diff --git a/diskspd_vs/CmdLineParser/CmdLineParser.vcxproj b/diskspd_vs/CmdLineParser/CmdLineParser.vcxproj index ca221f3..9f61248 100644 --- a/diskspd_vs/CmdLineParser/CmdLineParser.vcxproj +++ b/diskspd_vs/CmdLineParser/CmdLineParser.vcxproj @@ -21,34 +21,34 @@ {0EF5CE78-8E92-4A1B-A255-0F544AADA291} CmdLineParser - 10.0.16299.0 + 10.0 StaticLibrary true MultiByte - v141 + v142 StaticLibrary true MultiByte - v141 + v142 StaticLibrary false true MultiByte - v141 + v142 StaticLibrary false true MultiByte - v141 + v142 diff --git a/diskspd_vs/CmdRequestCreator/CmdRequestCreator.vcxproj b/diskspd_vs/CmdRequestCreator/CmdRequestCreator.vcxproj index 3b52a36..fa03d7c 100644 --- a/diskspd_vs/CmdRequestCreator/CmdRequestCreator.vcxproj +++ b/diskspd_vs/CmdRequestCreator/CmdRequestCreator.vcxproj @@ -21,34 +21,34 @@ {D238F8AA-DE12-49E7-B4A7-9B69579A69C0} CmdRequestCreator - 10.0.16299.0 + 10.0 Application true MultiByte - v141 + v142 Application true MultiByte - v141 + v142 Application false true MultiByte - v141 + v142 Application false true MultiByte - v141 + v142 diff --git a/diskspd_vs/Common/Common.vcxproj b/diskspd_vs/Common/Common.vcxproj index 65fd1af..ffe6406 100644 --- a/diskspd_vs/Common/Common.vcxproj +++ b/diskspd_vs/Common/Common.vcxproj @@ -21,34 +21,34 @@ {B253AB42-F482-417A-82CE-EDAFCD26F366} Common - 10.0.16299.0 + 10.0 StaticLibrary true MultiByte - v141 + v142 StaticLibrary true MultiByte - v141 + v142 StaticLibrary false true MultiByte - v141 + v142 StaticLibrary false true MultiByte - v141 + v142 diff --git a/diskspd_vs/IORequestGenerator/IORequestGenerator.vcxproj b/diskspd_vs/IORequestGenerator/IORequestGenerator.vcxproj index 4e47649..aedc0c5 100644 --- a/diskspd_vs/IORequestGenerator/IORequestGenerator.vcxproj +++ b/diskspd_vs/IORequestGenerator/IORequestGenerator.vcxproj @@ -21,34 +21,34 @@ {62DB1E99-FBA0-45FD-9355-423059BA03B8} IORequestGenerator - 10.0.16299.0 + 10.0 StaticLibrary true MultiByte - v141 + v142 StaticLibrary true MultiByte - v141 + v142 StaticLibrary false true MultiByte - v141 + v142 StaticLibrary false true MultiByte - v141 + v142 diff --git a/diskspd_vs/ResultParser/ResultParser.vcxproj b/diskspd_vs/ResultParser/ResultParser.vcxproj index f5a7355..b61f02e 100644 --- a/diskspd_vs/ResultParser/ResultParser.vcxproj +++ b/diskspd_vs/ResultParser/ResultParser.vcxproj @@ -21,34 +21,34 @@ {F6C211DC-B076-4716-BCDC-D7DE88973B66} ResultParser - 10.0.16299.0 + 10.0 StaticLibrary true MultiByte - v141 + v142 StaticLibrary true MultiByte - v141 + v142 StaticLibrary false true MultiByte - v141 + v142 StaticLibrary false true MultiByte - v141 + v142 diff --git a/diskspd_vs/UnitTests/CmdLineParser/CmdLineParser.vcxproj b/diskspd_vs/UnitTests/CmdLineParser/CmdLineParser.vcxproj index 3ed00f1..2b39861 100644 --- a/diskspd_vs/UnitTests/CmdLineParser/CmdLineParser.vcxproj +++ b/diskspd_vs/UnitTests/CmdLineParser/CmdLineParser.vcxproj @@ -33,32 +33,32 @@ {54186266-8BA1-438C-AE76-AD64503CA6E9} Win32Proj CmdLineParser - 10.0.16299.0 + 10.0 DynamicLibrary true - v141 + v142 Unicode DynamicLibrary false - v141 + v142 true Unicode DynamicLibrary true - v141 + v142 Unicode DynamicLibrary false - v141 + v142 true Unicode diff --git a/diskspd_vs/UnitTests/Common/Common.vcxproj b/diskspd_vs/UnitTests/Common/Common.vcxproj index 1f24a33..67466e6 100644 --- a/diskspd_vs/UnitTests/Common/Common.vcxproj +++ b/diskspd_vs/UnitTests/Common/Common.vcxproj @@ -33,32 +33,32 @@ {BA9F561C-B103-48C9-A7C8-CE2B6BD89511} Win32Proj Common - 10.0.16299.0 + 10.0 DynamicLibrary true - v141 + v142 Unicode DynamicLibrary false - v141 + v142 true Unicode DynamicLibrary true - v141 + v142 Unicode DynamicLibrary false - v141 + v142 true Unicode diff --git a/diskspd_vs/UnitTests/IORequestGenerator/IORequestGenerator.vcxproj b/diskspd_vs/UnitTests/IORequestGenerator/IORequestGenerator.vcxproj index 6eae559..922078d 100644 --- a/diskspd_vs/UnitTests/IORequestGenerator/IORequestGenerator.vcxproj +++ b/diskspd_vs/UnitTests/IORequestGenerator/IORequestGenerator.vcxproj @@ -33,32 +33,32 @@ {13683A8B-2641-4287-9D66-A87834885057} Win32Proj IORequestGenerator - 10.0.16299.0 + 10.0 DynamicLibrary true - v141 + v142 Unicode DynamicLibrary false - v141 + v142 true Unicode DynamicLibrary true - v141 + v142 Unicode DynamicLibrary false - v141 + v142 true Unicode diff --git a/diskspd_vs/UnitTests/ResultParser/ResultParser.vcxproj b/diskspd_vs/UnitTests/ResultParser/ResultParser.vcxproj index e909a8b..e17a7bd 100644 --- a/diskspd_vs/UnitTests/ResultParser/ResultParser.vcxproj +++ b/diskspd_vs/UnitTests/ResultParser/ResultParser.vcxproj @@ -33,32 +33,32 @@ {471E64C7-2C65-4E16-A82D-4BF22AE690DD} Win32Proj ResultParser - 10.0.16299.0 + 10.0 DynamicLibrary true - v141 + v142 Unicode DynamicLibrary false - v141 + v142 true Unicode DynamicLibrary true - v141 + v142 Unicode DynamicLibrary false - v141 + v142 true Unicode diff --git a/diskspd_vs/UnitTests/XmlProfileParser/XmlProfileParser.vcxproj b/diskspd_vs/UnitTests/XmlProfileParser/XmlProfileParser.vcxproj index 3f69abf..27253ef 100644 --- a/diskspd_vs/UnitTests/XmlProfileParser/XmlProfileParser.vcxproj +++ b/diskspd_vs/UnitTests/XmlProfileParser/XmlProfileParser.vcxproj @@ -33,32 +33,32 @@ {B20AA8CF-ADFB-487C-B8F9-DBD3037F53AD} Win32Proj XmlProfileParser - 10.0.16299.0 + 10.0 DynamicLibrary true - v141 + v142 Unicode DynamicLibrary false - v141 + v142 true Unicode DynamicLibrary true - v141 + v142 Unicode DynamicLibrary false - v141 + v142 true Unicode diff --git a/diskspd_vs/UnitTests/XmlResultParser/XmlResultParser.vcxproj b/diskspd_vs/UnitTests/XmlResultParser/XmlResultParser.vcxproj index 8181205..84991a4 100644 --- a/diskspd_vs/UnitTests/XmlResultParser/XmlResultParser.vcxproj +++ b/diskspd_vs/UnitTests/XmlResultParser/XmlResultParser.vcxproj @@ -33,32 +33,32 @@ {D52F964B-C636-4DCC-AA7E-EE83A7AC41AD} Win32Proj XmlResultParser - 10.0.16299.0 + 10.0 DynamicLibrary true - v141 + v142 Unicode DynamicLibrary false - v141 + v142 true Unicode DynamicLibrary true - v141 + v142 Unicode DynamicLibrary false - v141 + v142 true Unicode diff --git a/diskspd_vs/XmlProfileParser/XmlProfileParser.vcxproj b/diskspd_vs/XmlProfileParser/XmlProfileParser.vcxproj index 02c0d61..fefc2b1 100644 --- a/diskspd_vs/XmlProfileParser/XmlProfileParser.vcxproj +++ b/diskspd_vs/XmlProfileParser/XmlProfileParser.vcxproj @@ -21,34 +21,34 @@ {EFF06674-B068-45F1-9661-DB9363B025B3} XmlProfileParser - 10.0.16299.0 + 10.0 StaticLibrary true Unicode - v141 + v142 StaticLibrary true Unicode - v141 + v142 StaticLibrary false true Unicode - v141 + v142 StaticLibrary false true Unicode - v141 + v142 diff --git a/diskspd_vs/XmlResultParser/XmlResultParser.vcxproj b/diskspd_vs/XmlResultParser/XmlResultParser.vcxproj index bafde77..d92ed6a 100644 --- a/diskspd_vs/XmlResultParser/XmlResultParser.vcxproj +++ b/diskspd_vs/XmlResultParser/XmlResultParser.vcxproj @@ -22,34 +22,34 @@ {60A28E9C-C245-4D99-9C1C-EC911031743F} Win32Proj XmlResultParser - 10.0.16299.0 + 10.0 StaticLibrary true MultiByte - v141 + v142 StaticLibrary true MultiByte - v141 + v142 StaticLibrary false true MultiByte - v141 + v142 StaticLibrary false true MultiByte - v141 + v142