Py-spy-Python程序的采样分析器

PY-SPY是一个用于Python程序的采样分析器。它允许您可视化Python程序正在花费时间的内容,而无需重新启动程序或以任何方式修改代码。PY-SPY的开销非常低:为了提高速度,它是用Rust编写的,并且不会在与分析过的Python程序相同的进程中运行。这意味着对生产Python代码使用py-spy是安全的

PY-SPY可以在Linux、OSX、Windows和FreeBSD上运行,并支持分析所有最新版本的CPython解释器(版本2.3-2.7和3.3-3.9)

安装

可以通过以下方式从PyPI安装预制的双轮:

pip install py-spy

您也可以从下载预生成的二进制文件。GitHub Releases
Page
这包括ARM和FreeBSD的二进制文件,它们不能使用pip安装。如果您是铁锈用户,py-spy还可以安装:cargo install py-spy在Arch Linux上,py-spy is in AUR并且可以与安装在一起yay -S py-spy

用法

PY-SPY从命令行工作,并获取要从中采样的程序的PID或要运行的python程序的命令行。Py-spy有三个子命令recordtopdump

录制

Py-spy支持使用record指挥部。例如,您可以生成一个flame graph通过执行以下操作,对您的python进程执行以下操作:

py-spy record -o profile.svg --pid 12345
# OR
py-spy record -o profile.svg -- python myprogram.py

这将生成一个交互式SVG文件,如下所示:

您可以更改文件格式以生成speedscope属性的配置文件或原始数据--format参数。看见py-spy record --help有关其他选项的信息,包括更改采样率、过滤以仅包括持有GIL的线程、分析本机C扩展、显示线程ID、分析子进程等

顶部

顶部显示哪些函数在您的python程序中占用的时间最长的实时视图,这与unix类似。top指挥部。使用以下选项运行py-spy:

py-spy top --pid 12345
# OR
py-spy top -- python myprogram.py

将显示Python程序的实时更新高级视图:

转储

Py-spy还可以显示每个python线程的当前调用堆栈,dump命令:

py-spy dump --pid 12345

这会将每个线程的调用堆栈以及其他一些基本进程信息转储到控制台:

这对于只需要一个调用堆栈来确定Python程序挂起的位置的情况很有用。此命令还能够打印出与每个堆栈帧相关联的局部变量,方法是将--locals旗帜

常见问题解答

为什么我们需要另一个Python分析器?

此项目旨在让您分析和调试任何正在运行的Python程序,即使该程序正在服务于生产流量

虽然还有许多其他的Python评测项目,但几乎所有项目都需要以某种方式修改评测的程序。通常,分析代码在目标python进程内运行,这会减慢并改变程序的操作方式。这意味着使用这些分析器调试生产服务中的问题通常不安全,因为它们通常会对性能产生明显影响

间谍是怎么工作的?

Py-spy的工作方式是直接读取python程序的内存,方法是使用process_vm_readvLinux上的系统调用,vm_read访问OSX或ReadProcessMemory在Windows上调用

计算Python程序的调用堆栈的方法是查看全局PyInterpreterState变量以获取解释器中运行的所有Python线程,然后迭代每个线程中的每个PyFrameObject以获得调用堆栈。由于Python ABI在不同版本之间会有所不同,因此我们使用Rust的bindgen要为我们关心的每个Python解释器类生成不同的RUST结构,并使用这些生成的结构来确定Python程序中的内存布局

由于以下原因,获取Python解释器的内存地址可能有点棘手Address Space Layout Randomization如果目标python解释器附带符号,那么通过取消引用interp_head_PyRuntime变量取决于Python版本。但是,许多Python版本都附带了剥离的二进制文件,或者在Windows上没有相应的PDB符号文件。在这些情况下,我们扫描BSS部分,查找看起来可能指向有效PyInterpreterState的地址,并检查该地址的布局是否符合我们的预期

py-spy配置文件可以本地扩展吗?

是!PY-SPY支持在x86_64Linux和Windows上分析用C/C++或Cython等语言编写的本机Python扩展。您可以通过传递以下命令来启用此模式--native在命令行上。为了获得最佳效果,您应该使用符号编译Python扩展。对于Cython程序,同样值得注意的是,py-spy需要生成的C或C++文件才能返回原始.pyx文件的行号。请阅读blog post了解更多信息

如何评价子流程?

通过传入--subprocesses标志添加到记录或顶视图,py-spy还将包括作为目标程序的子进程的任何python进程的输出。这对于分析使用多处理或独角兽工作线程池的应用程序非常有用。PY-SPY将监视正在创建的新进程,并自动附加到它们,并在输出中包含它们的样本。记录视图将包括调用堆栈中每个程序的PID和cmdline,子进程显示为其父进程的子进程

你什么时候需要以sudo的身份跑步?

PY-SPY通过从不同的Python进程读取内存来工作,出于安全原因,这可能是不允许的,具体取决于您的操作系统和系统设置。在许多情况下,以root用户(使用sudo或类似用户)身份运行可以绕过这些安全限制。OSX总是需要以root用户身份运行,但在Linux上,这取决于您如何启动py-spy和系统安全设置

在Linux上,默认配置是在附加到非子进程时需要root权限。对于py-spy,这意味着您可以通过让py-spy创建进程(py-spy record -- python myprogram.py),但是通过指定PID附加到现有进程通常需要root(sudo py-spy record --pid 123456)。您可以通过在Linux上设置ptrace_scope sysctl variable

如何检测线程是否空闲?

