共用方式為


C++ 組建見解 SDK

C++ Build Insights SDK 與 Visual Studio 2017 和更新版本相容。 若要查看這些版本的檔,請將本文的 Visual Studio 版本 選取器控制項設定為 Visual Studio 2017 或更新版本。 其位於此頁面目錄頂端。

C++ Build Insights SDK 是 API 的集合,可讓您在 C++ Build Insights 平臺之上建立個人化工具。 此頁面提供高階概觀,可協助您開始使用。

取得 SDK

您可以遵循下列步驟,將 C++ Build Insights SDK 下載為 NuGet 套件:

  1. 從 Visual Studio 2017 和更新版本建立新的 C++ 專案。
  2. [方案總管] 窗格中,以滑鼠右鍵按一下您的專案。
  3. 從操作功能表中選取 [管理 NuGet 套件 ]。
  4. 在右上方,選取 nuget.org 套件來源。
  5. 搜尋最新版的 Microsoft.Cpp.BuildInsights 套件。
  6. 選擇 [ 安裝 ]。
  7. 接受授權。

如需 SDK 周圍一般概念的相關資訊,請閱讀 。 您也可以存取官方 的 C++ Build Insights 範例 GitHub 存放庫 ,以查看使用 SDK 的實際 C++ 應用程式範例。

收集追蹤

使用 C++ Build Insights SDK 來分析 MSVC 工具鏈中傳出的事件時,必須先收集追蹤。 SDK 會使用 Windows 事件追蹤(ETW)作為基礎追蹤技術。 收集追蹤的方式有兩種:

方法 1:在 Visual Studio 2019 和更新版本中使用 vcperf

  1. 開啟 VS 2019 提升許可權的 x64 Native Tools 命令提示字元。

  2. 執行下列命令:vcperf /start MySessionName

  3. 建立您的專案。

  4. 執行下列命令:vcperf /stopnoanalyze MySessionName outputTraceFile.etl

    重要

    使用 /stopnoanalyze vcperf 停止追蹤時使用 命令。 您無法使用 C++ Build Insights SDK 來分析由一般 /stop 命令停止的追蹤。

方法 2:以程式設計方式

使用這些 C++ Build Insights SDK 追蹤集合函式,以程式設計方式啟動和停止追蹤。 執行這些函式呼叫的程式必須具有系統管理許可權。 只有啟動和停止追蹤功能需要系統管理許可權。 C++ Build Insights SDK 中的所有其他函式都可以執行,而不需要它們。

功能 C++ API C API
啟動追蹤 StartTracingSession StartTracingSessionA
StartTracingSessionW
停止追蹤 StopTracingSession StopTracingSessionA
StopTracingSessionW
停止追蹤和
立即分析結果
StopAndAnalyzeTracingSession StopAndAnalyzeTracingSessionA
StopAndAnalyzeTracingSession
停止追蹤和
立即重新記錄結果
StopAndRelogTracingSession StopAndRelogTracingSessionA
StopAndRelogTracingSessionW

後續各節會示範如何設定分析或重新記錄會話。 合併的功能函式需要此功能,例如 StopAndAnalyzeTracingSession

取用追蹤

取得 ETW 追蹤之後,請使用 C++ Build Insights SDK 將其解壓縮。 SDK 提供事件格式,可讓您快速開發工具。 我們不建議您不使用 SDK 來取用原始 ETW 追蹤。 MSVC 所使用的事件格式未經記載、已優化以調整為大型組建,且難以理解。 此外,C++ Build Insights SDK API 是穩定的,而未經注意的原始 ETW 追蹤格式可能會變更。

功能 C++ API C API 備註
設定事件回呼 IAnalyzer
IRelogger
ANALYSIS_CALLBACKS
RELOG_CALLBACKS
C++ Build Insights SDK 會透過回呼函式提供事件。 在 C++ 中,建立繼承 IAnalyzer 或 IRelogger 介面的分析器或重新記錄器類別,以實作回呼函式。 在 C 中,在全域函式中實作回呼,並在ANALYSIS_CALLBACKS或RELOG_CALLBACKS結構中提供它們的指標。
建置群組 MakeStaticAnalyzerGroup
MakeStaticReloggerGroup
MakeDynamicAnalyzerGroup
MakeDynamicReloggerGroup
C++ API 提供協助程式函式和類型,以將多個分析器和重新記錄器物件分組在一起。 群組是將複雜分析分割成更簡單步驟的整齊方式。 vcperf 會以這種方式組織。
分析或重新記錄 分析
重新記錄
AnalyzeA
AnalyzeW
RelogA
RelogW

分析和重新記錄

