Visão geral da criação de perfil

Um criador de perfil é uma ferramenta que monitora a execução de outro aplicativo. Um criador de perfil do CLR (Common Language Runtime) é uma DLL (biblioteca de vínculo dinâmico) composta por funções que recebem mensagens do CLR e enviam mensagens para ele usando a API de criação de perfil. A DLL do criador de perfil é carregada pelo CLR em tempo de execução.

Ferramentas tradicionais de criação de perfil se concentram em medir a execução do aplicativo. Ou seja, eles medem o tempo gasto em cada função ou o uso de memória do aplicativo ao longo do tempo. A API de criação de perfil é voltada a uma classe mais ampla de ferramentas de diagnóstico, como utilitários de cobertura de código e até mesmo auxílios avançados de depuração. Todos esses usos são de natureza de diagnóstico. A API de criação de perfil não apenas mede, mas também monitora a execução de um aplicativo. Por esse motivo, ela nunca deve ser usada pelo próprio aplicativo, e a execução do aplicativo não deve depender do criador de perfil (nem ser afetada por ele).

A criação de perfil de um aplicativo CLR requer mais suporte do que a criação de perfil de código do computador compilado convencionalmente. Isso ocorre porque o CLR introduz conceitos como domínios de aplicativos, coleta de lixo, manipulação de exceções gerenciadas, compilação just-in-time (JIT) de código (convertendo código de linguagem intermediária comum, ou CIL, em código de máquina nativo) e recursos semelhantes. Mecanismos convencionais de criação de perfil não podem identificar nem fornecer informações úteis sobre esses recursos. A API de criação de perfil fornece essas informações ausentes com eficiência, com efeito mínimo sobre o desempenho do CLR e do aplicativo cujo perfil é criado.

A compilação JIT em tempo de execução proporciona boas oportunidades para criação de perfil. A API de criação de perfil permite que um criador de perfil altere o fluxo de código CIL na memória de uma rotina antes que ela seja compilada pelo JIT. Dessa maneira, o criador de perfil pode adicionar dinamicamente código de instrumentação a rotinas específicas que precisam de investigação mais profunda. Embora essa abordagem seja possível em cenários convencionais, é muito mais fácil implementá-la no CLR usando a API de criação de perfil.

A API de criação de perfil

Normalmente, a API de criação de perfil é usada para escrever um criador de perfil de código, que é um programa que monitora a execução de um aplicativo gerenciado.

A API de criação de perfil é usada por uma DLL do criador de perfil, que é carregada no mesmo processo que o aplicativo cujo perfil está sendo criado. A DLL do criador de perfil implementa uma interface de retorno de chamada (ICorProfilerCallback no .NET Framework versão 1.0 e 1.1, ICorProfilerCallback2 na versão 2.0 e posterior). O CLR chama os métodos nessa interface para notificar o criador de perfil de eventos no processo cujo perfil está sendo criado. O criador de perfil pode retornar a chamada no runtime usando os métodos nas interfaces ICorProfilerInfo e ICorProfilerInfo2 para obter informações sobre o estado do aplicativo cujo perfil está sendo criado.

Observação

Somente a parte de coleta de dados da solução de criador de perfil deve estar em execução no mesmo processo que o aplicativo cujo perfil está sendo criado. Toda a análise de dados e da interface do usuário deve ser executada em um processo separado.

A ilustração a seguir mostra como a DLL do criador de perfil interage com o aplicativo cujo perfil está sendo criado e com o CLR.

Captura de tela que mostra a arquitetura da criação de perfil.

As interfaces de notificação

ICorProfilerCallback e ICorProfilerCallback2 podem ser consideradas interfaces de notificação. Essas interfaces são compostas por métodos como ClassLoadStarted, ClassLoadFinished e JITCompilationStarted. Cada vez que o CLR carrega ou descarrega uma classe, compila uma função e assim por diante, ele chama o método correspondente na interface ICorProfilerCallback ou ICorProfilerCallback2 do criador de perfil.

