Affichage de la trace de pile à partir d'une application Python en cours d'exécution

340

J'ai cette application Python qui se bloque de temps en temps et je ne peux pas savoir où.

Existe-t-il un moyen de signaler à l'interpréteur Python de vous montrer le code exact en cours d'exécution?

Une sorte de stacktrace à la volée?

Questions connexes:

Seb
la source
en relation: stackoverflow.com/q/4163964/1449460
Nikana Reklawyks
connexes wiki.python.org/moin/DebuggingWithGdb
Trevor Boyd Smith

Réponses:

315

J'ai un module que j'utilise pour des situations comme celle-ci - où un processus s'exécutera pendant une longue période mais se bloquera parfois pour des raisons inconnues et irréprochables. C'est un peu hacky, et ne fonctionne que sur unix (nécessite des signaux):

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

Pour l'utiliser, il suffit d'appeler la fonction listen () à un moment donné au démarrage de votre programme (vous pouvez même la coller dans site.py pour que tous les programmes python l'utilisent), et laissez-la s'exécuter. À tout moment, envoyez au processus un signal SIGUSR1, en utilisant kill ou en python:

    os.kill(pid, signal.SIGUSR1)

Cela provoquera la rupture du programme sur une console python au point où il se trouve actuellement, vous montrant la trace de la pile et vous permettant de manipuler les variables. Utilisez control-d (EOF) pour continuer à fonctionner (mais notez que vous interromprez probablement toutes les E / S, etc. au point que vous signalez, donc ce n'est pas totalement non intrusif.

J'ai un autre script qui fait la même chose, sauf qu'il communique avec le processus en cours via un canal (pour permettre le débogage des processus en arrière-plan, etc.). C'est un peu grand pour poster ici, mais je l'ai ajouté comme recette de livre de recettes en python .

Brian
la source
1
Merci! C'est exactement ce que je cherchais. Peut-être pourriez-vous également publier ce script avec le support de pipe sur certains sites d'extraits de code Python?
Seb
2
Je l'ai maintenant posté sur le site du livre de recettes python - lien ajouté.
Brian
1
J'avais besoin d'ajouter "import readline" pour activer les fonctionnalités d'historique.
miracle2k
2
Bon conseil! Cela fonctionne également pour envoyer le signal, à tous les processus contenant le mot "mypythonapp": pkill -SIGUSR1 -f mypythonapp
Alexander
10
Si l'application est bloquée, la boucle d'interpréteur Python peut ne pas être en mesure de s'exécuter pour traiter le signal. Utilisez le faulthandlermodule (et son backport trouvé sur PyPI) pour un gestionnaire de signal de niveau C qui imprimera la pile Python sans exiger que la boucle d'interpréteur soit réactive.
gps
146

La suggestion d'installer un gestionnaire de signaux est bonne et je l'utilise beaucoup. Par exemple, bzr installe par défaut un gestionnaire SIGQUIT qui invoque pdb.set_trace()pour vous déposer immédiatement dans une invite pdb . (Voir la source du module bzrlib.breakin pour les détails exacts.) Avec pdb, vous pouvez non seulement obtenir la trace de la pile actuelle mais également inspecter les variables, etc.

Cependant, j'ai parfois besoin de déboguer un processus dans lequel je n'avais pas la prévoyance d'installer le gestionnaire de signaux. Sur Linux, vous pouvez attacher gdb au processus et obtenir une trace de pile python avec certaines macros gdb. Mettez http://svn.python.org/projects/python/trunk/Misc/gdbinit dans ~/.gdbinit, puis:

  • Attachez gdb: gdb -p PID
  • Obtenez la trace de la pile python: pystack

Ce n'est malheureusement pas totalement fiable, mais cela fonctionne la plupart du temps.

Enfin, l'attachement stracepeut souvent vous donner une bonne idée de ce que fait un processus.

spiv
la source
2
Brillant! La commande pystack se bloque parfois, mais avant cela, elle me donne une trace complète de la pile du processus, en lignes de code python, sans avoir eu à faire de préparation.
muudscope
26
Mise à jour mineure: cette technique gdb (et le code mis à jour) est documentée sur wiki.python.org/moin/DebuggingWithGdb Il y a eu quelques développements sur ce front, documentés à cette URL, et apparemment gdb 7 a un support Python.
Nelson
7
Pour autant que je sache, cela ne fonctionne vraiment que si vous avez des symboles de débogage compilés dans votre binaire python - par exemple: vous avez exécuté votre programme avec python2-dbg (sur Ubuntu, c'est dans un package séparé python-dbg). Sans ces symboles, vous ne semblez pas obtenir beaucoup d'informations utiles.
drevicko
1
dans mon cas, ce retour Unable to locate python frameà chaque commande
seriyPS
6
gdb 7+ --with-python est fourni par python-gdb.py. Plus de détails ici: chezsoi.org/lucas/blog/2014/11/07/en-gdb-python-macros
Lucas Cimon
71

J'ai presque toujours affaire à plusieurs threads et le thread principal ne fait généralement pas grand-chose, donc ce qui est le plus intéressant est de vider toutes les piles (ce qui ressemble plus au dump de Java). Voici une implémentation basée sur ce 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
la source
53

Obtenir une trace de pile d'un programme python non préparé , s'exécuter dans un python stock sans symboles de débogage peut être fait avec pyrasite . Fonctionné comme un charme pour moi sur 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

(Pointe du chapeau à @Albert, dont la réponse contenait un pointeur vers cela, entre autres outils.)

Nickolay
la source
5
Cela a très bien fonctionné pour moi, où dump_stacks.pyétait tout simplementimport traceback; traceback.print_stack()
John Lehmann
2
traceback -lvous donne une liste de scripts python prédéfinis que vous pouvez utiliser et en dump_stacks.pyfait partie. Si vous utilisez le vôtre (par exemple pour écrire la trace de la pile dans un fichier), il peut être judicieux d'utiliser un nom différent.
johndodo
12
apt-get install gdb python-dbgConseil important: exécutez (ou équivalent) avant d'exécuter la pyrasite, sinon elle échouera silencieusement. Fonctionne comme un charme sinon!
johndodo
La dernière version de pyrasite remonte à 2012
Boris
35
>>> import traceback
>>> def x():
>>>    print traceback.extract_stack()

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

Vous pouvez également bien formater la trace de la pile, voir la documentation .

Edit : Pour simuler le comportement de Java, comme suggéré par @Douglas Leeder, ajoutez ceci:

import signal
import traceback

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

au code de démarrage de votre application. Ensuite, vous pouvez imprimer la pile en l'envoyant SIGUSR1au processus Python en cours d'exécution.

Torsten Marek
la source
2
Cela n'imprimerait que la trace du thread principal. Je n'ai pas encore trouvé de solution pour voir les traces de tous les threads. En fait, python semble ne pas avoir d'API pour récupérer la pile de l'objet Thread, bien que threading.enumerate () donne accès à tous les objets Thread.
haridsv
Cela fonctionne très bien sur cygwin. Il n'imprime cependant que trois lignes de la trace de la pile, mais cela suffit pour obtenir un indice
slashdottir
28

Le module traceback a de belles fonctions, parmi lesquelles: print_stack:

import traceback

traceback.print_stack()
gulgi
la source
1
Pour écrire la trace de la pile dans un fichier, utilisez: import traceback; f = open('/tmp/stack-trace.log', 'w') traceback.print_stack(file=f) f.close()
GuruM
1
+1 à @gulgi pour sa réponse facile à utiliser. Certaines des autres réponses semblaient très compliquées pour ma tâche simple d'obtenir la trace de la pile d'appels à partir de la fonction d'un script.
GuruM
24

Vous pouvez essayer le module de traitement des erreurs . Installez-le en utilisant pip install faulthandleret ajoutez:

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

au début de votre programme. Envoyez ensuite SIGUSR1 à votre processus (ex:) pour kill -USR1 42afficher le traceback Python de tous les threads vers la sortie standard. Lisez la documentation pour plus d'options (ex: connectez-vous dans un fichier) et d'autres façons d'afficher la trace.

Le module fait maintenant partie de Python 3.3. Pour Python 2, voir http://faulthandler.readthedocs.org/

haypo
la source
20

Ce qui m'a vraiment aidé ici, c'est le conseil de spiv (que je voterais et commenterais si j'avais les points de réputation) pour obtenir une trace de pile d'un processus Python non préparé . Sauf que cela n'a pas fonctionné jusqu'à ce que je modifie le script gdbinit . Alors:

  • téléchargez http://svn.python.org/projects/python/trunk/Misc/gdbinit et placez-le dans~/.gdbinit

  • éditez-le, changez PyEval_EvalFrameenPyEval_EvalFrameEx[modifier: plus nécessaire; le fichier lié a déjà cette modification au 14/01/2010]

  • Attachez gdb: gdb -p PID

  • Obtenez la trace de la pile python: pystack

Gunnlaugur Briem
la source
Le gdbinit à l'URL mentionnée semble déjà avoir le correctif que vous proposez. Dans mon cas, lorsque j'ai tapé pystack, mon processeur vient de se bloquer. Pas certain de pourquoi.
Jesse Glick
2
Non, ce n'est pas - je n'étais pas clair, désolé, parce que cette ligne apparaît à trois endroits. Le patch auquel j'ai lié montre celui que j'avais changé lorsque j'ai vu ce travail.
Gunnlaugur Briem
2
Comme la réponse de @ spiv, cela nécessite que le programme s'exécute sous python compilé avec des symboles de débogage. Sinon, vous aurez justeNo symbol "co" in current context.
Nickolay
12

J'ajouterais ceci en tant que commentaire à la réponse de haridsv , mais je n'ai pas la réputation de le faire:

Certains d'entre nous sont toujours bloqués sur une version de Python antérieure à 2.6 (requise pour Thread.ident), j'ai donc fait fonctionner le code dans Python 2.5 (mais sans que le nom du thread soit affiché) en tant que tel:

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)
Konstantin Tarashchanskiy
la source
11

python -dv yourscript.py

Cela fera exécuter l'interpréteur en mode débogage et vous donnera une trace de ce que fait l'interpréteur.

Si vous souhaitez déboguer le code de manière interactive, vous devez l'exécuter comme ceci:

python -m pdb yourscript.py

Cela dit à l'interpréteur python d'exécuter votre script avec le module "pdb" qui est le débogueur python, si vous l'exécutez comme ça, l'interpréteur sera exécuté en mode interactif, un peu comme GDB

Gustavo Rubio
la source
Cela ne répond pas à la question. La question portait sur un processus déjà en cours.
dbn
11

Jetez un œil au faulthandlermodule, nouveau dans Python 3.3. Un faulthandlerbackport à utiliser dans Python 2 est disponible sur PyPI.

Matt Joiner
la source
2
Une réponse plus récente de @haypo couvre cela plus en détail. Je ne sais pas comment cela est généralement géré sur SO, mais il ne semble pas
correct
7

Sous Solaris, vous pouvez utiliser pstack (1) Aucune modification du code python n'est nécessaire. par exemple.

# 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.
Tim Foster
la source
2
Il semble y avoir un programme Debian / Ubuntu pstackqui fait la même chose
Rory
1
Il semble ne donner que la trace sous linux, pas le traceback Python avec le nom de fichier et les numéros de ligne.
ogrisel
6

Si vous êtes sur un système Linux, utilisez l'extraordinaire gdbavec les extensions de débogage Python (peut être dans python-dbgou python-debuginfopackage). Il aide également avec les applications multithread, les applications GUI et les modules C.

Exécutez votre programme avec:

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

Cela demande gdbde le préparer python <programname>.py <arguments>et de le rdéfaire.

Maintenant, lorsque vous programmez se bloque, basculez dans la gdbconsole, appuyez sur Ctr+Cet exécutez:

(gdb) thread apply all py-list

Voir l' exemple de session et plus d'informations ici et ici .

anatoly techtonik
la source
6

Je cherchais depuis un moment une solution pour déboguer mes threads et je l'ai trouvée ici grâce à haridsv. J'utilise une version légèrement simplifiée utilisant la 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)

Pour mes besoins, je filtre également les threads par nom.

Stefan
la source
3

Il vaut la peine de regarder Pydb , "une version étendue du débogueur Python vaguement basée sur le jeu de commandes gdb". Il comprend des gestionnaires de signaux qui peuvent prendre soin de démarrer le débogueur lorsqu'un signal spécifié est envoyé.

Un projet Summer of Code 2006 a cherché à ajouter des fonctionnalités de débogage à distance à pydb dans un module appelé mpdb .

rcoup
la source
On dirait qu'il a subi deux ( 1 ) réécritures ( 2 ) sans ajouter la fonction d'attachement par PID que je cherchais ...
Nickolay
3

J'ai piraté un outil qui s'attache à un processus Python en cours d'exécution et injecte du code pour obtenir un shell Python.

Voir ici: https://github.com/albertz/pydbattach

Albert
la source
1
Remarque: la façon de construire cela n'est pas évidente. Merci pour les liens que vous avez mis dans README: cela pyrasitea parfaitement fonctionné!
Nickolay
3

Cela peut être fait avec un excellent py-spy . Il s'agit d' un profileur d'échantillonnage pour les programmes Python , son travail consiste donc à s'attacher à un processus Python et à échantillonner leurs piles d'appels. Par conséquent, il py-spy dump --pid $SOME_PIDvous suffit de vider les piles d'appels de tous les threads du $SOME_PIDprocessus. En règle générale, il a besoin de privilèges élevés (pour lire la mémoire du processus cible).

Voici un exemple de l'apparence d'une application Python filetée.

$ 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)  
saaj
la source
2

pyringe est un débogueur qui peut interagir avec les processus python en cours d'exécution, imprimer les traces de pile, les variables, etc. sans aucune configuration a priori.

Bien que j'aie souvent utilisé la solution du gestionnaire de signaux dans le passé, il peut souvent être difficile de reproduire le problème dans certains environnements.

Dan Lecocq
la source
3
Apparemment, il est incompatible avec certaines versions de gdb (par exemple celle que j'avais installée sur ubuntu): github.com/google/pyringe/issues/16 , nécessitant une reconstruction manuelle. Un autre débogueur,, a pyrasitefonctionné comme un charme pour moi.
Nickolay
1

Il n'y a aucun moyen de se connecter à un processus Python en cours d'exécution et d'obtenir des résultats raisonnables. Ce que je fais si les processus se bloquent, c'est d'accrocher Strace et d'essayer de comprendre ce qui se passe exactement.

Malheureusement, souvent, Strace est l'observateur qui "fixe" les conditions de concurrence de sorte que la sortie est également inutile.

Armin Ronacher
la source
1
Ouais, c'est vrai. C'est dommage que thad pdb ne supporte pas l'attachement à un processus en cours ...
Bartosz Radaczyński
Ce n'est pas vrai. Voir la réponse par "spiv" ci-dessus, qui montre comment connecter gdb et obtenir une trace de pile Python.
Andrew Cooke
Ce n'est pas la même chose - ces macros gdb ne sont pas fiables et ne fournissent pas la pleine puissance / interface familière de pdb. Je souhaite souvent que quelqu'un ait écrit une petite application qui utiliserait ptrace pour injecter du bytecode Python dans un processus Python en cours d'exécution et le faire exécuter 'import pdb; pdb.set_trace () ', peut-être aussi après avoir redirigé temporairement sys.stdin / stdout.
Marius Gedminas
Ce n'est plus vrai, voir d'autres réponses pointant sur pyringe / pyrasite.
Nickolay
1

Vous pouvez utiliser PuDB , un débogueur Python avec une interface curses pour ce faire. Il suffit d'ajouter

from pudb import set_interrupt_handler; set_interrupt_handler()

à votre code et utilisez Ctrl-C lorsque vous souhaitez casser. Vous pouvez continuer cet interrompre à plusieurs reprises si vous le manquez et que vous souhaitez réessayer.

asmeurer
la source
Lorsque vous utilisez la commande ci-dessus dans django, n'oubliez pas d'exécuter un serveur correctement pour éviter les problèmes: "manage.py runserver --noreload --nothreading"
potar
1

Je suis dans le camp GDB avec les extensions python. Suivez https://wiki.python.org/moin/DebuggingWithGdb , ce qui signifie

  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

Considérez également info threadset thread apply all py-bt.

user7610
la source
est - il normal d'obtenir une réponse comme 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.lors de l' exécution py-bten gdb?
crookedleaf
1
ça ne fait rien. c'est parce que mon application s'exécutait en tant que sudo. j'avais aussi besoin de courir en gdb pyton <pid>tant que sudo.
crookedleaf
1

Comment déboguer n'importe quelle fonction dans la console :

Créez une fonction où vous utilisez pdb.set_trace () , puis la fonction que vous souhaitez déboguer.

>>> import pdb
>>> import my_function

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

Appelez ensuite la fonction créée:

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

Bon débogage :)

jakvb
la source
0

Je ne connais rien de similaire à la réponse de Java à SIGQUIT , vous devrez donc peut-être l' intégrer à votre application. Peut-être pourriez-vous créer un serveur dans un autre thread qui peut obtenir une trace de pile en réponse à un message quelconque?

Douglas Leeder
la source
0

utilisez le module d'inspection.

import inspect help (inspect.stack) Aide sur la pile de fonctions dans le module inspect:

stack (context = 1) Retourne une liste d'enregistrements pour la pile au-dessus de la trame de l'appelant.

Je trouve cela très utile en effet.

Alison R.
la source
0

En Python 3, pdb installera automatiquement un gestionnaire de signal la première fois que vous utiliserez c (ont (inue)) dans le débogueur. En appuyant sur Control-C par la suite, vous vous replongerez dedans. En Python 2, voici un one-liner qui devrait fonctionner même dans des versions relativement anciennes (testé en 2.7 mais j'ai vérifié la source Python en 2.4 et ça avait l'air correct):

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

pdb vaut la peine d'être appris si vous passez du temps à déboguer Python. L'interface est un peu obtuse mais devrait être familière à tous ceux qui ont utilisé des outils similaires, tels que gdb.

jtatum
la source
0

Si vous devez le faire avec uWSGI, il a Python Tracebacker intégré et il suffit de l'activer dans la configuration (le numéro est attaché au nom de chaque travailleur):

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

Une fois que vous avez fait cela, vous pouvez imprimer le backtrace simplement en vous connectant au socket:

uwsgi --connect-and-read /var/run/uwsgi/pytrace1
Michal Čihař
la source
0

Au moment où le code est exécuté, vous pouvez insérer ce petit extrait pour voir une trace de pile imprimée bien formatée. Il suppose que vous avez un dossier appelé logsdans le répertoire racine de votre projet.

# DEBUG: START DEBUG -->
import traceback

with open('logs/stack-trace.log', 'w') as file:
    traceback.print_stack(file=file)
# DEBUG: END DEBUG --!
Wayne Lambert
la source