您可以使用--format
参数更改文件格式以生成 speedscope 配置文件或原始数据。有关其他选项的信息,请参阅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 还可以使用dump
命令显示每个 python 线程的当前调用堆栈:
py-spy dump --pid 12345
这会将每个线程的调用堆栈以及其他一些基本进程信息转储到控制台:
这对于您只需要一个调用堆栈来确定您的 python 程序挂在哪里的情况很有用。该命令还能够通过设置--locals
标志来打印与每个堆栈帧关联的局部变量。
该项目旨在让您分析和调试任何正在运行的 Python 程序,即使该程序正在服务生产流量。
虽然还有许多其他 python 分析项目,但几乎所有项目都需要以某种方式修改分析程序。通常,分析代码在目标 python 进程内部运行,这会减慢并改变程序的运行方式。这意味着使用这些分析器来调试生产服务中的问题通常并不安全,因为它们通常会对性能产生显着影响。
py-spy 的工作原理是使用 Linux 上的 process_vm_readv 系统调用、OSX 上的 vm_read 调用或 Windows 上的 ReadProcessMemory 调用直接读取 python 程序的内存。
通过查看全局 PyInterpreterState 变量来获取解释器中运行的所有 Python 线程,然后迭代每个线程中的每个 PyFrameObject 以获取调用堆栈,可以找出 Python 程序的调用堆栈。由于Python ABI在版本之间发生变化,我们使用rust的bindgen为我们关心的每个Python解释器类生成不同的rust结构,并使用这些生成的结构来确定Python程序中的内存布局。
由于地址空间布局随机化,获取 Python 解释器的内存地址可能有点棘手。如果目标 python 解释器附带了符号,那么通过根据 Python 版本取消引用interp_head
或_PyRuntime
变量,可以很容易地找出解释器的内存地址。然而,许多 Python 版本要么附带了剥离的二进制文件,要么附带了 Windows 上相应的 PDB 符号文件。在这些情况下,我们扫描 BSS 部分,查找看起来可能指向有效 PyInterpreterState 的地址,并检查该地址的布局是否符合我们的预期。
是的! 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 和命令行,子进程显示为其父进程的子进程。
py-spy 的工作原理是从不同的 python 进程读取内存,出于安全原因,这可能是不允许的,具体取决于您的操作系统和系统设置。在许多情况下,以 root 用户身份运行(使用 sudo 或类似命令)可以绕过这些安全限制。 OSX 始终需要以 root 身份运行,但在 Linux 上,这取决于您启动 py-spy 的方式以及系统安全设置。
在 Linux 上,默认配置是在附加到非子进程时需要 root 权限。对于 py-spy,这意味着您可以通过让 py-spy 创建进程( py-spy record -- python myprogram.py
)来进行分析,无需 root 访问权限,但通过指定 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 调用,以及查看当前 SysCall 是否已知处于空闲状态在 Windows 上。
这种方法有一些限制,但可能会导致空闲线程仍然被标记为活动线程。首先,我们必须在暂停程序之前获取该线程活动信息,因为从暂停的程序中获取该信息将导致它始终返回该线程处于空闲状态。这意味着存在潜在的竞争条件,我们获取线程活动,然后当我们获取堆栈跟踪时线程处于不同的状态。对于 Linux 上的 FreeBSD 和 i686/ARM 处理器,查询操作系统的线程活动也尚未实现。在 Windows 上,IO 上阻塞的调用也不会被标记为空闲,例如从 stdin 读取输入时。最后,在某些 Linux 上,我们使用的 ptrace Attach 调用可能会导致空闲线程暂时唤醒,从而在从 procfs 读取时导致误报。由于这些原因,我们还有一个启发式回退,将 python 中已知的某些已知调用标记为空闲。
您可以通过设置--idle
标志来禁用此功能,该标志将包括 py-spy 认为空闲的帧。
我们通过查看 Python 3.6 及更早版本的_PyThreadState_Current
符号指向的 threadid 值以及从 Python 3.7 及更高版本中的_PyRuntime
结构中找出等效值来获取 GIL 活动。这些符号可能不包含在您的 python 发行版中,这将导致解析哪个线程保留 GIL 失败。当前 GIL 使用情况也在top
视图中显示为 %GIL。
传递--gil
标志将仅包含持有全局解释器锁的线程的跟踪。在某些情况下,这可能是您的 python 程序如何花费时间的更准确的视图,尽管您应该意识到这会错过在仍处于活动状态时释放 GIL 的扩展中的活动。
OSX 有一个称为系统完整性保护的功能,甚至可以防止 root 用户从 /usr/bin 中的任何二进制文件读取内存。不幸的是,这包括 OSX 附带的 python 解释器。
有几种不同的方法可以处理这个问题:
即使以 root 身份运行,在 docker 容器内运行 py-spy 通常也会出现权限被拒绝的错误。
这个错误是由于docker限制了我们正在使用的process_vm_readv系统调用而引起的。这可以通过在启动 docker 容器时设置--cap-add SYS_PTRACE
来覆盖。
或者,您可以编辑 docker-compose yaml 文件
your_service:
cap_add:
- SYS_PTRACE
请注意,您需要重新启动 docker 容器才能使此设置生效。
您还可以使用主机操作系统中的 py-spy 来分析 docker 容器内运行的正在运行的进程。
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-capability-for-a-container 请注意,这将删除现有的 pod 并再次创建它们。
Alpine python 选择退出manylinux
轮子:pypa/pip#3969(评论)。您可以覆盖此行为,以使用 pip 在 Alpine 上安装 py-spy,方法是:
echo 'manylinux1_compatible = True' > /usr/local/lib/python3.7/site-packages/_manylinux.py
或者,您可以从 GitHub 发布页面下载 musl 二进制文件。
通过设置--nonblocking
选项,py-spy 不会暂停您正在分析的目标 python。虽然使用 py-spy 从进程中采样对性能的影响通常极低,但设置此选项将完全避免中断正在运行的 python 程序。
设置此选项后,py-spy 将在 python 进程运行时读取解释器状态。由于我们用来读取内存的调用不是原子的,并且我们必须发出多个调用来获取堆栈跟踪,这意味着我们在采样时偶尔会遇到错误。这可能表现为采样时错误率增加,或者表现为输出中包含部分堆栈帧。
还没有=)。
如果您希望在 py-spy 中看到某些功能,请点赞相应的问题或创建一个新问题来描述缺少的功能。
py-spy 遵循 CLICOLOR 规范,因此在您的环境中设置CLICOLOR_FORCE=1
将使 py-spy 打印彩色输出,即使通过管道传输到寻呼机也是如此。
py-spy 很大程度上受到 Julia Evans 在 rbspy 上的出色工作的启发。特别是,生成 Flamegraph 和 speedscope 文件的代码直接取自 rbspy,并且该项目使用从 rbspy 衍生出来的 read-process-memory 和 proc-maps 包。
py-spy 是在 MIT 许可证下发布的,请参阅许可证文件以获取全文。