Por exemplo, um criador de perfil pode medir o desempenho do código usando duas funções de notificação: FunctionEnter2 e FunctionLeave2. Ele apenas carimba a hora de cada notificação, acumula os resultados e gera uma lista que indica quais funções consumiram mais tempo de relógio ou CPU durante a execução do aplicativo.

Interfaces de recuperação de informações

As outras interfaces principais envolvidas na criação de perfil são ICorProfilerInfo e ICorProfilerInfo2. O criador de perfil chama essas interfaces conforme necessário para obter mais informações que ajudam na análise. Por exemplo, sempre que o CLR chama a função FunctionEnter2, ele fornece um identificador de função. O criador de perfil pode obter mais informações sobre essa função chamando o método ICorProfilerInfo2::GetFunctionInfo2 para descobrir a classe pai da função, seu nome e assim por diante.

Recursos compatíveis

A API de criação de perfil fornece informações sobre uma variedade de eventos e ações que ocorrem no Common Language Runtime. Você pode usar essas informações para monitorar o funcionamento interno dos processos e analisar o desempenho de seu aplicativo .NET Framework.

A API de criação de perfil recupera informações sobre as seguintes ações e eventos que ocorrem no CLR:

  • Eventos de inicialização e desligamento do CLR.

  • Eventos de criação e desligamento do domínio do aplicativo.

  • Eventos de carregamento e descarregamento de assembly.

  • Eventos de carregamento e descarregamento de módulo.

  • Eventos de criação e destruição de vtable COM.

  • Eventos de compilação JIT (Just-In-Time) e de lançamento de código.

  • Eventos de carregamento e descarregamento de classe.

  • Eventos de criação e destruição de thread.

  • Eventos de entrada e saída de função.

  • Exceções.

  • Transições entre a execução de código gerenciado e não gerenciado.

  • Transições entre diferentes contextos de runtime.

  • Informações sobre suspensões de runtime.

  • Informações sobre o heap de memória do runtime e atividades de coleta de lixo.

A API de criação de perfil pode ser chamada de qualquer linguagem compatível com COM (não gerenciado).

A API é eficiente em relação ao consumo de CPU e de memória. A criação de perfil não envolve alterações no aplicativo cujo perfil está sendo criado que sejam significativas o suficiente para causar resultados enganosos.

A API de criação de perfil é útil para perfis com amostragem e sem amostragem. Um criador de perfil com amostragem inspeciona o perfil em tiques de relógio regulares, digamos, a cada 5 milissegundos. Um criador de perfil sem amostragem é informado de um evento de maneira síncrona com o thread que o causa.

Funcionalidade sem suporte

A API de criação de perfil não dá suporte à seguinte funcionalidade:

  • Código não gerenciado, cujo perfil deve ser criado usando métodos convencionais do Win32. No entanto, o criador de perfil CLR inclui eventos de transição para determinar os limites entre o código gerenciado e não gerenciado.

  • Aplicativos automodificadores, que modificam o próprio código para fins como programação orientada a aspectos.

  • Verificação de limites, pois a API de criação de perfil não fornece essas informações. O CLR tem suporte intrínseco para verificação de limites de todo o código gerenciado.

  • Criação de perfil remota, que não tem suporte pelos seguintes motivos:

    • A criação de perfil remota estende o tempo de execução. Ao usar as interfaces de criação de perfil, você precisa minimizar o tempo de execução para que os resultados da criação de perfil não sejam afetados indevidamente. Isso é especialmente verdadeiro quando o desempenho da execução está sendo monitorado. No entanto, a criação de perfil remota não é uma limitação quando as interfaces de criação de perfil são usadas para monitorar o uso da memória ou para obter informações em tempo de execução sobre registros de ativação, objetos e assim por diante.

    • O criador de perfil de código CLR precisa registrar uma ou mais interfaces de retorno de chamada com o runtime no computador local no qual o aplicativo cujo perfil está sendo criado está em execução. Isso limita a capacidade de criar um criador de perfil de código remoto.

Threads de notificação

