Mostrando o rastreamento de pilha de um aplicativo Python em execução


340

Eu tenho esse aplicativo Python que fica preso de tempos em tempos e não consigo descobrir onde.

Existe alguma maneira de sinalizar para o intérprete Python mostrar o código exato que está sendo executado?

Algum tipo de rastreamento de pilha on-the-fly?

Perguntas relacionadas:



Respostas:


315

Eu tenho um módulo que eu uso para situações como esta - em que um processo fica em execução por um longo tempo, mas às vezes fica preso por razões desconhecidas e improdutivas. É um pouco hacky, e só funciona em unix (requer sinais):

import code, traceback, signal

def debug(sig, frame):
    """Interrupt running process, and provide a python prompt for
    interactive debugging."""
    d={'_frame':frame}         # Allow access to frame object.
    d.update(frame.f_globals)  # Unless shadowed by global
    d.update(frame.f_locals)

    i = code.InteractiveConsole(d)
    message  = "Signal received : entering python shell.\nTraceback:\n"
    message += ''.join(traceback.format_stack(frame))
    i.interact(message)

def listen():
    signal.signal(signal.SIGUSR1, debug)  # Register handler

Para usar, basta chamar a função listen () em algum momento quando o programa iniciar (você pode até colocá-lo no site.py para que todos os programas python o usem) e deixá-lo executar. A qualquer momento, envie ao processo um sinal SIGUSR1, usando kill ou em python:

    os.kill(pid, signal.SIGUSR1)

