显示正在运行的Python应用程序的堆栈跟踪

395

相关链接:https://dev59.com/IG855IYBdhLWcg3wvnOE - Nikana Reklawyks
相关内容请参考:https://wiki.python.org/moin/DebuggingWithGdb - Trevor Boyd Smith
这个问题debugging - Get stacktrace from stuck python process - Stack Overflow询问了当信号不起作用时的情况(尽管下面的一些其他答案也解决了这种情况)。 - user202729
29个回答

350

我有一个模块可以用于这种情况——当一个进程将长时间运行但有时因未知和无法重现的原因而停滞不前。它有点粗糙,并且仅适用于Unix(需要信号):

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

使用时,只需在程序启动时的某个时间点调用listen()函数(甚至可以将其放入site.py中,让所有Python程序都使用它),然后让它运行。 在任何时候,使用kill或在Python中发送SIGUSR1信号给进程:

    os.kill(pid, signal.SIGUSR1)

这会导致程序在当前点中断,并进入Python控制台,显示堆栈跟踪,让您操作变量。使用control-d(EOF)继续运行(请注意,您可能会在发出信号的点中断任何I/O等,因此它并不完全是非侵入性的)。

我还有另一个脚本,它实现了相同的功能,但通过管道与正在运行的进程通信(以允许调试后台进程等)。这个脚本太长了,无法在此处发布,但我已将其添加为Python cookbook recipe


2
我现在已经将它发布到Python Cookbook网站上了 - 已添加链接。 - Brian
1
我需要添加 "import readline" 来启用历史记录功能。 - miracle2k
2
太好了!这也适用于向包含单词“mypythonapp”的所有进程发送信号:pkill -SIGUSR1 -f mypythonapp。 - Alexander
12
如果应用程序卡住了,Python 解释器循环可能无法运行以处理信号。使用 faulthandler 模块(以及其在 PyPI 上找到的后移版本)进行 C 级别的信号处理程序,它将打印 Python 堆栈而无需使解释器循环处于响应状态。 - gps
1
如果您只需要堆栈跟踪的简短版本,请使用以下代码:signal.signal(signal.SIGUSR1, lambda sig, frame: traceback.print_stack()) - Luca Marturana
显示剩余9条评论

155
安装信号处理程序的建议是很好的,我经常使用它。例如,默认情况下,bzr会安装一个SIGQUIT处理程序,该处理程序调用pdb.set_trace(),并立即将您带入pdb提示符中。(有关详细信息,请参见bzrlib.breakin模块的源代码。)借助pdb,您不仅可以获取当前堆栈跟踪(使用(w)here命令),还可以检查变量等。

然而,有时我需要调试一个我没有预先安装信号处理程序的进程。在Linux上,您可以将gdb附加到该进程,并使用一些gdb宏获取python堆栈跟踪。将http://svn.python.org/projects/python/trunk/Misc/gdbinit放入~/.gdbinit中,然后执行以下步骤:

  • 附加gdb:gdb -p PID
  • 获取Python堆栈跟踪:pystack

不幸的是,这并非总是可靠的,但它大多数时候都有效。还可以参见https://wiki.python.org/moin/DebuggingWithGdb

最后,附加strace通常可以让您很好地了解进程正在执行什么操作。


2
太棒了!pystack命令有时会锁定,但在它锁定之前,它会给我提供一个完整的进程堆栈跟踪,在Python代码行中,而无需进行任何准备工作。 - muudscope
28
小更新:这种GDB技巧(以及更新的代码)已记录在http://wiki.python.org/moin/DebuggingWithGdb中。在此方面有一些发展,该URL有记录,并且显然gdb 7具有一些Python支持。 - Nelson
8
据我所知,仅在您将调试符号编译到Python二进制文件中时才能实现此功能。例如:您使用python2-dbg运行程序(在Ubuntu上,这是一个单独的软件包 python-dbg)。如果没有这些符号,您似乎获得的信息不太有用。 - drevicko
1
在我的情况下,每个命令都会返回“无法定位Python框架”。 - seriyPS
6
python-gdb.py 提供了对 gdb 7+ 的 Python 支持。更多细节请参阅:https://chezsoi.org/lucas/blog/2014/11/07/en-gdb-python-macros/。 - Lucas Cimon

80

我几乎总是要处理多个线程,主线程通常没有做太多事情,所以最有趣的是转储所有堆栈(更像Java的dump)。这是基于这篇博客的实现:

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)

1
Python 3有一个更简单的解决方案-调用traceback.print_stack() - fafrd
print_stack 函数会打印哪些线程的堆栈信息? - dfrankow
请参见 https://dev59.com/RnNA5IYBdhLWcg3wQ7cw#24334576 了解如何在Python 3中打印所有线程。 - dfrankow

59

使用pyrasite可以在没有调试符号的标准Python环境中获取未经准备的Python程序的堆栈跟踪。在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
< p >(向@Albert致敬,他的答案中包含了指向此工具以及其他工具的指针。)


