Python 程序的采样分析器
项目描述
py-spy:Python 程序的采样分析器
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 安装预构建的二进制轮子:
pip install py-spy
您还可以从GitHub Releases Page下载预构建的二进制文件。
如果你是 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 有三个子命令
record,top和dump:
记录
py-spy 支持使用该record命令将配置文件记录到文件中。例如,您可以通过以下方式生成python 进程的火焰图:
py-spy record -o profile.svg --pid 12345
# OR
py-spy record -o profile.svg -- python myprogram.py
这将生成一个交互式 SVG 文件,如下所示:
您可以更改文件格式以使用参数生成
速度范围配置文件或原始数据--format。有关py-spy record --help其他选项的信息,包括更改采样率、过滤以仅包含持有 GIL 的线程、分析本机 C 扩展、显示线程 ID、分析子进程等,请参阅参考资料。
最佳
Top 显示了 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 是如何工作的?
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 解释器附带符号,则很容易通过
根据 Python 版本取消引用interp_head 或变量来确定解释器的内存地址。_PyRuntime但是,许多 Python 版本在 Windows 上都带有剥离的二进制文件或没有相应的 PDB 符号文件。在这些情况下,我们通过 BSS 部分扫描看起来可能指向有效 PyInterpreterState 的地址,并检查该地址的布局是否符合我们的预期。
py-spy 可以配置本机扩展吗?
是的!py-spy 支持在 x86_64 Linux 和 Windows 上分析用 C/C++ 或 Cython 等语言编写的原生 python 扩展。您可以通过传递--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 创建py-spy record -- python myprogram.py进程sudo py-spy record --pid 123456(您可以通过设置ptrace_scope sysctl 变量来消除 Linux 上的此限制。
如何检测线程是否空闲?
py-spy 试图只包含来自正在运行代码的线程的堆栈跟踪,并排除正在休眠或空闲的线程。如果可能,py-spy 会尝试从操作系统获取此线程活动信息:通过在 /proc/PID/statLinux 上读取,通过在 OSX 上使用 mach
thread_basic_info
调用,以及通过查看当前 SysCall
在 Windows 上是否已知是空闲的。
这种方法有一些限制,尽管可能会导致空闲线程仍被标记为活动线程。首先,我们必须在暂停程序之前获取此线程活动信息,因为从暂停的程序中获取此信息将导致它始终返回空闲状态。这意味着存在潜在的竞争条件,我们获得线程活动,然后当我们获得堆栈跟踪时线程处于不同的状态。Linux 上的 FreeBSD 和 i686/ARM 处理器还没有实现查询操作系统的线程活动。在 Windows 上,在 IO 上被阻止的调用也不会被标记为空闲,例如从标准输入读取输入时。最后,在某些 Linux 调用中,我们使用的 ptrace attach 可能会导致空闲线程瞬间唤醒,从而在从 procfs 读取时导致误报。
您可以通过设置标志来禁用此功能,该--idle标志将包括 py-spy 认为空闲的帧。
GIL 检测如何工作?
我们通过查看 Python 3.6 及更早版本的符号所指向的 threadid 值并从Python 3.7 及更高版本中_PyThreadState_Current的结构中找出等价物来获得 GIL 活动。_PyRuntime这些符号可能不包含在您的 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 系统调用引起的。这可以通过
--cap-add SYS_PTRACE在启动 docker 容器时设置来覆盖。
或者,您可以编辑 docker-compose yaml 文件
your_service:
cap_add:
- SYS_PTRACE
请注意,您需要重新启动 docker 容器才能使此设置生效。
您还可以使用主机操作系统中的 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.io/docs/tasks/configure-pod-container/security-context/#set-capabilities-for-a-container 请注意,这将删除现有的 pod 并再次创建它们.
如何在 Alpine Linux 上安装 py-spy?
高山蟒蛇选择退出manylinux轮子:pypa/pip#3969(评论)。您可以通过以下方式覆盖此行为以使用 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 Evans在rbspy上的出色工作的启发。特别是,生成火焰图和 speedscope 文件的代码直接取自 rbspy,并且该项目使用从 rbspy 分离出来的 read-process-memory和proc-maps crates。
执照
py-spy 是在 MIT 许可证下发布的,请参阅LICENSE文件以获取全文。