SDK do C++ Build Insights
O SDK do C++ Build Insights é compatível com o Visual Studio 2017 e posterior. Para ver a documentação dessas versões, defina o controle seletor de Versão do Visual Studio deste artigo para o Visual Studio 2017 ou posterior. Ele é encontrado na parte superior da tabela de conteúdo nesta página.
O SDK do C++ Build Insights é uma coleção de APIs que permite criar ferramentas personalizadas baseadas na plataforma C++ Build Insights. Esta página fornece uma visão geral de alto nível para ajudar você a começar.
Obter o SDK
Você pode baixar o SDK do C++ Build Insights como um pacote do NuGet seguindo estas etapas:
- No Visual Studio 2017 e versões superiores, crie um projeto do C++.
- No painel do Gerenciador de Soluções, clique com o botão direito do mouse em seu projeto.
- Selecione Gerenciar Pacotes do NuGet no menu de contexto.
- No canto superior direito, selecione o provedor de origem do pacote nuget.org.
- Pesquise a versão mais recente do pacote Microsoft.Cpp.BuildInsights.
- Escolha Instalar.
- Aceite a licença.
Leia mais informações sobre os conceitos gerais relativos ao SDK. Você também pode acessar o repositório oficial do GitHub de exemplos do C++ Build Insights para ver exemplos de aplicativos C++ reais que usam o SDK.
Coletando um rastreamento
Para usar o SDK do C++ Build Insights para analisar eventos que saem da cadeia de ferramentas do MSVC você precisa primeiro coletar um rastreamento. O SDK usa o ETW (Rastreamento de Eventos para Windows) como tecnologia de rastreamento subjacente. A coleta de um rastreamento pode ser feita de duas maneiras:
Método 1: usando vcperf no Visual Studio 2019 e versões superiores
Abra um Prompt de Comando de Ferramentas Nativas do x64 com privilégios elevados para o VS 2019.
Execute o seguinte comando:
vcperf /start MySessionName
Compile o projeto.
Execute o seguinte comando:
vcperf /stopnoanalyze MySessionName outputTraceFile.etl
Importante
Use o comando
/stopnoanalyze
ao interromper o rastreamento com o vcperf. Você não pode usar o SDK do C++ Build Insights para analisar rastreamentos interrompidos pelo comando regular/stop
.
Método 2: programaticamente
Use qualquer uma dessas funções de coleta de rastreamento do SDK do Build Insights do C++ para iniciar e parar rastreamentos programaticamente. O programa que executa essas chamadas de função deve ter privilégios administrativos. Somente as funções de iniciar e parar o rastreamento exigem privilégios administrativos. Todas as outras funções no SDK do C++ Build Insights podem ser executadas sem esses privilégios.
Funções do SDK relacionadas à coleta de rastreamento
Funcionalidade | API de C++ | API do C |
---|---|---|
Iniciar um rastreamento | StartTracingSession | StartTracingSessionA StartTracingSessionW |
Interromper um rastreamento | StopTracingSession | StopTracingSessionA StopTracingSessionW |
Parar um rastreamento e analisar imediatamente o resultado |
StopAndAnalyzeTracingSession | StopAndAnalyzeTracingSessionA StopAndAnalyzeTracingSession |
Parar um rastreamento e recriar imediatamente o registro do resultado |
StopAndRelogTracingSession | StopAndRelogTracingSessionA StopAndRelogTracingSessionW |
As seções a seguir mostram como configurar uma sessão de análise ou de recriação de registro. Ela é necessária para as funções com recursos combinados, como StopAndAnalyzeTracingSession.
Consumir um rastreamento
Após obter um rastreamento do ETW, use o SDK do C++ Build Insights para desempacotá-lo. O SDK fornece os eventos em um formato que permite que você desenvolva ferramentas rapidamente. Não recomendamos que você consuma o rastreamento do ETW bruto, sem usar o SDK. O formato de evento usado pelo MSVC não está documentado, é otimizado para escala de builds enormes e é difícil de entender. Além disso, a API do SDK do C++ Build Insights é estável, enquanto que o formato de rastreamento do ETW bruto está sujeito a alterações sem aviso prévio.
Tipos e funções do SDK relacionados ao consumo de rastreamento
Funcionalidade | API de C++ | API do C | Observações |
---|---|---|---|
Configurar retornos de chamada de evento | IAnalyzer IRelogger |
ANALYSIS_CALLBACKS RELOG_CALLBACKS |
O SDK do C++ Build Insights fornece eventos por meio de funções de retorno de chamada. No C++, você implementa as funções de retorno de chamada criando uma classe de analisador ou de criação de novo registro que herda a interface IAnalyzer ou IRelogger. No C, você implementa os retornos de chamada em funções globais e fornece ponteiros para elas na estrutura ANALYSIS_CALLBACKS ou RELOG_CALLBACKS. |
Criar grupos | MakeStaticAnalyzerGroup MakeStaticReloggerGroup MakeDynamicAnalyzerGroup MakeDynamicReloggerGroup |
A API do C++ fornece funções e tipos auxiliares para agrupar vários objetos de analisador e criação de novo registro. Os grupos são formas organizadas de dividir uma análise complexa em etapas mais simples. O vcperf é organizado dessa forma. | |
Analisar ou recriar o registro | Analisar Relog |
AnalisarA AnalisarW RelogA RelogW |
Analisar e recriar o registro
O consumo de um rastreamento é feito por meio de uma sessão de análise ou de uma sessão de recriação do registro do rastreamento.
O uso de uma análise regular é apropriado para a maioria dos cenários. Esse método oferece a flexibilidade para escolher o formato de saída: texto printf
, xml, JSON, banco de dados, chamadas REST e assim por diante.
A recriação do registro é útil para análises com finalidades especiais, que precisam produzir um arquivo de saída do ETW. Ao usar a recriação do registro do rastreamento, você consegue converter os eventos do C++ Build Insights em seu próprio formato de evento do ETW. Um uso adequado da recriação de registro seria conectar dados do C++ Build Insights às ferramentas e à infraestrutura existentes do ETW. Por exemplo, o vcperf usa as interfaces de recriação de registro. Isso porque ele deve produzir dados que o Windows Performance Analyzer, uma ferramenta de ETW, possa entender. Alguns conhecimentos prévios de como o ETW funciona são necessários se você planeja usar as interfaces de recriação de registro.
Criação de grupos de analisadores
É importante saber como criar grupos. Veja um exemplo que mostra como criar um grupo de analisadores que imprime Olá, mundo! para cada evento de início de atividade que ele recebe.
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;
}
Usar eventos
Tipos e funções de SDK relacionados a eventos
Atividades e eventos simples
Os eventos vêm em duas categorias: atividades e eventos simples. As atividades são processos contínuos no tempo que têm um começo e um fim. Os eventos simples são ocorrências pontuais que não têm uma duração. Ao analisar rastreamentos do MSVC com o SDK do C++ Build Insights, você receberá eventos separados quando uma atividade for iniciada e interrompida. Você receberá apenas um evento quando ocorrer um evento simples.
Relações pai-filho
Atividades e eventos simples estão relacionados entre si por meio de relações pai-filho. O pai de uma atividade ou um evento simples é a atividade abrangente na qual eles ocorrem. Por exemplo, ao compilar um arquivo de origem, o compilador precisa analisar o arquivo e, em seguida, gerar o código. As atividades de análise e geração de código são filhos da atividade do compilador.
Eventos simples não têm uma duração, portanto, nada mais pode acontecer dentro deles. Como tal, eles nunca têm filhos.
As relações pai-filho de cada atividade e evento simples são indicadas na tabela de eventos. Conhecer essas relações é importante ao consumir eventos do C++ Build Insights. Muitas vezes, você precisará se apoiar nessas informações para entender o contexto completo de um evento.
Propriedades
Todos os eventos têm as seguintes propriedades:
Propriedade | Descrição |
---|---|
Identificador de tipo | Um número que identifica exclusivamente o tipo de evento. |
Identificador da instância | Um número que identifica exclusivamente o evento no rastreamento. Se dois eventos do mesmo tipo ocorrerem em um rastreamento, ambos obterão um identificador de instância exclusivo. |
Hora de início | A hora em que uma atividade foi iniciada ou a hora em que ocorreu um evento simples. |
Identificador de processo | Um número que identifica o processo no qual o evento ocorreu. |
Identificador de thread | Um número que identifica o thread no qual o evento ocorreu. |
Índice do processador | Um índice baseado em zero que indica por qual processador lógico o evento foi emitido. |
Nome do evento | Uma cadeia de caracteres que descreve o tipo do evento. |
Todas as atividades que não sejam eventos simples também têm estas propriedades:
Propriedade | Descrição |
---|---|
Hora de término | A hora em que a atividade parou. |
Duração exclusiva | O tempo gasto em uma atividade, excluindo o tempo gasto nas respectivas atividades filho. |
Tempo de CPU | O tempo que a CPU gastou executando código no thread anexado à atividade. Este tempo não inclui a hora em que o thread anexado à atividade estava em suspensão. |
Tempo exclusivo da CPU | O mesmo que o tempo de CPU, mas excluindo o tempo de CPU gasto pelas atividades filho. |
Responsabilidade do tempo do relógio | A contribuição da atividade para o tempo geral do relógio. A responsabilidade de tempo do relógio leva em conta o paralelismo entre as atividades. Por exemplo, vamos supor que duas atividades não relacionadas são executadas em paralelo. Ambos têm uma duração de 10 segundos e exatamente o mesmo tempo de início e parada. Nesse caso, o Build Insights atribui uma responsabilidade de tempo de relógio de 5 segundos. Por outro lado, se essas atividades forem executadas uma após a outra sem sobreposição, ambas receberão uma responsabilidade de tempo de relógio de 10 segundos. |
Responsabilidade do tempo do relógio exclusiva | O mesmo que a responsabilidade de tempo do relógio, mas exclui a responsabilidade de tempo do relógio das atividades filho. |
Alguns eventos têm propriedades específicas além das mencionadas. Nesse caso, essas propriedades adicionais são listadas na tabela de eventos.
Consumir eventos fornecidos pelo SDK do C++ Build Insights
A pilha de eventos
Sempre que o SDK do C++ Build Insights lhe entrega um evento, ele vem na forma de uma pilha. A última entrada na pilha é o evento atual e as entradas antes dela são a hierarquia pai. Por exemplo, eventos de início e parada da LTCG ocorrem durante a passagem 1 do vinculador. Nesse caso, a pilha que você recebe conteria: [LINKER, PASS1, LTCG]. A hierarquia pai é conveniente porque você pode rastrear um evento até sua raiz. Se a atividade da LTCG mencionada acima estiver lenta, você poderá saber imediatamente qual invocação do vinculador estava envolvida.
Eventos e pilhas de eventos correspondentes
O SDK do C++ Build Insights lhe fornece todos os eventos em um rastreamento, mas na maioria das vezes você se interessa apenas com um subconjunto deles. Em alguns casos, você pode se interessar apenas por um subconjunto de pilhas de eventos. O SDK fornece meios para ajudá-lo a extrair rapidamente os eventos ou a pilha de eventos de que você precisa e rejeitar as que você não precisa. Isso é feito por meio dessas funções de correspondência:
Função | Descrição |
---|---|
MatchEvent | Manter um evento se ele corresponder a um dos tipos especificados. Encaminhar eventos correspondentes a um lambda ou a outro tipo que possa ser chamado. A hierarquia pai do evento não é considerada por essa função. |
MatchEventInMemberFunction | Manter um evento se ele corresponder ao tipo especificado no parâmetro de uma função membro. Encaminhar eventos correspondentes à função membro. A hierarquia pai do evento não é considerada por essa função. |
MatchEventStack | Manter um evento se o evento e sua hierarquia pai corresponderem aos tipos especificados. Encaminhar o evento e os eventos da hierarquia pai correspondentes para um lambda ou a outro tipo que possa ser chamado. |
MatchEventStackInMemberFunction | Manter um evento se o evento e sua hierarquia pai corresponderem aos tipos especificados na lista de parâmetros de uma função membro. Encaminhar o evento e os eventos da hierarquia pai correspondentes para a função membro. |
As funções correspondentes à pilha de eventos, como MatchEventStack
, permitem lacunas ao descrever a hierarquia pai para correspondência. Por exemplo, você pode dizer que está interessado na pilha [LINKER, LTCG]. Isso também corresponderia à pilha [LINKER, PASS1, LTCG]. O último tipo especificado deve ser o tipo de evento correspondente e não fazer parte da hierarquia pai.
Classes de captura
O uso das funções Match*
requer que você especifique os tipos que deseja obter correspondência. Esses tipos são selecionados em uma lista de classes de captura. As classes de captura vêm em várias categorias descritas abaixo.
Categoria | Descrição |
---|---|
Exato | Essas classes de captura são usadas para correspondência com um tipo de evento específico e nenhum outro. Um exemplo é a classe Compiler, que corresponde ao evento COMPILER. |
Curinga | Essas classes de captura podem ser usadas para correspondência com qualquer evento da lista de eventos a que dão suporte. Por exemplo, o curinga Activity corresponde a qualquer evento de atividade. Outro exemplo é o curinga CompilerPass, que pode corresponder ao evento FRONT_END_PASS ou BACK_END_PASS. |
Grupo | Os nomes das classes de captura de grupo terminam em Group. Elas são usadas para correspondência com vários eventos do mesmo tipo em uma linha, ignorando lacunas. As classes de captura de grupo só fazem sentido ao combinar eventos recursivos, porque você não sabe quantos existem na pilha de eventos. Por exemplo, a atividade FRONT_END_FILE acontece sempre que o compilador analisa um arquivo. Essa atividade é recursiva porque o compilador pode encontrar uma diretiva de inclusão ao analisar o arquivo. A classe FrontEndFile corresponde a apenas um evento FRONT_END_FILE na pilha. Use a classe FrontEndFileGroup para fazer correspondência com toda a hierarquia de inclusão. |
Grupo curinga | Um grupo curinga combina as propriedades de curingas e grupos. A única classe dessa categoria é InvocationGroup, que obtém a correspondência e captura todos os eventos LINKER e COMPILER em uma única pilha de eventos. |
Consulte a tabela de eventos para saber quais classes de captura podem ser usadas para correspondência com cada evento.
Após a correspondência: como usar eventos capturados
Depois que a correspondência for concluída com êxito, as funções Match*
construirão os objetos da classe de captura e os encaminharão para a função especificada. Use esses objetos de classe de captura para acessar as propriedades dos eventos.
Exemplo
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;
}
}