Você pode alterar o formato do arquivo para gerar perfis de speedscope ou dados brutos com o parâmetro --format
. Consulte py-spy record --help
para obter informações sobre outras opções, incluindo alteração da taxa de amostragem, filtragem para incluir apenas threads que contêm o GIL, criação de perfil de extensões C nativas, exibição de ids de thread, criação de perfil de subprocessos e muito mais.
Top mostra uma visualização ao vivo de quais funções estão demorando mais em seu programa python, semelhante ao comando top do Unix. Executando o py-spy com:
py-spy top --pid 12345
# OR
py-spy top -- python myprogram.py
exibirá uma visão de alto nível com atualização ao vivo do seu programa python:
py-spy também pode exibir a pilha de chamadas atual para cada thread python com o comando dump
:
py-spy dump --pid 12345
Isso despejará as pilhas de chamadas de cada thread e algumas outras informações básicas do processo no console:
Isso é útil no caso em que você só precisa de uma única pilha de chamadas para descobrir onde seu programa Python está travado. Este comando também tem a capacidade de imprimir as variáveis locais associadas a cada quadro de pilha, definindo o sinalizador --locals
.
Este projeto tem como objetivo permitir que você crie perfis e depure qualquer programa Python em execução, mesmo que o programa esteja atendendo ao tráfego de produção.
Embora existam muitos outros projetos de criação de perfil em Python, quase todos eles exigem a modificação do programa com perfil de alguma forma. Normalmente, o código de criação de perfil é executado dentro do processo python de destino, o que desacelera e altera a forma como o programa opera. Isso significa que geralmente não é seguro usar esses criadores de perfil para depurar problemas em serviços de produção, pois eles geralmente terão um impacto perceptível no desempenho.
py-spy funciona lendo diretamente a memória do programa python usando a chamada de sistema process_vm_readv no Linux, a chamada vm_read no OSX ou a chamada ReadProcessMemory no Windows.
Descobrir a pilha de chamadas do programa Python é feito observando a variável global PyInterpreterState para obter todos os threads Python em execução no interpretador e, em seguida, iterando cada PyFrameObject em cada thread para obter a pilha de chamadas. Como a ABI do Python muda entre as versões, usamos o bindgen do ferrugem para gerar diferentes estruturas de ferrugem para cada classe de interpretador Python de nosso interesse e usamos essas estruturas geradas para descobrir o layout da memória no programa Python.
Obter o endereço de memória do interpretador Python pode ser um pouco complicado devido à randomização do layout do espaço de endereço. Se o interpretador python de destino for fornecido com símbolos, é muito fácil descobrir o endereço de memória do interpretador desreferenciando as variáveis interp_head
ou _PyRuntime
dependendo da versão do Python. No entanto, muitas versões do Python são fornecidas com binários removidos ou sem os arquivos de símbolos PDB correspondentes no Windows. Nesses casos, examinamos a seção BSS em busca de endereços que pareçam apontar para um PyInterpreterState válido e verificamos se o layout desse endereço é o que esperamos.
Sim! py-spy suporta a criação de perfil de extensões python nativas escritas em linguagens como C/C++ ou Cython, em x86_64 Linux e Windows. Você pode ativar este modo passando --native
na linha de comando. Para obter melhores resultados, você deve compilar sua extensão Python com símbolos. Também digno de nota para os programas Cython é que o py-spy precisa do arquivo C ou C++ gerado para retornar os números das linhas do arquivo .pyx original. Leia a postagem do blog para obter mais informações.
Ao passar o sinalizador --subprocesses
para o registro ou para a visualização superior, o py-spy também incluirá a saída de qualquer processo python que seja um processo filho do programa de destino. Isso é útil para criar perfis de aplicativos que usam conjuntos de trabalhadores de multiprocessamento ou gunicorn. O py-spy monitorará novos processos sendo criados, anexará automaticamente a eles e incluirá amostras deles na saída. A visualização do registro incluirá o PID e o cmdline de cada programa na pilha de chamadas, com os subprocessos aparecendo como filhos de seus processos pais.
py-spy funciona lendo a memória de um processo python diferente e isso pode não ser permitido por motivos de segurança, dependendo do sistema operacional e das configurações do sistema. Em muitos casos, executar como usuário root (com sudo ou similar) contorna essas restrições de segurança. OSX sempre requer execução como root, mas no Linux depende de como você está iniciando o py-spy e das configurações de segurança do sistema.
No Linux, a configuração padrão é exigir permissões de root ao anexar a um processo que não é filho. Para py-spy, isso significa que você pode criar um perfil sem acesso root, fazendo com que o py-spy crie o processo ( py-spy record -- python myprogram.py
), mas anexar a um processo existente especificando um PID geralmente exigirá root ( sudo py-spy record --pid 123456
). Você pode remover essa restrição no Linux definindo a variável sysctl ptrace_scope.
py-spy tenta incluir apenas rastreamentos de pilha de threads que estão executando código ativamente e exclui threads que estão inativos ou ociosos. Quando possível, o py-spy tenta obter essas informações de atividade do thread do sistema operacional: lendo /proc/PID/stat
no Linux, usando a chamada mach thread_basic_info no OSX e verificando se o SysCall atual está ocioso. no Windows.
Existem algumas limitações com esta abordagem que podem fazer com que threads inativos ainda sejam marcados como ativos. Primeiramente, temos que obter essas informações de atividade do thread antes de pausar o programa, porque obtê-las de um programa pausado fará com que ele sempre retorne que está ocioso. Isso significa que há uma condição de corrida potencial, onde obtemos a atividade do thread e então o thread está em um estado diferente quando obtemos o rastreamento de pilha. Consultar o sistema operacional para atividade de thread também não está implementado ainda para processadores FreeBSD e i686/ARM no Linux. No Windows, as chamadas bloqueadas no IO também não serão marcadas como inativas ainda, por exemplo, ao ler a entrada do stdin. Finalmente, em algumas chamadas do Linux, o ptrace attachment que estamos usando pode fazer com que threads ociosos sejam ativados momentaneamente, causando falsos positivos ao ler procfs. Por essas razões, também temos um substituto heurístico que marca certas chamadas conhecidas em python como ociosas.
Você pode desativar essa funcionalidade definindo o sinalizador --idle
, que incluirá frames que o py-spy considera ociosos.
Obtemos a atividade GIL observando o valor threadid apontado pelo símbolo _PyThreadState_Current
para Python 3.6 e anteriores e descobrindo o equivalente da estrutura _PyRuntime
em Python 3.7 e posteriores. Esses símbolos podem não estar incluídos em sua distribuição python, o que fará com que a resolução de qual thread mantém o GIL falhe. O uso atual do GIL também é mostrado na visualização top
como% GIL.
Passar o sinalizador --gil
incluirá apenas rastreamentos para threads que estão retendo o Global Interpreter Lock. Em alguns casos, esta pode ser uma visão mais precisa de como seu programa python está gastando seu tempo, embora você deva estar ciente de que isso perderá atividade em extensões que liberam o GIL enquanto ainda estão ativas.
O OSX possui um recurso chamado System Integrity Protection que impede até mesmo o usuário root de ler a memória de qualquer binário localizado em /usr/bin. Infelizmente, isso inclui o interpretador python que acompanha o OSX.
Existem algumas maneiras diferentes de lidar com isso:
A execução do py-spy dentro de um contêiner docker também geralmente exibirá um erro de permissão negada, mesmo quando executado como root.
Este erro é causado pelo docker que restringe a chamada de sistema process_vm_readv que estamos usando. Isso pode ser substituído configurando --cap-add SYS_PTRACE
ao iniciar o contêiner do docker.
Alternativamente, você pode editar o arquivo yaml docker-compose
your_service:
cap_add:
- SYS_PTRACE
Observe que você precisará reiniciar o contêiner do docker para que essa configuração tenha efeito.
Você também pode usar o py-spy do sistema operacional host para criar o perfil de um processo em execução dentro do contêiner do docker.
py-spy precisa de SYS_PTRACE
para poder ler a memória do processo. O Kubernetes descarta esse recurso por padrão, resultando no erro
Permission Denied: Try running again with elevated permissions by going 'sudo env "PATH=$PATH" !!'
A maneira recomendada de lidar com isso é editar as especificações e adicionar esse recurso. Para uma implantação, isso é feito adicionando-o a Deployment.spec.template.spec.containers
securityContext:
capabilities:
add:
- SYS_PTRACE
Mais detalhes sobre isso aqui: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-capabilities-for-a-container Observe que isso removerá os pods existentes e os criará novamente .
Alpine python desativa as rodas manylinux
: pypa/pip#3969 (comentário). Você pode substituir esse comportamento para usar o pip para instalar o py-spy no Alpine indo:
echo 'manylinux1_compatible = True' > /usr/local/lib/python3.7/site-packages/_manylinux.py
Alternativamente, você pode baixar um binário musl na página de lançamentos do GitHub.
Ao definir a opção --nonblocking
, o py-spy não pausará o python alvo do qual você está criando o perfil. Embora o impacto no desempenho da amostragem de um processo com py-spy seja geralmente extremamente baixo, definir esta opção evitará totalmente a interrupção do programa Python em execução.
Com esta opção definida, o py-spy irá ler o estado do interpretador do processo python enquanto ele está em execução. Como as chamadas que usamos para ler a memória não são atômicas e temos que emitir várias chamadas para obter um rastreamento de pilha, isso significa que ocasionalmente obtemos erros durante a amostragem. Isso pode aparecer como uma taxa de erro aumentada durante a amostragem ou como quadros de pilha parcial incluídos na saída.
Ainda não =).
Se houver recursos que você gostaria de ver no py-spy, verifique o problema apropriado ou crie um novo que descreva quais funcionalidades estão faltando.
py-spy segue a especificação CLICOLOR, portanto, definir CLICOLOR_FORCE=1
em seu ambiente fará com que o py-spy imprima uma saída colorida mesmo quando canalizado para um pager.
py-spy é fortemente inspirado no excelente trabalho de Julia Evans no rbspy. Em particular, o código para gerar arquivos flamegraph e speedscope é obtido diretamente do rbspy, e este projeto usa as caixas read-process-memory e proc-maps que foram derivadas do rbspy.
py-spy é lançado sob a licença MIT, consulte o arquivo LICENSE para obter o texto completo.