Isso fará com que o programa seja interrompido em um console python no momento em que está, mostrando o rastreamento da pilha e permitindo que você manipule as variáveis. Use control-d (EOF) para continuar executando (observe que você provavelmente interromperá qualquer E / S etc no momento em que sinalizar, para que não seja totalmente não intrusivo.

Eu tenho outro script que faz a mesma coisa, exceto que ele se comunica com o processo em execução através de um pipe (para permitir a depuração de processos em segundo plano, etc.). É um pouco grande para postar aqui, mas eu o adicionei como uma receita de livro de receitas em python .


11
Obrigado! Isto é exatamente o que eu estava procurando. Talvez você também possa postar esse script com suporte a pipe em algum site de trechos de Python?
245/09 Seb

2
Agora eu o publiquei no site do livro de receitas python - link adicionado.
Brian

11
Eu precisava adicionar "importar linha de leitura" para ativar os recursos do histórico.
Miracle2k

2
Ótima dica! Isso também funciona para enviar o sinal, a todos os processos contendo a palavra "mypythonapp": pkill -SIGUSR1 -f mypythonapp
Alexander

10
Se o aplicativo estiver bloqueado, o loop do interpretador Python pode não ser capaz de executar para processar o sinal. Use o faulthandlermódulo (e seu backport encontrado no PyPI) para um manipulador de sinal de nível C que imprima a pilha Python sem exigir que o loop do interpretador seja responsivo.
gps

146

A sugestão para instalar um manipulador de sinal é boa, e eu o uso muito. Por exemplo, o bzr , por padrão, instala um manipulador SIGQUIT que chama pdb.set_trace()para soltá-lo imediatamente em um prompt pdb . (Veja a fonte do módulo bzrlib.breakin para obter detalhes exatos.) Com o pdb, você pode não apenas obter o rastreamento da pilha atual, mas também inspecionar variáveis, etc.

No entanto, às vezes eu preciso depurar um processo que não tinha a previsão para instalar o manipulador de sinal. No linux, você pode anexar o gdb ao processo e obter um rastreamento de pilha python com algumas macros do gdb. Coloque http://svn.python.org/projects/python/trunk/Misc/gdbinit em e ~/.gdbinit, em seguida:

  • Anexar gdb: gdb -p PID
  • Obtenha o rastreamento da pilha python: pystack

Infelizmente, não é totalmente confiável, mas funciona na maioria das vezes.

Finalmente, anexar stracegeralmente pode lhe dar uma boa idéia do que um processo está fazendo.


2
Brilhante! O comando pystack trava às vezes, mas antes disso, ele fornece um rastreamento completo da pilha do processo, em linhas de código python, sem precisar fazer nenhuma preparação.
muudscope 5/05

26
Atualização secundária : esta técnica gdb (e código atualizado) está documentada em wiki.python.org/moin/DebuggingWithGdb Houve algum desenvolvimento nesta frente, documentado nessa URL, e aparentemente o gdb 7 tem suporte para Python.
Nelson

7
Até onde eu sei, isso realmente funciona se você tiver símbolos de depuração compilados no seu binário python - por exemplo: você executou seu programa com python2-dbg (no Ubuntu, isso está em um pacote separado python-dbg). Sem esses símbolos, você não parece obter muitas informações úteis.
21413 drevicko

11
no meu caso esse retorno Unable to locate python framepara cada comando
seriyPS

6
O suporte ao gdb 7+ --with-python é fornecido pelo python-gdb.py. Mais detalhes aqui: chezsoi.org/lucas/blog/2014/11/07/en-gdb-python-macros
Lucas Cimon

71

Quase sempre estou lidando com vários threads e o thread principal geralmente não está fazendo muito, então o mais interessante é despejar todas as pilhas (o que é mais parecido com o despejo do Java). Aqui está uma implementação baseada neste blog :

import threading, sys, traceback

def dumpstacks(signal, frame):
    id2name = dict([(th.ident, th.name) for th in threading.enumerate()])
    code = []
    for threadId, stack in sys._current_frames().items():
        code.append("\n# Thread: %s(%d)" % (id2name.get(threadId,""), threadId))
        for filename, lineno, name, line in traceback.extract_stack(stack):
            code.append('File: "%s", line %d, in %s' % (filename, lineno, name))
            if line:
                code.append("  %s" % (line.strip()))
    print "\n".join(code)

import signal
signal.signal(signal.SIGQUIT, dumpstacks)

53

Obter um rastreamento de pilha de um programa python não preparado , executando um python padrão sem símbolos de depuração, pode ser feito com pyrasite . Funcionou como um encanto para mim no Ubuntu Trusty:

$ sudo pip install pyrasite
$ echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope
$ sudo pyrasite 16262 dump_stacks.py # dumps stacks to stdout/stderr of the python program

(Dica para o @Albert, cuja resposta continha um ponteiro para isso, entre outras ferramentas.)


5
Isso funcionou muito bem para mim, onde dump_stacks.pyestava simplesmenteimport traceback; traceback.print_stack()
John Lehmann

2
traceback -lfornece uma lista de scripts python predefinidos que você pode usar e dump_stacks.pyé um deles. Se você estiver usando o seu próprio (por exemplo, para gravar o rastreamento de pilha em um arquivo), pode ser aconselhável usar um nome diferente.
Johndodo 11/11

12
Dica importante: execute apt-get install gdb python-dbg(ou equivalente) antes de executar o pyrasite, caso contrário, ele falhará silenciosamente. Caso contrário, funciona como um encanto!
Johndodo 11/11

A última versão do pyrasite foi em 2012
Boris

35
>>> import traceback
>>> def x():
>>>    print traceback.extract_stack()

>>> x()
[('<stdin>', 1, '<module>', None), ('<stdin>', 2, 'x', None)]

Você também pode formatar bem o rastreamento de pilha, consulte os documentos .

Edit : Para simular o comportamento do Java, conforme sugerido por @Douglas Leeder, adicione este:

import signal
import traceback

signal.signal(signal.SIGUSR1, lambda sig, stack: traceback.print_stack(stack))

para o código de inicialização no seu aplicativo. Em seguida, você pode imprimir a pilha enviando SIGUSR1para o processo Python em execução.


2
Isso imprimiria apenas o backtrace do thread principal. Ainda estou para encontrar uma solução para ver rastreamentos para todos os threads. De fato, parece que o python não possui uma API para recuperar a pilha do objeto Thread, embora threading.enumerate () dê acesso a todos os objetos Thread.
haridsv

Isso funciona muito bem no cygwin. Ele só imprime três linhas de rastreamento de pilha embora, mas isso é o suficiente para obter uma pista
slashdottir

28

O módulo traceback possui algumas funções interessantes, entre elas: print_stack:

import traceback

traceback.print_stack()

11
Para gravar o rastreamento de pilha em um arquivo, use: import traceback; f = open('/tmp/stack-trace.log', 'w') traceback.print_stack(file=f) f.close()
GuruM 16/08/12

11
+1 a @gulgi por sua resposta fácil de usar. Algumas das outras respostas pareciam muito complicadas para minha tarefa simples de obter rastreamento da pilha de chamadas a partir da função de um script.
GuruM 16/08/12

24

Você pode experimentar o módulo do manipulador de falhas . Instale-o usando pip install faulthandlere adicione:

import faulthandler, signal
faulthandler.register(signal.SIGUSR1)

no início do seu programa. Em seguida, envie SIGUSR1 para o seu processo (ex:) para kill -USR1 42exibir o retorno do Python de todos os threads na saída padrão. Leia a documentação para obter mais opções (por exemplo, faça login em um arquivo) e outras maneiras de exibir o rastreamento.

O módulo agora faz parte do Python 3.3. Para Python 2, consulte http://faulthandler.readthedocs.org/


20

O que realmente me ajudou aqui é a dica do spiv (que eu votaria e comentaria se tivesse os pontos de reputação) para obter um rastreamento de pilha de um processo Python não preparado . Exceto que não funcionou até eu modificar o script gdbinit . Assim:

  • faça o download de http://svn.python.org/projects/python/trunk/Misc/gdbinit e coloque-o em~/.gdbinit

  • editá-lo, mudando PyEval_EvalFrameparaPyEval_EvalFrameEx[editar: não é mais necessário; o arquivo vinculado já possui essa alteração a partir de 14-01-2010]

  • Anexar gdb: gdb -p PID

  • Obtenha o rastreamento da pilha python: pystack


O gdbinit no URL mencionado já parece ter o patch que você sugere. No meu caso, quando digitei o pystack, minha CPU travou. Não sei por que.
Jesse Glick

2
Não, não - eu não estava claro, desculpe, porque essa linha aparece em três lugares. O patch ao qual vinculei mostra qual deles havia mudado quando vi esse trabalho.
Gunnlaugur Briem

2
Como a resposta do @ spiv, isso requer que o programa seja executado em python compilado com símbolos de depuração. Caso contrário, você apenas obteráNo symbol "co" in current context.
Nickolay

12

Eu adicionaria isso como um comentário à resposta do haridsv , mas não tenho a reputação de fazê-lo:

Alguns de nós ainda estão presos em uma versão do Python anterior a 2.6 (necessária para o Thread.ident), então eu consegui o código trabalhando no Python 2.5 (embora sem o nome do thread sendo exibido) como tal:

import traceback
import sys
def dumpstacks(signal, frame):
    code = []
    for threadId, stack in sys._current_frames().items():
            code.append("\n# Thread: %d" % (threadId))
        for filename, lineno, name, line in traceback.extract_stack(stack):
            code.append('File: "%s", line %d, in %s' % (filename, lineno, name))
            if line:
                code.append("  %s" % (line.strip()))
    print "\n".join(code)

import signal
signal.signal(signal.SIGQUIT, dumpstacks)

11

python -dv herscript.py

Isso fará com que o intérprete seja executado no modo de depuração e fornecerá um rastreamento do que o intérprete está fazendo.

Se você deseja depurar interativamente o código, execute-o assim:

python -m pdb herscript.py

Isso diz ao intérprete python para executar seu script com o módulo "pdb", que é o depurador python, se você executá-lo assim, o intérprete será executado no modo interativo, assim como o GDB


Isso não responde à pergunta. A questão era sobre um processo já em execução.
dbn

11

Dê uma olhada no faulthandlermódulo, novo no Python 3.3. Um faulthandlerbackport para uso no Python 2 está disponível no PyPI.


2
Uma resposta mais recente da @haypo aborda isso com mais detalhes. Não estou certo de como isso geralmente é tratada no SO, mas parece errado ter duas respostas essencialmente duplicados ...
Nickolay

7

No Solaris, você pode usar o pstack (1) Nenhuma alteração no código python é necessária. por exemplo.

# pstack 16000 | grep : | head
16000: /usr/bin/python2.6 /usr/lib/pkg.depotd --cfg svc:/application/pkg/serv
[ /usr/lib/python2.6/vendor-packages/cherrypy/process/wspbus.py:282 (_wait) ]
[ /usr/lib/python2.6/vendor-packages/cherrypy/process/wspbus.py:295 (wait) ]
[ /usr/lib/python2.6/vendor-packages/cherrypy/process/wspbus.py:242 (block) ]
[ /usr/lib/python2.6/vendor-packages/cherrypy/_init_.py:249 (quickstart) ]
[ /usr/lib/pkg.depotd:890 (<module>) ]
[ /usr/lib/python2.6/threading.py:256 (wait) ]
[ /usr/lib/python2.6/Queue.py:177 (get) ]
[ /usr/lib/python2.6/vendor-packages/pkg/server/depot.py:2142 (run) ]
[ /usr/lib/python2.6/threading.py:477 (run)
etc.

2
Parece haver um programa Debian / Ubuntu pstackque faz a mesma coisa
Rory

11
Parece dar apenas o backtrace no linux, não o traceback do Python com nome de arquivo e números de linha.
ogrisel

6

Se você estiver em um sistema Linux, use a grandiosidade dos gdbcom extensões de depuração Python (pode ser em python-dbgou python-debuginfopacote). Também ajuda com aplicativos multithread, aplicativos GUI e módulos C.

Execute seu programa com:

$ gdb -ex r --args python <programname>.py [arguments]

Isso instrui gdba prepará python <programname>.py <arguments>- rlo e uni-lo.

Agora, quando você programa travar, mude para o gdbconsole, pressione Ctr+Ce execute:

(gdb) thread apply all py-list

Veja exemplo de sessão e mais informações aqui e aqui .


6

Eu estava procurando por um tempo uma solução para depurar meus threads e a encontrei aqui graças ao haridsv. Eu uso a versão ligeiramente simplificada, empregando o traceback.print_stack ():

import sys, traceback, signal
import threading
import os

def dumpstacks(signal, frame):
  id2name = dict((th.ident, th.name) for th in threading.enumerate())
  for threadId, stack in sys._current_frames().items():
    print(id2name[threadId])
    traceback.print_stack(f=stack)

signal.signal(signal.SIGQUIT, dumpstacks)

os.killpg(os.getpgid(0), signal.SIGQUIT)

Para as minhas necessidades, também filtre os tópicos por nome.


3

Vale a pena olhar para o Pydb , "uma versão expandida do depurador Python vagamente baseada no conjunto de comandos gdb". Inclui gerenciadores de sinais que podem cuidar do início do depurador quando um sinal especificado é enviado.

Um projeto Summer of Code de 2006 analisou a adição de recursos de depuração remota ao pydb em um módulo chamado mpdb .


Parece que ele se foi através de dois ( 1 ) reescreve ( 2 ) sem adicionar a anexar-by-PID característica que eu estava procurando ...
Nickolay

3

Eu hackeei alguma ferramenta que se conecta a um processo Python em execução e injeta algum código para obter um shell Python.

Veja aqui: https://github.com/albertz/pydbattach


11
Nota: não é óbvio como construir isso. Obrigado pelos links que você colocou no README: pyrasitefuncionou perfeitamente!
Nickolay

3

Isso pode ser feito com excelente py-spy . É um perfilador de amostragem para programas Python , portanto, seu trabalho é se conectar a um processo Python e fazer uma amostra de suas pilhas de chamadas. Portanto, py-spy dump --pid $SOME_PIDé tudo o que você precisa fazer para despejar pilhas de chamadas de todos os threads no $SOME_PIDprocesso. Normalmente, ele precisa de privilégios escalados (para ler a memória do processo de destino).

Aqui está um exemplo de como ele se parece com um aplicativo Python encadeado.

$ sudo py-spy dump --pid 31080
Process 31080: python3.7 -m chronologer -e production serve -u www-data -m
Python v3.7.1 (/usr/local/bin/python3.7)

Thread 0x7FEF5E410400 (active): "MainThread"
    _wait (cherrypy/process/wspbus.py:370)
    wait (cherrypy/process/wspbus.py:384)
    block (cherrypy/process/wspbus.py:321)
    start (cherrypy/daemon.py:72)
    serve (chronologer/cli.py:27)
    main (chronologer/cli.py:84)
    <module> (chronologer/__main__.py:5)
    _run_code (runpy.py:85)
    _run_module_as_main (runpy.py:193)
Thread 0x7FEF55636700 (active): "_TimeoutMonitor"
    run (cherrypy/process/plugins.py:518)
    _bootstrap_inner (threading.py:917)
    _bootstrap (threading.py:885)
Thread 0x7FEF54B35700 (active): "HTTPServer Thread-2"
    accept (socket.py:212)
    tick (cherrypy/wsgiserver/__init__.py:2075)
    start (cherrypy/wsgiserver/__init__.py:2021)
    _start_http_thread (cherrypy/process/servers.py:217)
    run (threading.py:865)
    _bootstrap_inner (threading.py:917)
    _bootstrap (threading.py:885)
...
Thread 0x7FEF2BFFF700 (idle): "CP Server Thread-10"
    wait (threading.py:296)
    get (queue.py:170)
    run (cherrypy/wsgiserver/__init__.py:1586)
    _bootstrap_inner (threading.py:917)
    _bootstrap (threading.py:885)  

2

O pyringe é um depurador que pode interagir com os processos python em execução, rastrear a pilha de impressão, variáveis ​​etc. sem nenhuma configuração a priori.

Embora eu tenha usado a solução do manipulador de sinal no passado, ainda pode ser difícil reproduzir o problema em determinados ambientes.


3
Aparentemente, é incompatível com certas versões do gdb (por exemplo, a que eu havia instalado no ubuntu): github.com/google/pyringe/issues/16 , exigindo a reconstrução manual. Outro depurador pyrasitefuncionou como um encanto para mim.
Nickolay

1

Não há como conectar-se a um processo python em execução e obter resultados razoáveis. O que faço se os processos travam está conectando e tentando descobrir o que exatamente está acontecendo.

Infelizmente, muitas vezes o strace é o observador que "corrige" as condições da corrida, para que a saída também seja inútil.


11
Sim, isso é verdade. É uma vergonha que pdb Thad não suporta anexar a um processo em execução ...
Bartosz Radaczyński

Isso não é verdade. Veja a resposta "spiv" acima, que mostra como conectar o gdb e obter um rastreio de pilha Python.
precisa

Não é a mesma coisa - essas macros gdb não são confiáveis ​​e não fornecem toda a interface familiar / poder do pdb. Muitas vezes eu gostaria que alguém escrevesse um aplicativo pequeno que usasse o ptrace para injetar algum bytecode Python em um processo Python em execução e executá-lo 'import pdb; pdb.set_trace () ', talvez também depois de redirecionar temporariamente o sys.stdin / stdout.
Marius Gedminas

Isso não é mais verdade, veja outras respostas apontando para piringe / pirassita.
Nickolay

1

Você pode usar o PuDB , um depurador Python com uma interface de maldições para fazer isso. Basta adicionar

from pudb import set_interrupt_handler; set_interrupt_handler()

ao seu código e use Ctrl-C quando desejar quebrar. Você pode continuar ce interromper novamente várias vezes, se não entender e quiser tentar novamente.


Quando você usa o comando acima no django, não se esqueça de executar um servidor corretamente para evitar falhas: "manage.py runserver --neleload --nothreading"
potar 13/02/15

1

Estou no campo GDB com as extensões python. Siga https://wiki.python.org/moin/DebuggingWithGdb , o que significa

  1. dnf install gdb python-debuginfo ou sudo apt-get install gdb python2.7-dbg
  2. gdb python <pid of running process>
  3. py-bt

Considere também info threadse thread apply all py-bt.


é normal para obter uma resposta como Traceback (most recent call first): Python Exception <class 'gdb.error'> No frame is currently selected.: Error occurred in Python command: No frame is currently selected.quando executado py-btem gdb?
crookedleaf

11
deixa pra lá. é porque meu aplicativo estava sendo executado como sudo. eu também precisava rodar gdb pyton <pid>como sudo.
crookedleaf

1

Como depurar qualquer função no console :

Crie a função em que você usa pdb.set_trace () e , em seguida, a função que deseja depurar.

>>> import pdb
>>> import my_function

>>> def f():
...     pdb.set_trace()
...     my_function()
... 

Em seguida, chame a função criada:

>>> f()
> <stdin>(3)f()
(Pdb) s
--Call--
> <stdin>(1)my_function()
(Pdb) 

Feliz depuração :)



0

use o módulo de inspeção.

importar inspecionar ajuda (inspecionar.stack) Ajuda na pilha de funções no módulo inspecionar:

pilha (contexto = 1) Retorna uma lista de registros para a pilha acima do quadro do chamador.

Acho muito útil mesmo.


0

No Python 3, o pdb instalará automaticamente um manipulador de sinal na primeira vez que você usar c (ont (inue)) no depurador. Pressionar Control-C depois o levará de volta para lá. No Python 2, aqui está um one-liner que deve funcionar mesmo em versões relativamente antigas (testado em 2.7, mas eu verifiquei o código do Python de volta para 2.4 e parecia bom):

import pdb, signal
signal.signal(signal.SIGINT, lambda sig, frame: pdb.Pdb().set_trace(frame))

Vale a pena aprender pdb se você gastar algum tempo depurando o Python. A interface é um pouco obtusa, mas deve ser familiar para quem já usou ferramentas semelhantes, como o gdb.


0

Caso você precise fazer isso com o uWSGI, ele possui o Python Tracebacker embutido e é apenas uma questão de habilitá-lo na configuração (o número é anexado ao nome de cada trabalhador):

py-tracebacker=/var/run/uwsgi/pytrace

Depois de fazer isso, você pode imprimir o backtrace simplesmente conectando ao soquete:

uwsgi --connect-and-read /var/run/uwsgi/pytrace1

0

No ponto em que o código é executado, você pode inserir esse pequeno trecho para ver um rastreamento de pilha impresso bem formatado. Ele pressupõe que você tenha uma pasta chamada logsno diretório raiz do seu projeto.

# DEBUG: START DEBUG -->
import traceback

with open('logs/stack-trace.log', 'w') as file:
    traceback.print_stack(file=file)
# DEBUG: END DEBUG --!
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.