取用追蹤是透過分析會話或重新記錄會話來完成。

使用一般分析適用于大部分案例。 此方法可讓您彈性地選擇輸出格式: printf text、xml、JSON、database、REST 呼叫等等。

重新記錄適用于需要產生 ETW 輸出檔案的特殊用途分析。 使用重新記錄,您可以將 C++ Build Insights 事件轉譯成您自己的 ETW 事件格式。 適當的重新記錄用途是將 C++ Build Insights 資料連結至現有的 ETW 工具和基礎結構。 例如, vcperf 會使用重新記錄介面。 這是因為它必須產生 Windows 效能分析器,ETW 工具可以理解的資料。 如果您打算使用重新記錄介面,則需要一些先前知道 ETW 的運作方式。

建立分析器群組

請務必瞭解如何建立群組。 以下範例示範如何建立分析器群組,針對它收到的每個活動啟動事件,列印 Hello, world!

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;
}

使用事件

功能 C++ API C API 備註
比對和篩選事件 MatchEventStackInMemberFunction
MatchEventStack
MatchEventInMemberFunction
MatchEvent
C++ API 提供函式,可讓您輕鬆地從追蹤擷取您關心的事件。 使用 C API 時,必須手動完成此篩選。
事件資料類型 Activity
BackEndPass
BottomUp
C1DLL
C2DLL
CodeGeneration
CommandLine
編譯 器
CompilerPass
EnvironmentVariable
事件
EventGroup
EventStack
ExecutableImageOutput
ExpOutput
FileInput
FileOutput
ForceInlinee
FrontEndFile
FrontEndFileGroup
FrontEndPass
Function
HeaderUnit
ImpLibOutput
調用
InvocationGroup
LibOutput
連結器
LinkerGroup
LinkerPass
LTCG
模組
ObjOutput
OptICF
OptLBR
OptRef
Pass1
Pass2
PrecompiledHeader
PreLTCGOptRef
SimpleEvent
SymbolName
TemplateInstantiation
TemplateInstantiationGroup
Thread
TopDown
TraceInfo
TranslationUnitType
神秘leProgramAnalysis
CL_PASS_DATA
EVENT_COLLECTION_DATA
EVENT_DATA
EVENT_ID
FILE_DATA
FILE_TYPE_CODE
FRONT_END_FILE_DATA
FUNCTION_DATA
FUNCTION_FORCE_INLINEE_DATA
INVOCATION_DATA
INVOCATION_VERSION_DATA
MSVC_TOOL_CODE
NAME_VALUE_PAIR_DATA
SYMBOL_NAME_DATA
TEMPLATE_INSTANTIATION_DATA
TEMPLATE_INSTANTIATION_KIND_CODE
TRACE_INFO_DATA
TRANSLATION_UNIT_PASS_CODE
TRANSLATION_UNIT_TYPE
TRANSLATION_UNIT_TYPE_DATA

活動和簡單事件

事件分為兩種類別: 活動和 簡單事件 。 活動是有開始和結束的時間進行中的程式。 簡單事件是標點專案,而且沒有持續時間。 使用 C++ Build Insights SDK 分析 MSVC 追蹤時,您會在活動啟動和停止時收到個別的事件。 當發生簡單事件時,您只會收到一個事件。

父子關聯性

活動和簡單事件會透過父子關聯性彼此關聯。 活動或簡單事件的父代是活動所在的內含活動。 例如,編譯來源檔案時,編譯器必須剖析檔案,然後產生程式碼。 剖析和程式碼產生活動都是編譯器活動的子系。

簡單的事件沒有持續時間,因此它們內不會發生任何其他事件。 因此,他們從來沒有任何孩子。

每個活動和簡單事件的父子關聯性都會在事件資料表 指出。 在取用 C++ Build Insights 事件時,瞭解這些關聯性非常重要。 您通常必須依賴它們來瞭解事件的完整內容。

屬性

所有事件都有下列屬性:

屬性 說明
類型識別碼 可唯一識別事件種類的數位。
實例識別碼 可唯一識別追蹤內事件的數位。 如果追蹤中發生兩個相同類型的事件,兩者都會取得唯一的實例識別碼。
開始時間 活動開始的時間,或發生簡單事件的時間。
進程識別碼 數位,識別發生事件的進程。
執行緒識別碼 識別發生事件之執行緒的數位。
處理器索引 以零起始的索引,指出事件發出的邏輯處理器。
事件名稱 描述事件種類的字串。

簡單事件以外的所有活動也有下列屬性:

