Skip to content

Commit ba2dd59

Browse files
committed
Example of what can be done with the vcperf code base.
1 parent 078149c commit ba2dd59

File tree

8 files changed

+328
-53
lines changed

8 files changed

+328
-53
lines changed

‎src/Analyzers/ContextBuilder.cpp‎

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,15 @@ void ContextBuilder::OnInvocation(const Invocation& invocation)
226226
const wchar_t* wTool = invocation.Type() == Invocation::Type::LINK ?
227227
L"Link" : L"CL";
228228

229+
// Modify the Invocation Description string to indicate whether a linker
230+
// was restarted. The Invocation description string is what is used to
231+
// populate the Invocation Description column of the Build Explorer view.
232+
const wchar_t* wRestartedLabel = L"";
233+
234+
if (restartedLinkerDetector_->WasLinkerRestarted(invocation)) {
235+
wRestartedLabel = L"Restarted ";
236+
}
237+
229238
std::wstring invocationIdString = std::to_wstring(invocation.InvocationId());
230239

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

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

247-
std::wstring invocationDescription = std::wstring{wTool} + L" Invocation " + invocationIdString +
256+
std::wstring invocationDescription = std::wstring{wRestartedLabel} + wTool + L" Invocation " + invocationIdString +
248257
L" (" + newContext.Component + L")";
249258
newContext.InvocationDescription = CacheString(invocationDescriptions_, instanceId, std::move(invocationDescription));
250259
}

‎src/Analyzers/ContextBuilder.h‎

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include "VcperfBuildInsights.h"
1111
#include "Utility.h"
1212
#include "PayloadBuilder.h"
13+
#include "RestartedLinkerDetector.h"
1314

1415
namespace vcperf
1516
{
@@ -49,7 +50,7 @@ class ContextBuilder : public BI::IAnalyzer
4950
typedef std::unordered_map<unsigned long long, ContextLink> ContextLinkMap;
5051

5152
public:
52-
ContextBuilder() :
53+
ContextBuilder(const RestartedLinkerDetector* restartedLinkerDetector) :
5354
analysisCount_{0},
5455
analysisPass_{0},
5556
timelineCount_{0},
@@ -61,7 +62,8 @@ class ContextBuilder : public BI::IAnalyzer
6162
invocationDescriptions_{},
6263
timelineDescriptions_{},
6364
currentContextData_{nullptr},
64-
currentInstanceId_{0}
65+
currentInstanceId_{0},
66+
restartedLinkerDetector_{restartedLinkerDetector}
6567
{
6668
}
6769

@@ -194,6 +196,11 @@ class ContextBuilder : public BI::IAnalyzer
194196

195197
ContextData* currentContextData_;
196198
unsigned long long currentInstanceId_;
199+
200+
// This is a pointer to a RestartedLinkerDetector analyzer located
201+
// earlier in the analysis chain. It will be used by the ContextBuilder
202+
// to determine whether a linker has been restarted.
203+
const RestartedLinkerDetector* restartedLinkerDetector_;
197204
};
198205

