Depurar o alto uso de CPU no .NET Core

Este artigo se aplica a: ✔️ SDK do .NET Core 3.1 e versões posteriores

Neste tutorial, você aprenderá a depurar um cenário de uso excessivo de CPU. Usando o repositório de código-fonte do aplicativo Web ASP.NET Core de exemplo fornecido, você pode causar um deadlock intencionalmente. O ponto de extremidade interromperá a resposta e experimentará o acúmulo de threads. Você aprenderá a usar várias ferramentas para diagnosticar esse cenário com várias partes importantes dos dados de diagnóstico.

Neste tutorial, você irá:

  • Investigar o alto uso da CPU
  • Determinar o uso da CPU com dotnet-counters
  • Usar o dotnet-trace para geração de rastreamento
  • Desempenho de perfil no PerfView
  • Diagnosticar e resolver o uso excessivo de CPU

Pré-requisitos

O tutorial usa:

Contadores de CPU

Antes de tentar coletar dados de diagnóstico, você precisa observar uma condição de CPU alta. Execute o aplicativo de exemplo usando o comando a seguir no diretório raiz do projeto.

dotnet run

Para localizar a ID do processo, use o seguinte comando:

dotnet-trace ps

Anote a ID do processo da saída do comando. Nossa ID do processo era 22884, mas a sua será diferente. Para verificar o uso atual da CPU, use o comando da ferramenta dotnet-counters:

dotnet-counters monitor --refresh-interval 1 -p 22884

refresh-interval é o número de segundos entre os valores de CPU de sondagem do contador. A saída deve ser semelhante ao seguinte:

Press p to pause, r to resume, q to quit.
    Status: Running

[System.Runtime]
    % Time in GC since last GC (%)                         0
    Allocation Rate / 1 sec (B)                            0
    CPU Usage (%)                                          0
    Exception Count / 1 sec                                0
    GC Heap Size (MB)                                      4
    Gen 0 GC Count / 60 sec                                0
    Gen 0 Size (B)                                         0
    Gen 1 GC Count / 60 sec                                0
    Gen 1 Size (B)                                         0
    Gen 2 GC Count / 60 sec                                0
    Gen 2 Size (B)                                         0
    LOH Size (B)                                           0
    Monitor Lock Contention Count / 1 sec                  0
    Number of Active Timers                                1
    Number of Assemblies Loaded                          140
    ThreadPool Completed Work Item Count / 1 sec           3
    ThreadPool Queue Length                                0
    ThreadPool Thread Count                                7
    Working Set (MB)                                      63

Com o aplicativo Web em execução, imediatamente após a inicialização, a CPU não está sendo consumida e é relatada em 0%. Navegue até a rota api/diagscenario/highcpu com 60000 como o parâmetro de rota:

https://localhost:5001/api/diagscenario/highcpu/60000

Agora, execute novamente o comando dotnet-counters. Se você quiser monitorar apenas o contador cpu-usage, adicione '--counters System.Runtime[cpu-usage]` ao comando anterior. Não temos certeza se a CPU está sendo consumida, portanto, vamos monitorar a mesma lista de contadores acima para verificar se os valores de contadores estão dentro do intervalo esperado para o aplicativo.

dotnet-counters monitor -p 22884 --refresh-interval 1

Você deve ver um aumento no uso da CPU, conforme mostrado abaixo (dependendo do computador host, o uso da CPU pode variar):

Press p to pause, r to resume, q to quit.
    Status: Running

[System.Runtime]
    % Time in GC since last GC (%)                         0
    Allocation Rate / 1 sec (B)                            0
    CPU Usage (%)                                         25
    Exception Count / 1 sec                                0
    GC Heap Size (MB)                                      4
    Gen 0 GC Count / 60 sec                                0
    Gen 0 Size (B)                                         0
    Gen 1 GC Count / 60 sec                                0
    Gen 1 Size (B)                                         0
    Gen 2 GC Count / 60 sec                                0
    Gen 2 Size (B)                                         0
    LOH Size (B)                                           0
    Monitor Lock Contention Count / 1 sec                  0
    Number of Active Timers                                1
    Number of Assemblies Loaded                          140
    ThreadPool Completed Work Item Count / 1 sec           3
    ThreadPool Queue Length                                0
    ThreadPool Thread Count                                7
    Working Set (MB)                                      63

