Tiago Tartari

Conteúdo

Como identificar problemas de performance em .NET com dotnet-trace e dotnet-counters?

Conheça ferramentas poderosas como dotnet-trace e o dotnet-counters para identificar problemas de performance em aplicações .NET. Ao desenvolvermos aplicações em .NET, identificamos com frequência a necessidade de otimizar sua performance. São comuns questionamentos como: “Minha aplicação está consumindo muita memória. O que está acontecendo?”, “Por que minha aplicação utiliza muita CPU?” e “Minha aplicação está lenta, demorando muito para responder e frequentemente as requisições se acumulam.”. Esses questionamentos são claros indicativos de que há problemas subjacentes na aplicação.

Para entender melhor esta situação, pensemos em um hemograma, o exame de sangue amplamente utilizado para avaliar a saúde geral de um paciente. Quando o médico solicita um hemograma, está em busca de indicadores que revelem desequilíbrios ou doenças. Ele não espera que o exame forneça todas as respostas, mas sim pistas sobre onde investigar mais profundamente.

Da mesma forma, no universo .NET, temos ferramentas e técnicas que atuam como esse hemograma, identificando os sintomas de um problema. O dotnet-trace, por exemplo, pode ser comparado a este exame preliminar. Ele fornece uma visão geral da saúde da aplicação, mostrando áreas onde a performance, um atributo de qualidade, não está sendo atendida, como alto consumo de CPU ou sobrecarga de memória. No entanto, assim como o hemograma, ele pode apenas indicar que algo está errado, e não necessariamente dizer exatamente o que é.

Agora, quando encontramos anomalias no hemograma, é preciso um exame mais detalhado. No mundo .NET, isso se traduz no uso de técnicas mais avançadas e ferramentas especializadas. É aqui que técnicas como o Continuous Profiling entram, especialmente quando combinadas com ferramentas como o Pyroscope. Elas permitem uma análise mais aprofundada, identificando a causa raiz dos problemas de performance.

O que é o dotnet-trace e o dotnet-counters?

O dotnet-trace é uma ferramenta de rastreamento. Atuando como um profiler de baixo impacto, ele permite que os desenvolvedores coletem informações detalhadas sobre a execução de suas aplicações .NET. Se você já se perguntou sobre a sequência de eventos que ocorrem em seu aplicativo ou precisou investigar um gargalo de desempenho inesperado, é aqui que o dotnet-trace pode te ajudar. Utilizando o Event Tracing for Windows (ETW) e o LTTng no Linux, ele é capaz de capturar uma gama de eventos sem que seja necessário modificar o código da aplicação. Uma vez coletados, esses dados podem ser analisados por meio de ferramentas especializadas, lançando luz sobre as intricadas operações de seu aplicativo.

Por outro lado, temos o dotnet-counters, uma ferramenta focada no monitoramento de desempenho em tempo real. Enquanto o dotnet-trace capta uma imagem detalhada da execução do aplicativo, o dotnet-counters funciona como um termômetro, fornecendo leituras instantâneas de métricas vitais. Quer saber sobre o uso de memória atual, a taxa de coleta de lixo ou outros contadores de desempenho? O dotnet-counters pode lhe fornecer essas informações em tempo real, permitindo um entendimento imediato da saúde e do desempenho de sua aplicação. Com ele, é possível não apenas observar os contadores predefinidos, mas também criar e monitorar seus próprios, adaptando-se às necessidades específicas de cada aplicativo.

Como instalar o dotnet-trace e o dotnet-counters?

Há duas maneiras simples de instalar o dotnet-trace e o dotnet-counters. Uma por pacote Nuget e a outra pelo comando dotnet tool install.

Ambas as ferramentas são parte integrante do conjunto de diagnóstico do .NET. Enquanto o dotnet-trace é ideal para coletar rastreamentos detalhados do aplicativo, o dotnet-counters permite monitorar métricas de desempenho em tempo real, fornecendo insights instantâneos sobre o comportamento do aplicativo.

Como utilizar o dotnet-counters como ferramenta para hipóteses de problemas?

Primeiramente você deve localizar o ID do processo da sua aplicação. Para isso execute o comando abaixo.

Em seguida com o ID do processo execute o comando utilizando o dotnet-counters. Utilizaremos em nosso caso, o processo 9232.

