Skip to content

Commit

Permalink
Example of what can be done with the vcperf code base.
Browse files Browse the repository at this point in the history
  • Loading branch information
kevcadieux committed Mar 5, 2020
1 parent 078149c commit ba2dd59
Show file tree
Hide file tree
Showing 8 changed files with 328 additions and 53 deletions.
13 changes: 11 additions & 2 deletions src/Analyzers/ContextBuilder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,15 @@ void ContextBuilder::OnInvocation(const Invocation& invocation)
const wchar_t* wTool = invocation.Type() == Invocation::Type::LINK ?
L"Link" : L"CL";

// Modify the Invocation Description string to indicate whether a linker
// was restarted. The Invocation description string is what is used to
// populate the Invocation Description column of the Build Explorer view.
const wchar_t* wRestartedLabel = L"";

if (restartedLinkerDetector_->WasLinkerRestarted(invocation)) {
wRestartedLabel = L"Restarted ";
}

std::wstring invocationIdString = std::to_wstring(invocation.InvocationId());

unsigned long long instanceId = invocation.EventInstanceId();
Expand All @@ -237,14 +246,14 @@ void ContextBuilder::OnInvocation(const Invocation& invocation)
std::wstring component = L"<" + std::wstring{wTool} + L" Invocation " + invocationIdString + L" Info>";
newContext.Component = CacheString(activeComponents_, instanceId, std::move(component));

std::wstring invocationDescription = std::wstring{wTool} + L" Invocation " + invocationIdString;
std::wstring invocationDescription = std::wstring{wRestartedLabel} + wTool + L" Invocation " + invocationIdString;
newContext.InvocationDescription = CacheString(invocationDescriptions_, instanceId, std::move(invocationDescription));
}
else
{
newContext.Component = it->second.Path.c_str();

std::wstring invocationDescription = std::wstring{wTool} + L" Invocation " + invocationIdString +
std::wstring invocationDescription = std::wstring{wRestartedLabel} + wTool + L" Invocation " + invocationIdString +
L" (" + newContext.Component + L")";
newContext.InvocationDescription = CacheString(invocationDescriptions_, instanceId, std::move(invocationDescription));
}
Expand Down
13 changes: 10 additions & 3 deletions src/Analyzers/ContextBuilder.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include "VcperfBuildInsights.h"
#include "Utility.h"
#include "PayloadBuilder.h"
#include "RestartedLinkerDetector.h"

namespace vcperf
{
Expand Down Expand Up @@ -49,7 +50,7 @@ class ContextBuilder : public BI::IAnalyzer
typedef std::unordered_map<unsigned long long, ContextLink> ContextLinkMap;

public:
ContextBuilder() :
ContextBuilder(const RestartedLinkerDetector* restartedLinkerDetector) :
analysisCount_{0},
analysisPass_{0},
timelineCount_{0},
Expand All @@ -61,7 +62,8 @@ class ContextBuilder : public BI::IAnalyzer
invocationDescriptions_{},
timelineDescriptions_{},
currentContextData_{nullptr},
currentInstanceId_{0}
currentInstanceId_{0},
restartedLinkerDetector_{restartedLinkerDetector}
{
}

Expand Down Expand Up @@ -194,6 +196,11 @@ class ContextBuilder : public BI::IAnalyzer

ContextData* currentContextData_;
unsigned long long currentInstanceId_;

// This is a pointer to a RestartedLinkerDetector analyzer located
// earlier in the analysis chain. It will be used by the ContextBuilder
// to determine whether a linker has been restarted.
const RestartedLinkerDetector* restartedLinkerDetector_;
};

} // namespace vcperf
} // namespace vcperf
128 changes: 128 additions & 0 deletions src/Analyzers/RestartedLinkerDetector.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
#pragma once

#include <unordered_set>
#include <cassert>
#include "VcperfBuildInsights.h"