199-
} // namespace vcperf
206+
} // namespace vcperf
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
#pragma once
2+
3+
#include <unordered_set>
4+
#include <cassert>
5+
#include "VcperfBuildInsights.h"
6+
7+
namespace vcperf
8+
{
9+
10+
// Linker invocations sometimes restart themselves. This can happen in the
11+
// following cases:
12+
//
13+
// 1. The /LTCG switch was not used, and an object compiled with /GL was found.
14+
// The linker is restarted with /LTCG.
15+
// 2. A 32-bit linker runs out of memory address space. The linker is restarted
16+
// in 64-bit.
17+
//
18+
// These restarts may have a non-negligible impact on throughput so we would
19+
// like to identify them automatically. The following C++ Build Insights
20+
// analyzer implements this functionality.
21+
class RestartedLinkerDetector : public BI::IAnalyzer
22+
{
23+
public:
24+
RestartedLinkerDetector():
25+
pass_{0},
26+
restartedLinkers_{}
27+
{}
28+
29+
// Some analyses are done in multiple passes. The OnBeginAnalysisPass
30+
// function comes from the IAnalyzer interface, and will be called every
31+
// time a pass begins.
32+
BI::AnalysisControl OnBeginAnalysisPass() override
33+
{
34+
// Let's keep track of the pass we are in.
35+
++pass_;
36+
37+
// We return CONTINUE to pass control over to the next
38+
// analyzer in the analysis chain.
39+
return BI::AnalysisControl::CONTINUE;
40+
}
41+
42+
// The OnStartActivity function will be called every time an activity start
43+
// event is seen. An activity is something happening in MSVC that has a
44+
// start and a stop time. The list of activities that generate start
45+
// events can be found in the Microsoft::Cpp::BuildInsights::Activities
46+
// namespace.
47+
BI::AnalysisControl OnStartActivity(const BI::EventStack& eventStack) override
48+
{
49+
// This analyzer is only active in the first pass. During this pass,
50+
// it will remember which linkers have been restarted by storing the
51+
// information in a cache.
52+
if (pass_ > 1) return BI::AnalysisControl::CONTINUE;
53+
54+
// BACKGROUND:
55+
//
56+
// C++ Build Insights events are related to each other in the
57+
// form of a graph. Nodes are either 'activities' which have a start
58+
// and stop time, or 'simple events' which are punctual. Activity nodes
59+
// may have children nodes. Simple events are always leaves. When an
60+
// activity has a child node, it means that the child event occurred
61+
// while the parent was still ongoing. For example, a Linker activity
62+
// may encapsulate the LinkerPass2 activity, which itself may
63+
// encapsulate a LinkerLTCG activity. In this case, the graph would have
64+
// a branch that looks like: Linker --> LinkerPass2 --> LinkerLTCG.
65+
//
66+
// EVENT STACKS:
67+
//
68+
// The 'eventStack' parameter contains the path in the graph for the
69+
// current event. Using the example above, if the current event is the
70+
// start of LinkerPass2, the event stack would contain:
71+
// [Linker, LinkerPass2], where LinkerPass2 is at the top of the stack.
72+
// You can think of the event stack as a call stack, because it tells
73+
// you what MSVC was currently executing, as well as how it got there.
74+
//
75+
// IDENTIFY RELEVANT BRANCHES:
76+
//
77+
// When writing a C++ Build Insights analyzer, the first thing you will
78+
// typically want to do is identify the branches in the graph that are
79+
// relevant to your analysis. In our case, we want to identify linkers
80+
// that are being restarted (i.e. linkers that are encapsulated by
81+
// another linker). This can be represented by a branch of this
82+
// form: X --> Linker --> X --> Linker, where the X's are activities we
83+
// don't care about.
84+
//
85+
// MATCHING A BRANCH
86+
//
87+
// Once the branch of interest has been identified, the next step is to
88+
// match the event stack against it. Here, this is done using
89+
// MatchEventStackInMemberFunction, which looks at a member function's
90+
// parameters to determine the branch to match. The last parameter in
91+
// the list is always the leaf, while others can be anywhere along the
92+
// path from the root. In this case we are matching the stack in the
93+
// FlagRestartedLinker member function. Its parameters describe a branch
94+
// that ends in a Linker and that has a parent Linker somewhere along
95+
// the path. This is exactly what our analysis needs.
96+
BI::MatchEventStackInMemberFunction(eventStack, this,
97+
&RestartedLinkerDetector::FlagRestartedLinker);
98+
99+
return BI::AnalysisControl::CONTINUE;
100+
}
101+
102+
void FlagRestartedLinker(const A::Linker& parent, const A::Linker& child)
103+
{
104+
// Flag the parent linker as having been restarted
105+
// by inserting its instance ID into our cache.
106+
// In a given trace, each C++ Build Insights activity
107+
// or simple event has a unique instance ID. This ID is
108+
// useful to correlate events between different analysis
109+
// passes for the same trace.
110+
restartedLinkers_.insert(parent.EventInstanceId());
111+
}
112+
113+
// This function is intended to be called by other analyzers
114+
// in the chain if they need to know whether a linker was
115+
// restarted. Restarted linkers have already been flagged in pass 1,
116+
// so we look into the cache to gather the information.
117+
bool WasLinkerRestarted(const A::Invocation& linker) const
118+
{
119+
return restartedLinkers_.find(linker.EventInstanceId()) !=
120+
restartedLinkers_.end();
121+
}
122+
123+
private:
124+
unsigned pass_;
125+
std::unordered_set<unsigned long long> restartedLinkers_;
126+
};
127+
128+
} // namespace vcperf

