web-dev-qa-db-fra.com

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

J'ai cette application Python qui reste bloquée de temps en temps et je ne peux pas savoir où.

Est-il possible de signaler à l'interprète Python de vous montrer le code exact en cours d'exécution? 

Une sorte de pile stack à la volée?

Questions connexes:

313
Seb

J'ai un module que j'utilise pour des situations comme celle-ci - où un processus fonctionnera longtemps, mais est parfois bloqué pour des raisons inconnues et non reproductibles. C'est un peu hacky, et ne fonctionne que sous 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, appelez simplement 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. A tout moment, envoyez au processus un signal SIGUSR1 à l'aide de kill ou en python:

    os.kill(pid, signal.SIGUSR1)

Cela provoquera une rupture du programme sur une console python au niveau où il se trouve actuellement, ce qui vous montrera la trace de la pile et vous permettra de manipuler les variables. Utilisez control-d (EOF) pour continuer à fonctionner (notez que vous interromprez probablement les E/S, etc. au moment de la signalisation, pour que ce ne soit 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 gros pour poster ici, mais je l'ai ajouté en tant que recette de livre de cuisine python .

298
Brian

La suggestion d'installer un gestionnaire de signal est bonne et je l'utilise beaucoup. Par exemple, bzr par défaut installe un gestionnaire SIGQUIT qui appelle pdb.set_trace() pour vous laisser immédiatement tomber dans un pdb Prompt. (Voir la source du module bzrlib.breakin pour les détails exacts.) Avec pdb, vous pouvez non seulement obtenir le suivi de la pile actuel, mais aussi inspecter les variables, etc. 

Cependant, il est parfois nécessaire de déboguer un processus pour lequel je n'avais pas la vision d'installer le gestionnaire de signaux. Sous Linux, vous pouvez associer gdb au processus et obtenir une trace de pile Python avec des macros gdb. Mettez http://svn.python.org/projects/python/trunk/Misc/gdbinit in ~/.gdbinit, puis:

  • Attachez gdb: gdb -pPID
  • Récupère la trace de la pile python: pystack

Malheureusement, ce n’est pas totalement fiable, mais cela fonctionne la plupart du temps.

Enfin, attacher strace peut souvent vous donner une bonne idée du processus.

139
spiv

Je suis presque toujours confronté à plusieurs threads et le thread principal ne fait généralement pas grand chose, donc le plus intéressant est de vider toutes les piles (ce qui ressemble plus au dump 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)
65
haridsv

Obtenir une trace de pile d'un programme python unprepared, s'exécutant dans un stock Python sans symboles de débogage peut être effectué avec pyrasite . A fonctionné comme un charme pour moi dans 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, entre autres outils.)

43
Nickolay
>>> import traceback
>>> def x():
>>>    print traceback.extract_stack()

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

Vous pouvez aussi bien formater la trace de la pile, voir le docs .

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 dans votre application. Ensuite, vous pouvez imprimer la pile en envoyant SIGUSR1 au processus Python en cours d'exécution.

33
Torsten Marek

Le module traceback a quelques fonctions Nice, parmi lesquelles: print_stack:

import traceback

traceback.print_stack()
26
gulgi

Vous pouvez essayer le module faulthandler . Installez-le en utilisant pip install faulthandler et ajoutez:

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

au début de votre programme. Envoyez ensuite SIGUSR1 à votre processus (ex: kill -USR1 42) pour afficher le suivi Python de tous les threads sur la sortie standard. Lisez la documentation pour plus d'options (ex: connexion à un fichier) et d'autres moyens d'afficher le suivi.

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

