Mostra la traccia dello stack da un'applicazione Python in esecuzione


340

Ho questa applicazione Python che si blocca di tanto in tanto e non riesco a scoprire dove.

C'è un modo per segnalare l'interprete Python per mostrarti il ​​codice esatto in esecuzione?

Una specie di stacktrace al volo?

Domande correlate:



Risposte:


315

Ho un modulo che uso per situazioni come questa - in cui un processo sarà in esecuzione per molto tempo ma a volte si blocca per motivi sconosciuti e irriproducibili. È un po 'confuso, e funziona solo su unix (richiede segnali):

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

Per usare, basta chiamare la funzione hear () ad un certo punto all'avvio del programma (potresti persino attaccarlo in site.py per farlo usare da tutti i programmi Python) e lasciarlo funzionare. In qualsiasi momento, invia al processo un segnale SIGUSR1, usando kill, o in python:

    os.kill(pid, signal.SIGUSR1)

Ciò causerà il passaggio del programma a una console Python nel punto in cui si trova attualmente, mostrando la traccia dello stack e permettendoti di manipolare le variabili. Utilizzare control-d (EOF) per continuare a funzionare (sebbene si noti che probabilmente si interromperà qualsiasi I / O ecc. Nel punto in cui si segnala, quindi non è completamente non invadente.

Ho un altro script che fa la stessa cosa, tranne per il fatto che comunica con il processo in esecuzione attraverso una pipe (per consentire il debug di processi in background, ecc.). È un po 'grande pubblicare qui, ma l'ho aggiunto come una ricetta del ricettario in pitone .


1
Grazie! Questo è proprio quello che stavo cercando. Forse potresti anche pubblicare quello script con il supporto pipe su alcuni siti di snippet Python?
Seb

2
L'ho pubblicato sul sito del ricettario di Python - collegamento aggiunto.
Brian,

1
Avevo bisogno di aggiungere "readline di importazione" per abilitare le funzionalità della cronologia.
miracle2k,

2
Ottimo consiglio! Questo funziona anche per inviare il segnale, a tutti i processi contenenti la parola "mypythonapp": pkill -SIGUSR1 -f mypythonapp
Alexander

10
Se l'applicazione è bloccata, il ciclo dell'interprete Python potrebbe non essere in grado di eseguire per elaborare il segnale. Usa il faulthandlermodulo (e il suo backport trovato su PyPI) per un gestore di segnale di livello C che stamperà lo stack Python senza richiedere che il loop dell'interprete sia reattivo.
GPS

146

Il suggerimento di installare un gestore di segnale è buono e lo uso molto. Ad esempio, per impostazione predefinita bzr installa un gestore SIGQUIT che invoca pdb.set_trace()per rilasciarti immediatamente in un prompt pdb . (Vedi l' origine del modulo bzrlib.breakin per i dettagli esatti.) Con pdb puoi non solo ottenere la traccia dello stack corrente ma anche ispezionare le variabili, ecc.

Tuttavia, a volte ho bisogno di eseguire il debug di un processo in cui non avevo la lungimiranza per installare il gestore del segnale. Su Linux, puoi collegare gdb al processo e ottenere una traccia dello stack Python con alcune macro gdb. Mettere http://svn.python.org/projects/python/trunk/Misc/gdbinit in ~/.gdbinitpoi:

  • Allega gdb: gdb -p PID
  • Ottieni la traccia dello stack di Python: pystack

Purtroppo non è totalmente affidabile, ma funziona quasi sempre.

Infine, il collegamento stracepuò spesso darti una buona idea di cosa sta facendo un processo.


2
Brillante! Il comando pystack si blocca a volte, ma prima di farlo mi dà una traccia dello stack completa del processo, nelle righe di codice Python, senza aver bisogno di fare alcuna preparazione.
muudscope,

26
Aggiornamento minore: questa tecnica gdb (e codice aggiornato) è documentata su wiki.python.org/moin/DebuggingWithGdb C'è stato qualche sviluppo su questo fronte, documentato su quell'URL , e apparentemente gdb 7 ha un po 'di supporto per Python.
Nelson,

7
Per quanto posso dire, questo funziona davvero solo se hai simboli di debug compilati nel tuo binario python - ad esempio: hai eseguito il tuo programma con python2-dbg (su Ubuntu, questo è in un pacchetto separato python-dbg). Senza quei simboli, sembra che tu non ottenga molte informazioni utili.
drevicko,

1
nel mio caso questo ritorna Unable to locate python framead ogni comando
seriyPS

6
gdb 7+ - with-python support è fornito da python-gdb.py. Maggiori dettagli qui: chezsoi.org/lucas/blog/2014/11/07/en-gdb-python-macros
Lucas Cimon

71

Ho quasi sempre a che fare con più thread e generalmente il thread principale non sta facendo molto, quindi la cosa più interessante è scaricare tutti gli stack (che è più simile al dump di Java). Ecco un'implementazione basata su questo 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

Ottenere una traccia dello stack di un programma Python non preparato , eseguendo un Python di serie senza simboli di debug può essere fatto con pyrasite . Ha funzionato come un incantesimo per me su 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

(Hat tip to @Albert, la cui risposta conteneva un puntatore a questo, tra gli altri strumenti.)


5
Questo ha funzionato alla grande per me, dove dump_stacks.pyera semplicementeimport traceback; traceback.print_stack()
John Lehmann il

2
traceback -lti dà un elenco di script Python predefiniti che puoi usare ed dump_stacks.pyè uno di questi. Se stai usando il tuo (ad esempio per scrivere la traccia dello stack in un file), potrebbe essere saggio usare un nome diverso.
johndodo,

12
apt-get install gdb python-dbgSuggerimento importante: esegui (o equivalente) prima di eseguire il pirasite, altrimenti fallirà silenziosamente. Funziona come un fascino altrimenti!
johndodo,

L'ultima versione di pyrasite è stata nel 2012
Boris

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

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

Puoi anche formattare correttamente la traccia dello stack, vedi i documenti .

Modifica : per simulare il comportamento di Java, come suggerito da @Douglas Leeder, aggiungi questo:

import signal
import traceback

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

al codice di avvio nell'applicazione. Quindi è possibile stampare lo stack inviando SIGUSR1al processo Python in esecuzione.


2
Ciò stamperebbe solo la backtrace del thread principale. Devo ancora trovare una soluzione per vedere le tracce di tutti i thread. In effetti, a Python sembra mancare un'API per recuperare lo stack dall'oggetto Thread, sebbene threading.enumerate () dia accesso a tutti gli oggetti Thread.
haridsv,

Questo funziona alla grande su Cygwin. Tuttavia, stampa solo tre righe della traccia dello stack, ma è abbastanza per avere un indizio
slashdottir,

28

Il modulo traceback ha alcune belle funzioni, tra cui: print_stack:

import traceback

traceback.print_stack()

1
Per scrivere la traccia dello stack in un file usare: import traceback; f = open('/tmp/stack-trace.log', 'w') traceback.print_stack(file=f) f.close()
GuruM

1
+1 a @gulgi per la sua risposta facile da usare. Alcune delle altre risposte sembravano molto complicate per il mio semplice compito di ottenere la traccia dello stack di chiamate dalla funzione di uno script.
GuruM,

24

Puoi provare il modulo del gestore dei guasti . Installalo usando pip install faulthandlere aggiungi:

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

all'inizio del programma. Quindi inviare SIGUSR1 al processo (es:) per kill -USR1 42visualizzare il traceback Python di tutti i thread nell'output standard. Leggi la documentazione per ulteriori opzioni (es: accedi a un file) e altri modi per visualizzare il traceback.

Il modulo fa ora parte di Python 3.3. Per Python 2, vedere http://faulthandler.readthedocs.org/


20

Ciò che mi ha davvero aiutato qui è il suggerimento di spiv (che voterei e commenterei se avessi i punti reputazione) per ottenere una traccia dello stack da un processo Python non preparato . Solo che non ha funzionato fino a quando non ho modificato lo script gdbinit . Così:

  • scaricare http://svn.python.org/projects/python/trunk/Misc/gdbinit e inserirlo~/.gdbinit

  • modificarlo, cambiando PyEval_EvalFrameinPyEval_EvalFrameEx[modifica: non più necessario; il file collegato ha già questa modifica dal 14/01/2010]

  • Allega gdb: gdb -p PID

  • Ottieni la traccia dello stack di Python: pystack


Il gdbinit all'URL menzionato sembra già avere la patch che suggerisci. Nel mio caso, quando ho digitato pystack la mia CPU si è bloccata. Non so perché.
Jesse Glick,

2
No, non è così - non ero chiaro, scusa, perché quella linea appare in tre punti. La patch a cui ho collegato mostra quale avevo cambiato quando ho visto questo lavoro.
Gunnlaugur Briem,

2
Come la risposta di @ spiv, questo richiede che il programma sia eseguito con Python compilato con simboli di debug. Altrimenti otterrai soloNo symbol "co" in current context.
Nickolay il

12

Vorrei aggiungere questo come commento alla risposta di Haridsv , ma non ho la reputazione di farlo:

Alcuni di noi sono ancora bloccati su una versione di Python precedente alla 2.6 (richiesto per Thread.ident), quindi ho fatto funzionare il codice in Python 2.5 (anche se senza il nome del thread visualizzato) come tale:

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

Ciò farà funzionare l'interprete in modalità debug e ti darà una traccia di ciò che l'interprete sta facendo.

Se si desidera eseguire il debug interattivo del codice, è necessario eseguirlo in questo modo:

python -m pdb yourscript.py

Questo dice all'interprete Python di eseguire il tuo script con il modulo "pdb" che è il debugger di Python, se lo esegui in questo modo l'interprete verrà eseguito in modalità interattiva, proprio come GDB


Questo non risponde alla domanda. La domanda riguardava un processo già in corso.
dbn

11

Dai un'occhiata al faulthandlermodulo, nuovo in Python 3.3. Un faulthandlerbackport per l'uso in Python 2 è disponibile su PyPI.


2
Una risposta più recente di @haypo tratta questo aspetto in modo più dettagliato. Non sono sicuro di come questo di solito viene gestito su SO, ma mi sembra sbagliato avere due risposte essenzialmente duplicate ...
Nickolay,

7

Su Solaris, è possibile utilizzare pstack (1) Non sono necessarie modifiche al codice Python. per esempio.

# 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
Sembra che ci sia un programma Debian / Ubuntu pstackche fa la stessa cosa
Rory il

1
Sembra dare solo il backtrace sotto Linux, non il traceback di Python con nome file e numeri di riga.
Ogrisel,

6

Se usi un sistema Linux, usa la bellezza di gdbcon le estensioni di debug di Python (può essere dentro python-dbgo python-debuginfopacchetto). Aiuta anche con applicazioni multithread, applicazioni GUI e moduli C.

Esegui il tuo programma con:

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

Questo indica gdbdi prepararlo python <programname>.py <arguments>e rdisfarlo.

Ora quando il programma si blocca, passa alla gdbconsole, premi Ctr+Ced esegui:

(gdb) thread apply all py-list

Vedi sessione di esempio e maggiori informazioni qui e qui .


6

Stavo cercando da tempo una soluzione per eseguire il debug dei miei thread e l'ho trovata qui grazie a haridsv. Uso una versione leggermente semplificata utilizzando 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)

Per le mie esigenze, filtro anche i thread per nome.


3

Vale la pena guardare Pydb , "una versione espansa del debugger Python liberamente basata sul set di comandi gdb". Include i gestori di segnali che possono occuparsi dell'avvio del debugger quando viene inviato un segnale specificato.

Un progetto di Summer of Code del 2006 ha esaminato l'aggiunta di funzionalità di debug remoto a pydb in un modulo chiamato mpdb .


Sembra che abbia attraversato due ( 1 ) riscritture ( 2 ) senza aggiungere la funzione attach-by-PID che stavo cercando ...
Nickolay,

3

Ho hackerato insieme uno strumento che si collega a un processo Python in esecuzione e inietta del codice per ottenere una shell Python.

Vedi qui: https://github.com/albertz/pydbattach


1
Nota: non è ovvio come costruirlo. Grazie per i collegamenti che hai inserito in README: ha pyrasitefunzionato perfettamente!
Nickolay,

3

Può essere fatto con un'eccellente spia . È un profiler di campionamento per i programmi Python , quindi il suo compito è quello di collegarsi a processi Python e campionare i loro stack di chiamate. Quindi, py-spy dump --pid $SOME_PIDè tutto ciò che devi fare per scaricare stack di chiamate di tutti i thread nel file$SOME_PID processo. In genere necessita di privilegi di escalation (per leggere la memoria del processo di destinazione).

Ecco un esempio di come appare per un'applicazione Python con thread.

$ 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 è un debugger che può interagire con l'esecuzione di processi Python, tracce dello stack di stampa, variabili, ecc. senza alcuna configurazione a priori.

Anche se in passato ho usato spesso la soluzione di gestione dei segnali, può spesso essere difficile riprodurre il problema in determinati ambienti.


3
Apparentemente è incompatibile con alcuni build gdb (ad esempio quello che avevo installato su Ubuntu): github.com/google/pyringe/issues/16 , che richiedeva la ricostruzione manuale. Un altro debugger, ha pyrasitefunzionato come un incantesimo per me.
Nickolay,

1

Non c'è modo di collegarsi a un processo Python in esecuzione e ottenere risultati ragionevoli. Quello che faccio se i processi si bloccano è l'aggancio della striscia e il tentativo di capire cosa sta succedendo esattamente.

Sfortunatamente spesso lo strace è l'osservatore che "corregge" le condizioni della razza in modo che l'output sia inutile anche lì.


1
Sì, questo è vero. È un peccato anche se il pdb non supporta l'attacco a un processo in esecuzione ...
Bartosz Radaczyński,

Questo non è vero. Vedi la risposta di "spiv" sopra, che mostra come connettere gdb e ottenere una traccia dello stack Python.
Andrew Cooke,

Non è lo stesso: quelle macro gdb non sono affidabili e non forniscono l'interfaccia piena potenza / familiare di pdb. Vorrei spesso che qualcuno scrivesse una piccola app che usasse ptrace per iniettare del bytecode Python in un processo Python in esecuzione e farlo eseguire 'import pdb; pdb.set_trace () ', forse anche dopo il reindirizzamento temporaneo di sys.stdin / stdout.
Marius Gedminas,

Questo non è più vero, vedi altre risposte che puntano a piringe / pirasite.
Nickolay,

1

Per fare questo puoi usare PuDB , un debugger Python con un'interfaccia curses. Aggiungi

from pudb import set_interrupt_handler; set_interrupt_handler()

al tuo codice e usa Ctrl-C quando vuoi rompere. Puoi continuare ce rompere più volte se lo perdi e vuoi riprovare.


Quando si utilizza il comando sopra in django, non dimenticare di eseguire correttamente un server per evitare problemi: "manage.py corsa server --noreload --nothreading"
potar

1

Sono nel campo GDB con le estensioni di Python. Segui https://wiki.python.org/moin/DebuggingWithGdb , che 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

Considera anche info threadse thread apply all py-bt.


è normale per ottenere una risposta, come 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.quando si esegue py-btin gdb?
crookedleaf

1
non importa. è perché la mia app era in esecuzione come sudo. dovevo anche correre gdb pyton <pid>come sudo.
crookedleaf

1

Come eseguire il debug di qualsiasi funzione nella console :

Crea la funzione in cui usi pdb.set_trace () , quindi la funzione che desideri eseguire il debug.

>>> import pdb
>>> import my_function

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

Quindi chiama la funzione creata:

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

Buon debug :)



0

utilizzare il modulo di ispezione.

import inspect help (inspect.stack) Aiuto sullo stack di funzioni nel modulo inspect:

stack (context = 1) Restituisce un elenco di record per lo stack sopra il frame del chiamante.

Lo trovo davvero molto utile.


0

In Python 3, pdb installerà automaticamente un gestore di segnale la prima volta che usi c (ont (inue)) nel debugger. Premendo successivamente Control-C, ti riporterà di nuovo lì. In Python 2, ecco un one-liner che dovrebbe funzionare anche in versioni relativamente vecchie (testato in 2.7 ma ho ricontrollato la sorgente Python su 2.4 e sembrava a posto):

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

Vale la pena imparare pdb se passi del tempo a eseguire il debug di Python. L'interfaccia è un po 'ottusa ma dovrebbe essere familiare a chiunque abbia usato strumenti simili, come gdb.


0

Nel caso in cui sia necessario farlo con uWSGI, ha Python Tracebacker integrato ed è solo questione di abilitarlo nella configurazione (il numero è associato al nome per ciascun lavoratore):

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

Una volta fatto questo, è possibile stampare backtrace semplicemente collegandosi al socket:

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

0

Nel punto in cui viene eseguito il codice, è possibile inserire questo piccolo frammento per visualizzare una traccia dello stack stampata ben formattata. Presuppone che tu abbia una cartella chiamata logsnella directory principale del tuo progetto.

# DEBUG: START DEBUG -->
import traceback

with open('logs/stack-trace.log', 'w') as file:
    traceback.print_stack(file=file)
# DEBUG: END DEBUG --!
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.