Na maioria dos casos, o thread que gera um evento também executa notificações. Essas notificações (por exemplo, FunctionEnter e FunctionLeave) não precisam fornecer o ThreadID explícito. Além disso, o criador de perfil pode decidir usar o armazenamento local de thread para armazenar e atualizar seus blocos de análise em vez de indexar os blocos de análise no armazenamento global, com base no ThreadID do thread afetado.

Observe que esses retornos de chamada não são serializados. Os usuários precisam proteger seu código criando estruturas de dados thread-safe e bloqueando o código do criador de perfil quando necessário para impedir o acesso paralelo de vários threads. Portanto, em determinados casos, você pode receber uma sequência incomum de retornos de chamada. Por exemplo, suponha que um aplicativo gerenciado esteja gerando dois threads que estão executando código idêntico. Nesse caso, é possível receber um evento ICorProfilerCallback::JITCompilationStarted para alguma função de um thread e um retorno de chamada FunctionEnter do outro thread antes de receber o retorno de chamada ICorProfilerCallback::JITCompilationFinished. Nesse caso, o usuário receberá um retorno de chamada FunctionEnter para uma função que pode não ter sido totalmente compilada JIT (Just-in-time) ainda.

Segurança

Uma DLL do criador de perfil é uma DLL não gerenciada que é executada como parte do mecanismo de execução do Common Language Runtime. Como resultado, o código na DLL do criador de perfil não está sujeito às restrições de segurança de acesso do código gerenciado. As únicas limitações na DLL do criador de perfil são aquelas impostas pelo sistema operacional ao usuário que está executando o aplicativo cujo perfil está sendo criado.

Os autores do criador de perfil devem tomar as precauções apropriadas para evitar problemas relacionados à segurança. Por exemplo, durante a instalação, uma DLL do criador de perfil deve ser adicionada a uma ACL (lista de controle de acesso) para que um usuário mal-intencionado não possa modificá-la.

Combinando código gerenciado e não gerenciado em um criador de perfil de código

Um criador de perfil escrito incorretamente pode gerar referências circulares a si mesmo, resultando em um comportamento imprevisível.

Uma revisão da API de criação de perfil CLR pode criar a impressão de que você pode escrever um criador de perfil que contém componentes gerenciados e não gerenciados que chamam uns aos outros por meio de interoperabilidade COM ou de chamadas indiretas.

Embora isso seja possível de uma perspectiva de design, a API de criação de perfil não dá suporte a componentes gerenciados. Um criador de perfil CLR deve ser completamente não gerenciado. Tentativas de combinar código gerenciado e não gerenciado em um criador de perfil CLR podem causar violações de acesso, falha no programa ou deadlocks. Os componentes gerenciados do criador de perfil dispararão eventos de volta para os componentes não gerenciados, o que posteriormente chamaria os componentes gerenciados novamente, resultando em referências circulares.

O único local onde um criador de perfil CLR pode chamar código gerenciado com segurança é no corpo de linguagem intermediária comum (CIL) de um método. A prática recomendada para modificar o corpo CIL é usar os métodos de recompilação JIT na interface ICorProfilerCallback4 .

Também é possível usar os métodos de instrumentação mais antigos para modificar o CIL. Antes que a compilação just-in-time (JIT) de uma função seja concluída, o criador de perfil pode inserir chamadas gerenciadas no corpo CIL de um método e, em seguida, compilá-lo JIT (consulte o método ICorProfilerInfo::GetILFunctionBody ). Essa técnica pode ser usada com êxito para instrumentação seletiva de código gerenciado ou para coletar estatísticas e dados de desempenho sobre o JIT.

Como alternativa, um criador de perfil de código pode inserir ganchos nativos no corpo da CIL de cada função gerenciada que chama código não gerenciado. Essa técnica pode ser usada para instrumentação e cobertura. Por exemplo, um criador de perfil de código pode inserir ganchos de instrumentação após cada bloco CIL para garantir que o bloco tenha sido executado. A modificação do corpo CIL de um método é uma operação muito delicada, e há muitos fatores que devem ser levados em consideração.

Criação de perfil de código não gerenciado

