ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

py-spy 使用指南:零侵入采样 Python 程序性能剖析器的安装、命令与原理

py-spy 使用指南:零侵入采样 Python 程序性能剖析器的安装、命令与原理 开发工具性能剖析CLI【免费下载链接】py-spySampling profiler for Python programs项目地址https://gitcode.com/gh_mirrors/py/py-spy点击查看免费下载py-spy 是一个面向 Python 程序的采样型sampling性能剖析器它最大的特点是不需要重启程序、不需要修改任何代码就能可视化你的 Python 程序把时间花在了哪里。本指南基于 py-spy 仓库的官方 README 与其源码实现完整讲解安装方式、record/top/dump三个子命令的实战用法并深入剖析其“直接读取目标进程内存”的底层原理以及针对 Docker、Kubernetes、生产环境权限等场景的排障方案。读完本文你将掌握在本地与生产环境中安全剖析 Python 进程的完整技能。项目定位为什么需要这样一个剖析器py-spy 是一个用 Rust 编写的采样剖析器它通过系统调用直接读取目标 Python 进程的内存来获取调用栈运行在独立进程中而不是注入到被剖析的 Python 进程内部。这意味着无需在目标代码中埋点或引入任何 profiling 库剖析开销极低可以安全地用于正在服务生产流量的进程支持 Linux、OSX、Windows 与 FreeBSD 四个平台覆盖几乎所有主流 CPython 解释器版本2.3–2.7 与 3.3–3.14。从仓库源码看src/main.rs 定义了完整的命令行入口与三个子命令的调度逻辑src/config.rs 中集中了全部参数的解析这些都会在本指南中一一展开。安装方式py-spy 提供了多种安装途径可按运行环境选择# 方式一从 PyPI 安装预编译 wheel最推荐 pip install py-spyRust 用户可以从源码编译cargo install py-spy注意源码编译在 Linux 和 Windows 上需要libunwind依赖例如 Debian/Ubuntu 系执行apt install libunwind-dev。其他平台的一站式安装命令平台命令macOSHomebrewbrew install py-spyArch LinuxAURyay -S py-spyAlpine Linuxtesting 仓库apk add py-spy --update-cache --repository http://dl-3.alpinelinux.org/alpine/edge/testing/ --allow-untrusted此外也可以从预编译二进制发布页下载对应平台的可执行文件。若想验证参数解析行为仓库 src/config.rs 中还内置了针对各子命令的单元测试例如py-spy record --pid 1234 --output foo与短参数形式py-spy r -p 1234 -o foo会被解析为等价的Config。三大子命令实战py-spy 从命令行工作既可以指定目标程序的PID也可以直接给出要运行的python 程序命令行。它提供record、top、dump三个子命令分别对应“录制剖析数据”“实时监控视图”“单次抓取调用栈”三种使用场景。record录制剖析数据与火焰图record用于把剖析结果保存到文件。最典型的用法是生成火焰图flame graphpy-spy record -o profile.svg --pid 12345 # 或者直接指定要运行的程序 py-spy record -o profile.svg -- python myprogram.py第二条命令中--之后的全部内容被当作要启动的 python 程序命令行——由 py-spy 创建该进程并立即开始采样这也是免 root 权限剖析的关键技巧见下文“权限”一节。生成的 SVG 火焰图是交互式的即上文展示的效果。通过--format参数可以切换输出格式。从 src/config.rs 与 src/main.rs 可知py-spy 支持四种格式--format值输出扩展名用途flamegraph默认.svg交互式火焰图用浏览器打开即可查看speedscope.json导入 speedscope 进行时间线分析raw.txt原始的;分隔折叠栈文本可配合 Brendan Gregg 的flamegraph.pl脚本二次生成 SVGchrometrace.json导入chrome://tracing或 Perfetto 查看源码层面src/flamegraph.rs 展示了火焰图数据的生成方式把每次采样的栈帧倒序拼接成;分隔的折叠字符串并计数再交由inferno库渲染raw格式则直接输出这些计数行方便外部工具处理。除--format外record还支持以下高频参数均可在 src/config.rs 找到对应定义-r, --rate rate每秒采样次数默认100-d, --duration duration采样持续时间默认unlimited直到按下 Control-C 或目标进程退出-t, --threads在输出中附带线程 ID-g, --gil只统计持有 GIL 的线程-i, --idle把空闲线程的栈也纳入统计-F, --function按函数首行号聚合样本而不是当前执行行号--nolineno输出中不显示行号与--function互斥同时指定会直接报错退出-n, --native同时采集 C/C/Cython 原生扩展的栈仅支持的平台-s, --subprocesses一并剖析目标进程的子进程--nonblocking采样时不暂停目标进程。采样过程中 py-spy 会打印实时进度若采样跟不上采样率会提示 “behind in sampling, results may be inaccurate. Try reducing the sampling rate”参见 src/main.rs此时应降低--rate。top类 Unix top 的实时热力视图top展示一个持续刷新的“当前哪些函数占用 CPU 时间最多”的实时视图风格类似 Unix 的top命令py-spy top --pid 12345 # 或 py-spy top -- python myprogram.py视图中会实时显示各函数耗时占比、采样错误数并包含%GIL一列展示当前 GIL 的占用情况。该命令支持-r/--rate、-g/--gil、-i/--idle、-s/--subprocesses以及--delay seconds刷新间隔默认 1.0 秒。从 src/main.rs 可以看到top背后的实现是sample_console用一个后台采样线程持续产出Sample主循环把每条栈更新到ConsoleViewer界面。dump单次抓取全部线程调用栈当程序卡死、需要立刻定位“卡在哪一行”时dump是最高效的工具py-spy dump --pid 12345它会一次性把所有 Python 线程的调用栈以及基础进程信息打印到控制台从 src/dump.rs 的实现看输出以Process pid: cmdline开头随后列出Python v版本 (可执行文件路径)再按线程逐一打印栈帧。dump 还支持两个实用参数-l, --locals显示每个栈帧关联的局部变量重复传入-ll可提升输出详细度。从 src/python_spy.rs 可见局部变量会经由format_variable读取并格式化为可读字符串长度上限为128 * dump_locals字节-j, --json以 JSON 格式输出便于脚本解析集成。在 Linux 上dump 还额外支持-c, --core file可以从 Python 进程的coredump 文件中恢复并打印当时的 Python 调用栈见 src/main.rs 与 src/coredump.rs这在事后分析崩溃现场时非常有用。底层原理py-spy 是如何工作的读取目标进程内存py-spy 通过操作系统提供的跨进程内存读取原语直接读取目标 Python 程序的内存不同平台使用不同的系统调用Linuxprocess_vm_readvOSXvm_readWindowsReadProcessMemory解析调用栈与解释器 ABI拿到调用栈的思路是先找到全局的PyInterpreterState变量从而获得解释器内所有 Python 线程再逐个遍历每个线程的PyFrameObject链得到完整调用栈。由于 Python 的 ABI 随版本变化py-spy 使用 Rust 的 bindgen 为每一代需要支持的 CPython 生成不同的结构体见仓库 src/python_bindings/从 v2_7_15 一直覆盖到 v3_14_0并把这些结构体统一抽象成 traitInterpreterState/ThreadState/FrameObject/CodeObject等见 src/python_interpreters.rs运行时根据探测到的版本分派到对应的实现见 src/python_spy.rs。源码中还体现了很多版本差异的细节处理Python 3.11 之后帧对象被替换为_PyInterpreterFrame且多了一层间接寻址Python 3.10 之前行号表用co_lnotab3.10 之后改用co_linetable3.11 之后又切换为压缩变长整数编码的co_linetable——三种行号表的解码逻辑分别写在 src/python_interpreters.rs、同文件 3.10 实现与CompactCodeObjectImpl宏中3.12 起解释器状态中的线程链表头移到了threads.head字段GIL 也统一由_PyRuntime管理。定位解释器地址符号与 BSS 扫描找到 Python 解释器在内存中的地址并不总是容易因为存在ASLR地址空间布局随机化。py-spy 的策略分两步如果目标解释器带有符号表直接解引用interp_head旧版本或_PyRuntime3.7即可得到解释器地址但很多发行版要么剥离了符号要么在 Windows 上缺少对应 PDB 文件。此时 py-spy 会扫描 BSS 段寻找“看起来像有效PyInterpreterState”的地址再校验该地址处的内存布局是否符合预期。同样地src/python_process_info.rs 展示了版本探测的完整降级链先读Py_Version符号并按_Py_PACK_FULL_VERSION解码失败则尝试Py_GetVersion.version符号再失败则扫描主二进制与libpython.so的 BSS 段匹配版本字符串最后退化为从可执行文件路径如python3.5中解析版本号。另外对于./configure --enabled-shared编译的 Python符号与代码位于libpython.so中py-spy 对此也有专门处理src/python_process_info.rs。高频问题与生产环境排障为什么还需要一个新的 Python 剖析器已有的大多数 Python profiling 工具都要求以某种方式修改被剖析的程序——通常是把剖析代码运行在目标 Python 进程内部这会拖慢程序并改变其行为因此不适合用于生产服务的故障排查。py-spy 的设计目标就是让剖析与调试任何运行中的 Python 程序成为可能即使它正在承载生产流量。能剖析原生扩展吗可以。py-spy 在部分平台支持剖析用 C/C 或 Cython 编写的原生 Python 扩展命令行传入--native即可开启。需要注意几点最佳实践是编译扩展时保留符号symbols否则栈信息不完整对 Cython 程序py-spy 还需要生成的 C/C 源文件才能把行号还原到原始的.pyx文件--native依赖unwindfeaturesrc/config.rs在不支持的平台会被隐藏并在使用时直接报错它也不能与--nonblocking同时使用src/config.rs因为采集原生栈必须暂停进程Windows 上还不能与--subprocesses组合src/config.rs。平台支持矩阵如下以当前仓库配置为准平台架构LinuxWindowsOSXFreeBSDi686x86-64支持支持ARM支持Aarch64支持如何剖析子进程给record或top传入--subprocesses标志py-spy 会把目标程序的所有 Python 子进程一并纳入采样。这对使用multiprocessing或 gunicorn worker 池的应用尤其有用py-spy 会持续监控新进程的创建自动附加并采集它们的样本。在record输出中每个子进程会以带 PID 与命令行信息的独立栈出现并作为其父进程的孩子节点排列。从 src/sampler.rs 的实现可以看到子进程模式会为进程树中的每个进程创建独立的PythonSpyThread并启动一个监控线程每 100ms 检查一次是否有新的子进程加入采样池每条 trace 还会被标注上ProcessInfoPID、父进程、命令行这正是输出中层级结构的来源。什么时候需要 sudo / rootpy-spy 需要读取其他 Python 进程的内存出于安全考虑操作系统通常会限制这类操作OSX 上始终需要 rootLinux 上默认要求 root 权限才能附加到非自己创建的子进程免 root 的办法是让 py-spy 创建进程py-spy record -- python myprogram.py而通过 PID 附加sudo py-spy record --pid 123456通常需要 sudo可以通过设置内核的ptrace_scopesysctl 变量来放宽 Linux 上的这一限制。值得注意的实现细节当以 root 启动且存在SUDO_UID环境变量时py-spy 会主动降级回 sudo 用户再运行 python 子进程避免 profiling 输出混入 root 权限src/main.rs。如何判断线程是否空闲py-spy 默认只采集“正在活跃执行代码”的线程的栈排除睡眠或空闲线程。线程活动信息的来源因平台而异Linux读取/proc/PID/statOSX使用 mach 的thread_basic_info调用Windows判断当前系统调用是否属于已知的空闲调用。这一方案存在一些固有局限活动信息必须在暂停进程之前读取暂停后读取会恒定为空闲因此存在竞态窗口FreeBSD 以及 Linux 上的 i686/ARM 处理器尚未实现 OS 级查询Windows 上阻塞在 IO如等待 stdin的调用暂不会被标记为空闲Linux 上 ptrace 附加还可能短暂唤醒空闲线程造成误报。作为兜底py-spy 内置了一套启发式规则src/python_spy.rs当栈顶帧是threading.py中的wait、selectors.py中的select或asyncore/zmq/gevent/tornado中的poll时判定线程空闲。传入--idle标志可以禁用这一功能把 py-spy 认为空闲的帧也包含进输出。GIL 检测是怎么做的GIL 活动通过读取_PyThreadState_Current符号指向的 threadid 获取Python 3.6 及更早Python 3.7 及以后则从_PyRuntime结构体中推导等价信息。如果发行版未包含这些符号GIL 持有线程的解析就会失败。top视图中的%GIL列展示当前 GIL 占用情况。传入--gil标志后py-spy 将只输出持有 GIL 的线程的栈src/python_spy.rs 与 src/python_spy.rs 中甚至做了优化此模式下跳过线程活动查询因为持有 GIL 必然活跃且抓到一个 GIL 线程即可提前结束。注意这会漏掉那些释放了 GIL 但仍活跃的原生扩展活动。OSX 上剖析 /usr/bin/python 出问题怎么办OSX 的System Integrity ProtectionSIP会阻止包括 root 在内的任何用户读取/usr/bin下二进制文件的内存系统自带 Python 正在此列。三种应对方式安装其他发行版的 Python系统自带 Python 后续版本也会被移除且从 Python 2 迁移本就是大势所趋使用 virtualenv 运行系统 Python绕开 SIP 的适用范围关闭 System Integrity Protection。在 Docker 中运行 py-spy在 Docker 容器内即使以 root 运行py-spy 也常报权限错误原因是 Docker 默认限制了我们依赖的process_vm_readv系统调用。解决办法是启动容器时追加--cap-add SYS_PTRACE或在 docker-compose 中配置your_service: cap_add: - SYS_PTRACE注意修改后需要重启容器才能生效。另一种思路是完全不用容器内的 py-spy从宿主机直接剖析容器内运行的进程。值得一提的是py-spy 检测到容器环境时通过/proc/self/cgroup是否含/docker/会给出针对性提示src/main.rs同时它在 Docker 化的目标进程中会通过/proc/pid/root访问文件路径以保证跨命名空间的文件名解析正确src/python_spy.rs。在 Kubernetes 中运行 py-spyKubernetes 默认会丢弃SYS_PTRACE能力因此容器内运行 py-spy 会报Permission Denied: Try running again with elevated permissions by going sudo env PATH$PATH !!推荐的解决方案是修改 Pod 的 spec在Deployment.spec.template.spec.containers下添加securityContext: capabilities: add: - SYS_PTRACE注意修改 Deployment 资源会重建现有 Pod。另外还可以创建**临时容器ephemeral container**附加到运行中的 Pod并针对应用所在的特定 container 执行同时使用授予SYS_PTRACE的 profilekubectl debug --profilegeneral \ -n your-namespace \ --targetapp-container-name \ pod-name \ --imagepython:3.12-slim \ -it -- bash如何在 Alpine Linux 上安装Alpine 的 Python 默认选择不使用manylinuxwheel。可以通过如下方式强行让 pip 走 manylinuxecho manylinux1_compatible True /usr/local/lib/python3.7/site-packages/_manylinux.py或者直接下载 musl 版本的预编译二进制使用对应上文apk安装命令也是可行方案。如何避免暂停 Python 程序默认情况下 py-spy 在采样时会短暂暂停目标进程。设置--nonblocking后py-spy完全不暂停目标程序彻底避免打扰运行中的服务虽然常规采样的性能影响通常已经极低。代价是由于所用的内存读取调用并非原子操作而一条完整栈需要多次调用才能拼出来因此偶尔会出现采样错误、或者输出中包含残缺的栈帧表现为采样错误率升高或部分帧不完整。适合对“零打扰”要求极高的场景。平台能力边界以下能力目前尚不支持32 位 Windows、与 PyPy 集成、以及 Python 2 的 USC2 宽字符版本。如果你需要这些能力可以在仓库的 issue 列表中为相应需求点赞或新建 issue 说明缺失的功能。管道输出时如何保留颜色py-spy 遵循 CLICOLOR 规范因此在环境中设置CLICOLOR_FORCE1后即使输出被管道重定向到 pager也会强制打印彩色结果。总结与进一步探索py-spy 用“外部读取内存 按 CPython 版本定制 ABI 解析”的方式实现了真正零侵入、低开销的 Python 程序剖析其三大子命令覆盖了“持续录制火焰图/speedscope/chrome trace”“实时 top 视图”“一次性调用栈与局部变量转储”三类典型需求并针对生产环境给出了一套完整的权限与容器排障路径。如果你想进一步深入建议按以下顺序阅读当前仓库的源码src/config.rs全部命令行参数的定义、默认值与互斥校验含参数解析单元测试src/main.rs命令调度、进程启动、进度显示与各输出格式的写入流程src/sampler.rs单进程与子进程两种采样管线的实现src/python_spy.rs栈采集、线程活动判定、GIL 检测与文件名的缩短逻辑src/python_interpreters.rs跨版本 ABI 抽象与三种行号表解码算法src/python_process_info.rs版本探测与解释器地址定位符号 BSS 扫描src/dump.rsdump命令的格式化输出与子进程递归tests/integration_test.py 与 tests/scripts/覆盖 busy loop、子进程、线程名、递归栈等场景的集成测试脚本。py-spy 的火焰图与 speedscope 文件生成代码源自 Julia Evans 的 rbspy 项目见 README.md 的 Credits 一节并基于 MIT License 发布详见 LICENSE你可以在遵守许可的前提下放心集成与二次开发。赞分享开发工具性能剖析CLI【免费下载链接】py-spySampling profiler for Python programs项目地址https://gitcode.com/gh_mirrors/py/py-spy点击查看免费下载相关推荐AWS Lambda Go Shim终极指南Go在无服务器计算中的未来发展趋势AWS Lambda Go Shim终极指南Go在无服务器计算中的未来发展趋势 AWS Lambda Go Shim是一个革命性的开源项目它让开发者能够使用终极指南如何用py-spy低侵入采样技术快速解决Python性能瓶颈终极指南如何用py spy低侵入采样技术快速解决Python性能瓶颈 py spy是一款专为Python程序设计的低侵入式采样分析器它能帮助开发者在不中断程开发工具性能剖析CLI3步跑起你的第一台Switch模拟器yuzu免费上手完整指南3步跑起你的第一台Switch模拟器yuzu免费上手完整指南 yuzu 是一款开源免费的任天堂 Switch 模拟器用 C 编写同时维护 Window虚拟化桌面应用图形学上一篇Mouthful 审核系统实战如何搭建安全的第三方 OAuth 登录下一篇Qwen-Agent 工具调用配置3 行跑通第一次输出创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表