Mostrando el seguimiento de la pila desde una aplicación Python en ejecución
Tengo esta aplicación Python que se bloquea de vez en cuando y no puedo encontrar dónde.
¿Hay alguna forma de indicarle al intérprete de Python que le muestre el código exacto que se está ejecutando?
¿Algún tipo de seguimiento de pila sobre la marcha?
Preguntas relacionadas:
- Imprimir la pila de llamadas actual desde un método en código Python
- Compruebe lo que está haciendo un proceso en ejecución: imprimir el seguimiento de la pila de un programa Python no instrumentado
Tengo un módulo que uso para situaciones como esta, donde un proceso se ejecuta durante mucho tiempo pero a veces se atasca por razones desconocidas e irreproducibles. Es un poco complicado y solo funciona en Unix (requiere señales):
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 usarla, simplemente llame a la función listening() en algún momento cuando se inicie su programa (incluso podría colocarla en site.py para que todos los programas de Python la usen) y déjela ejecutar. En cualquier momento, envía al proceso una señal SIGUSR1, usando kill o en Python:
os.kill(pid, signal.SIGUSR1)
Esto hará que el programa acceda a una consola de Python en el punto en el que se encuentra actualmente, mostrándole el seguimiento de la pila y permitiéndole manipular las variables. Utilice control-d (EOF) para continuar con la ejecución (aunque tenga en cuenta que probablemente interrumpirá cualquier E/S, etc. en el punto de señal, por lo que no es completamente no intrusivo.
Tengo otro script que hace lo mismo, excepto que se comunica con el proceso en ejecución a través de una tubería (para permitir la depuración de procesos en segundo plano, etc.). Es un poco largo para publicarlo aquí, pero lo agregué como una receta de libro de cocina de Python .
La sugerencia de instalar un controlador de señales es buena y la uso mucho. Por ejemplo, bzr instala de forma predeterminada un controlador SIGQUIT que lo invoca pdb.set_trace()
para llevarlo inmediatamente a un indicador de pdb . (Consulte la fuente del módulo bzrlib.breakin para obtener detalles exactos). Con pdb no solo puede obtener el seguimiento de la pila actual (con el (w)here
comando), sino también inspeccionar variables, etc.
Sin embargo, a veces necesito depurar un proceso en el que no tuve la previsión de instalar el controlador de señales. En Linux, puede adjuntar gdb al proceso y obtener un seguimiento de la pila de Python con algunas macros de gdb. Coloque http://svn.python.org/projects/python/trunk/Misc/gdbinit y luego ~/.gdbinit
:
- Adjuntar gdb:
gdb -p
PID
- Obtenga el seguimiento de la pila de Python:
pystack
Desafortunadamente no es totalmente confiable, pero funciona la mayor parte del tiempo. Véase también https://wiki.python.org/moin/DebuggingWithGdb
Finalmente, adjuntar strace
a menudo puede darle una buena idea de lo que está haciendo un proceso.
Casi siempre estoy tratando con múltiples subprocesos y el subproceso principal generalmente no hace mucho, por lo que lo más interesante es volcar todas las pilas (que se parece más al volcado de Java). Aquí hay una implementación basada en este 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)