显示正在运行的Python应用程序的堆栈跟踪
我有一个Python应用程序,有时候会卡住,我找不到原因。
有没有办法让Python解释器告诉我正在运行的具体代码呢?
有没有什么方法可以实时查看调用栈的信息?
相关问题:
29 个回答
我几乎总是在处理多个线程,而主线程通常没什么事情可做,所以我最感兴趣的是查看所有线程的状态(这更像是Java中的状态转储)。下面是基于这个博客的实现:
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)
建议安装信号处理器是个不错的主意,我经常用到这个方法。比如,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
也常常能让你了解一个进程在做什么。
我有一个模块可以用在这种情况——当一个程序需要长时间运行,但有时会因为一些未知的原因卡住。这种方法有点小技巧,而且只适用于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命令发送一个SIGUSR1信号给这个进程,或者在python中这样做:
os.kill(pid, signal.SIGUSR1)
这样做会让程序在当前运行的位置中断,进入一个python控制台,显示出堆栈跟踪信息,并让你可以操作变量。使用control-d(EOF)可以继续运行(不过要注意,你可能会在发送信号的那一刻中断任何输入输出操作,所以这并不是完全不干扰的)。
我还有另一个脚本,功能类似,只不过它是通过管道与正在运行的进程通信(这样可以调试后台进程等)。这个脚本有点长,不能在这里全部贴出来,但我把它放在了一个python食谱里。