diff --git a/.hgignore b/.hgignore new file mode 100644 index 0000000..0f9be94 --- /dev/null +++ b/.hgignore @@ -0,0 +1,6 @@ +syntax: regexp +^out\/ +^packages\/ +\.vs\/ +Release\/ +Debug\/ \ No newline at end of file diff --git a/README.md b/README.md index 3ea9786..71f6ab2 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,36 @@ +# Fork changes + +Added `/sumof` command, which computes total time for all stuff matching given wildcard. Given a raw trace, it can be used as follows: + + vcperf /sumof *Eigen* rawTrace.etl + +It prints results to stdout: +``` +Total time for parsing files matching "*Eigen*": + CPU Time: 79.565226 / 96.567721 / 110.676782 + Duration: 448.373768 / 1112.570639 / 1168.585713 +Total time for template instantiations matching "*Eigen*": + CPU Time: 169.516602 / 169.516602 / 170.677942 + Duration: 122.637069 / 188.706707 / 190.002096 +``` + +Ideally, this should show how much time is spent on Eigen template library. +Better use "CPU Time" instead of "Duration", unless you disable parallel builds. + +Note that every line reports three numbers, which are slightly different. +The first one is the sum of exclusive time (as reported by framework) over all matching activities. +The second one also the sum of exclusive time over all matches, but exclusive time is computed as inclusive time minus inclusive time of all children. +The last one is the sum of inclusive time over all topmost matches. + +For instance, if we consider parsing files in the sample above: + +* 110.676782 seconds is spent on Eigen headers and all headers included from them (regardless of whether they are from Eigen of not). +* 96.567721 or 79.565226 seconds is spent on Eigen headers only, and excludes time for non-Eigen headers included from them. No idea why these two numbers are different... + +*(Original vcperf readme follows)* + +--------------------- + # vcperf ## Overview diff --git a/src/Commands.cpp b/src/Commands.cpp index 4d93c07..4bc9116 100644 --- a/src/Commands.cpp +++ b/src/Commands.cpp @@ -14,6 +14,8 @@ #include "WPA\Views\TemplateInstantiationsView.h" #include "TimeTrace\ExecutionHierarchy.h" #include "TimeTrace\TimeTraceGenerator.h" +#include "FilteredAggregator\FilteredAggregator.h" +#include "FilteredAggregator\StatisticsCollector.h" using namespace Microsoft::Cpp::BuildInsights; @@ -243,6 +245,29 @@ RESULT_CODE AnalyzeToTimeTrace(const std::filesystem::path& inputFile, const std return Analyze(inputFile.c_str(), analysisPassCount, analyzerGroup); } +std::string ws2s(const std::wstring& wstr) +{ + std::string res; + for (int i = 0; i < wstr.size(); i++) { + // I feel sorry =) + assert(wstr[i] >= 0 && wstr[i] < 128); + res += char(wstr[i]); + } + return res; +} +HRESULT DoFilteredAggregate(const std::filesystem::path& inputFile, const std::wstring& wildcard) +{ + FilteringAggregator analyzer{ ws2s(wildcard) }; + auto analyzerGroup = MakeStaticAnalyzerGroup(&analyzer); + return Analyze(inputFile.c_str(), 2, analyzerGroup); +} +HRESULT DoCollectStatistics(const std::filesystem::path& inputFile) +{ + StatisticsCollector analyzer; + auto analyzerGroup = MakeStaticAnalyzerGroup(&analyzer); + return Analyze(inputFile.c_str(), 2, analyzerGroup); +} + HRESULT DoStart(const std::wstring& sessionName, bool cpuSampling, VerbosityLevel verbosityLevel) { TRACING_SESSION_OPTIONS options{}; diff --git a/src/Commands.h b/src/Commands.h index 6dfd032..1925736 100644 --- a/src/Commands.h +++ b/src/Commands.h @@ -18,5 +18,7 @@ HRESULT DoStart(const std::wstring& sessionName, bool cpuSampling, VerbosityLeve HRESULT DoStop(const std::wstring& sessionName, const std::filesystem::path& outputFile, bool analyzeTemplates = false, bool generateTimeTrace = false); HRESULT DoStopNoAnalyze(const std::wstring& sessionName, const std::filesystem::path& outputFile); HRESULT DoAnalyze(const std::filesystem::path& inputFile, const std::filesystem::path& outputFile, bool analyzeTemplates = false, bool generateTimeTrace = false); +HRESULT DoFilteredAggregate(const std::filesystem::path& inputFile, const std::wstring& wildcard); +HRESULT DoCollectStatistics(const std::filesystem::path& inputFile); } // namespace vcperf \ No newline at end of file diff --git a/src/FilteredAggregator/FilteredAggregator.cpp b/src/FilteredAggregator/FilteredAggregator.cpp new file mode 100644 index 0000000..6b44ed5 --- /dev/null +++ b/src/FilteredAggregator/FilteredAggregator.cpp @@ -0,0 +1,168 @@ +#include "FilteredAggregator.h" +#include +#include +#include "Wildcard.h" +#include "Undecorate.h" + +using namespace Microsoft::Cpp::BuildInsights; +using namespace vcperf; + + +FilteringAggregator::FilteringAggregator(const std::string& wildcard) +{ + wildcard_ = wildcard; +} + +void FilteringAggregator::WildcardTime::Print() const +{ + printf(" CPU Time: %10.6lf / %10.6lf / %10.6lf\n", 1e-9 * exclusiveCpuTime_, 1e-9 * subtractedCpuTime_, 1e-9 * inclusiveCpuTime_); + printf(" Duration: %10.6lf / %10.6lf / %10.6lf\n", 1e-9 * exclusiveDuration_, 1e-9 * subtractedDuration_, 1e-9 * inclusiveDuration_); +} + +BI::AnalysisControl FilteringAggregator::OnEndAnalysisPass() +{ + if (passNumber_ == 0) { + std::sort(filteredKeys_.begin(), filteredKeys_.end()); + filteredKeys_.resize(std::unique(filteredKeys_.begin(), filteredKeys_.end()) - filteredKeys_.begin()); + + std::sort(functionNamesDecorated_.begin(), functionNamesDecorated_.end()); + functionNamesDecorated_.resize(std::unique(functionNamesDecorated_.begin(), functionNamesDecorated_.end()) - functionNamesDecorated_.begin()); + Undecorate(functionNamesDecorated_, functionNamesUndecorated_); + } + else if (passNumber_ == 1) { + filteredKeys_.clear(); + + printf("Total time for parsing files matching \"%s\":\n", wildcard_.c_str()); + _parsingTime.Print(); + printf("Total time for template instantiations matching \"%s\":\n", wildcard_.c_str()); + _instantiationsTime.Print(); + printf("Total time for code generation matching \"%s\":\n", wildcard_.c_str()); + _generationTime.Print(); + } + passNumber_++; + + return AnalysisControl::CONTINUE; +} + +AnalysisControl FilteringAggregator::OnStopActivity(const EventStack& eventStack) +{ + if (passNumber_ == 1) { + MatchEventStackInMemberFunction(eventStack, this, &FilteringAggregator::OnFinishTemplateInstantiation); + MatchEventStackInMemberFunction(eventStack, this, &FilteringAggregator::OnFileParse); + } + MatchEventStackInMemberFunction(eventStack, this, &FilteringAggregator::OnFunction); + + return AnalysisControl::CONTINUE; +} + +AnalysisControl FilteringAggregator::OnSimpleEvent(const EventStack& eventStack) +{ + if (passNumber_ == 0) { + MatchEventStackInMemberFunction(eventStack, this, &FilteringAggregator::OnSymbolName); + } + + return AnalysisControl::CONTINUE; +} + +void FilteringAggregator::OnSymbolName(const SE::SymbolName& symbolName) +{ + const char *name = symbolName.Name(); + size_t len = strlen(name); + if (WildcardMatch(wildcard_.c_str(), name)) { + filteredKeys_.push_back(symbolName.Key()); + } +} + +bool FilteringAggregator::InstantiationMatchesWildcard(const A::TemplateInstantiation& templateInstantiation) const +{ + uint64_t primaryKey = templateInstantiation.PrimaryTemplateSymbolKey(); + size_t pos = std::lower_bound(filteredKeys_.begin(), filteredKeys_.end(), primaryKey) - filteredKeys_.begin(); + return (pos < filteredKeys_.size() && filteredKeys_[pos] == primaryKey); +} + +bool FilteringAggregator::FileParseMatchesWildcard(const A::FrontEndFile& file) const +{ + const char *path = file.Path(); + //bool isHeader = !(WildcardMatch("*.cpp", path) || WildcardMatch("*.cxx", path) || WildcardMatch("*.c", path)); + return WildcardMatch(wildcard_.c_str(), path); +} + +bool FilteringAggregator::FunctionMatchesWildcard(const A::Function& function) const +{ + std::string decName = function.Name(); + size_t pos = std::lower_bound(functionNamesDecorated_.begin(), functionNamesDecorated_.end(), decName) - functionNamesDecorated_.begin(); + if (pos == functionNamesDecorated_.size() || functionNamesDecorated_[pos] != decName) + return false; //not even registered + const std::string& undecName = functionNamesUndecorated_[pos]; + return WildcardMatch(wildcard_.c_str(), undecName.c_str()); +} + +template void FilteringAggregator::UpdateWildcardTime(const BI::EventGroup& activityGroup, bool (FilteringAggregator::*matchFunc)(const Activity&) const, WildcardTime& totalTime) const +{ + int n = (int)activityGroup.Size(); + uint64_t inclCpuTime = activityGroup.Back().CPUTime().count(); + uint64_t inclDuration = activityGroup.Back().Duration().count(); + uint64_t exclCpuTime = activityGroup.Back().ExclusiveCPUTime().count(); + uint64_t exclDuration = activityGroup.Back().ExclusiveDuration().count(); + + if (n >= 1 && (this->*matchFunc)(activityGroup[n-1])) { + bool hasMatchingAncestor = false; + for (int i = n-2; i >= 0; i--) + if ((this->*matchFunc)(activityGroup[i])) { + hasMatchingAncestor = true; + break; + } + if (!hasMatchingAncestor) { + //instantiation stack looks like: no, no, no, ..., no, match + totalTime.inclusiveCpuTime_ += inclCpuTime; + totalTime.inclusiveDuration_ += inclDuration; + } + //instantiation stack looks like: any, any, any, ..., any, match + totalTime.subtractedCpuTime_ += inclCpuTime; + totalTime.subtractedDuration_ += inclDuration; + totalTime.exclusiveCpuTime_ += exclCpuTime; + totalTime.exclusiveDuration_ += exclDuration; + } + if (n >= 2 && (this->*matchFunc)(activityGroup[n-2])) { + //instantiation stack looks like: any any ... any match any + totalTime.subtractedCpuTime_ -= inclCpuTime; + totalTime.subtractedDuration_ -= inclDuration; + } +} + +template void FilteringAggregator::UpdateWildcardTime(const Activity& activity, bool (FilteringAggregator::* matchFunc)(const Activity&) const, WildcardTime& totalTime) const +{ + if (!(this->*matchFunc)(activity)) + return; + + uint64_t inclCpuTime = activity.CPUTime().count(); + uint64_t inclDuration = activity.Duration().count(); + uint64_t exclCpuTime = activity.ExclusiveCPUTime().count(); + uint64_t exclDuration = activity.ExclusiveDuration().count(); + totalTime.inclusiveCpuTime_ += inclCpuTime; + totalTime.inclusiveDuration_ += inclDuration; + totalTime.subtractedCpuTime_ += inclCpuTime; + totalTime.subtractedDuration_ += inclDuration; + totalTime.exclusiveCpuTime_ += exclCpuTime; + totalTime.exclusiveDuration_ += exclDuration; +} + +void FilteringAggregator::OnFinishTemplateInstantiation(const A::Activity& parent, const A::TemplateInstantiationGroup& templateInstantiationGroup) +{ + UpdateWildcardTime(templateInstantiationGroup, &FilteringAggregator::InstantiationMatchesWildcard, _instantiationsTime); +} + +void FilteringAggregator::OnFileParse(const A::FrontEndFileGroup& files) +{ + UpdateWildcardTime(files, &FilteringAggregator::FileParseMatchesWildcard, _parsingTime); +} + +void FilteringAggregator::OnFunction(const A::Function& function) +{ + if (passNumber_ == 0) { + functionNamesDecorated_.push_back(function.Name()); + } + if (passNumber_ == 1) { + UpdateWildcardTime(function, &FilteringAggregator::FunctionMatchesWildcard, _generationTime); + } +} diff --git a/src/FilteredAggregator/FilteredAggregator.h b/src/FilteredAggregator/FilteredAggregator.h new file mode 100644 index 0000000..737b8f7 --- /dev/null +++ b/src/FilteredAggregator/FilteredAggregator.h @@ -0,0 +1,58 @@ +#pragma once + +#include + +#include "VcperfBuildInsights.h" + + +namespace vcperf +{ + +class FilteringAggregator : public BI::IAnalyzer { +public: + FilteringAggregator(const std::string& wildcard); + + BI::AnalysisControl OnEndAnalysisPass() override; + BI::AnalysisControl OnStopActivity(const BI::EventStack& eventStack) override; + BI::AnalysisControl OnSimpleEvent(const BI::EventStack& eventStack) override; + +private: + void OnFinishTemplateInstantiation(const A::Activity& parent, const A::TemplateInstantiationGroup& templateInstantiationGroup); + void OnSymbolName(const SE::SymbolName& symbolName); + void OnFileParse(const A::FrontEndFileGroup& files); + void OnFunction(const A::Function& function); + + bool InstantiationMatchesWildcard(const A::TemplateInstantiation& templateInstantiation) const; + bool FileParseMatchesWildcard(const A::FrontEndFile& file) const; + bool FunctionMatchesWildcard(const A::Function& function) const; + + + struct WildcardTime { + uint64_t exclusiveCpuTime_ = 0; + uint64_t exclusiveDuration_ = 0; + uint64_t subtractedCpuTime_ = 0; + uint64_t subtractedDuration_ = 0; + uint64_t inclusiveCpuTime_ = 0; + uint64_t inclusiveDuration_ = 0; + + void Print() const; + }; + template void UpdateWildcardTime(const BI::EventGroup& activityGroup, bool (FilteringAggregator::*matchFunc)(const Activity&) const, WildcardTime& totalTime) const; + template void UpdateWildcardTime(const Activity& activity, bool (FilteringAggregator::* matchFunc)(const Activity&) const, WildcardTime& totalTime) const; + + //global + int passNumber_ = 0; + std::string wildcard_; + + //pass 0: + std::vector filteredKeys_; + std::vector functionNamesDecorated_; + std::vector functionNamesUndecorated_; + + //pass 1: + WildcardTime _instantiationsTime; + WildcardTime _parsingTime; + WildcardTime _generationTime; +}; + +} diff --git a/src/FilteredAggregator/StatisticsCollector.cpp b/src/FilteredAggregator/StatisticsCollector.cpp new file mode 100644 index 0000000..4e5ed78 --- /dev/null +++ b/src/FilteredAggregator/StatisticsCollector.cpp @@ -0,0 +1,232 @@ +#include "StatisticsCollector.h" +#include +#include +#include "Undecorate.h" +#include + +using namespace Microsoft::Cpp::BuildInsights; +using namespace vcperf; + + +void StatisticsCollector::Result::operator+= (const Result& other) { + count_ += other.count_; + totalTime_ += other.totalTime_; +} + +StatisticsCollector::StatisticsCollector() +{ +} + +BI::AnalysisControl StatisticsCollector::OnEndAnalysisPass() +{ + if (passNumber_ == 0) { + std::sort(parsedFilePaths_.begin(), parsedFilePaths_.end()); + parsedFilePaths_.resize(std::unique(parsedFilePaths_.begin(), parsedFilePaths_.end()) - parsedFilePaths_.begin()); + + std::sort(symbolNames_.begin(), symbolNames_.end()); + + std::sort(functionNamesDecorated_.begin(), functionNamesDecorated_.end()); + functionNamesDecorated_.resize(std::unique(functionNamesDecorated_.begin(), functionNamesDecorated_.end()) - functionNamesDecorated_.begin()); + Undecorate(functionNamesDecorated_, functionNamesUndecorated_); + + parseTimes_.resize(parsedFilePaths_.size()); + instantiationTimes_.resize(symbolNames_.size()); + generationTimes_.resize(functionNamesUndecorated_.size()); + } + else if (passNumber_ == 1) { + + auto BuildTree = [](const std::vector& paths, const std::vector& times, std::string separator, std::map& tree) { + for (int i = 0; i < paths.size(); i++) { + const std::string& text = paths[i]; + + size_t pos = 0; + while (1) { + tree["#" + text.substr(0, pos)] += times[i]; + if (pos == text.size()) + break; + + pos = text.find(separator, pos+1); + if (pos == std::string::npos) + pos = text.size(); + } + } + }; + + auto PrintTree = [](const std::map& tree, uint64_t totalTime) { + double invTotal = 1.0 / totalTime; + + uint64_t totalTreeTime = tree.at("#").totalTime_; + printf(" Total: %10.3lf (%6.3lf%%)\n", totalTreeTime * 1e-9, totalTreeTime * invTotal * 100.0); + + struct PrintLine { + std::string path; + double totalTimeSec; + int count; + double totalTimePercent; + }; + + std::vector arr; + for (auto& kv : tree) { + PrintLine pl; + pl.path = kv.first; + pl.totalTimeSec = kv.second.totalTime_ * 1e-9; + pl.count = kv.second.count_; + pl.totalTimePercent = kv.second.totalTime_ * invTotal * 100.0; + if (pl.path.size() > 120) + pl.path = pl.path.substr(0, 117) + "..."; + arr.push_back(pl); + } + + static const double MIN_TIME_SEC = 1e-3; + static const double MIN_TIME_PERCENT = 1e-2; + + printf("\n sorted by total time:\n"); + std::sort(arr.begin(), arr.end(), [](const PrintLine& a, const PrintLine& b) { + return a.totalTimeSec > b.totalTimeSec; + }); + for (int i = 0; i < arr.size(); i++) { + if (arr[i].totalTimeSec < MIN_TIME_SEC || arr[i].totalTimePercent < MIN_TIME_PERCENT) + break; + printf(" %10.3lf | %6d (%6.3lf%%) : %s\n", arr[i].totalTimeSec, arr[i].count, arr[i].totalTimePercent, arr[i].path.c_str()); + } + + printf("\n sorted lexicographically:\n"); + std::sort(arr.begin(), arr.end(), [](const PrintLine& a, const PrintLine& b) { + return a.path < b.path; + }); + for (int i = 0; i < arr.size(); i++) { + if (arr[i].totalTimeSec < MIN_TIME_SEC || arr[i].totalTimePercent < MIN_TIME_PERCENT) + continue; + printf(" %10.3lf | %6d (%6.3lf%%) : %s\n", arr[i].totalTimeSec, arr[i].count, arr[i].totalTimePercent, arr[i].path.c_str()); + } + }; + + auto FilterTemplateArguments = [](std::string path) -> std::string { + int totalBalance = 0; + int lastBalanceZero = 0; + for (int i = 0; i < path.size(); i++) { + if (path[i] == '<') + totalBalance++; + if (path[i] == '>') + totalBalance--; + if (totalBalance == 0) + lastBalanceZero = i + 1; + } + + std::string res; + int runningBalance = 0; + for (int i = 0; i < path.size(); i++) { + if (path[i] == '<') + runningBalance++; + if (runningBalance == 0 || i >= lastBalanceZero) + res += path[i]; + if (path[i] == '>') + runningBalance--; + } + return res; + }; + + std::vector generationPaths; + for (int i = 0; i < functionNamesUndecorated_.size(); i++) + generationPaths.push_back(FilterTemplateArguments(functionNamesUndecorated_[i])); + + std::vector symbolStrNames(symbolNames_.size()); + for (int i = 0; i < symbolNames_.size(); i++) + symbolStrNames[i] = FilterTemplateArguments(symbolNames_[i].second); + + std::map parseTree_; + std::map instantiationTree_; + std::map generationTree_; + BuildTree(parsedFilePaths_, parseTimes_, "\\", parseTree_); + BuildTree(symbolStrNames, instantiationTimes_, "::", instantiationTree_); + BuildTree(generationPaths, generationTimes_, "::", generationTree_); + + uint64_t totalTime = parseTree_["#"].totalTime_ + instantiationTree_["#"].totalTime_ + generationTree_["#"].totalTime_; + printf("All reported values are exclusive CPU times.\n"); + + printf("\n\n\nFile parsing:\n"); + PrintTree(parseTree_, totalTime); + printf("\n\n\nTemplate instantiation:\n"); + PrintTree(instantiationTree_, totalTime); + printf("\n\n\nCode generation:\n"); + PrintTree(generationTree_, totalTime); + } + passNumber_++; + + return AnalysisControl::CONTINUE; +} + +AnalysisControl StatisticsCollector::OnStopActivity(const EventStack& eventStack) +{ + if (passNumber_ == 1) { + MatchEventStackInMemberFunction(eventStack, this, &StatisticsCollector::OnFinishTemplateInstantiation); + } + MatchEventStackInMemberFunction(eventStack, this, &StatisticsCollector::OnFileParse); + MatchEventStackInMemberFunction(eventStack, this, &StatisticsCollector::OnFunction); + + return AnalysisControl::CONTINUE; +} + +AnalysisControl StatisticsCollector::OnSimpleEvent(const EventStack& eventStack) +{ + if (passNumber_ == 0) { + MatchEventStackInMemberFunction(eventStack, this, &StatisticsCollector::OnSymbolName); + } + + return AnalysisControl::CONTINUE; +} + +void StatisticsCollector::OnSymbolName(const SE::SymbolName& symbolName) +{ + symbolNames_.emplace_back(symbolName.Key(), symbolName.Name()); +} + +void StatisticsCollector::OnFinishTemplateInstantiation(const A::Activity& parent, const A::TemplateInstantiationGroup& templateInstantiationGroup) +{ + uint64_t primaryKey = templateInstantiationGroup.Back().PrimaryTemplateSymbolKey(); + uint64_t spentTime = templateInstantiationGroup.Back().ExclusiveCPUTime().count(); + + std::pair key(primaryKey, ""); + size_t pos = std::lower_bound(symbolNames_.begin(), symbolNames_.end(), key) - symbolNames_.begin(); + if (pos == symbolNames_.size() || symbolNames_[pos].first != primaryKey) + return; //not even registered + + instantiationTimes_[pos].totalTime_ += spentTime; + instantiationTimes_[pos].count_++; +} + +void StatisticsCollector::OnFileParse(const A::FrontEndFileGroup& files) +{ + if (passNumber_ == 0) { + parsedFilePaths_.push_back(files.Back().Path()); + } + if (passNumber_ == 1) { + std::string path = files.Back().Path(); + uint64_t spentTime = files.Back().ExclusiveCPUTime().count(); + + size_t pos = std::lower_bound(parsedFilePaths_.begin(), parsedFilePaths_.end(), path) - parsedFilePaths_.begin(); + if (pos == parsedFilePaths_.size() || parsedFilePaths_[pos] != path) + return; //not even registered + + parseTimes_[pos].totalTime_ += spentTime; + parseTimes_[pos].count_++; + } +} + +void StatisticsCollector::OnFunction(const A::Function& function) +{ + if (passNumber_ == 0) { + functionNamesDecorated_.push_back(function.Name()); + } + if (passNumber_ == 1) { + std::string decoratedName = function.Name(); + uint64_t spentTime = function.ExclusiveCPUTime().count(); + + size_t pos = std::lower_bound(functionNamesDecorated_.begin(), functionNamesDecorated_.end(), decoratedName) - functionNamesDecorated_.begin(); + if (pos == functionNamesDecorated_.size() || functionNamesDecorated_[pos] != decoratedName) + return; //not even registered + + generationTimes_[pos].totalTime_ += spentTime; + generationTimes_[pos].count_++; + } +} diff --git a/src/FilteredAggregator/StatisticsCollector.h b/src/FilteredAggregator/StatisticsCollector.h new file mode 100644 index 0000000..494cfb8 --- /dev/null +++ b/src/FilteredAggregator/StatisticsCollector.h @@ -0,0 +1,47 @@ +#pragma once + +#include +#include + +#include "VcperfBuildInsights.h" + + +namespace vcperf +{ + +class StatisticsCollector : public BI::IAnalyzer { +public: + StatisticsCollector(); + + BI::AnalysisControl OnEndAnalysisPass() override; + BI::AnalysisControl OnStopActivity(const BI::EventStack& eventStack) override; + BI::AnalysisControl OnSimpleEvent(const BI::EventStack& eventStack) override; + +private: + void OnFinishTemplateInstantiation(const A::Activity& parent, const A::TemplateInstantiationGroup& templateInstantiationGroup); + void OnSymbolName(const SE::SymbolName& symbolName); + void OnFileParse(const A::FrontEndFileGroup& files); + void OnFunction(const A::Function& function); + + //global + int passNumber_ = 0; + + //pass 0: + std::vector> symbolNames_; + std::vector functionNamesDecorated_; + std::vector functionNamesUndecorated_; + std::vector parsedFilePaths_; + + struct Result { + int count_ = 0; + uint64_t totalTime_ = 0; //CPU time, exclusive + void operator+= (const Result& other); + }; + + //pass 1: + std::vector parseTimes_; + std::vector instantiationTimes_; + std::vector generationTimes_; +}; + +} diff --git a/src/FilteredAggregator/Undecorate.cpp b/src/FilteredAggregator/Undecorate.cpp new file mode 100644 index 0000000..243515d --- /dev/null +++ b/src/FilteredAggregator/Undecorate.cpp @@ -0,0 +1,108 @@ +#include "Undecorate.h" +#define WIN32_LEAN_AND_MEAN +#include +#include + +static bool UndecorateImpl(const std::vector& decoratedNames, std::vector& undecoratedNames) +{ + static const char* INPUT_FILENAME = "__temp_undname_input.txt"; + static const char* OUTPUT_FILENAME = "__temp_undname_output.txt"; + + FILE* f = nullptr; + if (fopen_s(&f, INPUT_FILENAME, "wb") || !f) { + printf("Failed to create temporary input file\n"); + return false; + } + for (const std::string& name : decoratedNames) { + fwrite(name.data(), 1, name.size(), f); + fputc('\n', f); + } + fclose(f); + f = nullptr; + + SECURITY_ATTRIBUTES securityAttributes = { 0 }; + securityAttributes.nLength = sizeof(SECURITY_ATTRIBUTES); + securityAttributes.bInheritHandle = TRUE; + securityAttributes.lpSecurityDescriptor = NULL; + HANDLE hFile = CreateFile( + OUTPUT_FILENAME, + GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_WRITE | FILE_SHARE_READ, + &securityAttributes, + OPEN_ALWAYS, + FILE_ATTRIBUTE_NORMAL, + 0 + ); + if (hFile == INVALID_HANDLE_VALUE) { + printf("Failed to create temporary output file\n"); + return false; + } + + char cmd[256]; + sprintf_s(cmd, "undname.exe 0x%x %s", 0x29FFF, INPUT_FILENAME); + STARTUPINFO startInfo = { 0 }; + startInfo.cb = sizeof(STARTUPINFO); + startInfo.dwFlags |= STARTF_USESTDHANDLES; + startInfo.hStdInput = INVALID_HANDLE_VALUE; + startInfo.hStdError = INVALID_HANDLE_VALUE; + startInfo.hStdOutput = hFile; + PROCESS_INFORMATION processInfo = { 0 }; + BOOL ok = CreateProcess( + NULL, cmd, NULL, NULL, + TRUE, CREATE_NO_WINDOW, + NULL, NULL, + &startInfo, &processInfo + ); + if (!ok) { + printf("Failed to run undname.exe from Visual C++ environment\n"); + CloseHandle(hFile); + return false; + } + if (WaitForSingleObject(processInfo.hProcess, INFINITE) != WAIT_OBJECT_0) { + printf("Could not wait for process completion\n"); + CloseHandle(processInfo.hProcess); + CloseHandle(hFile); + return false; + } + + CloseHandle(processInfo.hProcess); + CloseHandle(hFile); + + if (fopen_s(&f, OUTPUT_FILENAME, "rb") || !f) { + printf("Failed to read temporary output file\n"); + return false; + } + for (int i = 0; i < decoratedNames.size(); i++) { + char buffer[1 << 16]; //undecorated names can be quite lengthy =( + buffer[0] = 0; + if (!fgets(buffer, sizeof(buffer), f)) + return false; + size_t len = strlen(buffer); + if (len + 1 >= sizeof(buffer)) { + printf("Undecorated name is too long\n"); + fclose(f); + return false; + } + while (len > 0 && isspace(buffer[len - 1])) + buffer[--len] = 0; + undecoratedNames.push_back(buffer); + } + fclose(f); + f = nullptr; + + return true; +} + +bool Undecorate(const std::vector& decoratedNames, std::vector& undecoratedNames) +{ + undecoratedNames.clear(); + + bool ok = UndecorateImpl(decoratedNames, undecoratedNames); + + if (undecoratedNames.size() != decoratedNames.size()) + ok = false; + if (!ok) + undecoratedNames.assign(decoratedNames.size(), ""); + + return ok; +} diff --git a/src/FilteredAggregator/Undecorate.h b/src/FilteredAggregator/Undecorate.h new file mode 100644 index 0000000..1b49e16 --- /dev/null +++ b/src/FilteredAggregator/Undecorate.h @@ -0,0 +1,6 @@ +#pragma once + +#include +#include + +bool Undecorate(const std::vector& decoratedNames, std::vector& undecoratedNames); diff --git a/src/FilteredAggregator/Wildcard.h b/src/FilteredAggregator/Wildcard.h new file mode 100644 index 0000000..6630d26 --- /dev/null +++ b/src/FilteredAggregator/Wildcard.h @@ -0,0 +1,35 @@ +#pragma once + +bool WildcardMatch(const char* pat, const char* str) { + int i, star; + +new_segment: + + star = 0; + if (*pat == '*') { + star = 1; + do { pat++; } while (*pat == '*'); /* enddo */ + } /* endif */ + +test_match: + + for (i = 0; pat[i] && (pat[i] != '*'); i++) { + if (str[i] != pat[i]) { + if (!str[i]) return 0; + if ((pat[i] == '?') && (str[i] != '.')) continue; + if (!star) return 0; + str++; + goto test_match; + } + } + if (pat[i] == '*') { + str += i; + pat += i; + goto new_segment; + } + if (!str[i]) return 1; + if (i && pat[i - 1] == '*') return 1; + if (!star) return 0; + str++; + goto test_match; +} diff --git a/src/main.cpp b/src/main.cpp index 8b59636..f97e001 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -169,6 +169,8 @@ int wmain(int argc, wchar_t* argv[]) std::wcout << L"vcperf.exe /stopnoanalyze sessionName outputRawFile.etl" << std::endl; std::wcout << L"vcperf.exe /analyze [/templates] inputRawFile.etl output.etl" << std::endl; std::wcout << L"vcperf.exe /analyze [/templates] inputRawFile.etl /timetrace output.json" << std::endl; + std::wcout << L"vcperf.exe /sumof {wildcard} inputRawFile.etl" << std::endl; + std::wcout << L"vcperf.exe /stats inputRawFile.etl" << std::endl; std::wcout << std::endl; @@ -293,6 +295,33 @@ int wmain(int argc, wchar_t* argv[]) return DoAnalyze(inputFile, outputFile, analyzeTemplates, generateTimeTrace); } + else if (CheckCommand(argv[1], L"sumof")) + { + if (argc < 4) { + return E_FAIL; + } + std::wstring wildcard = argv[2]; + std::wstring inputFile = argv[3]; + + if (!ValidateFile(inputFile, true, L".etl")) { + return E_FAIL; + } + + return DoFilteredAggregate(inputFile, wildcard); + } + else if (CheckCommand(argv[1], L"stats")) + { + if (argc < 3) { + return E_FAIL; + } + std::wstring inputFile = argv[2]; + + if (!ValidateFile(inputFile, true, L".etl")) { + return E_FAIL; + } + + return DoCollectStatistics(inputFile); + } else { std::wcout << L"ERROR: Unknown command " << argv[1] << std::endl; diff --git a/vcperf.vcxproj b/vcperf.vcxproj index b97c256..1ef7248 100644 --- a/vcperf.vcxproj +++ b/vcperf.vcxproj @@ -180,6 +180,9 @@ + + + @@ -194,6 +197,10 @@ + + + + diff --git a/vcperf.vcxproj.filters b/vcperf.vcxproj.filters index 8c28a01..2e26284 100644 --- a/vcperf.vcxproj.filters +++ b/vcperf.vcxproj.filters @@ -37,6 +37,12 @@ {f61a812e-1836-4138-94d5-51a333458674} + + {eda37a9b-4613-4d88-963d-17845f38d077} + + + {77b5c8c0-a632-4e3c-816f-5e9606de3010} + @@ -75,6 +81,15 @@ Source Files\TimeTrace + + Source Files\FilteredAggregator + + + Source Files\FilteredAggregator + + + Source Files\FilteredAggregator + @@ -122,6 +137,18 @@ Header Files\TimeTrace + + Header Files\FilteredAggregator + + + Header Files\FilteredAggregator + + + Header Files\FilteredAggregator + + + Header Files\FilteredAggregator +