屬性 說明
停止時間 活動停止的時間。
獨佔持續時間 活動所花費的時間,不包括其子活動所花費的時間。
CPU 時間 CPU 在附加至活動的執行緒中執行程式碼所花費的時間。 它不包含附加至活動之執行緒睡眠的時間。
獨佔 CPU 時間 與 CPU 時間相同,但不包括子活動花費的 CPU 時間。
時鐘時間責任 活動對整體時鐘時間的貢獻。 時鐘時間責任會考慮活動之間的平行處理原則。 例如,假設兩個不相關的活動會平行執行。 兩者都有 10 秒的持續時間,而且完全相同的開始和停止時間。 在此情況下,Build Insights 會指派 5 秒的時鐘時間責任。 相反地,如果這些活動執行一個接一個且沒有重迭,則兩者都會被指派 10 秒的時鐘時間責任。
專屬的時鐘時間責任 與時鐘時間責任相同,但排除兒童活動的時鐘時間責任。

有些事件除了提及的事件之外,有自己的屬性。 在此情況下,這些額外的屬性會列在事件資料表

取用 C++ Build Insights SDK 所提供的事件

事件堆疊

每當 C++ Build Insights SDK 提供事件時,就會以堆疊的形式出現。 堆疊中的最後一個專案是目前事件,以及其父階層之前的專案。 例如, LTCG 啟動和停止事件會在連結器傳遞 1 期間發生。 在此情況下,您收到的堆疊包含:[ LINKER PASS1 、LTCG]。 父階層很方便,因為您可以將事件追蹤到其根目錄。 如果上述 LTCG 活動緩慢,您可以立即瞭解涉及哪個連結器調用。

比對事件和事件堆疊

C++ Build Insights SDK 會提供追蹤中的每個事件,但大部分時候您只關心其中的子集。 在某些情況下,您可能只關心事件堆疊 子集。 SDK 提供可協助您快速擷取所需的事件或事件堆疊,並拒絕您不需要的事件或事件堆疊。 其可透過下列比對函式來完成:

函式 描述
MatchEvent 如果事件符合其中一個指定的類型,請保留事件。 將相符的事件轉送至 Lambda 或其他可呼叫類型。 此函式不會考慮事件的父階層。
MatchEventInMemberFunction 如果事件符合成員函式參數中指定的類型,請保留事件。 將相符的事件轉送至成員函式。 此函式不會考慮事件的父階層。
MatchEventStack 如果事件及其父階層都符合指定的類型,請保留事件。 將事件和相符的父階層事件轉送至 Lambda 或其他可呼叫類型。
MatchEventStackInMemberFunction 如果事件及其父階層都符合成員函式參數清單中指定的類型,請保留事件。 將事件和相符的父階層事件轉送至成員函式。

事件堆疊比對函式,例如 MatchEventStack 在描述要比對的父階層時允許間距。 例如,您可以假設您有興趣 [ LINKER LTCG ] 堆疊。 它也會比對 [LINKER, PASS1 , LTCG] 堆疊。 指定的最後一個類型必須是符合的事件種類,而且不是父階層的一部分。

擷取類別

使用 函 Match* 式需要您指定您想要比對的類型。 從擷取類別 清單中 選取這些類型。 擷取類別有數個類別,如下所述。

類別 描述
精確 這些擷取類別可用來比對特定事件種類,而沒有其他類別。 例如,編譯器 類別符合 COMPILER 事件。
通 配 符 這些擷取類別可用來比對所支援事件清單中的任何事件。 例如, 活動 萬用字元符合任何活動事件。 另一個範例是 CompilerPass 萬用字元,它可以比對 FRONT_END_PASS BACK_END_PASS 事件。
群組 群組擷取類別的名稱結尾為 Group 。 它們用來比對資料列中相同類型的多個事件,忽略間距。 它們只有在比對遞迴事件時才有意義,因為您不知道事件堆疊中有多少存在。 例如, 每次編譯器剖析檔案時,就會發生FRONT_END_FILE 活動。 此活動是遞迴的,因為編譯器在剖析檔案時可能會找到 include 指示詞。 FrontEndFile 類別只會比對堆疊中的一個FRONT_END_FILE事件。 使用 FrontEndFileGroup 類別來比對整個 Include 階層。
萬用字元群組 萬用字元群組會結合萬用字元和群組的屬性。 此類別的唯一類別是 InvocationGroup ,它會比對並擷取單一事件堆疊中的所有 LINKER COMPILER 事件。

請參閱事件資料表 ,以瞭解哪些擷取類別可用來比對每個事件。

比對之後:使用擷取的事件

比對成功完成之後,函式會 Match* 建構擷取類別物件,並將其轉送至指定的函式。 使用這些擷取類別物件來存取事件的屬性。

範例

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;
    }
}