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

我有一个Python应用程序,它不时卡住,我不知道在哪里。

是否有任何方法向Python解释器发出信号,以显示正在运行的确切代码?

某种飞行中的堆叠痕迹?

< em >相关问题:< / em >

162052 次浏览

Python -dv yourscript.py

这将使解释器以调试模式运行,并为您提供解释器正在做什么的跟踪。

如果你想交互调试代码,你应该像这样运行它:

Python -m PDB yourscript.py

它告诉python解释器使用模块“pdb”(python调试器)运行你的脚本,如果你这样运行它,解释器将以交互模式执行,很像GDB

< >强回溯< / >强模块有一些不错的函数,其中包括:

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

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

我不知道任何类似java对SIGQUIT的响应的东西,所以你可能必须在你的应用程序中构建它。也许您可以在另一个线程中创建一个服务器,以便在响应某种类型的消息时获得堆栈跟踪?

没有办法钩入正在运行的python进程并获得合理的结果。如果进程锁定,我所做的是连接strace并试图弄清楚到底发生了什么。

不幸的是,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或python向进程发送一个SIGUSR1信号:

    os.kill(pid, signal.SIGUSR1)

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

我有另一个脚本,做同样的事情,除了它通过管道与正在运行的进程通信(允许调试后台进程等)。在这里发布它有点大,但我已经将它添加为Python食谱

安装信号处理程序的建议很好,我经常使用它。例如,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通常可以让你很好地了解进程在做什么。

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

2006年夏天的一个代码项目研究了在名为mpdb的模块中向pydb添加远程调试功能。

在这里真正帮助我的是打扮得衣冠楚楚的小贴士(如果我有声誉点,我会投票并评论它),它可以从毫无准备的 Python进程中获得堆栈跟踪。除了它没有工作,直到我修改gdbinit脚本。所以:

使用inspect模块。

< p >进口检查 帮助(inspect.stack) 模块检查中的函数堆栈帮助:

< p >堆栈(上下文= 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)

我想把它作为注释添加到haridsv的反应,但我缺乏这样做的声誉:

我们中的一些人仍然停留在2.6以上的Python版本(thread .ident需要),所以我让代码在Python 2.5中工作(尽管没有显示线程名称):

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)

在Solaris上,你可以使用pstack(1)。不需要修改python代码。如。

# 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.

看看faulthandler模块,这是Python 3.3中的新模块。在Python 2中使用的faulthandler补丁在PyPI上可用。

我把一些工具连接到一个正在运行的Python进程中,并注入一些代码来获得一个Python shell。

看这里:https://github.com/albertz/pydbattach

我正在寻找一段时间的解决方案来调试我的线程,我发现它在这里感谢haridsv。我使用稍微简化的版本,使用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)

为了满足我的需要,我还按名称筛选线程。

你可以试试faulthandler模块。使用pip install faulthandler安装它,并添加:

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

在程序开始的时候。然后将SIGUSR1发送到您的进程(例如:kill -USR1 42),以显示所有线程的Python回溯到标准输出。阅读文档用于更多选项(例如:登录到文件)和其他显示回溯的方法。

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

你可以使用PuDB,一个带有curses接口的Python调试器来做到这一点。只需添加

from pudb import set_interrupt_handler; set_interrupt_handler()

到您的代码中,并在需要中断时使用Ctrl-C。你可以继续使用c,如果你错过了它,想再试一次,你可以再次打破多次。

如果你是在Linux系统上,使用带有Python调试扩展的gdb(可以在python-dbgpython-debuginfo包中)。它还有助于多线程应用程序、GUI应用程序和C模块。

使用以下命令运行程序:

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

这指示gdb为它准备python <programname>.py <arguments>run。

现在当你的程序挂起时,切换到gdb控制台,按Ctr + C并执行:

(gdb) thread apply all py-list

参见例子会话和更多信息在这里在这里

pyringe是一个调试器,可以与正在运行的python进程交互,打印堆栈跟踪,变量等,而不需要任何先验设置。

虽然我过去经常使用信号处理程序解决方案,但在某些环境中仍然很难重现这个问题。

在Python 3中,当你第一次在调试器中使用c(ont(inue))时,pdb会自动安装一个信号处理程序。然后按Control-C会让你回到那里。在Python 2中,这里有一个单行程序,即使在相对较旧的版本中也可以工作(在2.7中测试过,但我检查了Python源代码回到2.4,看起来还可以):

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

如果你花时间调试Python, pdb是值得学习的。这个界面有点迟钝,但是对于使用过类似工具(比如gdb)的人来说应该很熟悉。

可以使用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

(向@Albert致敬,在其他工具中,他的回答包含指向this的指针。)

如果你需要用uWSGI来做这件事,它有Python回溯内置,这只是在配置中启用它的问题(编号附加到每个worker的名称):

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

一旦你这样做了,你可以简单地通过连接到套接字打印反向跟踪:

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

我是GDB阵营的python扩展。遵循https://wiki.python.org/moin/DebuggingWithGdb,这意味着

  1. dnf install gdb python-debuginfosudo apt-get install gdb python2.7-dbg
  2. gdb python <pid of running process>
  3. py-bt

还要考虑info threadsthread apply all py-bt

如何调试任意函数在控制台:

创建函数,其中你使用pdb.set_trace (),然后你要调试的函数。

>>> import pdb
>>> import my_function


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

然后调用创建的函数:

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

愉快的调试:)

它可以用优秀的py-spy来完成。它是Python程序的抽样分析器,所以它的任务是附加到Python进程并对它们的调用堆栈进行采样。因此,要转储$SOME_PID进程中所有线程的调用堆栈,只需要py-spy dump --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)

在运行代码时,您可以插入这个小片段,以查看格式化良好的打印堆栈跟踪。它假设在项目的根目录下有一个名为logs的文件夹。

# DEBUG: START DEBUG -->
import traceback


with open('logs/stack-trace.log', 'w') as file:
traceback.print_stack(file=file)
# DEBUG: END DEBUG --!

你可以像这样使用hypno包:

hypno <pid> "import traceback; traceback.print_stack()"

这将把堆栈跟踪打印到程序的标准输出中。

或者,如果你不想打印任何东西到stdout,或者你没有访问它的权限(例如守护进程),你可以使用madbg包,这是一个python调试器,允许你附加到一个正在运行的python程序并在当前终端调试它。它类似于pyrasitepyringe,但更新,不需要gdb,并使用IPython作为调试器(这意味着颜色和自动完成)。

要查看正在运行的程序的堆栈跟踪,你可以运行:

madbg attach <pid>
在调试器shell中,输入: bt < / p >

免责声明——这两个包都是我写的

自Austin 3.3以来,您可以使用-w/--where选项来发出当前堆栈跟踪。看到https://stackoverflow.com/a/70905181/1838793

enter image description here

如果你想查看一个正在运行的Python应用程序,看看“live"调用堆栈时,你可以使用austin-tui (https://github.com/p403n1x87/austin-tui)。你可以从PyPI安装它。

pipx install austin-tui

注意,它需要austin二进制文件才能工作(https://github.com/p403n1x87/austin),但随后你可以使用

austin-tui -p <pid>