20
haypo

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 modifié le script gdbinit } _. Alors:

  • télécharger http://svn.python.org/projects/python/trunk/Misc/gdbinit } et le mettre dans ~/.gdbinit

  • éditez-le en remplaçant PyEval_EvalFrame par PyEval_EvalFrameEx [edit: n'est plus nécessaire; le fichier lié contient déjà cette modification à partir du 14/01/2010]

  • Attachez gdb: gdb -p PID

  • Récupère la trace de la pile python: pystack

19
Gunnlaugur Briem

python -dv yourscript.py

Cela obligera l’interprète à s’exécuter en mode débogage et à vous donner une trace de ce qu’il fait.

Si vous voulez déboguer le code de manière interactive, exécutez-le comme suit:

python -m pdb yourscript.py

Cela dit à l'interprète python d'exécuter votre script avec le module "pdb" qui est le débogueur de python. Si vous l'exécutez comme cela, l'interprète sera exécuté en mode interactif, un peu comme GDB.

10
Gustavo Rubio

J'ajouterais ceci comme commentaire à à la réponse de haridsv , mais la réputation me manque pour le faire:

Certains d'entre nous sont toujours bloqués sur une version de Python antérieure à la version 2.6 (requise pour Thread.ident). J'ai donc utilisé le code dans Python 2.5 (bien que le nom du thread ne soit pas 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)
10

Jetez un coup d’œil au module faulthandler , nouveau dans Python 3.3. Un faulthandler backport à utiliser dans Python 2 est disponible sur PyPI.

8
Matt Joiner

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.
6
Tim Foster

Si vous utilisez un système Linux, utilisez le nom génial gdb avec les extensions de débogage Python (vous pouvez utiliser le paquetage python-dbg ou python-debuginfo). Il est également utile pour les applications multithreads, les applications à interface graphique et les modules C.

Exécutez votre programme avec:

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

Ceci indique à gdb de le préparer python <programname>.py <arguments> et run.

Maintenant, lorsque vous programmez des blocages, passez à la console gdb, appuyez sur Ctr+C et exécuter:

(gdb) thread apply all py-list

Voir exemple de session et plus d'infos ici et ici .

6
anatoly techtonik

Je cherchais depuis longtemps 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 en utilisant 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.

5
Stefan

J'ai piraté ensemble un outil qui se fixe dans un processus Python en cours d'exécution et injecte du code pour obtenir un shell Python.

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

3
Albert

Il vaut la peine de regarder Pydb , "une version développée du débogueur Python librement basée sur le jeu de commandes gdb". Cela inclut les gestionnaires de signaux qui peuvent s’occuper de démarrer le débogueur lorsqu’un signal spécifié est envoyé.

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

3
rcoup

pyringe est un débogueur pouvant interagir avec les processus Python en cours d’exécution, les traces de pile d’impression, les variables, etc. sans configuration a priori.

Bien que j'aie souvent utilisé la solution de traitement de signal par le passé, il peut toujours être difficile de reproduire le problème dans certains environnements.

2
Dan Lecocq

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 de relier strace et d’essayer de comprendre ce qui se passe exactement.

Malheureusement, strace est souvent l’observateur qui "corrige" les conditions de concurrence de sorte que la sortie y soit également inutile.

1
Armin Ronacher

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

from pudb import set_interrupt_handler; set_interrupt_handler()

à votre code et utilisez Ctrl-C quand vous voulez casser. Vous pouvez continuer avec c et interrompre à nouveau plusieurs fois si vous le manquez et que vous voulez réessayer. 

1
asmeurer

Comment déboguer une fonction dans la console :

Créer une fonction où vous utilisez pdb.set_trace () , puis vous voulez déboguer.

>>> import pdb
>>> import my_function

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

Ensuite, appelez la fonction créée:

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

Bon débogage :)

1
jakvb

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 threads et thread apply all py-bt.

0
user7610

utilisez le module inspecter.

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

pile (contexte = 1) Renvoie une liste d'enregistrements pour la pile au-dessus du cadre de l'appelant.

Je trouve cela très utile en effet.

0
Bhaskar

Si vous avez besoin de le faire avec uWSGI, il a Python Tracebacker intégré et il est juste de l'activer dans la configuration (le numéro est associé au nom de chaque opérateur):

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

Une fois que cela est fait, vous pouvez imprimer la trace en connectant simplement à la prise:

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

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

0
Douglas Leeder

Dans Python 3, pdb installera automatiquement un gestionnaire de signaux la première fois que vous utilisez c(ont(inue)) dans le débogueur. Appuyez ensuite sur Ctrl-C pour vous y laisser tomber. En Python 2, voici un one-liner qui devrait fonctionner même dans des versions relativement anciennes (testé dans la version 2.7 mais j’ai vérifié l’origine du code Python à la version 2.4 et cela semblait 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 tout votre temps à déboguer Python. L'interface est un peu obtuse mais devrait être familière à quiconque a utilisé des outils similaires, tels que gdb.

0
jtatum