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


340

Tengo esta aplicación Python que se atasca de vez en cuando y no puedo averiguar 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 stacktrace sobre la marcha?

Preguntas relacionadas:



Respuestas:


315

Tengo un módulo que uso para situaciones como esta, donde un proceso se ejecutará durante mucho tiempo pero a veces se atasca por razones desconocidas e irreproducibles. Es un poco hacky, 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 usar, simplemente llame a la función listen () en algún momento cuando su programa se inicie (incluso podría pegarlo en site.py para que todos los programas de Python lo usen) y dejarlo correr. En cualquier momento, envíe al proceso una señal SIGUSR1, usando kill o en python:

    os.kill(pid, signal.SIGUSR1)

Esto hará que el programa se rompa 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. Use control-d (EOF) para continuar ejecutándose (aunque tenga en cuenta que probablemente interrumpirá cualquier E / S, etc. en el punto que indique, por lo que no es totalmente 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 grande para publicar aquí, pero lo he agregado como una receta de libro de cocina de Python .


1
¡Gracias! Esto es justo lo que estaba buscando. ¿Quizás también podría publicar ese script con soporte de canalización en algún sitio de fragmentos de Python?
Seb

2
Ahora lo publiqué en el sitio del libro de cocina de Python - enlace agregado.
Brian

1
Necesitaba agregar "import readline" para habilitar las características del historial.
miracle2k

2
Gran consejo! Esto también funciona para enviar la señal a todos los procesos que contienen la palabra "mypythonapp": pkill -SIGUSR1 -f mypythonapp
Alexander

10
Si la aplicación está atascada, es posible que el bucle de intérprete de Python no pueda ejecutarse para procesar la señal. Use el faulthandlermódulo (y su puerto de respaldo que se encuentra en PyPI) para un controlador de señal de nivel C que imprima la pila de Python sin requerir que el bucle de intérprete responda.
GPS

146

La sugerencia de instalar un controlador de señal es buena, y la uso mucho. Por ejemplo, bzr por defecto instala un controlador SIGQUIT que invoca pdb.set_trace()para colocarlo inmediatamente en un indicador pdb . (Consulte la fuente del módulo bzrlib.breakin para conocer los detalles exactos). Con pdb no solo puede obtener el seguimiento de la pila actual sino también inspeccionar variables, etc.

Sin embargo, a veces necesito depurar un proceso en el que no tenía la previsión de instalar el controlador de señal. En Linux, puede adjuntar gdb al proceso y obtener un seguimiento de la pila de Python con algunas macros de gdb. Ponga http://svn.python.org/projects/python/trunk/Misc/gdbinit en ~/.gdbinit, a continuación:

  • Adjunte 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.

Finalmente, adjuntar a stracemenudo puede darle una buena idea de lo que está haciendo un proceso.


2
¡Brillante! El comando pystack se bloquea a veces, pero antes de hacerlo, me da un seguimiento completo de la pila del proceso, en líneas de código python, sin necesidad de hacer ninguna preparación.
muudscope

26
Actualización menor: esta técnica de gdb (y código actualizado) está documentada en wiki.python.org/moin/DebuggingWithGdb Ha habido algún desarrollo en este frente, documentado en esa URL, y aparentemente gdb 7 tiene algún soporte para Python.
Nelson

77
Por lo que puedo decir, esto solo funciona realmente si tiene símbolos de depuración compilados en su binario de python, por ejemplo: ejecutó su programa con python2-dbg (en Ubuntu, esto está en un paquete separado python-dbg). Sin esos símbolos, parece que no obtienes mucha información útil.
drevicko

1
en mi caso este regreso Unable to locate python framea cada comando
seriyPS

66
gdb 7+ - python-gdb.py proporciona soporte conwith-python Más detalles aquí: chezsoi.org/lucas/blog/2014/11/07/en-gdb-python-macros
Lucas Cimon

71

Casi siempre estoy tratando con múltiples subprocesos y el subproceso principal generalmente no está haciendo mucho, por lo que lo más interesante es volcar todas las pilas (que es más como el 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)

53

Obtener un seguimiento de la pila de un programa de Python no preparado , que se ejecuta en un stock de Python sin símbolos de depuración se puede hacer con pyrasite . Me funcionó de maravilla en 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

(Un consejo para @Albert, cuya respuesta contenía un puntero a esto, entre otras herramientas).


55
Esto funcionó muy bien para mí, donde dump_stacks.pysimplemente estabaimport traceback; traceback.print_stack()
John Lehmann

2
traceback -lle brinda una lista de scripts de Python predefinidos que puede usar, y dump_stacks.pyes uno de ellos. Si está utilizando el suyo (por ejemplo, para escribir el seguimiento de la pila en un archivo), podría ser conveniente utilizar un nombre diferente.
johndodo

12
apt-get install gdb python-dbgConsejo importante: ejecute (o equivalente) antes de ejecutar pyrasite, de lo contrario fallará silenciosamente. Funciona como un encanto de lo contrario!
johndodo

La última versión de pyrasite fue en 2012
Boris

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

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

También puede formatear muy bien el seguimiento de la pila, consulte los documentos .

Editar : para simular el comportamiento de Java, como lo sugiere @Douglas Leeder, agregue esto:

import signal
import traceback

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

al código de inicio en su aplicación. Luego puede imprimir la pila enviándola SIGUSR1al proceso de Python en ejecución.


2
Esto solo imprimiría la traza inversa del hilo principal. Todavía tengo que encontrar una solución para ver los rastros de todos los hilos. De hecho, python parece carecer de una API para recuperar la pila del objeto Thread, aunque threading.enumerate () da acceso a todos los objetos Thread.
haridsv

Esto funciona muy bien en Cygwin. Sin embargo, solo imprime tres líneas del trazado de la pila, pero eso es suficiente para obtener una pista
slashdottir

28

El módulo de rastreo tiene algunas buenas funciones, entre ellas: print_stack:

import traceback

traceback.print_stack()

1
Para escribir el seguimiento de la pila en un archivo, utilice: import traceback; f = open('/tmp/stack-trace.log', 'w') traceback.print_stack(file=f) f.close()
GuruM

1
+1 a @gulgi por su respuesta fácil de usar. Algunas de las otras respuestas parecían muy complicadas para mi simple tarea de obtener el seguimiento de la pila de llamadas de la función de un script.
GuruM

24

Puedes probar el módulo de manejo de fallas . Instálelo usando pip install faulthandlery agregue:

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

Al comienzo de su programa. Luego envíe SIGUSR1 a su proceso (ej . kill -USR1 42:) para mostrar el rastreo de Python de todos los hilos a la salida estándar. Lea la documentación para obtener más opciones (por ejemplo, iniciar sesión en un archivo) y otras formas de mostrar el rastreo.

El módulo ahora es parte de Python 3.3. Para Python 2, consulte http://faulthandler.readthedocs.org/


20

Lo que realmente me ayudó aquí es la sugerencia de spiv (que votaría y comentaría si tuviera los puntos de reputación) para obtener un seguimiento de la pila de un proceso Python no preparado . Excepto que no funcionó hasta que modifiqué el script gdbinit . Entonces:

  • descargue http://svn.python.org/projects/python/trunk/Misc/gdbinit y póngalo en~/.gdbinit

  • editarlo, cambiando PyEval_EvalFrameaPyEval_EvalFrameEx[editar: ya no es necesario; el archivo vinculado ya tiene este cambio a partir del 2010-01-14]

  • Adjunte gdb: gdb -p PID

  • Obtenga el seguimiento de la pila de Python: pystack


El gdbinit en la URL mencionada ya parece tener el parche que sugiere. En mi caso, cuando escribí pystack, mi CPU se colgó. No estoy seguro de por qué.
Jesse Glick el

2
No, no. No estaba claro, lo siento, porque esa línea aparece en tres lugares. El parche que vinculé muestra cuál había cambiado cuando vi este trabajo.
Gunnlaugur Briem el

2
Al igual que la respuesta de @ spiv, esto requiere que el programa se ejecute en Python compilado con símbolos de depuración. De lo contrario, solo obtendráNo symbol "co" in current context.
Nickolay

12

Agregaría esto como un comentario a la respuesta de haridsv , pero me falta la reputación para hacerlo:

Algunos de nosotros todavía estamos atascados en una versión de Python anterior a 2.6 (requerida para Thread.ident), por lo que obtuve el código funcionando en Python 2.5 (aunque sin que se muestre el nombre del hilo) 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 yourscript.py

Eso hará que el intérprete se ejecute en modo de depuración y le dará un rastro de lo que está haciendo el intérprete.

Si desea depurar interactivamente el código, debe ejecutarlo así:

python -m pdb yourscript.py

Eso le dice al intérprete de Python que ejecute su script con el módulo "pdb", que es el depurador de Python, si lo ejecuta así, el intérprete se ejecutará en modo interactivo, al igual que GDB


Esto no responde la pregunta. La pregunta era sobre un proceso que ya se está ejecutando.
dbn

11

Eche un vistazo al faulthandlermódulo, nuevo en Python 3.3. Un faulthandlerbackport para usar en Python 2 está disponible en PyPI.


2
Una respuesta más reciente de @haypo cubre esto con más detalle. No estoy seguro de cómo se maneja esto normalmente en SO, pero se siente mal tener dos respuestas esencialmente duplicadas ...
Nickolay

7

En Solaris, puede usar pstack (1) No es necesario realizar cambios en el código de Python. p.ej.

# 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 que hay un programa Debian / Ubuntu pstackque hace lo mismo
Rory

1
Parece que solo da la traza inversa bajo Linux, no la trazabilidad de Python con nombre de archivo y números de línea.
Ogrisel

6

Si estás en un sistema Linux, utilice la maravilla de gdblas extensiones de depuración de Python (puede estar en python-dbgo python-debuginfopaquete). También ayuda con aplicaciones multiproceso, aplicaciones GUI y módulos C.

Ejecute su programa con:

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

Esto instruye gdba preparar python <programname>.py <arguments>y rdeshacer.

Ahora, cuando el programa se cuelga, cambia a la gdbconsola, presiona Ctr+Cy ejecuta:

(gdb) thread apply all py-list

Ver sesión de ejemplo y más información aquí y aquí .


6

Estuve buscando una solución para depurar mis hilos y la encontré aquí gracias a haridsv. Utilizo una versión ligeramente simplificada que emplea 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 mis necesidades también filtro hilos por nombre.


3

Vale la pena mirar a Pydb , "una versión expandida del depurador de Python basada libremente en el conjunto de comandos gdb". Incluye gestores de señales que pueden encargarse de iniciar el depurador cuando se envía una señal específica.

Un proyecto de Summer of Code 2006 buscó agregar características de depuración remota a pydb en un módulo llamado mpdb .


Parece que ha pasado por dos ( 1 ) reescrituras ( 2 ) sin agregar la función adjuntar por PID que estaba buscando ...
Nickolay

3

Pirateé alguna herramienta que se conecta a un proceso de Python en ejecución e inyecta algo de código para obtener un shell de Python.

Ver aquí: https://github.com/albertz/pydbattach


1
Nota: no es obvio cómo construir esto. Sin embargo, gracias por los enlaces que ha puesto en README: ¡ pyrasitefuncionó perfectamente!
Nickolay

3

Se puede hacer con excelente py-spy . Es un generador de perfiles de muestreo para programas de Python , por lo que su trabajo es adjuntar a procesos de Python y muestrear sus pilas de llamadas. Por lo tanto, py-spy dump --pid $SOME_PIDes todo lo que necesita hacer para volcar las pilas de llamadas de todos los hilos en el $SOME_PIDproceso. Por lo general, necesita privilegios escalados (para leer la memoria del proceso de destino).

Aquí hay un ejemplo de cómo se ve para una aplicación Python roscada.

$ 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

pyringe es un depurador que puede interactuar con la ejecución de procesos de python, imprimir rastros de pila, variables, etc. sin ninguna configuración a priori.

Aunque a menudo he usado la solución de controlador de señal en el pasado, aún puede ser difícil reproducir el problema en ciertos entornos.


3
Aparentemente es incompatible con ciertas compilaciones de gdb (por ejemplo, la que había instalado en ubuntu): github.com/google/pyringe/issues/16 , que requiere una reconstrucción manual. Otro depurador, pyrasitefuncionó como un encanto para mí.
Nickolay

1

No hay forma de conectarse a un proceso de Python en ejecución y obtener resultados razonables. Lo que hago si los procesos se bloquean es engancharse e intentar averiguar qué está sucediendo exactamente.

Desafortunadamente, a menudo es extraño el observador que "arregla" las condiciones de carrera para que la salida también sea inútil allí.


1
Si, esto es verdad. Es una pena que thad pdb no admita adjuntar a un proceso en ejecución ...
Bartosz Radaczyński

Esto no es verdad. Vea la respuesta de "spiv" arriba, que muestra cómo conectar gdb y obtener un seguimiento de la pila de Python.
Andrew Cooke

No es lo mismo: esas macros de gdb no son confiables y no proporcionan la potencia / interfaz familiar de pdb. A menudo deseo que alguien haya escrito una pequeña aplicación que use ptrace para inyectar algún código de bytes de Python en un proceso de Python en ejecución y que ejecute 'import pdb; pdb.set_trace () ', quizás también después de redirigir temporalmente sys.stdin / stdout.
Marius Gedminas

Esto ya no es cierto, vea otras respuestas que apuntan a pyringe / pyrasite.
Nickolay

1

Puede usar PuDB , un depurador de Python con una interfaz de maldiciones para hacer esto. Solo agrega

from pudb import set_interrupt_handler; set_interrupt_handler()

a su código y use Ctrl-C cuando quiera romper. Puede continuar ce interrumpir nuevamente varias veces si se lo pierde y desea intentarlo nuevamente.


Cuando utilice el comando anterior en django, no olvide ejecutar un servidor correctamente para evitar problemas técnicos: "manage.py runserver --noreload --nothreading"
potar

1

Estoy en el campamento de GDB con las extensiones de Python. Siga https://wiki.python.org/moin/DebuggingWithGdb , lo que significa

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

También considere info threadsy thread apply all py-bt.


¿es normal para obtener una respuesta 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.cuando se ejecuta py-bten gdb?
crookedleaf

1
no importa. es porque mi aplicación se estaba ejecutando como sudo. Yo también necesitaba correr gdb pyton <pid>como sudo.
crookedleaf

1

Cómo depurar cualquier función en la consola :

Cree la función donde usa pdb.set_trace () , luego la función que desea depurar.

>>> import pdb
>>> import my_function

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

Luego llame a la función creada:

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

Feliz depuración :)