PY-SPY尝试仅包括来自活动运行代码的线程的堆栈跟踪,并排除休眠或空闲的线程。如果可能,py-spy尝试从OS获取此线程活动信息:通过读入/proc/PID/stat在Linux上,通过使用machthread_basic_info调用OSX,并通过查看当前系统调用是否known to be
idle
在Windows上

这种方法有一些限制,不过这可能会导致空闲线程仍然被标记为活动的。首先,我们必须在暂停程序之前获取此线程活动信息,因为从暂停的程序中获取此信息将导致它总是返回此信息为空闲。这意味着存在潜在的竞争条件,在这种情况下,我们获得线程活动,然后当我们获得堆栈跟踪时,线程处于不同的状态。对于Linux上的FreeBSD和i686/ARM处理器,查询操作系统的线程活动也尚未实现。在Windows上,IO上被阻塞的调用也不会被标记为空闲,例如在从标准输入读取输入时。最后,在某些Linux调用中,我们正在使用的ptrace Attach可能会导致空闲线程暂时唤醒,从而导致从procfs读取时出现误报。出于这些原因,我们还有一个启发式回退,它将Python中已知的某些已知调用标记为空闲

您可以通过设置--idle标志,该标志将包括py-spy认为空闲的帧。

GIL检测是如何工作的?

我们通过查看_PyThreadState_Current符号(适用于Python3.6和更早版本),并通过从_PyRuntimePython3.7及更高版本中的struct。这些符号可能不包括在您的python发行版中,这将导致解析哪个线程持有GIL失败。当前的GIL使用情况也显示在top查看为%Gil

通过--gil标志将仅包括对挂起的线程的跟踪。Global Interpreter Lock在某些情况下,这可能是更准确地了解您的python程序是如何花费时间的,尽管您应该意识到,这将错过在仍处于活动状态时发布GIL的扩展中的活动

为什么在OSX上分析/usr/bin/python会出现问题?

OSX有一项功能,称为System Integrity Protection这可以防止甚至root用户从/usr/bin中的任何二进制文件中读取内存。不幸的是,这包括OSX附带的python解释器

有几种不同的方法来处理此问题:

我怎么在码头里运行py-spy?

在停靠容器中运行py-spy通常也会出现权限被拒绝的错误,即使在以root身份运行时也是如此

此错误是由docker限制我们正在使用的process_vm_readv系统调用引起的。可以通过设置--cap-add SYS_PTRACE当启动船坞容器时

或者,您也可以编辑docker编写的YAML文件

your_service:
   cap_add:
     - SYS_PTRACE

请注意,您需要重新启动停靠容器才能使此设置生效

您还可以从主机操作系统使用py-spy来分析在停靠容器内运行的进程

我怎么才能在库伯内斯运营Py-Spy?

Py-Spy需求SYS_PTRACE才能读取进程内存。默认情况下,Kubernetes会删除该功能,从而导致错误

Permission Denied: Try running again with elevated permissions by going 'sudo env "PATH=$PATH" !!'

解决此问题的推荐方法是编辑规范并添加该功能。对于部署,这是通过将以下内容添加到Deployment.spec.template.spec.containers

securityContext:
  capabilities:
    add:
    - SYS_PTRACE

有关这一点的更多详细信息,请单击此处:https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-capabilities-for-a-container请注意,这将删除现有Pod并重新创建这些Pod

如何在Alpine Linux上安装py-spy?

高山python选择退出manylinux轮子:pypa/pip#3969 (comment)您可以通过执行以下操作来覆盖此行为,以使用pip在Alpine上安装py-spy:

echo 'manylinux1_compatible = True' > /usr/local/lib/python3.7/site-packages/_manylinux.py

或者,您可以从以下位置下载MUSL二进制文件GitHub releases page

如何避免暂停Python程序?

通过设置--nonblocking选项,py-spy不会暂停您正在分析的目标python。虽然使用py-spy对进程进行采样的性能影响通常非常低,但设置此选项将完全避免中断正在运行的python程序。

设置此选项后,py-spy将在运行时从python进程读取解释器状态。由于我们用来读取内存的调用不是原子的,并且我们必须发出多个调用才能获得堆栈跟踪,这意味着在采样时偶尔会遇到错误。这可能表现为采样时错误率增加,或者部分堆栈帧包含在输出中

您如何通过PyPI分发Rust可执行二进制文件?

好的,实际上从来没有人问过我这个问题,但我想分享一下,因为这是一个非常可怕的黑客攻击,可能对其他人有用

我真的很想通过PyPI分发这个包,因为使用pip安装会使大多数Python程序员更容易在他们的系统上安装。不幸的是,installing executables as python
scripts isn’t something that setuptools supports

为了解决这个问题,我使用setuptools_rust包来构建py-spy二进制文件,然后覆盖distutils install command将构建的二进制文件复制到python脚本文件夹中。通过为支持的平台预置轮子来实现这一点,意味着我们可以使用pip安装py-spy,而不需要在要安装它的机器上安装Rust编译器。

py-spy是否支持32位Windows?与PyPy集成?使用USc2版本的Python2吗?

尚未=)

如果您希望在py-spy中看到一些功能,请竖起大拇指appropriate
issue
或者创建一个新的文档来描述缺少的功能

学分

Py-Spy在很大程度上受到了Julia Evans在……方面的出色工作rbspy特别是,生成火焰图和速度范围文件的代码直接取自rbspy,该项目使用read-process-memoryproc-maps从rbspy剥离出来的板条箱

许可证

Py-spy是在麻省理工学院的许可下释放的,请参阅LICENSE用于全文的文件