用 py-spy 采样分析运行中的 Python 进程
用 py-spy 采样分析运行中的 Python 进程
一、问题场景
线上或测试环境中的 Python 服务 CPU 占用偏高,但服务正在运行,不方便改代码、加日志或重启。传统的 cProfile 需要改启动方式,日志打印又难以覆盖全部调用路径。此时可以用 py-spy 这类采样分析器:它通过读取目标进程的内存和调用栈,按固定频率采样,不需要修改业务代码,也不需要重启进程。
py-spy 的核心是采样(sampling)而非逐函数插桩。它在采样点上记录 Python 调用栈,统计各函数出现在栈顶的次数,从而估算热点。因为采样是统计行为,结果反映的是概率分布,适合先快速定位“哪段代码最可疑”,再用其他手段精确验证。
二、安装与权限准备
py-spy 是独立可执行文件,常用安装方式:
python3 -m pip install py-spy
py-spy --version
它需要读取目标进程信息,因此在 Linux 上依赖 ptrace 权限。常见情况:
- 与目标进程相同的普通用户,通常可以 attach 自己的进程;
- 跨用户或容器内进程,可能需要 root;
- 容器里运行的服务,可在启动容器时增加 ptrace 能力,例如
--cap-add SYS_PTRACE。
可以先查看系统限制(不同发行版路径可能不同):
cat /proc/sys/kernel/yama/ptrace_scope
如果值为 1,普通用户对非子进程的 attach 会受限。测试环境可按需评估调整,生产环境不建议随意放宽系统级限制,优先用 root 或容器能力方案。
三、最小可运行示例
用一个会持续消耗 CPU 的小脚本模拟服务:
# demo_service.py
import hashlib
import time
def hot_loop(n=200_000):
total = 0
for i in range(n):
total += i * i
return total
def hash_work(data=b"huawei-cloud-demo" * 1000):
for _ in range(2000):
hashlib.sha256(data).hexdigest()
def io_wait():
time.sleep(0.05)
def main():
while True:
hot_loop()
hash_work()
io_wait()
if __name__ == "__main__":
main()
运行:
python3 demo_service.py &
echo $! # 假设输出 1234,后文用 1234 作为占位 PID
四、实时观察与火焰图
1. 实时热点:py-spy top
py-spy top --pid 1234
该命令进入交互界面,按采样频率(默认每秒 100 次左右)刷新各函数的采样占比。可以观察 hot_loop、hash_work 等函数在栈顶的比例,快速判断是纯 Python 计算、哈希计算还是等待时间占主导。top 界面可按提示键排序或退出,具体按键以当前版本界面为准。
如果只想要快照,可以用 dump:
py-spy dump --pid 1234
它会打印此刻各线程的调用栈,适合确认“进程卡在哪个函数”。
2. 生成火焰图
py-spy record -o profile.svg --pid 1234 --duration 30 --rate 100
参数含义:
-o profile.svg:输出 SVG 火焰图;--duration 30:采样 30 秒后自动结束;--rate 100:采样频率,单位为 Hz。
采样结束后用浏览器打开 profile.svg。火焰图中的每个方块代表一个函数,宽度表示该函数出现在采样栈中的比例。查找瓶颈时,重点看“较宽且靠近栈顶”的方块:它意味着大量采样时间消耗在该函数自身,而不是它调用的子函数。若某个函数很宽但下面还有更宽的叶子函数,则真正热点往往在叶子处。
3. 其他常用选项
--nonblocking:以非阻塞方式读取目标进程,降低对目标的影响,但可能丢失部分栈;--native:尝试解析 C/C++ 扩展帧,需要相应符号信息;--subprocesses:一并跟踪子进程;--format speedscope:输出 speedscope 可读的 JSON,便于分享与在线查看;--idle:把空闲线程也纳入统计。
这些选项并非所有版本都完全一致,使用前可用 py-spy record --help 确认。
五、边界与限制
- 采样是统计推断。短时间运行的进程、极小热点或交替出现的多个热点,可能需要更长采样时间或多次采样才能稳定复现。
- 采样频率越高,时间分辨率越好,但读取目标进程的开销也越大。具体影响与进程规模、调用栈深度有关,建议先在测试环境验证。
- py-spy 主要解析 Python 层符号;C 扩展、内核态或剥离符号的二进制,需要额外的调试信息或
--native支持,否则火焰图会出现未知帧。 - attach 需要 ptrace 权限。部分安全加固环境、禁止 ptrace 的容器或受限 shell 中可能无法使用。
- 对已启动进程做分析时,PID 会随重启变化;生产环境建议同时记录启动方式,避免误 attach 到其他进程。
- 火焰图反映的是采样时刻的调用关系,函数内联、动态派发等可能让结果与源码直觉不完全一致,最终仍需结合日志、指标或压测验证。
六、小结
py-spy 的价值在于“先看再改”:用 py-spy top --pid 1234 快速看实时热点,用 py-spy record -o profile.svg --pid 1234 得到可分享的火焰图,在不修改代码、不重启进程的前提下定位 Python 进程的 CPU 瓶颈。把采样结果与业务逻辑、压测数据交叉比对,再决定优化对象,通常比盲目加缓存或改结构更稳妥。
- 点赞
- 收藏
- 关注作者
评论(0)