A partir dos resultados do dotnet-counters, podemos começar a construir a hipótese sobre o possível memory leak na aplicação. Vamos analisar os pontos-chave:

  1. Allocation Rate (B / 1 sec) – 1.6529e+09:
    Este valor indica a taxa na qual a memória está sendo alocada. Uma alocação de aproximadamente 1.65 GB por segundo é extremamente alta. Isso sugere que há um uso excessivo de memória em algum lugar no código.
  2. GC Heap Size (MB) – 33.544:
    Esta métrica nos mostra o tamanho do heap gerenciado. Se este valor estiver crescendo continuamente durante a execução do programa, é um forte indicativo de um vazamento de memória.
  3. Gen 0, 1 e 2 GC Counts:
    Estes contadores indicam quantas coletas de lixo ocorreram para cada geração desde o início da aplicação. Valores altos, especialmente nas gerações 0 e 1, são comuns em aplicações com alto throughput. Entretanto, um valor alto para a Gen 2, como Gen 2 GC Count - 50, pode ser preocupante, pois as coletas de lixo da Gen 2 são mais raras e indicam que objetos mais antigos – aqueles que sobreviveram às coletas de lixo das gerações 0 e 1 – ainda estão ocupando memória.
  4. LOH Size (B) – 33,231,976:
    O LOH (Large Object Heap) armazena objetos grandes que requerem uma quantidade significativa de memória. Um aumento contínuo deste valor também pode indicar um vazamento de memória, especialmente se você souber que seu código não deve estar alocando muitos objetos grandes.
  5. Working Set (MB) – 58.962:
    Este é o conjunto de páginas na memória virtual que foram recentemente referenciadas. Um aumento constante neste valor pode indicar um crescimento no uso de memória.

Hipótese: Dada a alta taxa de alocação de memória, o crescimento contínuo do heap gerenciado, a frequente coleta de lixo da Gen 2 e o tamanho significativo do LOH, é altamente provável que exista um vazamento de memória na aplicação. A alocação excessiva de strings, como observado no código fornecido anteriormente, pode ser a principal causadora deste comportamento.

IMPORTANTE: O código abaixo é um exemplo que simula um vazamento de memória, portanto foi utilizado para fins teóricos.

Compreendendo os contadores de performance

  1. % Time in GC since last GC (%) – 3%:
    Representa a porcentagem de tempo gasto na coleta de lixo desde a última coleta. Se este valor for consistentemente alto, pode indicar que o GC está trabalhando arduamente, o que pode afetar a performance da aplicação.
    Hipótese: Uma alta porcentagem poderia sugerir alocação excessiva ou ineficiências na gerência de memória.
  2. Allocation Rate (B / 1 sec) – 1.6529e+09:
    Taxa na qual a memória está sendo alocada.
    Hipótese: Um valor alto sugere uso excessivo de memória ou alocações frequentes, como a criação constante de novos objetos.
  3. CPU Usage (%) – 4.6%:
    Representa o uso de CPU pela aplicação.
    Hipótese: Um aumento no uso da CPU pode indicar processamento intenso ou loops infinitos no código.
  4. Exception Count (Count / 1 sec) – 0:
    Número de exceções lançadas por segundo.
    Hipótese: Um número alto pode indicar problemas no código que levam a falhas frequentes.
  5. GC Committed Bytes (MB) – 38.625:
    Quantidade de memória comprometida pelo GC.
    Hipótese: Aumento contínuo pode sugerir vazamento de memória ou uso excessivo de memória.
  6. GC Fragmentation (%) – 4.719:
    Porcentagem de memória que é considerada fragmentada.
    Hipótese: Valores elevados podem indicar que muitos objetos estão sendo alocados e descartados, levando a fragmentação.
  7. GC Heap Size (MB) – 33.544:
    Tamanho do heap gerenciado.
    Hipótese: Crescimento constante indica possível vazamento de memória.
  8. Gen 0, 1, e 2 GC Counts e Sizes:
    Indica quantas coletas de lixo ocorreram e o tamanho dos heaps para cada geração.
    Hipótese: Contagens altas, especialmente na Gen 2, sugerem vazamento de memória ou retenção de objetos.
  9. IL Bytes Jitted (B) – 42,938:
    Quantidade de IL (Intermediate Language) que foi just-in-time compilado.
    Hipótese: Se este número crescer rapidamente, pode indicar que há muita compilação JIT ocorrendo, o que pode afetar a performance.
  10. LOH Size (B) – 33,231,976:
    Tamanho do Large Object Heap.
    Hipótese: Crescimento constante indica possível vazamento de memória ou alocação excessiva de grandes objetos.
  11. Monitor Lock Contention Count (Count / 1 sec) – 0:
    Contador de disputas por bloqueios (locks) de monitor.
    Hipótese: Valores altos podem indicar problemas de concorrência ou threads esperando frequentemente por bloqueios.
  12. Number of Active Timers – 0:
    Número de timers ativos na aplicação.
    Hipótese: Um aumento súbito pode indicar que timers estão sendo criados, mas não descartados adequadamente.
  13. Number of Assemblies Loaded – 17:
    Quantidade de assemblies carregados.
    Hipótese: Crescimento constante pode indicar carregamento dinâmico de assemblies ou plugins.
  14. Number of Methods Jitted – 305:
    Quantidade de métodos JIT compilados.
    Hipótese: Um aumento constante pode indicar que há muita compilação JIT ocorrendo.
  15. POH (Pinned Object Heap) Size (B) – 39,952:
    Tamanho do heap de objetos fixados (pinned).
    Hipótese: Um aumento súbito pode indicar objetos sendo fixados na memória, o que pode levar a problemas de gerência de memória.
  16. ThreadPool Metrics (Completed Work Item Count, Queue Length, Thread Count):
    Métricas relacionadas ao pool de threads.
    Hipótese: Alterações nestes contadores podem indicar problemas de concorrência ou utilização inadequada do pool de threads.
  17. Working Set (MB) – 58.962:
    Conjunto de páginas na memória virtual que foram recentemente referenciadas.
    Hipótese: Aumento contínuo indica crescimento no uso de memória.

