18 个版本

0.3.14 2022年9月7日
0.3.12 2022年5月17日
0.3.11 2021年11月13日
0.3.8 2021年7月31日
0.1.8 2018年9月22日

#74 in 性能分析

Download history 134/week @ 2024-03-13 197/week @ 2024-03-20 86/week @ 2024-03-27 412/week @ 2024-04-03 230/week @ 2024-04-10 136/week @ 2024-04-17 256/week @ 2024-04-24 110/week @ 2024-05-01 126/week @ 2024-05-08 102/week @ 2024-05-15 359/week @ 2024-05-22 283/week @ 2024-05-29 337/week @ 2024-06-05 359/week @ 2024-06-12 92/week @ 2024-06-19 99/week @ 2024-06-26

927 每月下载量
用于 3 个 crate (2 直接使用)

MIT 许可

1MB
26K SLoC

Rust 20K SLoC // 0.0% comments C 5K SLoC // 0.0% comments Python 141 SLoC // 0.4% comments Shell 34 SLoC // 0.1% comments Forge Config 21 SLoC Cython 6 SLoC // 0.4% comments

py-spy: Python 程序的采样分析器

Build Status FreeBSD Build Status

py-spy 是一个 Python 程序的采样分析器。它允许您在不重启程序或修改代码的任何方式下可视化您的 Python 程序花费时间的地方。py-spy 具有极低的开销:它是用 Rust 编写的,以速度著称,并且不与被分析的 Python 程序运行在同一个进程中。这意味着 py-spy 在生产 Python 代码上使用是安全的。

py-spy 在 Linux、OSX、Windows 和 FreeBSD 上运行,支持分析所有最新的 CPython 解释器版本(版本 2.3-2.7 和 3.3-3.10)。

安装

可以从 PyPI 使用预构建的二进制 wheel 安装

pip install py-spy

您还可以从 GitHub 发布页面 下载预构建的二进制文件。

如果您是 Rust 用户,也可以使用以下命令安装 py-spy:cargo install py-spy

在 macOS 上,py-spy 在 Homebrew 中,可以使用以下命令安装:brew install py-spy

在 Arch Linux 上,py-spy 在 AUR 中,可以使用以下命令安装:yay -S py-spy

在 Alpine Linux 上,py-spy 在测试仓库中,可以使用以下命令安装:apk add py-spy --update-cache --repository http://dl-3.alpinelinux.org/alpine/edge/testing/ --allow-untrusted

用法

py-spy 从命令行工作,接受您想要采样的程序的 PID 或您想要运行的 Python 程序的命令行。py-spy 有三个子命令 recordtopdump

record

py-spy 支持使用 record 命令将配置文件记录到文件中。例如,您可以通过以下操作生成您 Python 进程的 火焰图

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

这将生成一个类似以下交互式 SVG 文件:

flame graph

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

top

Top 显示了您的 Python 程序中耗时最多的函数的实时视图,类似于 Unix 的 top 命令。运行 py-spy 与以下命令:

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

将显示您 Python 程序的高层次实时视图

console viewer demo

dump

py-spy 还可以使用 dump 命令显示每个 Python 线程当前的调用栈

py-spy dump --pid 12345

这将输出每个线程的调用栈和一些其他基本进程信息到控制台

dump output

这在您只需要一个调用栈来找出您的 Python 程序卡住在哪里的情况很有用。此命令还可以通过设置 --locals 标志来打印出与每个堆栈帧关联的局部变量。

常见问题解答

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

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

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

py-spy 如何工作?

py-spy 通过在 Linux 上使用 process_vm_readv 系统调用、在 OSX 上使用 vm_read 调用或在 Windows 上使用 ReadProcessMemory 调用直接读取 Python 程序的内存来工作。

确定 Python 程序的调用栈是通过查看全局 PyInterpreterState 变量来获取解释器中运行的所有 Python 线程,然后迭代每个线程中的每个 PyFrameObject 来获取调用栈。由于 Python ABI 在不同版本之间有所变化,我们使用 rust 的 bindgen 为我们关心的每个 Python 解释器类生成不同的 rust 结构,并使用这些生成的结构来了解 Python 程序中的内存布局。

由于地址空间布局随机化,获取Python解释器的内存地址可能有些棘手。如果目标Python解释器包含符号,则可以通过解引用interp_head_PyRuntime变量来轻松地找出解释器的内存地址,具体取决于Python版本。然而,许多Python版本在Windows上发布时带有去除符号的二进制文件,或者没有相应的PDB符号文件。在这些情况下,我们会扫描BSS部分以查找可能指向有效PyInterpreterState的地址,并检查该地址的布局是否符合我们的预期。

py-spy可以分析本地扩展吗?

是的!py-spy支持分析用C/C++或Cython等语言编写的本地Python扩展,在x86_64 Linux和Windows上。您可以通过在命令行中传递--native来启用此模式。为了获得最佳结果,您应该用符号编译您的Python扩展。对于Cython程序来说,也值得注意的一点是,py-spy需要生成的C或C++文件来返回原始.pyx文件的行号。请阅读博客文章以获取更多信息。

