ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

10 分钟上手 bpftrace:eBPF 内核追踪与 Linux 性能分析快速入门

10 分钟上手 bpftrace:eBPF 内核追踪与 Linux 性能分析快速入门

10 分钟上手 bpftrace:eBPF 内核追踪与 Linux 性能分析快速入门

【免费下载链接】bpftraceHigh-level tracing language for Linux项目地址: https://gitcode.com/gh_mirrors/bp/bpftrace

bpftrace 是构建在 eBPF 之上的高级追踪语言,用于 Linux 性能分析与内核追踪:一条命令即可挂载探针并实时统计,不改内核代码、无需重启。读完本文,你能独立处理 CPU 高占用、系统调用量、延迟直方图等常见排查任务。

bpftrace 能干什么

bpftrace 把你写的类 awk 脚本编译成 eBPF 字节码,由内核校验后加载。程序运行在内核沙箱里,只允许白名单指令,写错会被拒绝或中断,不会搞挂宿主机。排查的代价从“动内核”降到“跑一条命令”。

它能挂的事件类型很全:系统调用、内核函数入口与返回、块设备 I/O、调度器事件、定时器、用户态函数(uprobe / USDT)。动作里可以读进程上下文(comm、pid、tid)、读高精度时间戳、抓内核栈和用户栈,再做聚合统计。

和改内核源码、编译加载内核模块这条传统路线相比,区别一句话:不碰内核二进制,探针秒级挂上、随时中断,观测的风险和迭代成本完全不同量级。

bpftrace 一行安装命令

主流发行版官方源都有包,Debian / Ubuntu 上:

sudo apt install bpftrace bpftrace --version

Fedora / CentOS 换成sudo dnf install bpftrace。

⚠️ 先确认版本号:0.19 起探针参数写法从args->xxx变成args.xxx,网上老脚本可能要改后才能跑,迁移细节见 docs/migration_guide.md。

30 秒看懂 bpftrace 探针原理

任何脚本都由三要素组成:

探针 { 动作 } 探针 /过滤条件/ { 动作 }
  • 探针:事件检测点,比如“openat 系统调用进入”“vfs_read 函数返回”“每 5 秒一次”。前缀决定类型:tracepoint、kprobe、profile、interval 等。
  • 过滤器:/.../里的布尔表达式,为真的事件才执行动作,也是控制开销的第一开关。
  • 动作:花括号内的语句,如 printf() 打日志、@x = count()做统计。

各类探针在内核中的挂载位置如下图所示,先有个整体印象:

按场景上手的 Linux 性能分析

谁在打开文件

现象:怀疑某进程反复读 /proc 或某个配置文件,strace 在生产上又不敢长期开着。

bpftrace -e 'tracepoint:syscalls:sys_enter_openat { printf("%s %s\n", comm, str(args.filename)); }'

输出每次打开一行:进程名 + 路径,Ctrl-C 停止。哪个文件高频出现就一目了然。注意一个容易漏的点:现代 glibc 的 open() 实际走的是 openat 系统调用,只追 open 会漏事件。

各进程的系统调用量

现象:sys 时间占比上升,要确认是谁在疯狂发系统调用。

bpftrace -e 'tracepoint:raw_syscalls:sys_enter { @[comm] = count(); }'

@是 map,按进程名分桶计数,退出时自动打印汇总,数最大的就是重点对象。这条探针每次系统调用都触发,繁忙机器上建议先加/comm == "目标进程"/过滤,开销才可控。

用直方图统计 read() 延迟

现象:服务 I/O 平时快、偶尔卡,你需要的是延迟的分布而不是均值。

bpftrace -e 'kprobe:vfs_read { @s[tid] = nsecs; } kretprobe:vfs_read /@s[tid]/ { @d[comm] = hist(nsecs - @s[tid]); delete(@s, tid); }'

这里的诀窍是把线程号当“在途记录”:一个线程同一时刻最多在一个 read 里,所以以 tid 为 key,每次在途调用都有且只有一个起点。返回时查回起点做差,耗时落进 hist() 的 2 的幂分桶。/@s[tid]/这个过滤器排除“只看到返回、没看到进入”的残缺数据。坑在结尾:起点必须 delete(),否则遇到不返回的线程,map 会持续增长直到撞上 BPF 内存上限。

CPU 热点采样,火焰图原料

现象:某个核长期 100%,top 只告诉你是谁,不告诉你代码路径。

bpftrace -e 'profile:hz:99 { @[kstack] = count(); }'

每个 CPU 以 99Hz 采样一次,用内核调用栈做 key 计数,count 高的路径就是 CPU 大头,输出喂给 flamegraph.pl 就是 CPU 火焰图。选 99 而不是 100,是为了避免采样节奏和系统周期事件共振。注意默认 kstack 只含内核侧栈,热点在用户态时要改用 ustack。

上下文切换:进程在等什么

现象:CPU 占用不高,但请求延迟高——大概率不是忙,是等。

bpftrace -e 'tracepoint:sched:sched_switch { @[kstack] = count(); }'

sched_switch 在线程离开 CPU 的瞬间触发,抓到的栈就是“它为什么交出 CPU”,通常是等 I/O、等锁或等定时器,按 count 排序就是 off-CPU 原因排行。坑点:此探针运行在被切出线程的上下文里,此刻的 comm / pid 都是被切出那一方,别和刚调度进来的任务混淆。

bpftrace 常见坑

  • 陌生事件先bpftrace -l '名字*'确认存在,-vl看参数
  • 能用 tracepoint 就不用 kprobe,后者换内核版本要重新验证
  • 高频探针必须加过滤器收窄,否则开销失控
  • 跨调用记的时间戳 map 记得 delete(),否则撞 BPF 内存上限
  • 0.19 起参数用 args.xxx,老脚本的 args->xxx 会报错
  • 内核结构体成员读取依赖头文件,内核带 BTF 则自动可用

下一步学习路线

  • 官方 12 条单行命令教程逐课过一遍:docs/tutorial_one_liners.md
  • str()、hist() 等标准库函数清单:docs/stdlib.md
  • 完整命令行选项与语义:man/adoc/bpftrace.adoc
  • opensnoop、biolatency 等现成工具,改改就能套进自己的场景:tools/

【免费下载链接】bpftraceHigh-level tracing language for Linux项目地址: https://gitcode.com/gh_mirrors/bp/bpftrace

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进