基于 eBPF 的 Python GIL(全局解释器锁)切换追踪工具。通过 uprobe 挂载 CPython 内部的 take_gil/drop_gil 函数,精确检测所有 GIL 切换(包括 C/C++ 扩展中的),并在 eBPF 内直接读取 Python 帧链获取 Python 级别调用栈。
特性:
- 外部进程附加,无需修改目标代码
- 捕获 Python 调用栈 + 原生 C/C++ 栈
- 支持导出 Chrome Trace JSON,可用
chrome://tracing或 Perfetto 可视化 - C/C++ 扩展中的 GIL 释放/获取完全可见
- 低性能开销(仅 GIL 操作时触发)
- 当前适配 CPython 3.13.4,提供工具适配其他版本
系统要求: Linux(WSL2 可用),root 权限。支持共享库(--enable-shared)和静态链接的 Python,但 python 二进制或 libpython*.so 需保留符号表(未被 strip)。
容器环境: 需要在 Pod/容器的 securityContext 中添加 BPF 相关权限:
securityContext:
privileged: true # 或使用下面更细粒度的配置
# capabilities:
# add: ["SYS_ADMIN", "BPF", "PERFMON", "SYS_PTRACE"]# 1. 安装 BCC
sudo apt install -y bpfcc-tools python3-bpfcc
# 2. 安装内核头文件
sudo apt install -y linux-headers-$(uname -r)
# 如果找不到对应包(如自定义内核、云厂商内核),安装通用版本:
# sudo apt install -y linux-headers-generic
# 3. 内核头文件链接(WSL2 / 自定义内核必需)
# 当运行的内核与 apt 可安装的 headers 版本不一致时,需要手动创建符号链接:
KVER=$(uname -r)
if [ ! -d "/lib/modules/$KVER/build" ]; then
EXISTING=$(ls -d /lib/modules/*/build 2>/dev/null | head -1)
sudo mkdir -p /lib/modules/$KVER
sudo ln -sf "$EXISTING" /lib/modules/$KVER/build
fi
bash check_env.sh检查项包括:Python 共享库、BCC 安装、内核头文件、BTF、sudo 权限、take_gil/drop_gil 符号、结构体偏移量。
trace_gil_ebpf.py 中的结构体偏移量硬编码为 CPython 3.13.4。如需适配其他版本:
PYINC=$(python3 -c "import sysconfig; print(sysconfig.get_path('include'))")
gcc -I"$PYINC" -I"$PYINC/internal" get_offsets.c -o get_offsets && ./get_offsets将输出的偏移量更新到 trace_gil_ebpf.py 的 OFFSETS 字典中。
外部附加到正在运行的 Python 进程:
# 终端 1:运行目标 Python 程序
python your_program.py
# 输出: PID = 12345
# 终端 2:附加 tracer
sudo /usr/bin/python3 trace_gil_ebpf.py -p 12345
# 生成 Chrome Trace 可视化文件(可选)
sudo /usr/bin/python3 trace_gil_ebpf.py -p 12345 --chrometrace gil_trace.json
# Ctrl+C 停止,输出汇总[#8] +0.158457s GIL switch: tid=309132 -> tid=309186
Python stack (tid=309186, acquiring GIL):
fib (example_target.py:18)
fib (example_target.py:18)
cpu_worker (example_target.py:25)
run (threading.py:983)
_bootstrap_inner (threading.py:1027)
_bootstrap (threading.py:1000)
Native stack:
take_gil+0x0 [libpython3.13.so.1.0]
退出时输出汇总:
============================================================
GIL Switch Report: 1933 switches detected
============================================================
Summary (from_tid -> to_tid: count):
309185 -> 309188: 512
309186 -> 309187: 498
...
============================================================
使用 --chrometrace 参数可在退出时生成 Chrome Trace JSON 文件,用于可视化每个线程的 GIL 持有时间段:
sudo /usr/bin/python3 trace_gil_ebpf.py -p <PID> --chrometrace gil_trace.json生成的 JSON 文件可通过以下方式查看:
- Chrome 浏览器访问
chrome://tracing,点击 Load 加载文件 - 或打开 Perfetto UI,拖入文件
每个线程的 GIL 持有区间以横条展示,点击可查看该区间的 Python 调用栈和 Native 调用栈。
# 终端 1
python example_target.py
# 终端 2 (用终端 1 输出的 PID)
sudo /usr/bin/python3 trace_gil_ebpf.py -p <PID>example_target.py 启动 4 个线程:2 个 CPU 密集型(纯 fib 计算)+ 2 个混合型(fib + time.sleep)。预期:
- CPU 线程:Python 栈显示
fib递归调用链 - Mixed 线程:Python 栈显示
mixed_worker->run->_bootstrap_inner->_bootstrap(time.sleep在 C 层释放 GIL,eBPF 能捕获到这些切换)
trace_gil_ebpf.py eBPF GIL 追踪工具
example_target.py 测试目标进程
get_offsets.c 获取 CPython 结构体偏移量的辅助工具
check_env.sh 环境与权限检查脚本
- 结构体偏移量与 CPython 版本绑定,换版本需用
get_offsets.c重新获取 - Python 字符串读取假设 compact ASCII 编码
- Python 栈深度限制 16 层
- 原生栈中可能出现
[unknown]帧:因 Python 未用-fno-omit-frame-pointer编译 - 需要 root 权限
- 需要
libpython*.so中未被 strip 的符号表