5
这个方法对我非常有效,其中 dump_stacks.py 只是一个简单的 import traceback; traceback.print_stack() - John Lehmann
2
traceback -l会给你提供一份预定义的Python脚本列表,其中之一便是 dump_stacks.py。如果你使用自己的脚本(例如将堆栈跟踪写入文件),最好使用不同的名称。 - johndodo
15
重要提示:在运行pyrasite之前,运行apt-get install gdb python-dbg(或等效命令),否则它将悄无声息地失败。除此之外,它的功能非常好! - johndodo
6
Pyrasite 的最后一个版本发布于2012年。 - user3064538
(免责声明:我的程序包)我有一个分支pyrasite-ng,修复了各种已报告的错误。 - user202729
另外,Pyrasite-Shell对我来说卡住了。 - dfrankow

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

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

您也可以优美地格式化堆栈跟踪,参见文档

编辑:为了模拟Java的行为,如@Douglas Leeder所建议的那样,添加以下内容:

import signal
import traceback

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

将以下代码添加到您应用程序的启动代码中,然后发送SIGUSR1到正在运行的Python进程即可打印堆栈信息。


5
这只会打印主线程的回溯信息。我还没有找到查看所有线程跟踪信息的解决方案。实际上,Python似乎缺少从Thread对象中检索堆栈的API,尽管threading.enumerate()可以访问所有Thread对象。 - haridsv
这在cygwin上运行得很好。虽然它只打印了三行堆栈跟踪,但这已经足够提供线索了。 - slashdottir

30

traceback 模块有一些不错的函数,其中包括:print_stack:

import traceback

traceback.print_stack()

3
要将堆栈跟踪写入文件,请使用以下代码:
import traceback; f = open('/tmp/stack-trace.log', 'w') traceback.print_stack(file=f) f.close()
- GuruM
2
对于他易于使用的答案,给@gulgi加上一个+1。其他一些答案对于我这个简单任务——从脚本的函数中获取调用堆栈跟踪——看起来非常复杂。 - GuruM

27

您可以尝试使用faulthandler模块。使用pip install faulthandler安装它,然后添加:

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

在你的程序开头导入faulthandler模块。然后向你的进程发送SIGUSR1信号(例如:kill -USR1 42),以将所有线程的Python traceback显示到标准输出。 阅读文档 以获取更多选项(例如:记录到文件)和其他显示traceback的方法。

该模块现已成为Python 3.3的一部分。对于Python 2,请参见http://faulthandler.readthedocs.org/.


这个方法对我很有效。我的应用程序一直崩溃,而且我不知道原因。幸亏有了faulthandler,我能够找到导致崩溃的代码行。我之前试图在其他线程中直接设置几个主要UI元素的状态,而没有使用信号在主线程中进行操作。 - DannyG

20

这里真正帮助了我解决问题的是 spiv的提示(如果我有声望分数,我会为此点赞并评论),可以从一个未准备好的Python进程中获取堆栈跟踪。但在我修改gdbinit脚本之前它是不起作用的。所以:

  • 下载 https://svn.python.org/projects/python/trunk/Misc/gdbinit 并将其放入 ~/.gdbinit

  • 编辑它,将 PyEval_EvalFrame 更改为 PyEval_EvalFrameEx [编辑:不再需要;截至2010年1月14日,链接的文件已经具有此更改]

  • 附加 gdb:gdb -p PID

  • 获取Python堆栈跟踪:pystack


提到的URL中的gdbinit似乎已经有了您建议的补丁。在我的情况下,当我键入pystack时,我的CPU就会卡住。不确定原因。 - Jesse Glick
2
不,它不行 — 对不起,我表达不清楚,因为这一行出现在三个地方。我链接的补丁显示了我看到此工作时更改的位置。 - Gunnlaugur Briem
3
与 @spiv 的回答类似,这需要在使用带有调试符号的编译过的 Python 程序下运行。否则,你将只会得到 No symbol "co" in current context. 的结果。 - Nickolay

16

使用出色的py-spy可以完成。它是Python程序的抽样性能分析器,因此它的工作是连接到Python进程并对其调用堆栈进行采样。因此,py-spy dump --pid $SOME_PID就足以转储$SOME_PID进程中所有线程的调用堆栈。通常需要提升权限(以读取目标进程的内存)。

以下是一个多线程Python应用程序的示例。

$ 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)  

这个可以运行,但是@kmaork的答案中的hypno会产生一个Python回溯,这对于找出问题所在非常有用。 - Kiran Jonnalagadda

14

python -dv yourscript.py

这将使解释器运行在调试模式下,并给你提供解释器正在执行的跟踪信息。

如果您想要交互式地调试代码,您应该像这样运行它:

python -m pdb yourscript.py

这告诉python解释器使用"pdb"模块来运行您的脚本,"pdb"是python调试器,如果您以这种方式运行它,解释器将以交互模式执行,就像GDB一样。


1
这并没有回答问题。问题是关于一个已经运行的进程的。 - dbn
什么是 -dv?有文档链接吗? - Nathan B

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接