/* 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 "XmlProfileParser.h" #include #include #include #include HRESULT ReportXmlError( const char *pszName, IXMLDOMParseError *pXMLError ) { long line; long linePos; long errorCode = E_FAIL; CComBSTR bReason; BSTR bstr; HRESULT hr; hr = pXMLError->get_line(&line); if (FAILED(hr)) { line = 0; } hr = pXMLError->get_linepos(&linePos); if (FAILED(hr)) { linePos = 0; } hr = pXMLError->get_errorCode(&errorCode); if (FAILED(hr)) { errorCode = E_FAIL; } hr = pXMLError->get_reason(&bstr); if (SUCCEEDED(hr)) { bReason.Attach(bstr); } fprintf(stderr, "ERROR: failed to load %s, line %lu, line position %lu, errorCode %08x\nERROR: reason: %S\n", pszName, line, linePos, errorCode, (PWCHAR)bReason); return errorCode; } bool XmlProfileParser::ParseFile(const char *pszPath, Profile *pProfile, vector *pvSubstTargets, HMODULE hModule) { assert(pszPath != nullptr); assert(pProfile != nullptr); // import schema from the named resource HRSRC hSchemaXmlResource = FindResource(hModule, L"DISKSPD.XSD", RT_HTML); assert(hSchemaXmlResource != NULL); HGLOBAL hSchemaXml = LoadResource(hModule, hSchemaXmlResource); assert(hSchemaXml != NULL); LPVOID pSchemaXml = LockResource(hSchemaXml); assert(pSchemaXml != NULL); // convert from utf-8 produced by the xsd authoring tool to utf-16 int cchSchemaXml = MultiByteToWideChar(CP_UTF8, 0, (LPCSTR)pSchemaXml, -1, NULL, 0); vector vWideSchemaXml(cchSchemaXml); int dwcchWritten = MultiByteToWideChar(CP_UTF8, 0, (LPCSTR)pSchemaXml, -1, vWideSchemaXml.data(), cchSchemaXml); UNREFERENCED_PARAMETER(dwcchWritten); assert(dwcchWritten == cchSchemaXml); // ... and finally, packed in a bstr for the loadXml interface CComBSTR bSchemaXml(vWideSchemaXml.data()); bool fComInitialized = false; HRESULT hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED); if (SUCCEEDED(hr)) { fComInitialized = true; CComPtr spXmlDoc = nullptr; CComPtr spXmlSchema = nullptr; CComPtr spXmlSchemaColl = nullptr; CComPtr spXmlParseError = nullptr; // create com objects and decorate hr = CoCreateInstance(__uuidof(DOMDocument60), nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&spXmlSchema)); if (SUCCEEDED(hr)) { hr = spXmlSchema->put_async(VARIANT_FALSE); } if (SUCCEEDED(hr)) { hr = spXmlSchema->setProperty(CComBSTR("ProhibitDTD"), CComVariant(VARIANT_FALSE)); } if (SUCCEEDED(hr)) { hr = CoCreateInstance(__uuidof(XMLSchemaCache60), nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&spXmlSchemaColl)); } if (SUCCEEDED(hr)) { hr = spXmlSchemaColl->put_validateOnLoad(VARIANT_TRUE); } if (SUCCEEDED(hr)) { hr = CoCreateInstance(__uuidof(DOMDocument60), nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&spXmlDoc)); } if (SUCCEEDED(hr)) { hr = spXmlDoc->put_async(VARIANT_FALSE); } if (SUCCEEDED(hr)) { hr = spXmlDoc->put_validateOnParse(VARIANT_TRUE); } if (SUCCEEDED(hr)) { VARIANT_BOOL fvIsOk; hr = spXmlSchema->loadXML(bSchemaXml, &fvIsOk); if (FAILED(hr) || fvIsOk != VARIANT_TRUE) { hr = spXmlSchema->get_parseError(&spXmlParseError); if (SUCCEEDED(hr)) { ReportXmlError("schema", spXmlParseError); } hr = E_FAIL; } } if (SUCCEEDED(hr)) { CComVariant vXmlSchema(spXmlSchema); CComBSTR bNull(""); hr = spXmlSchemaColl->add(bNull, vXmlSchema); } if (SUCCEEDED(hr)) { CComVariant vSchemaCache(spXmlSchemaColl); hr = spXmlDoc->putref_schemas(vSchemaCache); } if (SUCCEEDED(hr)) { VARIANT_BOOL fvIsOk; CComVariant vPath(pszPath); hr = spXmlDoc->load(vPath, &fvIsOk); if (FAILED(hr) || fvIsOk != VARIANT_TRUE) { hr = spXmlDoc->get_parseError(&spXmlParseError); if (SUCCEEDED(hr)) { ReportXmlError("profile", spXmlParseError); } hr = E_FAIL; } } // // 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 b; hr = _GetBool(spXmlDoc, "//Profile/Verbose", &b); if (SUCCEEDED(hr) && (hr != S_FALSE)) { pProfile->SetVerbose(b); } } if (SUCCEEDED(hr)) { bool b; hr = _GetBool(spXmlDoc, "//Profile/VerboseStats", &b); if (SUCCEEDED(hr) && (hr != S_FALSE)) { pProfile->SetVerboseStats(b); } } if (SUCCEEDED(hr)) { DWORD i; hr = _GetDWORD(spXmlDoc, "//Profile/ExperimentFlags", &i); if (SUCCEEDED(hr) && (hr != S_FALSE)) { g_ExperimentFlags = i; } } if (SUCCEEDED(hr)) { DWORD i; hr = _GetDWORD(spXmlDoc, "//Profile/Progress", &i); if (SUCCEEDED(hr) && (hr != S_FALSE)) { pProfile->SetProgress(i); } } if (SUCCEEDED(hr)) { string sResultFormat; hr = _GetString(spXmlDoc, "//Profile/ResultFormat", &sResultFormat); if (SUCCEEDED(hr) && (hr != S_FALSE) && sResultFormat == "xml") { pProfile->SetResultsFormat(ResultsFormat::Xml); } } if (SUCCEEDED(hr)) { string sCreateFiles; hr = _GetString(spXmlDoc, "//Profile/PrecreateFiles", &sCreateFiles); if (SUCCEEDED(hr) && (hr != S_FALSE)) { 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; } } } if (SUCCEEDED(hr)) { hr = _ParseEtw(spXmlDoc, pProfile); } 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) { 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(); } return SUCCEEDED(hr); } HRESULT XmlProfileParser::_ParseEtw(IXMLDOMDocument2 *pXmlDoc, Profile *pProfile) { bool fEtwProcess; HRESULT hr = _GetBool(pXmlDoc, "//Profile/ETW/Process", &fEtwProcess); if (SUCCEEDED(hr) && (hr != S_FALSE)) { pProfile->SetEtwEnabled(true); pProfile->SetEtwProcess(fEtwProcess); } if (SUCCEEDED(hr)) { bool fEtwThread; hr = _GetBool(pXmlDoc, "//Profile/ETW/Thread", &fEtwThread); if (SUCCEEDED(hr) && (hr != S_FALSE)) { pProfile->SetEtwEnabled(true); pProfile->SetEtwThread(fEtwThread); } } if (SUCCEEDED(hr)) { bool fEtwImageLoad; hr = _GetBool(pXmlDoc, "//Profile/ETW/ImageLoad", &fEtwImageLoad); if (SUCCEEDED(hr) && (hr != S_FALSE)) { pProfile->SetEtwEnabled(true); pProfile->SetEtwImageLoad(fEtwImageLoad); } } if (SUCCEEDED(hr)) { bool fEtwDiskIO; hr = _GetBool(pXmlDoc, "//Profile/ETW/DiskIO", &fEtwDiskIO); if (SUCCEEDED(hr) && (hr != S_FALSE)) { pProfile->SetEtwEnabled(true); pProfile->SetEtwDiskIO(fEtwDiskIO); } } if (SUCCEEDED(hr)) { bool fEtwMemoryPageFaults; hr = _GetBool(pXmlDoc, "//Profile/ETW/MemoryPageFaults", &fEtwMemoryPageFaults); if (SUCCEEDED(hr) && (hr != S_FALSE)) { pProfile->SetEtwEnabled(true); pProfile->SetEtwMemoryPageFaults(fEtwMemoryPageFaults); } } if (SUCCEEDED(hr)) { bool fEtwMemoryHardFaults; hr = _GetBool(pXmlDoc, "//Profile/ETW/MemoryHardFaults", &fEtwMemoryHardFaults); if (SUCCEEDED(hr) && (hr != S_FALSE)) { pProfile->SetEtwEnabled(true); pProfile->SetEtwMemoryHardFaults(fEtwMemoryHardFaults); } } if (SUCCEEDED(hr)) { bool fEtwNetwork; hr = _GetBool(pXmlDoc, "//Profile/ETW/Network", &fEtwNetwork); if (SUCCEEDED(hr) && (hr != S_FALSE)) { pProfile->SetEtwEnabled(true); pProfile->SetEtwNetwork(fEtwNetwork); } } if (SUCCEEDED(hr)) { bool fEtwRegistry; hr = _GetBool(pXmlDoc, "//Profile/ETW/Registry", &fEtwRegistry); if (SUCCEEDED(hr) && (hr != S_FALSE)) { pProfile->SetEtwEnabled(true); pProfile->SetEtwRegistry(fEtwRegistry); } } if (SUCCEEDED(hr)) { bool fEtwUsePagedMemory; hr = _GetBool(pXmlDoc, "//Profile/ETW/UsePagedMemory", &fEtwUsePagedMemory); if (SUCCEEDED(hr) && (hr != S_FALSE)) { pProfile->SetEtwEnabled(true); pProfile->SetEtwUsePagedMemory(fEtwUsePagedMemory); } } if (SUCCEEDED(hr)) { bool fEtwUsePerfTimer; hr = _GetBool(pXmlDoc, "//Profile/ETW/UsePerfTimer", &fEtwUsePerfTimer); if (SUCCEEDED(hr) && (hr != S_FALSE)) { pProfile->SetEtwEnabled(true); pProfile->SetEtwUsePerfTimer(fEtwUsePerfTimer); } } if (SUCCEEDED(hr)) { bool fEtwUseSystemTimer; hr = _GetBool(pXmlDoc, "//Profile/ETW/UseSystemTimer", &fEtwUseSystemTimer); if (SUCCEEDED(hr) && (hr != S_FALSE)) { pProfile->SetEtwEnabled(true); pProfile->SetEtwUseSystemTimer(fEtwUseSystemTimer); } } if (SUCCEEDED(hr)) { bool fEtwUseCyclesCounter; hr = _GetBool(pXmlDoc, "//Profile/ETW/UseCyclesCounter", &fEtwUseCyclesCounter); if (SUCCEEDED(hr) && (hr != S_FALSE)) { pProfile->SetEtwEnabled(true); pProfile->SetEtwUseCyclesCounter(fEtwUseCyclesCounter); } } return hr; } HRESULT XmlProfileParser::_ParseTimeSpans(IXMLDOMDocument2 *pXmlDoc, Profile *pProfile, vector>& vSubsts) { CComPtr spNodeList = nullptr; CComVariant query("//Profile/TimeSpans/TimeSpan"); HRESULT hr = pXmlDoc->selectNodes(query.bstrVal, &spNodeList); if (SUCCEEDED(hr)) { long cNodes; hr = spNodeList->get_length(&cNodes); if (SUCCEEDED(hr)) { for (int i = 0; i < cNodes; i++) { CComPtr spNode = nullptr; hr = spNodeList->get_item(i, &spNode); if (SUCCEEDED(hr)) { TimeSpan timeSpan; hr = _ParseTimeSpan(spNode, &timeSpan, vSubsts); if (SUCCEEDED(hr)) { pProfile->AddTimeSpan(timeSpan); } } } } } return hr; } HRESULT XmlProfileParser::_ParseTimeSpan(IXMLDOMNode *pXmlNode, TimeSpan *pTimeSpan, vector>& vSubsts) { UINT32 ulDuration; HRESULT hr = _GetUINT32(pXmlNode, "Duration", &ulDuration); if (SUCCEEDED(hr) && (hr != S_FALSE)) { pTimeSpan->SetDuration(ulDuration); } if (SUCCEEDED(hr)) { UINT32 ulWarmup; hr = _GetUINT32(pXmlNode, "Warmup", &ulWarmup); if (SUCCEEDED(hr) && (hr != S_FALSE)) { pTimeSpan->SetWarmup(ulWarmup); } } if (SUCCEEDED(hr)) { UINT32 ulCooldown; hr = _GetUINT32(pXmlNode, "Cooldown", &ulCooldown); if (SUCCEEDED(hr) && (hr != S_FALSE)) { pTimeSpan->SetCooldown(ulCooldown); } } if (SUCCEEDED(hr)) { UINT32 ulRandSeed; hr = _GetUINT32(pXmlNode, "RandSeed", &ulRandSeed); if (SUCCEEDED(hr) && (hr != S_FALSE)) { pTimeSpan->SetRandSeed(ulRandSeed); } } if (SUCCEEDED(hr)) { bool fRandomWriteData; hr = _GetBool(pXmlNode, "RandomWriteData", &fRandomWriteData); if (SUCCEEDED(hr) && (hr != S_FALSE)) { pTimeSpan->SetRandomWriteData(fRandomWriteData); } } if (SUCCEEDED(hr)) { UINT32 ulThreadCount; hr = _GetUINT32(pXmlNode, "ThreadCount", &ulThreadCount); if (SUCCEEDED(hr) && (hr != S_FALSE)) { pTimeSpan->SetThreadCount(ulThreadCount); } } if (SUCCEEDED(hr)) { UINT32 ulRequestCount; hr = _GetUINT32(pXmlNode, "RequestCount", &ulRequestCount); if (SUCCEEDED(hr) && (hr != S_FALSE)) { pTimeSpan->SetRequestCount(ulRequestCount); } } if (SUCCEEDED(hr)) { bool fDisableAffinity; hr = _GetBool(pXmlNode, "DisableAffinity", &fDisableAffinity); if (SUCCEEDED(hr) && (hr != S_FALSE)) { pTimeSpan->SetDisableAffinity(fDisableAffinity); } } if (SUCCEEDED(hr)) { bool fCompletionRoutines; hr = _GetBool(pXmlNode, "CompletionRoutines", &fCompletionRoutines); if (SUCCEEDED(hr) && (hr != S_FALSE)) { pTimeSpan->SetCompletionRoutines(fCompletionRoutines); } } if (SUCCEEDED(hr)) { bool fMeasureLatency; hr = _GetBool(pXmlNode, "MeasureLatency", &fMeasureLatency); if (SUCCEEDED(hr) && (hr != S_FALSE)) { pTimeSpan->SetMeasureLatency(fMeasureLatency); } } if (SUCCEEDED(hr)) { bool fCalculateIopsStdDev; hr = _GetBool(pXmlNode, "CalculateIopsStdDev", &fCalculateIopsStdDev); if (SUCCEEDED(hr) && (hr != S_FALSE)) { pTimeSpan->SetCalculateIopsStdDev(fCalculateIopsStdDev); } } if (SUCCEEDED(hr)) { UINT32 ulIoBucketDuration; hr = _GetUINT32(pXmlNode, "IoBucketDuration", &ulIoBucketDuration); if (SUCCEEDED(hr) && (hr != S_FALSE)) { pTimeSpan->SetIoBucketDurationInMilliseconds(ulIoBucketDuration); } } // Look for downlevel non-group aware assignment if (SUCCEEDED(hr)) { hr = _ParseAffinityAssignment(pXmlNode, pTimeSpan); } // Look for uplevel group aware assignment. if (SUCCEEDED(hr)) { hr = _ParseAffinityGroupAssignment(pXmlNode, pTimeSpan); } if (SUCCEEDED(hr)) { hr = _ParseTargets(pXmlNode, pTimeSpan, vSubsts); } return hr; } 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; HRESULT hr = pXmlNode->selectNodes(query.bstrVal, &spNodeList); if (SUCCEEDED(hr)) { long cNodes; hr = spNodeList->get_length(&cNodes); if (SUCCEEDED(hr)) { for (int i = 0; i < cNodes; i++) { CComPtr spNode = nullptr; hr = spNodeList->get_item(i, &spNode); if (SUCCEEDED(hr)) { Target target; hr = _ParseTarget(spNode, &target); if (SUCCEEDED(hr)) { hr = _SubstTarget(&target, vSubsts); } if (SUCCEEDED(hr)) { pTimeSpan->AddTarget(target); } } if (!SUCCEEDED(hr)) { break; } } } } return hr; } HRESULT XmlProfileParser::_ParseRandomDataSource(IXMLDOMNode *pXmlNode, Target *pTarget) { CComPtr spNodeList = nullptr; CComVariant query("RandomDataSource"); HRESULT hr = pXmlNode->selectNodes(query.bstrVal, &spNodeList); if (SUCCEEDED(hr)) { long cNodes; hr = spNodeList->get_length(&cNodes); if (SUCCEEDED(hr) && (cNodes == 1)) { CComPtr spNode = nullptr; hr = spNodeList->get_item(0, &spNode); if (SUCCEEDED(hr)) { UINT64 cb; hr = _GetUINT64(spNode, "SizeInBytes", &cb); if (SUCCEEDED(hr) && (S_FALSE != hr)) { pTarget->SetRandomDataWriteBufferSize(cb); string sPath; hr = _GetString(spNode, "FilePath", &sPath); if (SUCCEEDED(hr) && (S_FALSE != hr)) { pTarget->SetRandomDataWriteBufferSourcePath(sPath); } } } } } return hr; } HRESULT XmlProfileParser::_ParseWriteBufferContent(IXMLDOMNode *pXmlNode, Target *pTarget) { CComPtr spNodeList = nullptr; CComVariant query("WriteBufferContent"); HRESULT hr = pXmlNode->selectNodes(query.bstrVal, &spNodeList); if (SUCCEEDED(hr)) { long cNodes; hr = spNodeList->get_length(&cNodes); if (SUCCEEDED(hr) && (cNodes == 1)) { CComPtr spNode = nullptr; hr = spNodeList->get_item(0, &spNode); if (SUCCEEDED(hr)) { string sPattern; hr = _GetString(spNode, "Pattern", &sPattern); if (SUCCEEDED(hr) && (hr != S_FALSE)) { if (sPattern == "sequential") { // that's the default option - do nothing } else if (sPattern == "zero") { pTarget->SetZeroWriteBuffers(true); } else if (sPattern == "random") { hr = _ParseRandomDataSource(spNode, pTarget); } else { hr = E_INVALIDARG; } } } } } return hr; } 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)) { pTarget->SetPath(sPath); } if (SUCCEEDED(hr)) { DWORD dwBlockSize; hr = _GetDWORD(pXmlNode, "BlockSize", &dwBlockSize); if (SUCCEEDED(hr) && (hr != S_FALSE)) { pTarget->SetBlockSizeInBytes(dwBlockSize); } } if (SUCCEEDED(hr)) { bool fInterlockedSequential; hr = _GetBool(pXmlNode, "InterlockedSequential", &fInterlockedSequential); if (SUCCEEDED(hr) && (hr != S_FALSE)) { pTarget->SetUseInterlockedSequential(fInterlockedSequential); } } if (SUCCEEDED(hr)) { UINT64 ullBaseFileOffset; hr = _GetUINT64(pXmlNode, "BaseFileOffset", &ullBaseFileOffset); if (SUCCEEDED(hr) && (hr != S_FALSE)) { pTarget->SetBaseFileOffsetInBytes(ullBaseFileOffset); } } if (SUCCEEDED(hr)) { bool fBool; hr = _GetBool(pXmlNode, "SequentialScan", &fBool); if (SUCCEEDED(hr) && (hr != S_FALSE)) { pTarget->SetSequentialScanHint(fBool); } } if (SUCCEEDED(hr)) { bool fBool; hr = _GetBool(pXmlNode, "RandomAccess", &fBool); if (SUCCEEDED(hr) && (hr != S_FALSE)) { pTarget->SetRandomAccessHint(fBool); } } if (SUCCEEDED(hr)) { bool fBool; hr = _GetBool(pXmlNode, "TemporaryFile", &fBool); if (SUCCEEDED(hr) && (hr != S_FALSE)) { pTarget->SetTemporaryFileHint(fBool); } } if (SUCCEEDED(hr)) { bool fUseLargePages; hr = _GetBool(pXmlNode, "UseLargePages", &fUseLargePages); if (SUCCEEDED(hr) && (hr != S_FALSE)) { pTarget->SetUseLargePages(fUseLargePages); } } if (SUCCEEDED(hr)) { DWORD dwRequestCount; hr = _GetDWORD(pXmlNode, "RequestCount", &dwRequestCount); if (SUCCEEDED(hr) && (hr != S_FALSE)) { pTarget->SetRequestCount(dwRequestCount); } } 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)) { // 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); } } } if (SUCCEEDED(hr)) { bool fBool; hr = _GetBool(pXmlNode, "DisableOSCache", &fBool); if (SUCCEEDED(hr) && (hr != S_FALSE) && fBool) { pTarget->SetCacheMode(TargetCacheMode::DisableOSCache); } } if (SUCCEEDED(hr)) { bool fBool; hr = _GetBool(pXmlNode, "MemoryMappedIo", &fBool); if (SUCCEEDED(hr) && (hr != S_FALSE) && fBool) { pTarget->SetMemoryMappedIoMode(MemoryMappedIoMode::On); } } if (SUCCEEDED(hr)) { bool fBool; hr = _GetBool(pXmlNode, "DisableAllCache", &fBool); if (SUCCEEDED(hr) && (hr != S_FALSE) && fBool) { pTarget->SetCacheMode(TargetCacheMode::DisableOSCache); pTarget->SetWriteThroughMode(WriteThroughMode::On); } } if (SUCCEEDED(hr)) { bool fBool; hr = _GetBool(pXmlNode, "DisableLocalCache", &fBool); if (SUCCEEDED(hr) && (hr != S_FALSE) && fBool) { pTarget->SetCacheMode(TargetCacheMode::DisableLocalCache); } } if (SUCCEEDED(hr)) { bool fBool; hr = _GetBool(pXmlNode, "WriteThrough", &fBool); if (SUCCEEDED(hr) && (hr != S_FALSE) && fBool) { pTarget->SetWriteThroughMode(WriteThroughMode::On); } } if (SUCCEEDED(hr)) { string sFlushType; hr = _GetString(pXmlNode, "FlushType", &sFlushType); if (SUCCEEDED(hr) && (hr != S_FALSE)) { if (sFlushType == "ViewOfFile") { pTarget->SetMemoryMappedIoFlushMode(MemoryMappedIoFlushMode::ViewOfFile); } else if (sFlushType == "NonVolatileMemory") { pTarget->SetMemoryMappedIoFlushMode(MemoryMappedIoFlushMode::NonVolatileMemory); } else if (sFlushType == "NonVolatileMemoryNoDrain") { pTarget->SetMemoryMappedIoFlushMode(MemoryMappedIoFlushMode::NonVolatileMemoryNoDrain); } else { hr = E_INVALIDARG; } } } if (SUCCEEDED(hr)) { hr = _ParseWriteBufferContent(pXmlNode, pTarget); } if (SUCCEEDED(hr)) { DWORD dwBurstSize; hr = _GetDWORD(pXmlNode, "BurstSize", &dwBurstSize); if (SUCCEEDED(hr) && (hr != S_FALSE)) { pTarget->SetBurstSize(dwBurstSize); pTarget->SetUseBurstSize(true); } } if (SUCCEEDED(hr)) { DWORD dwThinkTime; hr = _GetDWORD(pXmlNode, "ThinkTime", &dwThinkTime); if (SUCCEEDED(hr) && (hr != S_FALSE)) { pTarget->SetThinkTime(dwThinkTime); pTarget->SetEnableThinkTime(true); } } if (SUCCEEDED(hr)) { hr = _ParseThroughput(pXmlNode, pTarget); } if (SUCCEEDED(hr)) { DWORD dwThreadsPerFile; hr = _GetDWORD(pXmlNode, "ThreadsPerFile", &dwThreadsPerFile); if (SUCCEEDED(hr) && (hr != S_FALSE)) { pTarget->SetThreadsPerFile(dwThreadsPerFile); } } if (SUCCEEDED(hr)) { UINT64 ullFileSize; hr = _GetUINT64(pXmlNode, "FileSize", &ullFileSize); if (SUCCEEDED(hr) && (hr != S_FALSE)) { pTarget->SetFileSize(ullFileSize); pTarget->SetCreateFile(true); } } if (SUCCEEDED(hr)) { UINT64 ullMaxFileSize; hr = _GetUINT64(pXmlNode, "MaxFileSize", &ullMaxFileSize); if (SUCCEEDED(hr) && (hr != S_FALSE)) { pTarget->SetMaxFileSize(ullMaxFileSize); } } if (SUCCEEDED(hr)) { UINT32 ulWriteRatio; hr = _GetUINT32(pXmlNode, "WriteRatio", &ulWriteRatio); if (SUCCEEDED(hr) && (hr != S_FALSE)) { pTarget->SetWriteRatio(ulWriteRatio); } } if (SUCCEEDED(hr)) { bool fParallelAsyncIO; hr = _GetBool(pXmlNode, "ParallelAsyncIO", &fParallelAsyncIO); if (SUCCEEDED(hr) && (hr != S_FALSE)) { pTarget->SetUseParallelAsyncIO(fParallelAsyncIO); } } if (SUCCEEDED(hr)) { UINT64 ullThreadStride; hr = _GetUINT64(pXmlNode, "ThreadStride", &ullThreadStride); if (SUCCEEDED(hr) && (hr != S_FALSE)) { pTarget->SetThreadStrideInBytes(ullThreadStride); } } if (SUCCEEDED(hr)) { UINT32 ulIOPriority; hr = _GetUINT32(pXmlNode, "IOPriority", &ulIOPriority); if (SUCCEEDED(hr) && (hr != S_FALSE)) { PRIORITY_HINT hint[] = { IoPriorityHintVeryLow, IoPriorityHintLow, IoPriorityHintNormal }; pTarget->SetIOPriorityHint(hint[ulIOPriority - 1]); } } if (SUCCEEDED(hr)) { UINT32 ulWeight; hr = _GetUINT32(pXmlNode, "Weight", &ulWeight); if (SUCCEEDED(hr) && (hr != S_FALSE)) { pTarget->SetWeight(ulWeight); } } // // 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); } 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"); CComPtr spNodeList = nullptr; HRESULT hr = pXmlNode->selectNodes(query.bstrVal, &spNodeList); if (SUCCEEDED(hr)) { long cNodes; hr = spNodeList->get_length(&cNodes); if (SUCCEEDED(hr)) { for (int i = 0; i < cNodes; i++) { CComPtr spNode = nullptr; hr = spNodeList->get_item(i, &spNode); if (SUCCEEDED(hr)) { ThreadTarget threadTarget; _ParseThreadTarget(spNode, &threadTarget); pTarget->AddThreadTarget(threadTarget); } } } } return hr; } HRESULT XmlProfileParser::_ParseThreadTarget(IXMLDOMNode *pXmlNode, ThreadTarget *pThreadTarget) { UINT32 ulThread; HRESULT hr = _GetUINT32(pXmlNode, "Thread", &ulThread); if (SUCCEEDED(hr) && (hr != S_FALSE)) { pThreadTarget->SetThread(ulThread); } if (SUCCEEDED(hr)) { UINT32 ulWeight; hr = _GetUINT32(pXmlNode, "Weight", &ulWeight); if (SUCCEEDED(hr) && (hr != S_FALSE)) { pThreadTarget->SetWeight(ulWeight); } } 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 // specification -a#,#,# (contrast to -ag#,#,#,...). While not strictly equivalent to the old non-group aware // behavior, this should be acceptably good-enough. // // The XML result parser no longer emits this form. HRESULT XmlProfileParser::_ParseAffinityAssignment(IXMLDOMNode *pXmlNode, TimeSpan *pTimeSpan) { CComPtr spNodeList = nullptr; CComVariant query("Affinity/AffinityAssignment"); HRESULT hr = pXmlNode->selectNodes(query.bstrVal, &spNodeList); if (SUCCEEDED(hr)) { long cNodes; hr = spNodeList->get_length(&cNodes); if (SUCCEEDED(hr)) { for (int i = 0; i < cNodes; i++) { CComPtr spNode = nullptr; hr = spNodeList->get_item(i, &spNode); if (SUCCEEDED(hr)) { BSTR bstrText; hr = spNode->get_text(&bstrText); if (SUCCEEDED(hr)) { pTimeSpan->AddAffinityAssignment((WORD)0, (BYTE)_wtoi((wchar_t *)bstrText)); SysFreeString(bstrText); } } } } } return hr; } // Group aware affinity assignment. This is the only form emitted by the XML result parser. HRESULT XmlProfileParser::_ParseAffinityGroupAssignment(IXMLDOMNode *pXmlNode, TimeSpan *pTimeSpan) { CComPtr spNodeList = nullptr; CComVariant query("Affinity/AffinityGroupAssignment"); HRESULT hr = pXmlNode->selectNodes(query.bstrVal, &spNodeList); if (SUCCEEDED(hr)) { long cNodes; hr = spNodeList->get_length(&cNodes); if (SUCCEEDED(hr)) { for (int i = 0; i < cNodes; i++) { CComPtr spNode = nullptr; hr = spNodeList->get_item(i, &spNode); if (SUCCEEDED(hr)) { UINT32 dwGroup = 0, dwProc = 0; hr = _GetUINT32Attr(spNode, "Group", &dwGroup); if (SUCCEEDED(hr)) { _GetUINT32Attr(spNode, "Processor", &dwProc); } if (SUCCEEDED(hr)) { if (dwProc > MAXBYTE) { fprintf(stderr, "ERROR: profile specifies group assignment to core %u, out of range\n", dwProc); hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA); } if (dwGroup > MAXWORD) { fprintf(stderr, "ERROR: profile specifies group assignment group %u, out of range\n", dwGroup); hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA); } if (SUCCEEDED(hr)) { pTimeSpan->AddAffinityAssignment((WORD)dwGroup, (BYTE)dwProc); } } } } } } return hr; } HRESULT XmlProfileParser::_GetUINT32(IXMLDOMNode *pXmlNode, const char *pszQuery, UINT32 *pulValue) const { CComPtr spNode = nullptr; CComVariant query(pszQuery); HRESULT hr = pXmlNode->selectSingleNode(query.bstrVal, &spNode); if (SUCCEEDED(hr) && (hr != S_FALSE)) { BSTR bstrText; hr = spNode->get_text(&bstrText); if (SUCCEEDED(hr)) { *pulValue = _wtoi((wchar_t *)bstrText); // TODO: make sure it works on large unsigned ints SysFreeString(bstrText); } } return hr; } HRESULT XmlProfileParser::_GetUINT32Attr(IXMLDOMNode *pXmlNode, const char *pszAttr, UINT32 *pulValue) const { CComPtr spNamedNodeMap = nullptr; CComBSTR attr(pszAttr); HRESULT hr = pXmlNode->get_attributes(&spNamedNodeMap); if (SUCCEEDED(hr) && (hr != S_FALSE)) { CComPtr spNode = nullptr; HRESULT hr = spNamedNodeMap->getNamedItem(attr, &spNode); if (SUCCEEDED(hr) && (hr != S_FALSE)) { BSTR bstrText; hr = spNode->get_text(&bstrText); if (SUCCEEDED(hr)) { *pulValue = _wtoi((wchar_t *)bstrText); // TODO: make sure it works on large unsigned ints SysFreeString(bstrText); } } } return hr; } HRESULT XmlProfileParser::_GetString(IXMLDOMNode *pXmlNode, const char *pszQuery, string *psValue) const { CComPtr spNode = nullptr; CComVariant query(pszQuery); HRESULT hr = pXmlNode->selectSingleNode(query.bstrVal, &spNode); if (SUCCEEDED(hr) && (hr != S_FALSE)) { BSTR bstrText; hr = spNode->get_text(&bstrText); if (SUCCEEDED(hr)) { // TODO: use wstring? char path[MAX_PATH] = {}; WideCharToMultiByte(CP_UTF8, 0 /*dwFlags*/, (wchar_t *)bstrText, static_cast(wcslen((wchar_t *)bstrText)), path, sizeof(path)-1, 0 /*lpDefaultChar*/, 0 /*lpUsedDefaultChar*/); *psValue = string(path); } SysFreeString(bstrText); } return hr; } HRESULT XmlProfileParser::_GetUINT64(IXMLDOMNode *pXmlNode, const char *pszQuery, UINT64 *pullValue) const { CComPtr spNode = nullptr; CComVariant query(pszQuery); HRESULT hr = pXmlNode->selectSingleNode(query.bstrVal, &spNode); if (SUCCEEDED(hr) && (hr != S_FALSE)) { BSTR bstrText; hr = spNode->get_text(&bstrText); if (SUCCEEDED(hr)) { *pullValue = _wtoi64((wchar_t *)bstrText); } SysFreeString(bstrText); } return hr; } HRESULT XmlProfileParser::_GetDWORD(IXMLDOMNode *pXmlNode, const char *pszQuery, DWORD *pdwValue) const { UINT32 value = 0; HRESULT hr = _GetUINT32(pXmlNode, pszQuery, &value); if (SUCCEEDED(hr)) { *pdwValue = value; } return hr; } HRESULT XmlProfileParser::_GetBool(IXMLDOMNode *pXmlNode, const char *pszQuery, bool *pfValue) const { HRESULT hr = S_OK; CComPtr spNode = nullptr; CComVariant query(pszQuery); hr = pXmlNode->selectSingleNode(query.bstrVal, &spNode); if (SUCCEEDED(hr) && (hr != S_FALSE)) { BSTR bstrText; hr = spNode->get_text(&bstrText); if (SUCCEEDED(hr)) { *pfValue = (_wcsicmp(L"true", (wchar_t *)bstrText) == 0); SysFreeString(bstrText); } } return hr; }