Lembrando que todas essas hipóteses são sugestões iniciais baseadas nos contadores e devem ser investigadas apropriadamente para confirmar ou refutar a existência de um problema.

Conclusão

Garantir a performance otimizada de aplicações .NET é uma necessidade incontestável. Ferramentas como dotnet-trace e dotnet-counters não apenas facilitam a detecção de problemas, mas também dão caminhos para soluções eficazes. Ao se familiarizar com essas ferramentas e aplicá-las adequadamente, desenvolvedores podem elevar significativamente a qualidade e a eficiência de suas aplicações. Contudo, é essencial entender que a otimização é um processo contínuo, exigindo monitoramento regular e atualizações constantes.

FAQ: Perguntas Frequentes

1. O que é dotnet-trace?

O dotnet-trace é uma ferramenta de rastreamento global para aplicações .NET. Ela ajuda a coletar informações de diagnóstico de aplicativos em execução, sem a necessidade de uma interrupção ou impacto significativo na performance.

2. Como dotnet-counters pode ajudar a melhorar a performance de uma aplicação?

Dotnet-counters monitora métricas de desempenho em tempo real, permitindo que desenvolvedores identifiquem pontos de estrangulamento ou ineficiências no código. Ao saber onde os problemas estão, fica mais fácil otimizar a aplicação.

3. Existe impacto na performance ao usar essas ferramentas de diagnóstico?

Enquanto essas ferramentas são projetadas para ter o mínimo impacto possível, sempre há uma pequena sobrecarga ao coletar dados de diagnóstico. No entanto, os benefícios em termos de insights de otimização geralmente superam a pequena sobrecarga.

4. Como iniciar a coleta de dados com dotnet-trace?

Geralmente, você inicia a coleta usando o comando “dotnet-trace collect”, seguido dos parâmetros apropriados para sua sessão de rastreamento.

5. Os dados coletados por essas ferramentas são seguros?

Enquanto dotnet-trace e dotnet-counters coletam dados de diagnóstico, eles não foram projetados para capturar informações sensíveis. No entanto, é sempre uma boa prática revisar os dados coletados e garantir que nenhuma informação confidencial seja inadvertidamente registrada.

6. Existem alternativas para dotnet-trace e dotnet-counters?

Sim, existem outras ferramentas de diagnóstico e monitoramento disponíveis para .NET, como PerfView e Visual Studio Profiler. A escolha da ferramenta depende das necessidades específicas do projeto e das preferências do desenvolvedor.

7. Essas ferramentas são compatíveis com todas as versões do .NET?

Dotnet-trace e dotnet-counters foram introduzidos no .NET Core 3.0. Eles também são compatíveis com .NET 5, .NET 6, .NET 7 e .NET 8.

Compartilhe:

Tiago Tartari

Tiago Tartari

Eu ajudo e capacito pessoas e organizações a transformar problemas complexos em soluções práticas usando a tecnologia para atingir resultados extraordinários.

Qual é o desafio
que você tem hoje?