Durante todo o período da solicitação, o uso da CPU ficará em torno do percentual aumentado.

Dica

Para visualizar um uso ainda maior da CPU, você pode executar esse ponto de extremidade em várias guias do navegador simultaneamente.

Neste ponto, você pode dizer com segurança que a CPU está com uma execução maior do que a esperada. A identificação dos efeitos de um problema é fundamental para localizar a causa. Usaremos o efeito do alto consumo de CPU além das ferramentas de diagnóstico para encontrar a causa do problema.

Analisar o alto uso de CPU com um criador de perfil

Ao analisar um aplicativo com alto uso de CPU, você precisa de uma ferramenta de diagnóstico que forneça insights sobre o que o código está fazendo. A escolha usual é um criador de perfil e há diferentes opções de criador de perfil para escolher. O dotnet-trace pode ser usado em todos os sistemas operacionais, no entanto, suas limitações de desvio de ponto seguro e as pilhas de chamadas somente gerenciadas resultam em informações mais gerais em comparação com um criador de perfil com reconhecimento de kernel, como o 'perf' para Linux ou o ETW para Windows. Se a investigação de desempenho envolver apenas o código gerenciado, geralmente dotnet-trace será suficiente.

A ferramenta perf pode ser usada para gerar perfis de aplicativo .NET Core. Demonstraremos essa ferramenta, embora o dotnet-trace também possa ser usado. Saia da instância anterior do destino de depuração de exemplo.

Defina a variável de ambiente DOTNET_PerfMapEnabled para fazer com que o aplicativo .NET crie um arquivo map no diretório /tmp. Esse arquivo map é usado para perf mapear endereços de CPU para funções geradas de modo JIT por nome. Para obter mais informações, confira Exportar mapas de desempenho e despejos JIT.

Observação

O .NET 6 usa o prefixo DOTNET_ como padrão em vez de COMPlus_ para variáveis de ambiente que configuram o comportamento de tempo de execução do .NET. No entanto, o prefixo COMPlus_ continuará funcionando. Se você estiver usando uma versão anterior do runtime do .NET, continue usando o prefixo COMPlus_ para variáveis de ambiente.

Execute o destino de depuração de exemplo na mesma sessão de terminal.

export DOTNET_PerfMapEnabled=1
dotnet run

Execute o ponto de extremidade da API com alto uso de CPU (https://localhost:5001/api/diagscenario/highcpu/60000) novamente. Durante a execução na solicitação de 1 minuto, execute o comando perf com a ID do processo:

sudo perf record -p 2266 -g

O comando perf inicia o processo de coleta de desempenho. Deixe-o em execução por cerca de 20 a 30 segundos e pressione Ctrl+C para sair do processo de coleta. Você pode usar o mesmo comando perf para ver a saída do rastreamento.

sudo perf report -f

Você também pode gerar um gráfico de chamas usando os seguintes comandos:

git clone --depth=1 https://github.com/BrendanGregg/FlameGraph
sudo perf script | FlameGraph/stackcollapse-perf.pl | FlameGraph/flamegraph.pl > flamegraph.svg

Esse comando gera um flamegraph.svg que você pode ver no navegador para investigar o problema de desempenho:

Flame graph SVG image

Como analisar dados de alto uso de CPU com o Visual Studio

Todos os arquivos *.nettrace podem ser analisados no Visual Studio. Para analisar um arquivo *.nettrace do Linux no Visual Studio, transfira o arquivo *.nettrace, além dos outros documentos necessários, para um computador Windows e abra o arquivo *.nettrace no Visual Studio. Para obter mais informações, confira Como analisar dados de alto uso de CPU).

Confira também

Próximas etapas