用 py-spy 采样分析运行中的 Python 进程

举报
yd_240403232 发表于 2026/10/27 00:06:23 2026/10/27
【摘要】 面向无法重启或改代码的 Python 服务,介绍用 py-spy 对运行中进程做无侵入采样:准备 ptrace 权限后,用 py-spy top 观察实时热点,用 py-spy record 生成火焰图,并结合示例说明如何读图定位 CPU 瓶颈与使用限制。

用 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 确认。

五、边界与限制

  1. 采样是统计推断。短时间运行的进程、极小热点或交替出现的多个热点,可能需要更长采样时间或多次采样才能稳定复现。
  2. 采样频率越高,时间分辨率越好,但读取目标进程的开销也越大。具体影响与进程规模、调用栈深度有关,建议先在测试环境验证。
  3. py-spy 主要解析 Python 层符号;C 扩展、内核态或剥离符号的二进制,需要额外的调试信息或 --native 支持,否则火焰图会出现未知帧。
  4. attach 需要 ptrace 权限。部分安全加固环境、禁止 ptrace 的容器或受限 shell 中可能无法使用。
  5. 对已启动进程做分析时,PID 会随重启变化;生产环境建议同时记录启动方式,避免误 attach 到其他进程。
  6. 火焰图反映的是采样时刻的调用关系,函数内联、动态派发等可能让结果与源码直觉不完全一致,最终仍需结合日志、指标或压测验证。

六、小结

py-spy 的价值在于“先看再改”:用 py-spy top --pid 1234 快速看实时热点,用 py-spy record -o profile.svg --pid 1234 得到可分享的火焰图,在不修改代码、不重启进程的前提下定位 Python 进程的 CPU 瓶颈。把采样结果与业务逻辑、压测数据交叉比对,再决定优化对象,通常比盲目加缓存或改结构更稳妥。

【版权声明】本文为华为云社区用户原创内容,未经允许不得转载,如需转载请自行联系原作者进行授权。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。