Mostrando el seguimiento de la pila desde una aplicación Python en ejecución

Resuelto Seb asked hace 16 años • 29 respuestas

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
Seb avatar Sep 25 '08 15:09 Seb
Aceptado

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 .

Brian avatar Sep 25 '2008 13:09 Brian

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)herecomando), 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 stracea menudo puede darle una buena idea de lo que está haciendo un proceso.

spiv avatar Sep 29 '2008 00:09 spiv

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)
haridsv avatar Apr 02 '2010 23:04 haridsv