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

时间:2020-03-06 14:42:31  来源:igfitidea点击:

我有这个Python应用程序,它有时会卡住,我找不到位置。

有什么方法可以让Python解释器向我们显示正在运行的确切代码吗?

某种动态堆栈跟踪?

相关问题:

  • 从Python代码中的方法打印当前调用堆栈
  • 检查正在运行的进程在做什么:打印未执行的Python程序的堆栈跟踪

解决方案

python -dv yourscript.py

这将使解释器以调试模式运行,并为我们提供解释器正在做什么的痕迹。

如果要交互式调试代码,则应按以下方式运行它:

python -m pdb yourscript.py

这告诉python解释器使用模块" pdb"(即python调试器)运行脚本,如果我们像这样运行解释器,则解释器将以交互模式执行,就像GDB一样

追溯模块具有一些不错的功能,其中包括:print_stack:

import traceback

traceback.print_stack()

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

到应用程序中的启动代码。然后,我们可以通过向正在运行的Python进程发送" SIGUSR1"来打印堆栈。

我不知道任何类似于Java对SIGQUIT的响应,因此我们可能必须将其内置到应用程序中。也许我们可以在另一个线程中创建一个服务器,该服务器可以在响应某种消息时获取堆栈跟踪信息?

无法挂接到正在运行的python进程中并获得合理的结果。如果进程锁定,我该怎么做就挂勾strace并试图弄清楚到底发生了什么。

不幸的是,strace经常是观察者"修复"竞态条件,因此输出在那里也无用。

我有用于这样的情况的模块,在这种情况下,一个进程将长时间运行,但有时由于未知且不可复制的原因而卡住。它有点hacky,并且只能在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食谱。

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

但是,有时我需要调试一个没有先见之明的进程来安装信号处理程序。在linux上,我们可以将gdb添加到该进程并获取带有某些gdb宏的python堆栈跟踪。将http://svn.python.org/projects/python/trunk/Misc/gdbinit放在〜/ .gdbinit中,然后:

  • 添加gdb:gdb -p``PID
  • 获取python堆栈跟踪:pystack

不幸的是,它并不是完全可靠的,但是大多数情况下它都可以工作。

最后,添加strace通常可以使我们很好地了解一个进程在做什么。

值得一看的是Pydb,"它是基于gdb命令集的宽松版本的Python调试器的扩展版本"。它包括信号管理器,该管理器可以在发送指定信号时负责启动调试器。

2006年的Code Summer项目研究了在名为mpdb的模块中向pydb添加远程调试功能。

spiv的技巧(如果我有信誉点,我会投赞成票并进行评论),这对我真正有所帮助。除非在我修改gdbinit脚本之前它不起作用。所以:

  • 下载http://svn.python.org/projects/python/trunk/Misc/gdbinit并将其放在〜/ .gdbinit
  • 编辑它,将PyEval_EvalFrame更改为PyEval_EvalFrameEx[编辑:不再需要;截至2010年1月14日,链接文件已具有此更改]
  • 添加gdb:gdb -p PID
  • 获取python堆栈跟踪:pystack

使用检查模块。

import inspect
      help(inspect.stack)
      Help on function stack in module inspect:

堆栈(上下文= 1)
返回调用者框架上方的堆栈的记录列表。

我确实确实非常有帮助。

我几乎总是与多个线程打交道,而主线程通常不会做很多事情,所以最有趣的是转储所有堆栈(这更像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)