在生产环境中排查性能问题或定位疑难故障时,传统的日志分析和监控往往力不从心。当问题发生在内核态、驱动层或库函数调用链中时,strace 和 lsof 这类传统工具要么开销过大、要么粒度不足。Linux 内核提供了多种动态追踪(Dynamic Tracing)机制,允许你在不修改源码、不重启系统的前提下,实时观测内核和用户态程序的执行细节。本文将系统讲解 ftrace、perf 和 bpftrace 三大主流工具的原理与实战用法,帮助你构建从浅层监控到深层追踪的完整可观测性能力。

一、动态追踪技术全景:为什么需要它,有哪些选择
动态追踪的核心思想是:在运行中的程序里动态插入探针(probe),收集你关心的数据,而不需要重新编译或重启。Linux 生态中有四代追踪技术,它们各有侧重:
| 工具 | 引入年代 | 能力层级 | 典型场景 |
|---|---|---|---|
| ftrace | 2.6.27 (2008) | 内核函数追踪 | 函数调用图、事件追踪 |
| perf (perf_events) | 2.6.31 (2009) | 性能计数 + 采样 | CPU 火焰图、缓存命中率 |
| eBPF / bpftrace | 4.x+ (2015+) | 可编程追踪 + 网络 | 复杂聚合、网络数据包过滤 |
| SystemTap | 2005 | 脚本化探针 | 企业级内核脚本(RHEL 生态) |
三者的关系可以这样理解:ftrace 是内核自带的「调试开关」,perf 是「性能采样器」,bpftrace 则是「可编程的瑞士军刀」。它们的底层都依赖内核提供的探点机制——主要包括 kprobe(内核函数入口)、uprobe(用户态函数入口)、tracepoint(内核预定义静态探点)和 perf_event(硬件性能计数器)。
二、ftrace:内核自带的函数追踪利器
ftrace 通过 debugfs 暴露控制接口,主目录在
1 | /sys/kernel/debug/tracing/ |
(较新内核为
1 | /sys/kernel/tracing/ |
)。它最基础也最强大的能力是函数调用图追踪,让你看清一个系统调用到底触发了哪些内核函数。
2.1 通过 tracefs 进行函数追踪
以下命令追踪
1 | openat |
系统调用关联的内核函数调用链:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 # 确认 ftrace 可用
mount | grep tracefs
# 通常已自动挂载: tracefs on /sys/kernel/tracing type tracefs
# 开启函数图追踪,过滤只看 do_sys_openat2
cd /sys/kernel/tracing
echo function_graph > current_tracer
echo 'do_sys_openat2' > set_graph_function
echo > trace # 清空缓冲
# 然后在另一个终端执行: cat /etc/hosts
cat trace | head -40
# 输出示例:
# 1) | do_sys_openat2() {
# 1) 0.580 us | getname() {
# 1) 0.120 us | getname_flags();
# 1) 0.090 us | }
# 1) 1.230 us | do_filp_open() {
# 1) 0.310 us | path_openat();
# 1) 0.080 us | }
# 1) + 12.450 us | }
每行前面的数字是 CPU 编号,
1 | us |
列是函数耗时。你能直观看到
1 | openat |
调用了
1 | getname |
和
1 | do_filp_open |
,并能发现哪一层最慢。
2.2 使用 tracepoint 观测事件
内核预定义了大量静态 tracepoint,开销比 kprobe 低且稳定。列出所有可用事件:
1
2
3
4
5
6
7
8
9
10
11 ls /sys/kernel/tracing/events/
# sched/ block/ net/ ext4/ syscalls/ ...
# 启用 sched 调度器切换事件
echo 1 > /sys/kernel/tracing/events/sched/sched_switch/enable
echo 1 > tracing_on
cat trace | head -10
# 输出会显示进程切换:
# <task> <pid> [cpu] sched_switch: prev=bash:1234 next=sshd:5678
echo 0 > events/sched/sched_switch/enable # 关闭
2.3 trace-cmd:ftrace 的友好封装
直接操作 tracefs 文件繁琐,
1 | trace-cmd |
提供了命令行封装:
1
2
3
4
5
6
7
8
9
10
11
12 # 安装
apt install trace-cmd # Debian/Ubuntu
yum install trace-cmd # RHEL/CentOS
# 记录 5 秒的 sched 事件
trace-cmd record -e sched:sched_switch -e sched:sched_wakeup sleep 5
# 交互式查看(类似 perf report 的 TUI)
trace-cmd report
# 导出为文本
trace-cmd report > sched_trace.txt
ftrace 的核心局限是它只能追踪,不能做复杂的统计聚合。当你需要「统计每个函数的平均耗时」「按调用次数排序」「按条件过滤后求和」时,就需要 perf 或 bpftrace 登场了。
三、perf:性能采样的瑞士军刀
perf 基于
1 | perf_event |
子系统,既能做基于硬件计数器(如 cache miss、branch miss)的精确采样,也能做软件事件(context switch、page fault)统计。它是定位 CPU 热点、缓存问题和系统级性能瓶颈的首选。
3.1 CPU 火焰图:定位热点函数
火焰图是排查 CPU 问题的杀手锏。perf 采集调用栈,再配合 flamegraph 工具可视化:
1
2
3
4
5
6
7
8
9
10
11
12
13 # 1. 采集 30 秒 CPU 采样,频率 99Hz(避开 100Hz 与定时器共振)
perf record -F 99 -g -a -- sleep 30
# -F 99: 采样频率 99Hz
# -g: 记录调用栈
# -a: 采样所有 CPU
# 2. 查看函数级热点排行
perf report --stdio | head -30
# 3. 生成火焰图(需先克隆 flamegraph 仓库)
git clone https://github.com/brendangregg/FlameGraph.git
perf script | FlameGraph/stackcollapse-perf.pl | FlameGraph/flamegraph.pl > cpu.svg
# 用浏览器打开 cpu.svg 即可看到直观的调用栈火焰图
火焰图中越宽的函数占用的 CPU 时间越多,能一眼看出热点瓶颈。例如你可能发现 40% 的时间花在
1 | mutex_lock |
上,这就指向了锁竞争。
3.2 硬件性能计数器分析
perf 能直接读取 CPU 的硬件性能计数器(PMC),诊断缓存命中、分支预测等微架构问题:
1
2
3
4
5
6
7
8
9
10
11
12
13 # 列出本机支持的硬件事件
perf list hw
# branch-instructions / branch-misses / cache-misses / cache-references / cycles ...
# 测量某程序的 L1 cache miss 率
perf stat -e L1-dcache-loads,L1-dcache-load-misses ./your_program
# 输出示例:
# 12,345,678 L1-dcache-loads
# 456,789 L1-dcache-load-misses # 3.70% of all loads
# 测量分支预测失败率
perf stat -e branch-instructions,branch-misses ./your_program
缓存 miss 率超过 10% 就值得优化数据布局;分支预测失败率高通常意味着大量随机条件跳转,可考虑用查表替代分支。
<
h3>3.3 软件事件:上下文切换与系统调用
1
2
3
4
5
6
7
8 # 统计 10 秒内每秒的上下文切换次数
perf stat -e context-switches -I 1000 -a sleep 10
# 追踪某个进程的系统调用(类似 strace 但开销低得多)
perf trace -p $(pgrep -f nginx)
# 统计某程序的系统调用次数排行
perf trace -p $(pgrep -f nginx) --summary
1 | perf trace |
是 strace 的高性能替代品,基于采样而非 ptrace,开销从 strace 的 3-10 倍降低到几乎无感。
四、bpftrace:可编程的现代追踪语言
bpftrace 基于 eBPF,提供类似 awk 的脚本语法,能做 ftrace 和 perf 做不到的复杂聚合。它编写的程序经内核验证器(verifier)检查后以 JIT 方式运行,性能开销极低,是当前最有前景的追踪技术。
4.1 基础语法与一键脚本
bpftrace 程序由 probe + filter + action 三部分组成:
1
2
3
4
5
6
7
8
9 // hello.bt — 最简单的探针
BEGIN { printf("Hello eBPF\n"); }
// 统计所有进程的 openat 调用次数,按进程名分组
bpftrace -e 'tracepoint:syscalls:sys_enter_openat { @[comm] = count(); }'
# 输出示例(按调用次数排序):
# @[bash]: 142
# @[sshd]: 38
# @[systemd]: 567
1 | comm |
是内置变量(进程名),
1 | @[key] = count() |
是 map 聚合语法,程序结束时自动打印结果。
4.2 追踪函数耗时分布
用
1 | hist() |
函数生成直方图,是分析延迟分布的神器:
1
2
3
4
5 // 追踪 read 系统调用的返回值分布(字节数)
bpftrace -e '
tracepoint:syscalls:sys_exit_read {
@bytes = hist(args->ret);
}'
输出会是一个 ASCII 直方图,让你看清读操作的字节数分布——是大量小块读还是少量大块读。
4.3 用 kprobe 追踪内核函数并过滤
1
2
3
4
5
6
7 // 统计某个进程打开文件超过 50 次时打印警告
bpftrace -e '
tracepoint:syscalls:sys_enter_openat
/comm == "nginx"/
{
@opens[comm, str(args->filename)] = count();
}'
这里
1 | /comm == "nginx"/ |
是过滤器,只在 nginx 进程触发时执行动作。
1 | str() |
把内核指针转成字符串。
4.4 实战:定位磁盘 I/O 延迟来源
以下脚本追踪 block 层 I/O 提交到完成的耗时,定位慢盘:
1
2
3
4
5
6
7
8
9
10
11 // iolatency.bt — 块设备 I/O 延迟追踪
tracepoint:block:block_rq_issue { @start[args->dev, args->sector] = nsecs; }
tracepoint:block:block_rq_complete
/@start[args->dev, args->sector]/
{
$lat = (nsecs - @start[args->dev, args->sector]) / 1000; // 微秒
@us[args->dev] = hist($lat);
delete(@start[args->dev, args->sector]);
}
// 运行 30 秒后 Ctrl+C,会打印每个块设备的 I/O 延迟分布直方图
如果某个设备
1 | 8,0 |
的延迟集中在 10000us 以上,而其他设备都在 100us 内,说明该盘有问题(可能是机械盘旋转延迟或坏道重读)。
五、生产环境实战:综合排查一例慢请求
假设线上服务出现偶发慢请求,P99 延迟从 50ms 飙升到 2s。我们用三件套联合排查:
第一步:perf 定位 CPU 热点
1
2
3
4 # 采集 30 秒 on-CPU 数据
perf record -F 99 -g -p $(pgrep -f your_service) -- sleep 30
perf script | FlameGraph/stackcollapse-perf.pl | FlameGraph/flamegraph.pl > cpu.svg
# 发现大量时间花在 __schedule 和 try_to_wake_up,指向调度频繁
第二步:bpftrace 追踪调度延迟
1
2
3
4
5
6
7
8
9 # 统计进程被唤醒到实际运行的时间(runqueue latency)
bpftrace -e '
tracepoint:sched:sched_wakeup { @wake[tid] = nsecs; }
tracepoint:sched:sched_switch
/@wake[args->next_pid]/
{
@runq_lat[comm] = hist((nsecs - @wake[args->next_pid]) / 1000);
delete(@wake[args->next_pid]);
}' -p $(pgrep -f your_service)
如果发现 runqueue latency 直方图长尾严重,意味着 CPU 被其他进程抢占,你的服务经常「等 CPU」。
第三步:ftrace 定位锁竞争源头
1
2
3
4
5 # 追踪 futex 系统调用的内核路径
echo function_graph > /sys/kernel/tracing/current_tracer
echo 'futex_wait_queue_me' > /sys/kernel/tracing/set_graph_function
echo 1 > /sys/kernel/tracing/tracing_on
# 观察是否大量线程卡在 futex 上 → 说明存在锁竞争
三个工具配合使用,可以完整还原「CPU 热点 → 调度延迟 → 锁竞争」的因果链,比单纯看监控大盘有效得多。
六、开销控制与生产安全要点
动态追踪虽然强大,但在生产环境使用必须控制开销,否则会引入新的问题:
- 采样频率适度:perf 的
1-F 99
比
1-F 999安全得多。高频采样对高负载系统可能造成 5% 以上的额外 CPU 占用。
- 过滤先行:用 filter 缩小追踪范围。追踪全部进程的 syscalls 比只追踪目标进程开销高几个数量级。
- 善用 tracepoint 而非 kprobe:tracepoint 是内核稳定的 ABI,升级不会失效;kprobe 依赖函数符号,内核版本变化可能失效。
- 设运行时长上限:所有 bpftrace 脚本都应配合
1interval:s:30 { exit(); }
或在命令行用
1timeout 30 bpftrace ...,避免遗忘导致长时间运行。
- 权限管理:这些工具大多需要 root 或
1CAP_SYS_ADMIN
+
1CAP_PERFMON。生产环境建议通过 sudo 白名单严格控制可执行的脚本。
- 开箱即用的 BCC 工具集:如果你不想手写脚本,BCC(BPF Compiler Collection)提供了上百个现成工具,如
1biolatency
、
1execsnoop、
1opensnoop、
1tcplife,覆盖绝大多数常见场景。
七、工具选型决策矩阵
面对不同问题,如何选择最合适的工具?以下是实战总结:
| 问题类型 | 首选工具 | 替代方案 |
|---|---|---|
| 定位 CPU 热点函数 | perf + 火焰图 | ftrace function_graph |
| 分析延迟分布(P99) | bpftrace hist() | perf sched |
| 追踪系统调用序列 | perf trace / strace | bpftrace tracepoint |
| 统计函数调用次数 | bpftrace count() | ftrace + filter |
| 网络数据包分析 | bpftrace + kprobe:tcp_* | tcpdump + BCC tcplife |
| 磁盘 I/O 延迟 | BCC biolatency | bpftrace block tracepoint |
| 缓存命中率 | perf stat -e cache-misses | perf c2c |
| 内核函数调用图 | ftrace function_graph | trace-cmd |
总结
ftrace、perf、bpftrace 构成了 Linux 内核可观测性的三层武器库。ftrace 适合快速查看内核函数调用关系,开销低但能力单一;perf 是性能采样的标准工具,火焰图和硬件计数器能力无可替代;bpftrace 则以其可编程性和低开销代表了未来方向。三者并非互斥,而是层层递进——先用 perf 拍全景定位热点区域,再用 bpftrace 精确追踪特定事件,用 ftrace 补充函数调用图细节。掌握这套工具链,你就能在不依赖额外监控基础设施的前提下,独立诊断从用户态到内核态的绝大多数性能疑难问题。
建议在测试环境先用 BCC 工具集熟悉常见场景,再逐步过渡到自己编写 bpftrace 脚本。Kernel 版本越新(5.10+ 最佳,4.x 功能受限),eBPF 能力越强、可用的 helper 越多。投资几天时间上手这些工具,会在未来的每一次故障排查中获得成倍的回报。
汤不热吧