Skip to content

esp0r/trace_gil_ebpf

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

6 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

trace_gil

基于 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 符号、结构体偏移量。

适配其他 Python 版本

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.pyOFFSETS 字典中。

使用方法

外部附加到正在运行的 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
  ...
============================================================

Chrome Trace 可视化

使用 --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 -> _bootstraptime.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 的符号表

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors