C++ Build Insights SDK
The C++ Build Insights SDK is compatible with Visual Studio 2017 and later. To see the documentation for these versions, set the Visual Studio Version selector control for this article to Visual Studio 2017 or later. It's found at the top of the table of contents on this page.
The C++ Build Insights SDK is a collection of APIs that allow you to create personalized tools on top of the C++ Build Insights platform. This page provides a high-level overview to help you get started.
Obtaining the SDK
You can download the C++ Build Insights SDK as a NuGet package by following these steps:
- From Visual Studio 2017 and above, create a new C++ project.
- In the Solution Explorer pane, right-click on your project.
- Select Manage NuGet Packages from the context menu.
- At the top right, select the nuget.org package source.
- Search for the latest version of the Microsoft.Cpp.BuildInsights package.
- Choose Install.
- Accept the license.
Read on for information about the general concepts surrounding the SDK. You can also access the official C++ Build Insights samples GitHub repository to see examples of real C++ applications that use the SDK.
Collecting a trace
Using the C++ Build Insights SDK to analyze events coming out of the MSVC toolchain requires that you first collect a trace. The SDK makes use of Event Tracing for Windows (ETW) as the underlying tracing technology. Collecting a trace can be done in two ways:
Method 1: using vcperf in Visual Studio 2019 and above
Open an elevated x64 Native Tools Command Prompt for VS 2019.
Run the following command:
vcperf /start MySessionName
Build your project.
Run the following command:
vcperf /stopnoanalyze MySessionName outputTraceFile.etl
Important
Use the
/stopnoanalyze
command when stopping your trace with vcperf. You can't use the C++ Build Insights SDK to analyze traces stopped by the regular/stop
command.
Method 2: programmatically
Use any of these C++ Build Insights SDK trace collection functions to start and stop traces programmatically. The program that executes these function calls must have administrative privileges. Only the start and stop tracing functions require administrative privileges. All other functions in the C++ Build Insights SDK can be executed without them.
SDK functions related to trace collection
Functionality | C++ API | C API |
---|---|---|
Starting a trace | StartTracingSession | StartTracingSessionA StartTracingSessionW |
Stopping a trace | StopTracingSession | StopTracingSessionA StopTracingSessionW |
Stopping a trace and immediately analyzing the result |
StopAndAnalyzeTracingSession | StopAndAnalyzeTracingSessionA StopAndAnalyzeTracingSession |
Stopping a trace and immediately relogging the result |
StopAndRelogTracingSession | StopAndRelogTracingSessionA StopAndRelogTracingSessionW |
The sections that follow show you how to configure an analysis or a relogging session. It's required for the combined functionality functions, such as StopAndAnalyzeTracingSession.
Consuming a trace
Once you have an ETW trace, use the C++ Build Insights SDK to unpack it. The SDK gives you the events in a format that allows you to develop your tools quickly. We don't recommend you consume the raw ETW trace without using the SDK. The event format used by MSVC is undocumented, optimized to scale to huge builds, and hard to make sense of. Additionally, the C++ Build Insights SDK API is stable, while the raw ETW trace format is subject to change without notice.
SDK types and functions related to trace consumption
Functionality | C++ API | C API | Notes |
---|---|---|---|
Setting up event callbacks | IAnalyzer IRelogger |
ANALYSIS_CALLBACKS RELOG_CALLBACKS |
The C++ Build Insights SDK provides events through callback functions. In C++, implement the callback functions by creating an analyzer or relogger class that inherits the IAnalyzer or IRelogger interface. In C, implement the callbacks in global functions and provide pointers to them in the ANALYSIS_CALLBACKS or RELOG_CALLBACKS structure. |
Building groups | MakeStaticAnalyzerGroup MakeStaticReloggerGroup MakeDynamicAnalyzerGroup MakeDynamicReloggerGroup |
The C++ API provides helper functions and types to group multiple analyzer and relogger objects together. Groups are a neat way to divide a complex analysis into simpler steps. vcperf is organized in this way. | |
Analyzing or relogging | Analyze Relog |
AnalyzeA AnalyzeW RelogA RelogW |
Analyzing and relogging
Consuming a trace is done through either an analysis session or a relogging session.
Using a regular analysis is appropriate for most scenarios. This method gives you the flexibility to choose your output format: printf
text, xml, JSON, database, REST calls, and so on.
Relogging is for special-purpose analyses that need to produce an ETW output file. Using relogging, you can translate the C++ Build Insights events into your own ETW event format. An appropriate use of relogging would be to hook C++ Build Insights data to your existing ETW tools and infrastructure. For example, vcperf makes use of the relogging interfaces. That's because it must produce data the Windows Performance Analyzer, an ETW tool, can understand. Some prior knowledge of how ETW works is required if you plan on using the relogging interfaces.
Creating analyzer groups
It's important to know how to create groups. Here's an example that shows how to create an analyzer group that prints Hello, world! for every activity start event it receives.
using namespace Microsoft::Cpp::BuildInsights;
class Hello : public IAnalyzer
{
public:
AnalysisControl OnStartActivity(
const EventStack& eventStack) override
{
std::cout << "Hello, " << std::endl;
return AnalysisControl::CONTINUE;
}
};
class World : public IAnalyzer
{
public:
AnalysisControl OnStartActivity(
const EventStack& eventStack) override
{
std::cout << "world!" << std::endl;
return AnalysisControl::CONTINUE;
}
};
int main()
{
Hello hello;
World world;
// Let's make Hello the first analyzer in the group
// so that it receives events and prints "Hello, "
// first.
auto group = MakeStaticAnalyzerGroup(&hello, &world);
unsigned numberOfAnalysisPasses = 1;
// Calling this function initiates the analysis and
// forwards all events from "inputTrace.etl" to my analyzer
// group.
Analyze("inputTrace.etl", numberOfAnalysisPasses, group);
return 0;
}
Using events
SDK types and functions related to events
Activities and simple events
Events come in two categories: activities and simple events. Activities are ongoing processes in time that have a beginning and an end. Simple events are punctual occurrences and don't have a duration. When analyzing MSVC traces with the C++ Build Insights SDK, you'll receive separate events when an activity starts and stops. You'll receive only one event when a simple event occurs.
Parent-child relationships
Activities and simple events are related to each other via parent-child relationships. The parent of an activity or simple event is the encompassing activity in which they occur. For example, when compiling a source file the compiler has to parse the file, then generate the code. The parsing and code generation activities are both children of the compiler activity.
Simple events don't have a duration, so nothing else can happen inside them. As such, they never have any children.
The parent-child relationships of each activity and simple event are indicated in the event table. Knowing these relationships is important when consuming C++ Build Insights events. You'll often have to rely on them to understand the full context of an event.
Properties
All events have the following properties:
Property | Description |
---|---|
Type identifier | A number that uniquely identifies the event type. |
Instance identifier | A number that uniquely identifies the event within the trace. If two events of the same type occur in a trace, both get a unique instance identifier. |
Start time | The time when an activity started, or the time when a simple event occurred. |
Process identifier | A number that identifies the process in which the event occurred. |
Thread identifier | A number that identifies the thread in which the event occurred. |
Processor index | A zero-based index indicating which logical processor the event was emitted by. |
Event name | A string that describes the event type. |
All activities other than simple events also have these properties:
Property | Description |
---|---|
Stop time | The time when the activity stopped. |
Exclusive duration | The time spent in an activity, excluding the time spent in its child activities. |
CPU time | The time that the CPU spent executing code in the thread attached to the activity. It doesn't include time when the thread attached to the activity was sleeping. |
Exclusive CPU time | Same as CPU time, but excluding the CPU time spent by child activities. |
Wall-clock time responsibility | The activity's contribution to overall wall-clock time. Wall-clock time responsibility takes into account parallelism between activities. For example, let's assume two unrelated activities run in parallel. Both have a duration of 10 seconds, and exactly the same start and stop time. In this case, Build Insights assigns both a wall-clock time responsibility of 5 seconds. In contrast, if these activities run one after the other with no overlap, they're both assigned a wall-clock time responsibility of 10 seconds. |
Exclusive wall-clock time responsibility | Same as wall-clock time responsibility, but excludes the wall-clock time responsibility of child activities. |
Some events have their own properties beyond the ones mentioned. In this case, these additional properties are listed in the event table.
Consuming events provided by the C++ Build Insights SDK
The event stack
Whenever the C++ Build Insights SDK gives you an event, it comes in the form of a stack. The last entry in the stack is the current event, and entries before it are its parent hierarchy. For example, LTCG start and stop events occur during pass 1 of the linker. In this case, the stack you'd receive contains: [LINKER, PASS1, LTCG]. The parent hierarchy is convenient because you can trace back an event to its root. If the LTCG activity mentioned above is slow, you can immediately learn which linker invocation was involved.
Matching events and event stacks
The C++ Build Insights SDK gives you every event in a trace, but most of the time you only care about a subset of them. In some cases, you may only care about a subset of event stacks. The SDK provides facilities to help you quickly extract the events or event stack you need, and reject the ones you don't. It's done through these matching functions:
Function | Description |
---|---|
MatchEvent | Keep an event if it matches one of the specified types. Forward matched events to a lambda or other callable type. The event's parent hierarchy isn't considered by this function. |
MatchEventInMemberFunction | Keep an event if it matches the type specified in a member function's parameter. Forward matched events to the member function. The event's parent hierarchy isn't considered by this function. |
MatchEventStack | Keep an event if both the event and its parent hierarchy match the types specified. Forward the event and the matched parent hierarchy events to a lambda or other callable type. |
MatchEventStackInMemberFunction | Keep an event if both the event and its parent hierarchy match the types specified in a member function's parameter list. Forward the event and the matched parent hierarchy events to the member function. |
The event stack matching functions like MatchEventStack
allow gaps when describing the parent hierarchy to match. For example, you can say you're interested in the [LINKER, LTCG] stack. It would also match the [LINKER, PASS1, LTCG] stack. The last type specified must be the event type to match, and isn't part of the parent hierarchy.
Capture classes
Using the Match*
functions requires that you specify the types you want to match. These types are selected from a list of capture classes. Capture classes come in several categories, described below.
Category | Description |
---|---|
Exact | These capture classes are used to match a specific event type and none other. An example is the Compiler class, which matches the COMPILER event. |
Wildcard | These capture classes can be used to match any event from the list of events they support. For example, the Activity wildcard matches any activity event. Another example is the CompilerPass wildcard, which can match either the FRONT_END_PASS or the BACK_END_PASS event. |
Group | The names of group capture classes end in Group. They're used to match multiple events of the same type in a row, ignoring gaps. They only make sense when matching recursive events, because you don't know how many exist in the event stack. For example, the FRONT_END_FILE activity happens every time the compiler parses a file. This activity is recursive because the compiler may find an include directive while it's parsing the file. The FrontEndFile class matches only one FRONT_END_FILE event in the stack. Use the FrontEndFileGroup class to match the entire include hierarchy. |
Wildcard group | A wildcard group combines the properties of wildcards and groups. The only class of this category is InvocationGroup, which match and capture all LINKER and COMPILER events in a single event stack. |
Refer to the event table to learn which capture classes can be used to match each event.
After matching: using captured events
Once a match completes successfully, the Match*
functions construct the capture class objects and forward them to the specified function. Use these capture class objects to access the events' properties.
Example
AnalysisControl MyAnalyzer::OnStartActivity(const EventStack& eventStack)
{
// The event types to match are specified in the PrintIncludes function
// signature.
MatchEventStackInMemberFunction(eventStack, this, &MyAnalyzer::PrintIncludes);
}
// We want to capture event stacks where:
// 1. The current event is a FrontEndFile activity.
// 2. The current FrontEndFile activity has at least one parent FrontEndFile activity
// and possibly many.
void PrintIncludes(FrontEndFileGroup parentIncludes, FrontEndFile currentFile)
{
// Once we reach this point, the event stack we are interested in has been matched.
// The current FrontEndFile activity has been captured into 'currentFile', and
// its entire inclusion hierarchy has been captured in 'parentIncludes'.
cout << "The current file being parsed is: " << currentFile.Path() << endl;
cout << "This file was reached through the following inclusions:" << endl;
for (auto& f : parentIncludes)
{
cout << f.Path() << endl;
}
}