‎src/Commands.cpp‎

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include "Analyzers\ExpensiveTemplateInstantiationCache.h"
99
#include "Analyzers\ContextBuilder.h"
1010
#include "Analyzers\MiscellaneousCache.h"
11+
#include "Analyzers\RestartedLinkerDetector.h"
1112
#include "Views\BuildExplorerView.h"
1213
#include "Views\FunctionsView.h"
1314
#include "Views\FilesView.h"
@@ -210,15 +211,42 @@ HRESULT DoStop(const std::wstring& sessionName, const std::filesystem::path& out
210211
{
211212
TRACING_SESSION_STATISTICS statistics{};
212213

214+
// The C++ Build Insights SDK supports two types of analyses:
215+
//
216+
// 1. A regular analysis that simply receives each event in the trace in
217+
// succession. The output of this analysis type is up to the author's
218+
// discretion. This type of analysis makes use of a chain of analyzers
219+
// that each take a turn in processing each event. Multipass analyses are
220+
// allowed, in which the trace is passed through the analysis chain several
221+
// times.
222+
// 2. A relogging analysis which also receives each event in succession, but
223+
// allows transforming and writing them back in a new ETW output trace.
224+
// This type of analysis make use of two analyzer chains:
225+
//
226+
// a) A regular analyzer chain that runs before the relogging phase.
227+
// This is useful if you want to precompute information before
228+
// generating the output trace. Multipass analyses are allowed.
229+
// b) A relogger chain which allows transforming and writing the
230+
// events in a new trace. Only one pass is allowed.
231+
//
232+
// vcperf makes use of a relogging analysis.
233+
//
234+
// The RestartedLinkerDetector analyzer requires a prepass to cache
235+
// the required information. We add it to the analyzer chain that runs
236+
// before the relogging phase.
237+
//
238+
// Both the ContextBuilder and the BuildExplorerView query this analyzer
239+
// so we pass a pointer to it in their constructors.
213240
ExpensiveTemplateInstantiationCache etic{analyzeTemplates};
214-
ContextBuilder cb;
241+
RestartedLinkerDetector rld;
242+
ContextBuilder cb{&rld};
215243
MiscellaneousCache mc;
216-
BuildExplorerView bev{&cb, &mc};
244+
BuildExplorerView bev{&rld, &cb, &mc};
217245
FunctionsView funcv{&cb, &mc};
218246
FilesView fv{&cb, &mc};
219247
TemplateInstantiationsView tiv{&cb, &etic, &mc, analyzeTemplates};
220-
221-
auto analyzerGroup = MakeStaticAnalyzerGroup(&cb, &etic, &mc);
248+
249+
auto analyzerGroup = MakeStaticAnalyzerGroup(&rld, &cb, &etic, &mc);
222250
auto reloggerGroup = MakeStaticReloggerGroup(&etic, &mc, &cb, &bev, &funcv, &fv, &tiv);
223251

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

273301
HRESULT DoAnalyze(const std::filesystem::path& inputFile, const std::filesystem::path& outputFile, bool analyzeTemplates)
274302
{
303+
// See above for an explanation of how this code works.
275304
ExpensiveTemplateInstantiationCache etic{analyzeTemplates};
276-
ContextBuilder cb;
305+
RestartedLinkerDetector rld;
306+
ContextBuilder cb{&rld};
277307
MiscellaneousCache mc;
278-
BuildExplorerView bev{&cb, &mc};
308+
BuildExplorerView bev{&rld, &cb, &mc};
279309
FunctionsView funcv{&cb, &mc};
280310
FilesView fv{&cb, &mc};
281311
TemplateInstantiationsView tiv{&cb, &etic, &mc, analyzeTemplates};
282312

283-
auto analyzerGroup = MakeStaticAnalyzerGroup(&cb, &etic, &mc);
313+
auto analyzerGroup = MakeStaticAnalyzerGroup(&rld, &cb, &etic, &mc);
284314
auto reloggerGroup = MakeStaticReloggerGroup(&etic, &mc, &cb, &bev, &funcv, &fv, &tiv);
285315

286316
std::wcout << L"Analyzing..." << std::endl;

0 commit comments

Comments
 (0)