[PowerShell Script] Isolando as Threads Consumindo Alta CPU

Por Roberto Alexis Farah

Quando ajudando meus clientes em cenários cujo sintoma é alta CPU, muito frequentemente tenho apenas um arquivo de dump coletado no momento do sintoma e nada mais.

Quando você está investigando um cenário de alta CPU e você tem arquivo de dumps, também é necessário se ter um log de Performance Monitor para ver as threads consumindo alta CPU. Se você não tem logs de Performance Monitor, então você pode usar Process Explorer, Task Manager ou qualquer outra ferramenta similar.

Deixe-me explicar porque o Performance Monitor é uma ferramenta chave para problemas de alta CPU; usando Performance Monitor você pode isolar o processo consumindo alta CPU, e então selecionar o objeto Thread, usar o contador % Processor Time e localizar as threads consumindo alta CPU, e, finalmente, usar o contador Thread ID para obter o número de ID da thread usando a coluna Average.

Depois disso, você converte o valor decimal para hexadecimal, obtendo o thread id da mesma forma que o WinDbg usa.

Então você abre o arquivo de dump e analisa as threads que você isolou com o Performance Monitor.

Tudo é maravilhoso e todos ficam felizes! J

Voltando para as trincheiras; algumas vezes simplesmente não temos o log de Performance Monitor porque ele não foi coletado no tempo que o dump foi coletado, ou porque a Lei de Murphy estava presente durante seu troubleshooting e [preencha esse espaço] saiu errado.

Nesse cenário você tem muito mais trabalho técnico para descobrir as threads consumindo alta CPU.

O algoritmo para isolar threads consumindo alta CPU quando fazendo live debugging ou analisando um arquivo de dump, é:

a) Verifique se as threads tem, de fato, tempo de CPU elevado.

b) Se a) é verdadeiro, identifique se a maior parte do consume de CPU vem de Kernel mode ou User Mode. Na maioria dos casos o tempo de User Mode deverá ser mais alto que o tempo de Kernel mode.

c) Depois disso, selecione as threads consumindo tempo de User ou Kernel por mais de “n” segundos ou minutos, conforme a descrição do sintoma. Por exemplo, se o usuário reclama que a aplicação dele/dela está consumindo 100% de CPU por mais que 2 minutos, nós não vamos querer ver threads consumindo tempo de CPU por, digamos, menos que 1 minuto.

d) Investigue a pilha de execução de cada thread para entender se a thread está, de fato, trabalhando ou apenas aguardando por trabalho.

Essa é a parte mais difícil, pois você precisa estar familiarizado com API’s, e a análise de cada thread costuma ser demorada.

Aproveito aqui para colocar um lembrete: o fato de você ter threads com tempo de CPU alto não significa, necessariamente, que elas estão consumindo alta CPU!

Exemplo: Você poderia ter uma thread chamando Sleep() usando 500 milisegundos como argument. Essa thread poderia estar executando por muito tempo, mas não é o tipo de thread que estaria consumindo 100% de CPU. Quando usando Sleep(500), estamos dando um descanso para a thread e o processador.

 

Essa é a razão para esse script! O script PowerDbgScriptHighCPU faz o trabalho pesado e apresenta as threads consumindo alta CPU, para qualquer processo rodando código native ou gerenciado.

Logo que o script termina a execução ele notifica o usuário.

Então, na janela do PowerShell você terá as threads consumindo alta CPU e o que elas estão fazendo. A janela do Windbg dá a você hyperlinks que, quando clicados, dão mais detalhes sobre cada thread e módulos do processo.

Mesmo tendo logs de Performance Monitor você poderia querer usar esse script para economizar tempo.

Esse script usa o cmdlet Classify-PowerDbgThreads. Esse cmdlet é a peça central desse script. Você pode facilmente melhorar esse cmdlet e criar scripts mais específicos para diagnosticar problemas como hangs, exceções e operações específicas de componentes.

Para ver as telas e código fonte, clique aqui:

Debugging Toolbox

 

Atenção! Antes de rodar esse ou qualquer outro dos meus scripts, tenha certeza de estar usando a última versão da biblioteca PowerDbg , então faça isso:

a) Mude o título do WinDbg usando: .wtitle PowerDbg

b) Carregue a extensão SOS.dll

c) Carregue todos os símbolos: .reload

Se você não carregar os símbolos, eles serão resolvidos durante o tempo de execução do script,o que poderia causar problemas relacionados ao tempo de execução dos comandos.

Se você não carregar a extensão SOS.DLL o script não poderá usá-la.

Essa extensão não é carregada pelo script porque tem algumas particularidades que poderiam afetar diferentes usuários, embora seja possível se alterar o script e fazer isso se você quiser.

Ei, você tem o código fonte! J

Outra coisa: O valor padrão do script é 10 segundos. Você deveria usar um valor próximo do tempo quando a aplicação começou a consumir alta CPU; senão você poderá ter falsos positivos.

Por exemplo, se a aplicação está consumindo alta CPU por mais que 3 minutos, você deveria usar algo como “00:01:30”.

Como diagnosticar problemas causando alta CPU usando scripts:

a) Para alta CPU em um processo que não usa código gerenciado: apenas rode o PowerDbgScriptHighCPU.

b) Para alta CPU e uma aplicação ASP: Rode o PowerDbgScriptHighCPU e, depois de isolar as threads consumindo alta CPU, use GET_ASP_INFORMATION para ver as páginas ASP e código ASP que as threads consumindo alta CPU estão executando.

c) Para alta CPU em uma aplicação ASP.NET ou aplicação usando código gerenciado: rode GET_PERFMON caso você não tenha log de Performance Monitor para vefificar se há fatores pressionando o heap gerenciado como: tamanho da geração 2 comparado com as outras gerações, tempo consumido no Garbage Collector, exceções, etc… Depois rode PowerDbgScriptHighCPU para isolar as threads consumindo alta CPU. Se necessário use PowerDbgScriptSaveModule para extrair os módulos do método na thread consumindo alta CPU, então use .NET Reflector para decompilar o código.

Boa caçada!