0

No sé de nada similar a la respuesta de Java a SIGQUIT , por lo que es posible que deba compilarlo en su aplicación. ¿Quizás podría hacer un servidor en otro hilo que pueda obtener un seguimiento de pila en respuesta a un mensaje de algún tipo?


0

use el módulo de inspección.

importación inspeccionar ayuda (inspect.stack) Ayuda sobre la pila de funciones en el módulo inspeccionar:

stack (context = 1) Devuelve una lista de registros para la pila sobre el marco de la persona que llama.

Lo encuentro muy útil de hecho.


0

En Python 3, pdb instalará automáticamente un controlador de señal la primera vez que use c (ont (inue)) en el depurador. Presionando Control-C luego te dejará de nuevo allí. En Python 2, aquí hay una línea que debería funcionar incluso en versiones relativamente antiguas (probado en 2.7 pero revisé la fuente de Python nuevamente a 2.4 y se veía bien):

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

Vale la pena aprender pdb si pasa algún tiempo depurando Python. La interfaz es un poco obtusa, pero debería ser familiar para cualquiera que haya usado herramientas similares, como gdb.


0

En caso de que necesite hacer esto con uWSGI, tiene incorporado Python Tracebacker y solo es cuestión de habilitarlo en la configuración (el número se adjunta al nombre de cada trabajador):

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

Una vez que haya hecho esto, puede imprimir la traza inversa simplemente conectándose al zócalo:

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

0

En el punto donde se ejecuta el código, puede insertar este pequeño fragmento para ver una traza de pila impresa bien formateada. Se supone que tiene una carpeta llamada logsen el directorio raíz de su proyecto.

# DEBUG: START DEBUG -->
import traceback

with open('logs/stack-trace.log', 'w') as file:
    traceback.print_stack(file=file)
# DEBUG: END DEBUG --!
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.