namespace vcperf
{

// Linker invocations sometimes restart themselves. This can happen in the
// following cases:
//
// 1. The /LTCG switch was not used, and an object compiled with /GL was found.
// The linker is restarted with /LTCG.
// 2. A 32-bit linker runs out of memory address space. The linker is restarted
// in 64-bit.
//
// These restarts may have a non-negligible impact on throughput so we would
// like to identify them automatically. The following C++ Build Insights
// analyzer implements this functionality.
class RestartedLinkerDetector : public BI::IAnalyzer
{
public:
RestartedLinkerDetector():
pass_{0},
restartedLinkers_{}
{}

// Some analyses are done in multiple passes. The OnBeginAnalysisPass
// function comes from the IAnalyzer interface, and will be called every
// time a pass begins.
BI::AnalysisControl OnBeginAnalysisPass() override
{
// Let's keep track of the pass we are in.
++pass_;

// We return CONTINUE to pass control over to the next
// analyzer in the analysis chain.
return BI::AnalysisControl::CONTINUE;
}

// The OnStartActivity function will be called every time an activity start
// event is seen. An activity is something happening in MSVC that has a
// start and a stop time. The list of activities that generate start
// events can be found in the Microsoft::Cpp::BuildInsights::Activities
// namespace.
BI::AnalysisControl OnStartActivity(const BI::EventStack& eventStack) override
{
// This analyzer is only active in the first pass. During this pass,
// it will remember which linkers have been restarted by storing the
// information in a cache.
if (pass_ > 1) return BI::AnalysisControl::CONTINUE;

// BACKGROUND:
//
// C++ Build Insights events are related to each other in the
// form of a graph. Nodes are either 'activities' which have a start
// and stop time, or 'simple events' which are punctual. Activity nodes
// may have children nodes. Simple events are always leaves. When an
// activity has a child node, it means that the child event occurred
// while the parent was still ongoing. For example, a Linker activity
// may encapsulate the LinkerPass2 activity, which itself may
// encapsulate a LinkerLTCG activity. In this case, the graph would have
// a branch that looks like: Linker --> LinkerPass2 --> LinkerLTCG.
//
// EVENT STACKS:
//
// The 'eventStack' parameter contains the path in the graph for the
// current event. Using the example above, if the current event is the
// start of LinkerPass2, the event stack would contain:
// [Linker, LinkerPass2], where LinkerPass2 is at the top of the stack.
// You can think of the event stack as a call stack, because it tells
// you what MSVC was currently executing, as well as how it got there.
//
// IDENTIFY RELEVANT BRANCHES:
//
// When writing a C++ Build Insights analyzer, the first thing you will
// typically want to do is identify the branches in the graph that are
// relevant to your analysis. In our case, we want to identify linkers
// that are being restarted (i.e. linkers that are encapsulated by
// another linker). This can be represented by a branch of this
// form: X --> Linker --> X --> Linker, where the X's are activities we
// don't care about.
//
// MATCHING A BRANCH
//
// Once the branch of interest has been identified, the next step is to
// match the event stack against it. Here, this is done using
// MatchEventStackInMemberFunction, which looks at a member function's
// parameters to determine the branch to match. The last parameter in
// the list is always the leaf, while others can be anywhere along the
// path from the root. In this case we are matching the stack in the
// FlagRestartedLinker member function. Its parameters describe a branch
// that ends in a Linker and that has a parent Linker somewhere along
// the path. This is exactly what our analysis needs.
BI::MatchEventStackInMemberFunction(eventStack, this,
&RestartedLinkerDetector::FlagRestartedLinker);

return BI::AnalysisControl::CONTINUE;
}

void FlagRestartedLinker(const A::Linker& parent, const A::Linker& child)
{
// Flag the parent linker as having been restarted
// by inserting its instance ID into our cache.
// In a given trace, each C++ Build Insights activity
// or simple event has a unique instance ID. This ID is
// useful to correlate events between different analysis
// passes for the same trace.
restartedLinkers_.insert(parent.EventInstanceId());
}

// This function is intended to be called by other analyzers
// in the chain if they need to know whether a linker was
// restarted. Restarted linkers have already been flagged in pass 1,
// so we look into the cache to gather the information.
bool WasLinkerRestarted(const A::Invocation& linker) const
{
return restartedLinkers_.find(linker.EventInstanceId()) !=
restartedLinkers_.end();
}

private:
unsigned pass_;
std::unordered_set<unsigned long long> restartedLinkers_;
};

} // namespace vcperf
44 changes: 37 additions & 7 deletions src/Commands.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include "Analyzers\ExpensiveTemplateInstantiationCache.h"
#include "Analyzers\ContextBuilder.h"
#include "Analyzers\MiscellaneousCache.h"
#include "Analyzers\RestartedLinkerDetector.h"
#include "Views\BuildExplorerView.h"
#include "Views\FunctionsView.h"
#include "Views\FilesView.h"
Expand Down Expand Up @@ -210,15 +211,42 @@ HRESULT DoStop(const std::wstring& sessionName, const std::filesystem::path& out
{
TRACING_SESSION_STATISTICS statistics{};

// The C++ Build Insights SDK supports two types of analyses:
//
// 1. A regular analysis that simply receives each event in the trace in
// succession. The output of this analysis type is up to the author's
// discretion. This type of analysis makes use of a chain of analyzers
// that each take a turn in processing each event. Multipass analyses are
// allowed, in which the trace is passed through the analysis chain several
// times.
// 2. A relogging analysis which also receives each event in succession, but
// allows transforming and writing them back in a new ETW output trace.
// This type of analysis make use of two analyzer chains:
//
// a) A regular analyzer chain that runs before the relogging phase.
// This is useful if you want to precompute information before
// generating the output trace. Multipass analyses are allowed.
// b) A relogger chain which allows transforming and writing the
// events in a new trace. Only one pass is allowed.
//
// vcperf makes use of a relogging analysis.
//
// The RestartedLinkerDetector analyzer requires a prepass to cache
// the required information. We add it to the analyzer chain that runs
// before the relogging phase.
//
// Both the ContextBuilder and the BuildExplorerView query this analyzer
// so we pass a pointer to it in their constructors.
ExpensiveTemplateInstantiationCache etic{analyzeTemplates};
ContextBuilder cb;
RestartedLinkerDetector rld;
ContextBuilder cb{&rld};
MiscellaneousCache mc;
BuildExplorerView bev{&cb, &mc};
BuildExplorerView bev{&rld, &cb, &mc};
FunctionsView funcv{&cb, &mc};
FilesView fv{&cb, &mc};
TemplateInstantiationsView tiv{&cb, &etic, &mc, analyzeTemplates};

auto analyzerGroup = MakeStaticAnalyzerGroup(&cb, &etic, &mc);
auto analyzerGroup = MakeStaticAnalyzerGroup(&rld, &cb, &etic, &mc);
auto reloggerGroup = MakeStaticReloggerGroup(&etic, &mc, &cb, &bev, &funcv, &fv, &tiv);

std::wcout << L"Stopping and analyzing tracing session " << sessionName << L"..." << std::endl;
Expand Down Expand Up @@ -272,15 +300,17 @@ HRESULT DoStopNoAnalyze(const std::wstring& sessionName, const std::filesystem::

HRESULT DoAnalyze(const std::filesystem::path& inputFile, const std::filesystem::path& outputFile, bool analyzeTemplates)
{
// See above for an explanation of how this code works.
ExpensiveTemplateInstantiationCache etic{analyzeTemplates};
ContextBuilder cb;
RestartedLinkerDetector rld;
ContextBuilder cb{&rld};
MiscellaneousCache mc;
BuildExplorerView bev{&cb, &mc};
BuildExplorerView bev{&rld, &cb, &mc};
FunctionsView funcv{&cb, &mc};
FilesView fv{&cb, &mc};
TemplateInstantiationsView tiv{&cb, &etic, &mc, analyzeTemplates};

auto analyzerGroup = MakeStaticAnalyzerGroup(&cb, &etic, &mc);
auto analyzerGroup = MakeStaticAnalyzerGroup(&rld, &cb, &etic, &mc);
auto reloggerGroup = MakeStaticReloggerGroup(&etic, &mc, &cb, &bev, &funcv, &fv, &tiv);

std::wcout << L"Analyzing..." << std::endl;
Expand Down
Loading

0 comments on commit ba2dd59

Please sign in to comment.