621 lines
25 KiB
C++
621 lines
25 KiB
C++
/*
|
|
|
|
DISKSPD
|
|
|
|
Copyright(c) Microsoft Corporation
|
|
All rights reserved.
|
|
|
|
MIT License
|
|
|
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
of this software and associated documentation files (the "Software"), to deal
|
|
in the Software without restriction, including without limitation the rights
|
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
copies of the Software, and to permit persons to whom the Software is
|
|
furnished to do so, subject to the following conditions:
|
|
|
|
The above copyright notice and this permission notice shall be included in all
|
|
copies or substantial portions of the Software.
|
|
|
|
THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
|
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
SOFTWARE.
|
|
|
|
*/
|
|
|
|
#include "xmlresultparser.h"
|
|
|
|
// TODO: refactor to a single function shared with the ResultParser
|
|
char printBuffer[4096] = {};
|
|
|
|
/// for CrystalDiskMark
|
|
int XmlResultParser::GetTotalScore()
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
double XmlResultParser::GetAverageLatency()
|
|
{
|
|
return 0.0;
|
|
}
|
|
|
|
void XmlResultParser::_PrintV(const char *format, va_list listArg)
|
|
{
|
|
_sResult.append(_indent, ' ');
|
|
vsprintf_s(printBuffer, _countof(printBuffer), format, listArg);
|
|
_sResult += printBuffer;
|
|
}
|
|
|
|
void XmlResultParser::_Print(const char *format, ...)
|
|
{
|
|
assert(nullptr != format);
|
|
va_list listArg;
|
|
va_start(listArg, format);
|
|
|
|
_PrintV(format, listArg);
|
|
va_end(listArg);
|
|
}
|
|
|
|
void XmlResultParser::_PrintInc(const char *format, ...)
|
|
{
|
|
assert(nullptr != format);
|
|
va_list listArg;
|
|
va_start(listArg, format);
|
|
|
|
// Print & Increment Indent
|
|
// e.g., <StartOfSection>
|
|
|
|
_PrintV(format, listArg);
|
|
_indent += 2;
|
|
va_end(listArg);
|
|
}
|
|
|
|
void XmlResultParser::_PrintDec(const char *format, ...)
|
|
{
|
|
assert(nullptr != format);
|
|
va_list listArg;
|
|
va_start(listArg, format);
|
|
|
|
// Decrement Indent & Print
|
|
// e.g., </EndOfSection>
|
|
|
|
_indent -= 2;
|
|
_PrintV(format, listArg);
|
|
va_end(listArg);
|
|
}
|
|
|
|
void XmlResultParser::_PrintTargetResults(const TargetResults& results)
|
|
{
|
|
// TODO: results.readBucketizer;
|
|
// TODO: results.writeBucketizer;
|
|
|
|
_Print("<Path>%s</Path>\n", results.sPath.c_str());
|
|
_Print("<BytesCount>%I64u</BytesCount>\n", results.ullBytesCount);
|
|
_Print("<FileSize>%I64u</FileSize>\n", results.ullFileSize);
|
|
_Print("<IOCount>%I64u</IOCount>\n", results.ullIOCount);
|
|
_Print("<ReadBytes>%I64u</ReadBytes>\n", results.ullReadBytesCount);
|
|
_Print("<ReadCount>%I64u</ReadCount>\n", results.ullReadIOCount);
|
|
_Print("<WriteBytes>%I64u</WriteBytes>\n", results.ullWriteBytesCount);
|
|
_Print("<WriteCount>%I64u</WriteCount>\n", results.ullWriteIOCount);
|
|
|
|
if (results.vDistributionRange.size())
|
|
{
|
|
_PrintInc("<Distribution>\n");
|
|
_PrintInc("<Absolute>\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("<Range IO=\"%u\">%I64u</Range>\n", 0, r._dst.first - expectBase);
|
|
}
|
|
|
|
_Print("<Range IO=\"%u\">%I64u</Range>\n", r._span, r._dst.second);
|
|
expectBase = r._dst.first + r._dst.second;
|
|
}
|
|
|
|
_PrintDec("</Absolute>\n");
|
|
_PrintDec("</Distribution>\n");
|
|
}
|
|
}
|
|
|
|
void XmlResultParser::_PrintTargetLatency(const TargetResults& results)
|
|
{
|
|
if (results.readLatencyHistogram.GetSampleSize() > 0)
|
|
{
|
|
_Print("<AverageReadLatencyMilliseconds>%.3f</AverageReadLatencyMilliseconds>\n", results.readLatencyHistogram.GetAvg() / 1000);
|
|
_Print("<ReadLatencyStdev>%.3f</ReadLatencyStdev>\n", results.readLatencyHistogram.GetStandardDeviation() / 1000);
|
|
}
|
|
if (results.writeLatencyHistogram.GetSampleSize() > 0)
|
|
{
|
|
_Print("<AverageWriteLatencyMilliseconds>%.3f</AverageWriteLatencyMilliseconds>\n", results.writeLatencyHistogram.GetAvg() / 1000);
|
|
_Print("<WriteLatencyStdev>%.3f</WriteLatencyStdev>\n", results.writeLatencyHistogram.GetStandardDeviation() / 1000);
|
|
}
|
|
Histogram<float> totalLatencyHistogram;
|
|
totalLatencyHistogram.Merge(results.readLatencyHistogram);
|
|
totalLatencyHistogram.Merge(results.writeLatencyHistogram);
|
|
if (totalLatencyHistogram.GetSampleSize() > 0)
|
|
{
|
|
_Print("<AverageLatencyMilliseconds>%.3f</AverageLatencyMilliseconds>\n", totalLatencyHistogram.GetAvg() / 1000);
|
|
_Print("<LatencyStdev>%.3f</LatencyStdev>\n", totalLatencyHistogram.GetStandardDeviation() / 1000);
|
|
}
|
|
}
|
|
|
|
void XmlResultParser::_PrintTargetIops(const IoBucketizer& readBucketizer, const IoBucketizer& writeBucketizer, UINT32 bucketTimeInMs)
|
|
{
|
|
_PrintInc("<Iops>\n");
|
|
|
|
IoBucketizer totalIoBucketizer;
|
|
totalIoBucketizer.Merge(readBucketizer);
|
|
totalIoBucketizer.Merge(writeBucketizer);
|
|
|
|
if (readBucketizer.GetNumberOfValidBuckets() > 0)
|
|
{
|
|
_Print("<ReadIopsStdDev>%.3f</ReadIopsStdDev>\n", readBucketizer.GetStandardDeviationIOPS() / (bucketTimeInMs / 1000.0));
|
|
}
|
|
if (writeBucketizer.GetNumberOfValidBuckets() > 0)
|
|
{
|
|
_Print("<WriteIopsStdDev>%.3f</WriteIopsStdDev>\n", writeBucketizer.GetStandardDeviationIOPS() / (bucketTimeInMs / 1000.0));
|
|
}
|
|
if (totalIoBucketizer.GetNumberOfValidBuckets() > 0)
|
|
{
|
|
_Print("<IopsStdDev>%.3f</IopsStdDev>\n", totalIoBucketizer.GetStandardDeviationIOPS() / (bucketTimeInMs / 1000.0));
|
|
}
|
|
_PrintIops(readBucketizer, writeBucketizer, bucketTimeInMs);
|
|
_PrintDec("</Iops>\n");
|
|
}
|
|
|
|
void XmlResultParser::_PrintETWSessionInfo(struct ETWSessionInfo sessionInfo)
|
|
{
|
|
_PrintInc("<ETWSessionInfo>\n");
|
|
_Print("<BufferSizeKB>%lu</BufferSizeKB>\n", sessionInfo.ulBufferSize);
|
|
_Print("<MinimimBuffers>%lu</MinimimBuffers>\n", sessionInfo.ulMinimumBuffers);
|
|
_Print("<MaximumBuffers>%lu</MaximumBuffers>\n", sessionInfo.ulMaximumBuffers);
|
|
_Print("<FreeBuffers>%lu</FreeBuffers>", sessionInfo.ulFreeBuffers);
|
|
_Print("<BuffersWritten>%lu</BuffersWritten>\n", sessionInfo.ulBuffersWritten);
|
|
_Print("<FlushTimerSeconds>%lu</FlushTimerSeconds>\n", sessionInfo.ulFlushTimer);
|
|
_Print("<AgeLimitMinutes>%d</AgeLimitMinutes>\n", sessionInfo.lAgeLimit);
|
|
|
|
_Print("<AllocatedBuffers>%lu</AllocatedBuffers>\n", sessionInfo.ulNumberOfBuffers);
|
|
_Print("<LostEvents>%lu</LostEvents>\n", sessionInfo.ulEventsLost);
|
|
_Print("<LostLogBuffers>%lu</LostLogBuffers>\n", sessionInfo.ulLogBuffersLost);
|
|
_Print("<LostRealTimeBuffers>%lu</LostRealTimeBuffers>\n", sessionInfo.ulRealTimeBuffersLost);
|
|
_PrintDec("</ETWSessionInfo>\n");
|
|
}
|
|
|
|
void XmlResultParser::_PrintETW(struct ETWMask ETWMask, struct ETWEventCounters EtwEventCounters)
|
|
{
|
|
_PrintInc("<ETW>\n");
|
|
if (ETWMask.bDiskIO)
|
|
{
|
|
_PrintInc("<DiskIO>\n");
|
|
_Print("<Read>%I64u</Read>\n", EtwEventCounters.ullIORead);
|
|
_Print("<Write>%I64u</Write>\n", EtwEventCounters.ullIOWrite);
|
|
_PrintDec("</DiskIO>\n");
|
|
}
|
|
if (ETWMask.bImageLoad)
|
|
{
|
|
_Print("<LoadImage>%I64u</LoadImage>\n", EtwEventCounters.ullImageLoad);
|
|
}
|
|
if (ETWMask.bMemoryPageFaults)
|
|
{
|
|
_PrintInc("<MemoryPageFaults>\n");
|
|
_Print("<CopyOnWrite>%I64u</CopyOnWrite>\n", EtwEventCounters.ullMMCopyOnWrite);
|
|
_Print("<DemandZeroFault>%I64u</DemandZeroFault>\n", EtwEventCounters.ullMMDemandZeroFault);
|
|
_Print("<GuardPageFault>%I64u</GuardPageFault>\n", EtwEventCounters.ullMMGuardPageFault);
|
|
_Print("<HardPageFault>%I64u</HardPageFault>\n", EtwEventCounters.ullMMHardPageFault);
|
|
_Print("<TransitionFault>%I64u</TransitionFault>\n", EtwEventCounters.ullMMTransitionFault);
|
|
_PrintDec("</MemoryPageFaults>\n");
|
|
}
|
|
if (ETWMask.bMemoryHardFaults && !ETWMask.bMemoryPageFaults)
|
|
{
|
|
_Print("<HardPageFault>%I64u</HardPageFault>\n", EtwEventCounters.ullMMHardPageFault);
|
|
}
|
|
if (ETWMask.bNetwork)
|
|
{
|
|
_PrintInc("<Network>\n");
|
|
_Print("<Accept>%I64u</Accept>\n", EtwEventCounters.ullNetAccept);
|
|
_Print("<Connect>%I64u</Connect>\n", EtwEventCounters.ullNetConnect);
|
|
_Print("<Disconnect>%I64u</Disconnect>\n", EtwEventCounters.ullNetDisconnect);
|
|
_Print("<Reconnect>%I64u</Reconnect>\n", EtwEventCounters.ullNetReconnect);
|
|
_Print("<Retransmit>%I64u</Retransmit>\n", EtwEventCounters.ullNetRetransmit);
|
|
_Print("<TCPIPSend>%I64u</TCPIPSend>\n", EtwEventCounters.ullNetTcpSend);
|
|
_Print("<TCPIPReceive>%I64u</TCPIPReceive>\n", EtwEventCounters.ullNetTcpReceive);
|
|
_Print("<UDPIPSend>%I64u</UDPIPSend>\n", EtwEventCounters.ullNetUdpSend);
|
|
_Print("<UDPIPReceive>%I64u</UDPIPReceive>\n", EtwEventCounters.ullNetUdpReceive);
|
|
_PrintDec("</Network>\n");
|
|
}
|
|
if (ETWMask.bProcess)
|
|
{
|
|
_PrintInc("<Process>\n");
|
|
_Print("<Start>%I64u</Start>\n", EtwEventCounters.ullProcessStart);
|
|
_Print("<End>%I64u</End>\n", EtwEventCounters.ullProcessEnd);
|
|
_PrintDec("</Process>\n");
|
|
}
|
|
if (ETWMask.bRegistry)
|
|
{
|
|
_PrintInc("<Registry>\n");
|
|
_Print("<NtCreateKey>%I64u</NtCreateKey>\n", EtwEventCounters.ullRegCreate);
|
|
_Print("<NtDeleteKey>%I64u</NtDeleteKey>\n", EtwEventCounters.ullRegDelete);
|
|
_Print("<NtDeleteValueKey>%I64u</NtDeleteValueKey>\n", EtwEventCounters.ullRegDeleteValue);
|
|
_Print("<NtEnumerateKey>%I64u</NtEnumerateKey>\n", EtwEventCounters.ullRegEnumerateKey);
|
|
_Print("<NtEnumerateValueKey>%I64u</NtEnumerateValueKey>\n", EtwEventCounters.ullRegEnumerateValueKey);
|
|
_Print("<NtFlushKey>%I64u</NtFlushKey>\n", EtwEventCounters.ullRegFlush);
|
|
_Print("<NtOpenKey>%I64u</NtOpenKey>\n", EtwEventCounters.ullRegOpen);
|
|
_Print("<NtQueryKey>%I64u</NtQueryKey>\n", EtwEventCounters.ullRegQuery);
|
|
_Print("<NtQueryMultipleValueKey>%I64u</NtQueryMultipleValueKey>\n", EtwEventCounters.ullRegQueryMultipleValue);
|
|
_Print("<NtQueryValueKey>%I64u</NtQueryValueKey>\n", EtwEventCounters.ullRegQueryValue);
|
|
_Print("<NtSetInformationKey>%I64u</NtSetInformationKey>\n", EtwEventCounters.ullRegSetInformation);
|
|
_Print("<NtSetValueKey>%I64u</NtSetValueKey>\n", EtwEventCounters.ullRegSetValue);
|
|
_PrintDec("</Registry>\n");
|
|
}
|
|
if (ETWMask.bThread)
|
|
{
|
|
_PrintInc("<Thread>\n");
|
|
_Print("<Start>%I64u</Start>\n", EtwEventCounters.ullThreadStart);
|
|
_Print("<End>%I64u</End>\n", EtwEventCounters.ullThreadEnd);
|
|
_PrintDec("</Thread>\n");
|
|
}
|
|
_PrintDec("</ETW>\n");
|
|
}
|
|
|
|
void XmlResultParser::_PrintCpuUtilization(const Results& results, const SystemInformation& system)
|
|
{
|
|
const auto& topo = system.processorTopology;
|
|
size_t procCount = results.vSystemProcessorPerfInfo.size();
|
|
size_t baseProc = 0;
|
|
BYTE efficiencyClass = 0;
|
|
BYTE processorCore = 0;
|
|
|
|
_PrintInc("<CpuUtilization>\n");
|
|
|
|
double busyTime = 0;
|
|
double totalIdleTime = 0;
|
|
double totalUserTime = 0;
|
|
double totalKrnlTime = 0;
|
|
|
|
for (const auto& group : topo._vProcessorGroupInformation) {
|
|
|
|
// Sanity assert - results are sized to the sum of active processors
|
|
assert(baseProc + group._activeProcessorCount <= procCount);
|
|
|
|
for (BYTE processor = 0; processor < group._activeProcessorCount; processor++) {
|
|
|
|
long long fTime = results.vSystemProcessorPerfInfo[baseProc + processor].KernelTime.QuadPart +
|
|
results.vSystemProcessorPerfInfo[baseProc + processor].UserTime.QuadPart;
|
|
|
|
double idleTime = 100.0 * results.vSystemProcessorPerfInfo[baseProc + processor].IdleTime.QuadPart / fTime;
|
|
double krnlTime = 100.0 * results.vSystemProcessorPerfInfo[baseProc + processor].KernelTime.QuadPart / fTime;
|
|
double userTime = 100.0 * results.vSystemProcessorPerfInfo[baseProc + processor].UserTime.QuadPart / fTime;
|
|
double usedTime = (krnlTime - idleTime) + userTime;
|
|
|
|
_PrintInc("<CPU>\n");
|
|
_Print("<Socket>%d</Socket>\n", topo.GetSocketOfProcessor(group._groupNumber, processor));
|
|
_Print("<Node>%d</Node>\n", topo.GetNumaOfProcessor(group._groupNumber, processor));
|
|
_Print("<Group>%d</Group>\n", group._groupNumber);
|
|
processorCore = topo.GetCoreOfProcessor(group._groupNumber, processor, efficiencyClass);
|
|
_Print("<Core>%d</Core>\n", processorCore);
|
|
_Print("<EfficiencyClass>%d</EfficiencyClass>\n", efficiencyClass);
|
|
_Print("<Id>%d</Id>\n", processor);
|
|
_Print("<UsagePercent>%.2f</UsagePercent>\n", usedTime);
|
|
_Print("<UserPercent>%.2f</UserPercent>\n", userTime);
|
|
_Print("<KernelPercent>%.2f</KernelPercent>\n", krnlTime - idleTime);
|
|
_Print("<IdlePercent>%.2f</IdlePercent>\n", idleTime);
|
|
_PrintDec("</CPU>\n");
|
|
|
|
busyTime += usedTime;
|
|
totalIdleTime += idleTime;
|
|
totalUserTime += userTime;
|
|
totalKrnlTime += krnlTime;
|
|
}
|
|
|
|
baseProc += group._activeProcessorCount;
|
|
}
|
|
|
|
assert(baseProc == procCount);
|
|
|
|
_PrintInc("<Average>\n");
|
|
_Print("<UsagePercent>%.2f</UsagePercent>\n", busyTime / procCount);
|
|
_Print("<UserPercent>%.2f</UserPercent>\n", totalUserTime / procCount);
|
|
_Print("<KernelPercent>%.2f</KernelPercent>\n", (totalKrnlTime - totalIdleTime) / procCount);
|
|
_Print("<IdlePercent>%.2f</IdlePercent>\n", totalIdleTime / procCount);
|
|
_PrintDec("</Average>\n");
|
|
|
|
_PrintDec("</CpuUtilization>\n");
|
|
}
|
|
|
|
// emit the iops time series (this obviates needing perfmon counters, in common cases, and provides file level data)
|
|
void XmlResultParser::_PrintIops(const IoBucketizer& readBucketizer, const IoBucketizer& writeBucketizer, UINT32 bucketTimeInMs)
|
|
{
|
|
bool done = false;
|
|
for (size_t i = 0; !done; i++)
|
|
{
|
|
done = true;
|
|
|
|
double r = 0.0;
|
|
double r_min = 0.0;
|
|
double r_max = 0.0;
|
|
double r_avg = 0.0;
|
|
double r_stddev = 0.0;
|
|
|
|
double w = 0.0;
|
|
double w_min = 0.0;
|
|
double w_max = 0.0;
|
|
double w_avg = 0.0;
|
|
double w_stddev = 0.0;
|
|
|
|
if (readBucketizer.GetNumberOfValidBuckets() > i)
|
|
{
|
|
r = readBucketizer.GetIoBucketCount(i) / (bucketTimeInMs / 1000.0);
|
|
r_min = readBucketizer.GetIoBucketMinDurationUsec(i) / 1000.0;
|
|
r_max = readBucketizer.GetIoBucketMaxDurationUsec(i) / 1000.0;
|
|
r_avg = readBucketizer.GetIoBucketAvgDurationUsec(i) / 1000.0;
|
|
r_stddev = readBucketizer.GetIoBucketDurationStdDevUsec(i) / 1000.0;
|
|
done = false;
|
|
}
|
|
if (writeBucketizer.GetNumberOfValidBuckets() > i)
|
|
{
|
|
w = writeBucketizer.GetIoBucketCount(i) / (bucketTimeInMs / 1000.0);
|
|
w_min = writeBucketizer.GetIoBucketMinDurationUsec(i) / 1000.0;
|
|
w_max = writeBucketizer.GetIoBucketMaxDurationUsec(i) / 1000.0;
|
|
w_avg = writeBucketizer.GetIoBucketAvgDurationUsec(i) / 1000.0;
|
|
w_stddev = writeBucketizer.GetIoBucketDurationStdDevUsec(i) / 1000.0;
|
|
done = false;
|
|
}
|
|
if (!done)
|
|
{
|
|
_Print("<Bucket SampleMillisecond=\"%lu\" Read=\"%.0f\" Write=\"%.0f\" Total=\"%.0f\" "
|
|
"ReadMinLatencyMilliseconds=\"%.3f\" ReadMaxLatencyMilliseconds=\"%.3f\" ReadAvgLatencyMilliseconds=\"%.3f\" ReadLatencyStdDev=\"%.3f\" "
|
|
"WriteMinLatencyMilliseconds=\"%.3f\" WriteMaxLatencyMilliseconds=\"%.3f\" WriteAvgLatencyMilliseconds=\"%.3f\" WriteLatencyStdDev=\"%.3f\"/>\n",
|
|
bucketTimeInMs*(i + 1), r, w, r + w,
|
|
r_min, r_max, r_avg, r_stddev,
|
|
w_min, w_max, w_avg, w_stddev);
|
|
}
|
|
}
|
|
}
|
|
|
|
void XmlResultParser::_PrintOverallIops(const Results& results, UINT32 bucketTimeInMs)
|
|
{
|
|
IoBucketizer readBucketizer;
|
|
IoBucketizer writeBucketizer;
|
|
|
|
for (const auto& thread : results.vThreadResults)
|
|
{
|
|
for (const auto& target : thread.vTargetResults)
|
|
{
|
|
readBucketizer.Merge(target.readBucketizer);
|
|
writeBucketizer.Merge(target.writeBucketizer);
|
|
}
|
|
}
|
|
|
|
_PrintTargetIops(readBucketizer, writeBucketizer, bucketTimeInMs);
|
|
}
|
|
|
|
void XmlResultParser::_PrintLatencyPercentiles(const Results& results)
|
|
{
|
|
Histogram<float> readLatencyHistogram;
|
|
Histogram<float> writeLatencyHistogram;
|
|
Histogram<float> totalLatencyHistogram;
|
|
|
|
for (const auto& thread : results.vThreadResults)
|
|
{
|
|
for (const auto& target : thread.vTargetResults)
|
|
{
|
|
readLatencyHistogram.Merge(target.readLatencyHistogram);
|
|
|
|
writeLatencyHistogram.Merge(target.writeLatencyHistogram);
|
|
|
|
totalLatencyHistogram.Merge(target.writeLatencyHistogram);
|
|
totalLatencyHistogram.Merge(target.readLatencyHistogram);
|
|
}
|
|
}
|
|
|
|
_PrintInc("<Latency ReadHistoBuckets=\"%lu\" WriteHistoBuckets=\"%lu\" TotalHistoBuckets=\"%lu\">\n",
|
|
readLatencyHistogram.GetSampleBuckets(),
|
|
writeLatencyHistogram.GetSampleBuckets(),
|
|
totalLatencyHistogram.GetSampleBuckets());
|
|
|
|
if (readLatencyHistogram.GetSampleSize() > 0)
|
|
{
|
|
_Print("<AverageReadMilliseconds>%.3f</AverageReadMilliseconds>\n", readLatencyHistogram.GetAvg() / 1000);
|
|
_Print("<ReadLatencyStdev>%.3f</ReadLatencyStdev>\n", readLatencyHistogram.GetStandardDeviation() / 1000);
|
|
}
|
|
if (writeLatencyHistogram.GetSampleSize() > 0)
|
|
{
|
|
_Print("<AverageWriteMilliseconds>%.3f</AverageWriteMilliseconds>\n", writeLatencyHistogram.GetAvg() / 1000);
|
|
_Print("<WriteLatencyStdev>%.3f</WriteLatencyStdev>\n", writeLatencyHistogram.GetStandardDeviation() / 1000);
|
|
}
|
|
if (totalLatencyHistogram.GetSampleSize() > 0)
|
|
{
|
|
_Print("<AverageTotalMilliseconds>%.3f</AverageTotalMilliseconds>\n", totalLatencyHistogram.GetAvg() / 1000);
|
|
_Print("<LatencyStdev>%.3f</LatencyStdev>\n", totalLatencyHistogram.GetStandardDeviation() / 1000);
|
|
}
|
|
|
|
_PrintInc("<Bucket>\n");
|
|
_Print("<Percentile>0</Percentile>\n");
|
|
if (readLatencyHistogram.GetSampleSize() > 0)
|
|
{
|
|
_Print("<ReadMilliseconds>%.3f</ReadMilliseconds>\n", readLatencyHistogram.GetMin() / 1000);
|
|
}
|
|
if (writeLatencyHistogram.GetSampleSize() > 0)
|
|
{
|
|
_Print("<WriteMilliseconds>%.3f</WriteMilliseconds>\n", writeLatencyHistogram.GetMin() / 1000);
|
|
}
|
|
if (totalLatencyHistogram.GetSampleSize() > 0)
|
|
{
|
|
_Print("<TotalMilliseconds>%.3f</TotalMilliseconds>\n", totalLatencyHistogram.GetMin() / 1000);
|
|
}
|
|
_PrintDec("</Bucket>\n");
|
|
|
|
// Construct vector of percentiles and decimal precision to squelch trailing zeroes. This is more
|
|
// detailed than summary text output, and does not contain the decorated names (15th, etc.)
|
|
|
|
vector<pair<int, double>> vPercentiles;
|
|
for (int p = 1; p <= 99; p++)
|
|
{
|
|
vPercentiles.push_back(make_pair(0, p));
|
|
}
|
|
|
|
vPercentiles.push_back(make_pair(1, 99.9));
|
|
vPercentiles.push_back(make_pair(2, 99.99));
|
|
vPercentiles.push_back(make_pair(3, 99.999));
|
|
vPercentiles.push_back(make_pair(4, 99.9999));
|
|
vPercentiles.push_back(make_pair(5, 99.99999));
|
|
vPercentiles.push_back(make_pair(6, 99.999999));
|
|
vPercentiles.push_back(make_pair(7, 99.9999999));
|
|
|
|
for (auto p : vPercentiles)
|
|
{
|
|
_PrintInc("<Bucket>\n");
|
|
_Print("<Percentile>%.*f</Percentile>\n", p.first, p.second);
|
|
if (readLatencyHistogram.GetSampleSize() > 0)
|
|
{
|
|
_Print("<ReadMilliseconds>%.3f</ReadMilliseconds>\n", readLatencyHistogram.GetPercentile(p.second / 100) / 1000);
|
|
}
|
|
if (writeLatencyHistogram.GetSampleSize() > 0)
|
|
{
|
|
_Print("<WriteMilliseconds>%.3f</WriteMilliseconds>\n", writeLatencyHistogram.GetPercentile(p.second / 100) / 1000);
|
|
}
|
|
if (totalLatencyHistogram.GetSampleSize() > 0)
|
|
{
|
|
_Print("<TotalMilliseconds>%.3f</TotalMilliseconds>\n", totalLatencyHistogram.GetPercentile(p.second / 100) / 1000);
|
|
}
|
|
_PrintDec("</Bucket>\n");
|
|
}
|
|
|
|
_PrintInc("<Bucket>\n");
|
|
_Print("<Percentile>100</Percentile>\n");
|
|
if (readLatencyHistogram.GetSampleSize() > 0)
|
|
{
|
|
_Print("<ReadMilliseconds>%.3f</ReadMilliseconds>\n", readLatencyHistogram.GetMax() / 1000);
|
|
}
|
|
if (writeLatencyHistogram.GetSampleSize() > 0)
|
|
{
|
|
_Print("<WriteMilliseconds>%.3f</WriteMilliseconds>\n", writeLatencyHistogram.GetMax() / 1000);
|
|
}
|
|
if (totalLatencyHistogram.GetSampleSize() > 0)
|
|
{
|
|
_Print("<TotalMilliseconds>%.3f</TotalMilliseconds>\n", totalLatencyHistogram.GetMax() / 1000);
|
|
}
|
|
_PrintDec("</Bucket>\n");
|
|
_PrintDec("</Latency>\n");
|
|
}
|
|
|
|
string XmlResultParser::ParseProfile(const Profile& profile)
|
|
{
|
|
_sResult = profile.GetXml(0);
|
|
return _sResult;
|
|
}
|
|
|
|
void XmlResultParser::_PrintWaitStats(const ThreadResults &threadResult)
|
|
{
|
|
_PrintInc("<WaitStatistics>\n");
|
|
_Print("<CompletionWait>%llu</CompletionWait>\n", threadResult.WaitStats.Wait);
|
|
_Print("<ThrottleWait>%llu</ThrottleWait>\n", threadResult.WaitStats.ThrottleWait);
|
|
_Print("<ThrottleSleep>%llu</ThrottleSleep>\n", threadResult.WaitStats.ThrottleSleep);
|
|
_Print("<Lookaside>%llu</Lookaside>\n", threadResult.WaitStats.Lookaside);
|
|
_Print("<LookasideCompletion>%llu %llu %llu %llu %llu %llu %llu %llu</LookasideCompletion>\n",
|
|
threadResult.WaitStats.LookasideCompletion[0],
|
|
threadResult.WaitStats.LookasideCompletion[1],
|
|
threadResult.WaitStats.LookasideCompletion[2],
|
|
threadResult.WaitStats.LookasideCompletion[3],
|
|
threadResult.WaitStats.LookasideCompletion[4],
|
|
threadResult.WaitStats.LookasideCompletion[5],
|
|
threadResult.WaitStats.LookasideCompletion[6],
|
|
threadResult.WaitStats.LookasideCompletion[7]);
|
|
_PrintDec("</WaitStatistics>\n");
|
|
}
|
|
|
|
string XmlResultParser::ParseResults(const Profile& profile, const SystemInformation& system, vector<Results> vResults)
|
|
{
|
|
_sResult.clear();
|
|
|
|
_PrintInc("<Results>\n");
|
|
|
|
_sResult += system.GetXml(_indent);
|
|
_sResult += profile.GetXml(_indent);
|
|
for (size_t iResults = 0; iResults < vResults.size(); iResults++)
|
|
{
|
|
const Results& results = vResults[iResults];
|
|
const TimeSpan& timeSpan = profile.GetTimeSpans()[iResults];
|
|
|
|
_PrintInc("<TimeSpan>\n");
|
|
|
|
double fTime = PerfTimer::PerfTimeToSeconds(results.ullTimeCount); //test duration
|
|
if (fTime >= 0.0000001)
|
|
{
|
|
// There either is a fixed number of threads for all files to share (GetThreadCount() > 0) or a number of threads per file.
|
|
// In the latter case vThreadResults.size() == number of threads per file * file count
|
|
size_t ulThreadCnt = (timeSpan.GetThreadCount() > 0) ? timeSpan.GetThreadCount() : results.vThreadResults.size();
|
|
|
|
_Print("<TestTimeSeconds>%.2f</TestTimeSeconds>\n", fTime);
|
|
_Print("<ThreadCount>%u</ThreadCount>\n", ulThreadCnt);
|
|
_Print("<RequestCount>%u</RequestCount>\n", timeSpan.GetRequestCount());
|
|
_Print("<ProcCount>%u</ProcCount>\n", system.processorTopology._ulProcessorCount);
|
|
|
|
_PrintCpuUtilization(results, system);
|
|
|
|
if (timeSpan.GetMeasureLatency())
|
|
{
|
|
_PrintLatencyPercentiles(results);
|
|
}
|
|
|
|
if (timeSpan.GetCalculateIopsStdDev())
|
|
{
|
|
_PrintOverallIops(results, timeSpan.GetIoBucketDurationInMilliseconds());
|
|
}
|
|
|
|
if (results.fUseETW)
|
|
{
|
|
_PrintETW(results.EtwMask, results.EtwEventCounters);
|
|
_PrintETWSessionInfo(results.EtwSessionInfo);
|
|
}
|
|
|
|
for (size_t iThread = 0; iThread < results.vThreadResults.size(); iThread++)
|
|
{
|
|
const ThreadResults& threadResults = results.vThreadResults[iThread];
|
|
_PrintInc("<Thread>\n");
|
|
_Print("<Id>%u</Id>\n", iThread);
|
|
for (const auto& targetResults : threadResults.vTargetResults)
|
|
{
|
|
_PrintInc("<Target>\n");
|
|
_PrintTargetResults(targetResults);
|
|
if (timeSpan.GetMeasureLatency())
|
|
{
|
|
_PrintTargetLatency(targetResults);
|
|
}
|
|
if (timeSpan.GetCalculateIopsStdDev())
|
|
{
|
|
_PrintTargetIops(targetResults.readBucketizer, targetResults.writeBucketizer, timeSpan.GetIoBucketDurationInMilliseconds());
|
|
}
|
|
_PrintDec("</Target>\n");
|
|
}
|
|
if (profile.GetVerboseStats())
|
|
{
|
|
_PrintWaitStats(threadResults);
|
|
}
|
|
_PrintDec("</Thread>\n");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
_Print("<Error>The test was interrupted before the measurements began. No results are displayed.</Error>\n");
|
|
}
|
|
|
|
_PrintDec("</TimeSpan>\n");
|
|
}
|
|
|
|
_PrintDec("</Results>");
|
|
return _sResult;
|
|
}
|