我如何分析子进程?

通过将--subprocesses标志传递给记录视图或顶部视图,py-spy还将包括任何是目标程序的子进程的Python进程的输出。这对于分析使用多进程或gunicorn工作池的应用程序非常有用。py-spy将监视新进程的创建,并自动将其附加到进程中,并将它们的样本包括在输出中。记录视图将包括每个程序的PID和cmdline,子进程将作为其父进程的子进程出现。

何时需要以sudo运行?

py-spy通过从不同的Python进程读取内存来工作,这可能因操作系统和系统设置而受到安全限制。在许多情况下,以root用户(使用sudo或类似方式)运行可以绕过这些安全限制。OSX始终需要以root身份运行,但在Linux上则取决于您如何启动py-spy以及系统安全设置。

在Linux上,默认配置是在附加到非子进程的进程时要求root权限。对于py-spy来说,这意味着您可以通过让py-spy创建进程来无需root权限进行分析(py-spy record -- python myprogram.py),但通过指定PID来附加到现有进程通常需要root权限(sudo py-spy record --pid 123456)。您可以通过设置ptrace_scope sysctl变量来在Linux上取消此限制。

如何检测线程是否空闲?

py-spy试图仅包含活动运行代码的线程的堆栈跟踪,并排除处于休眠状态或其他空闲状态的线程。当可能时,py-spy会尝试从操作系统获取这些线程活动信息:在Linux上通过读取/proc/PID/stat,在OSX上通过使用mach thread_basic_info调用,以及在Windows上通过查看当前SysCall是否已知为空闲

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

您可以通过设置--idle标志来禁用此功能,这将包括py-spy认为的空闲帧。

GIL检测是如何工作的?

我们通过查看指向Python 3.6及以前版本中_PyThreadState_Current符号的线程id值来获取GIL活动,并通过确定Python 3.7及以后版本中_PyRuntime结构的等效值来获取GIL活动。这些符号可能不包括在您的Python发行版中,这将导致解析哪个线程持有GIL失败。当前的GIL使用情况也在top视图中以%GIL的形式显示。

传递--gil标志将仅包括持有全局解释器锁(Global Interpreter Lock)的线程的跟踪。在某些情况下,这可能更准确地反映您的Python程序如何花费其时间,尽管您应该意识到这可能会错过在仍然活跃时释放GIL的扩展中的活动。

为什么我在OSX上对/usr/bin/python进行性能分析时遇到问题?

OSX有一个名为系统完整性保护的功能,该功能阻止即使是root用户也无法从位于/usr/bin中的任何二进制文件读取内存。不幸的是,这包括随OSX一起提供的Python解释器。

处理此问题有几个不同的方法

  • 您可以安装不同的Python发行版。内置的Python将在未来的OSX中被移除,您可能还希望迁移到Python 2之外的版本。
  • 您可以使用virtualenv在SIP不适用的环境中运行系统Python。
  • 您可以通过禁用系统完整性保护

如何在Docker中运行py-spy?

在Docker容器内运行py-spy通常也会出现权限拒绝错误,即使以root身份运行。

此错误是由于Docker限制了我们所使用的process_vm_readv系统调用。可以通过在启动Docker容器时设置--cap-add SYS_PTRACE来覆盖此限制。

或者,您可以编辑docker-compose yaml文件

your_service:
   cap_add:
     - SYS_PTRACE

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

您还可以使用主机OS中的py-spy来分析运行在Docker容器内的正在运行的过程。

如何在Kubernetes中运行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.ac.cn/docs/tasks/configure-pod-container/security-context/#set-capabilities-for-a-container 注意,这将删除现有的Pod并重新创建。

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

Alpine的python默认不支持manylinux wheels: pypa/pip#3969 (comment)。您可以通过以下步骤使用pip在Alpine上安装py-spy来覆盖此行为:

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

或者您可以从GitHub发行页面下载musl二进制文件。

如何避免暂停Python程序?

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

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

py-spy支持32位Windows吗?与PyPy集成?与Python2的USC2版本一起工作?

目前还不支持。

如果您希望在py-spy中看到某些功能,请为相应的问题点赞或创建一个新的问题,描述缺少的功能。

如何强制在管道到分页器时输出彩色信息?

py-spy遵循CLICOLOR规范,因此将CLICOLOR_FORCE=1设置到您的环境中,py-spy将即使在管道到分页器时也会打印彩色输出。

致谢

py-spy深受Julia Evansrbspy上的出色工作的启发。特别是,生成火焰图和speedscope文件的代码直接来自rbspy,该项目使用了从rbspy分离出来的read-process-memoryproc-maps crate。

许可证

py-spy在MIT许可证下发布,有关完整文本,请参阅LICENSE文件。

依赖关系

~16–28MB
~442K SLoC