A API de criação de perfil CLR (Common Language Runtime) tem suporte mínimo para a criação de perfil de código não gerenciado. A seguinte funcionalidade é fornecida:

  • Enumeração de cadeias de pilha. Esse recurso permite que um criador de perfil de código determine o limite entre o código gerenciado e o código não gerenciado.

  • Determinar se uma cadeia de pilha corresponde ao código gerenciado ou ao código nativo.

Nas versões 1.0 e 1.1 do .NET Framework, esses métodos ficam disponíveis por meio do subconjunto em processo da API de depuração de CLR. Eles são definidos no arquivo CorDebug.idl.

No .NET Framework 2.0 e posterior, você pode usar o método ICorProfilerInfo2::DoStackSnapshot para essa funcionalidade.

Usando COM

Embora as interfaces de criação de perfil sejam definidas como interfaces COM, o CLR (Common Language Runtime) não inicializa o COM para usar essas interfaces. Isso ocorre para evitar a necessidade de definir o modelo de threading usando a função CoInitialize antes que o aplicativo gerenciado tenha a chance de especificar o modelo de threading desejado. De maneira semelhante, o próprio criador de perfil não deve chamar CoInitialize, porque ele pode escolher um modelo de threading incompatível com o aplicativo cujo perfil está sendo criado e pode causar falha no aplicativo.

Pilhas de Chamadas

A API de criação de perfil oferece duas maneiras de obter pilhas de chamadas: o método de instantâneo de pilha, que permite a coleta esparsa de pilhas de chamadas, e o método de pilha de sombra, que rastreia a pilha de chamadas a cada instante.

Instantâneo de pilha

Um instantâneo de pilha é um rastreamento da pilha de um thread em um instante no tempo. A API de criação de perfil dá suporte ao rastreamento de funções gerenciadas na pilha, mas deixa o rastreamento de funções não gerenciadas para o navegador de pilha do criador de perfil.

Para obter mais informações sobre como programar o criador de perfil para navegar pilhas gerenciadas, consulte o método ICorProfilerInfo2::DoStackSnapshot neste conjunto de documentação e Navegação de pilha do criador de perfil no .NET Framework 2.0: noções básicas e além.

Pilha de sombra

Usar o método de instantâneo com muita frequência pode criar rapidamente um problema de desempenho. Se você quiser fazer rastreamentos de pilha com frequência, o criador de perfil deverá criar uma pilha de sombra usando os retornos de chamada de exceção FunctionEnter2, FunctionLeave2, FunctionTailcall2 e ICorProfilerCallback2. A pilha de sombra sempre é atual e poderá ser copiada rapidamente para o armazenamento sempre que um instantâneo de pilha for necessário.

Uma pilha de sombra pode obter argumentos de função, valores retornados e informações sobre instanciações genéricas. Essas informações ficam disponíveis apenas por meio da pilha de sombra e podem ser obtidas quando o controle é entregue a uma função. No entanto, essas informações podem não estar disponíveis posteriormente durante a execução da função.

Retornos de chamada e profundidade da pilha

Os retornos de chamada do criador de perfil podem ser emitidos em circunstâncias muito restritas à pilha, e um estouro de pilha em um retorno de chamada do criador de perfil levará a uma saída imediata do processo. Um criador de perfil deve usar o mínimo possível da pilha em resposta a retornos de chamada. Se o criador de perfil for destinado ao uso em processos robustos contra estouro de pilha, ele também deverá evitar disparar o estouro de pilha.

Título Descrição
Configurando um ambiente de criação de perfil Explica como inicializar um criador de perfil, definir notificações de eventos e criar um perfil de um Serviço do Windows.
Criação de perfil de interfaces Descreve as interfaces não gerenciadas que a API de criação de perfil usa.
Criando perfil de funções estáticas globais Descreve as funções estáticas globais não gerenciadas que a API de criação de perfil usa.
Criando perfil de enumerações Descreve as enumerações não gerenciadas que a API de criação de perfil usa.
Estruturas de criação de perfil Descreve as estruturas não gerenciadas que a API de criação de perfil usa.