3、IO性能分析工具(上):Linux perf原理与使用,ftrace跟踪IO事件,iostat/vmstat/mpstat系统级监控
说到IO性能分析,很多同学第一反应就是「装个工具跑一下」。但说实话,工具只是手段,关键是你要知道它在背后干了什么。这一章我带你从底层往上走,把Linux世界里最常用的几把「手术刀」摸清楚。
3.1 Linux perf:不只是采样工具
perf 这玩意儿,我最早接触是在做某个手机项目的时候。当时系统偶尔卡顿,但常规手段就是抓不到根因。后来我试着用 perf 去抓 CPU 周期事件,才发现是某个驱动在 IO 路径上频繁自旋。嗯,从那以后 perf 就成了我排查 IO 问题的标配。
3.1.1 perf 的工作原理
perf 的核心机制是 硬件性能计数器 + 内核事件源。说白了,CPU 内部有一组寄存器,专门记录各种事件发生的次数,比如 cache miss、分支预测失败、CPU 周期数。perf 就是通过读取这些寄存器来获取数据的。
你可能会问:「那 IO 事件怎么抓?」其实 perf 不光能抓硬件事件,还能通过内核的 tracepoint 来捕获软件事件。比如 block:block_rq_insert 这个 tracepoint,就能抓到 IO 请求入队列的时刻。
关键点:perf 有两种工作模式——计数模式和采样模式。计数模式只统计事件总数,开销极小;采样模式会记录事件发生时的上下文(比如调用栈),信息更丰富但开销稍大。
3.1.2 常用 perf 命令实战
我个人习惯先用 perf list 看看当前系统支持哪些事件。然后根据场景选命令:
# 统计系统级 cache miss 事件
perf stat -e cache-misses -a sleep 5
# 采样 CPU 周期事件,记录调用栈
perf record -e cycles -a -g -- sleep 10
# 查看采样结果
perf report -g graph
这里有个坑我要提醒你:-g 参数会记录调用栈,但默认只记录用户态栈。如果你想看内核态调用栈,得加上 --call-graph dwarf 或者 --call-graph fp。我曾经因为没加这个参数,白白浪费了一整天去猜调用关系。
3.1.3 用 perf 分析 IO 延迟
perf 有一个专门针对块设备 IO 的 tracepoint 集合。我最常用的是:
# 跟踪块设备请求的插入和完成事件
perf record -e block:block_rq_insert,block:block_rq_complete -a -g
# 查看每个 IO 请求的延迟分布
perf script | awk '{print $4}' | sort -n | uniq -c
为什么这样能分析延迟?因为 block_rq_insert 记录的是请求入队时间,block_rq_complete 记录的是完成时间。两者相减,就是 IO 请求在块设备层的停留时间。你想想看,如果这个时间远大于磁盘的物理寻道时间,那大概率是 IO 调度器或者驱动层出了问题。
3.2 ftrace:内核里的「显微镜」
如果说 perf 是手术刀,那 ftrace 就是显微镜。它能让你看到内核函数级别的调用关系。我当年调试一个文件系统 bug 时,就是靠 ftrace 一步步跟踪到了 ext4 的某个函数里。
3.2.1 ftrace 的基本用法
ftrace 的接口在 /sys/kernel/tracing/ 目录下。你不需要装任何额外工具,直接操作文件就行:
# 进入 trace 目录
cd /sys/kernel/tracing
# 选择跟踪器
echo function_graph > current_tracer
# 设置要跟踪的函数
echo ext4_* > set_ftrace_filter
# 开始跟踪
echo 1 > tracing_on
# 等待几秒后停止
echo 0 > tracing_on
# 查看结果
cat trace
嗯,这里要注意:function_graph 跟踪器会记录每个函数的进入和退出,输出非常详细。如果你只关心某个特定事件,建议用 function 跟踪器,它只记录函数被调用的次数,开销小很多。
3.2.2 跟踪 IO 事件
ftrace 同样支持 tracepoint。对于 IO 分析,我推荐你关注这几个事件:
# 查看可用的 IO 相关 tracepoint
cat available_events | grep block
# 启用块设备请求事件
echo 1 > events/block/block_rq_issue/enable
echo 1 > events/block/block_rq_complete/enable
# 开始跟踪
echo 1 > tracing_on
输出结果里会包含每个 IO 请求的设备号、扇区号、大小等信息。我曾经用这个手段抓到一个问题:某个进程在短时间内发起了大量 4KB 的随机写请求,导致磁盘 IOPS 飙升。后来发现是数据库的日志刷盘策略没调好。
我的小技巧:ftrace 的输出可以直接用 trace-cmd 工具来解析,它会把时间戳、进程名、函数名等字段格式化好,比直接看原始文件舒服多了。
3.3 iostat/vmstat/mpstat:系统级监控三剑客
这三兄弟是每个 Android 工程师的必修课。它们不依赖任何特殊硬件,纯靠读取 /proc 和 /sys 下的内核统计信息。说白了,就是内核把 IO 和 CPU 的「账本」记好了,你拿工具去查账。
3.3.1 iostat:IO 性能的「体检报告」
iostat 是我用得最多的工具,没有之一。它的输出分三部分:CPU 使用率、设备利用率、设备 IO 统计。
# 每 1 秒输出一次,共输出 5 次
iostat -x 1 5
输出中最重要的几个字段:
| 字段 | 含义 | 我的经验阈值 |
|---|---|---|
| %util | 设备忙绿时间百分比 | 超过 80% 说明磁盘是瓶颈 |
| await | IO 请求平均等待时间(ms) | 超过 10ms 说明有排队 |
| svctm | IO 请求平均服务时间(ms) | 超过 5ms 说明磁盘性能不足 |
| r_await / w_await | 读/写请求平均等待时间 | 读写差异过大说明有锁竞争 |
你可能会问:「%util 到 100% 是不是就代表磁盘满了?」其实不一定。%util 只统计设备是否在忙,如果磁盘支持 NCQ(原生命令队列),它可以同时处理多个请求,这时候 %util 可能只有 60%,但实际吞吐已经到顶了。所以别只看 %util,要结合 avgqu-sz(平均队列长度)一起看。
3.3.2 vmstat:系统整体健康度
vmstat 虽然名字叫「虚拟内存统计」,但它其实能反映整个系统的 IO 压力。我最关注的是 bi 和 bo 这两列:
# 每 1 秒输出一次
vmstat 1
输出示例:
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
1 0 0 123456 1234 56789 0 0 100 200 500 300 10 5 80 5 0
bi 是每秒从块设备读入的块数(1块=512字节),bo 是每秒写出的块数。如果 bi 持续大于 1000,说明系统在读大量数据,可能是内存不足导致频繁换页。我遇到过一台设备,bi 飙到 5000 以上,结果发现是某个服务在疯狂读日志文件。
注意:vmstat 的 wa 列代表 CPU 等待 IO 的时间百分比。如果 wa 超过 30%,说明 CPU 在大量等待 IO 完成,这时候优化 CPU 是没用的,得从 IO 路径下手。
3.3.3 mpstat:多核 CPU 的 IO 等待分布
mpstat 是 iostat 的 CPU 版本,它能告诉你每个 CPU 核心的利用率情况。对于 IO 问题,我主要看 %iowait 列:
# 每 1 秒输出一次,显示所有 CPU
mpstat -P ALL 1
输出示例:
10:30:01 AM CPU %usr %nice %sys %iowait %irq %soft %steal %guest %gnice %idle
10:30:02 AM all 5.00 0.00 2.00 10.00 0.00 0.00 0.00 0.00 0.00 83.00
10:30:02 AM 0 8.00 0.00 3.00 15.00 0.00 0.00 0.00 0.00 0.00 74.00
10:30:02 AM 1 2.00 0.00 1.00 5.00 0.00 0.00 0.00 0.00 0.00 92.00
如果某个核心的 %iowait 明显高于其他核心,说明 IO 中断可能被绑定到了这个核心上。我曾经在调试一个多线程 IO 程序时,发现所有 IO 中断都落在了 CPU0 上,导致 CPU0 的 %iowait 高达 40%,而其他核心几乎为 0。后来通过调整中断亲和性,把 IO 中断分散到多个核心,性能提升了 20%。
3.4 三款工具的配合使用
在实际项目中,我不会只用单一工具。我的习惯是:
- 先用 iostat 看全局:确认磁盘是不是瓶颈,哪个分区压力最大。
- 再用 vmstat 看系统层面:确认 IO 压力是否来自内存换页或文件系统。
- 最后用 mpstat 看 CPU 分布:确认 IO 中断是否均衡,有没有某个核心被 IO 拖死。
- 如果还定位不到:上 perf 或 ftrace 做细粒度跟踪。
举个例子,有一次线上反馈手机拍照后保存照片特别慢。我先用 iostat 看到 sda 的 await 高达 50ms,再用 vmstat 发现 bo 只有 200 左右,说明不是写量太大。然后用 mpstat 看到 CPU0 的 %iowait 高达 60%。最后用 perf 抓了 block:block_rq_insert 事件,发现是某个驱动在每次写操作前都做了一次同步刷新缓存的操作。去掉这个冗余操作后,拍照保存时间从 3 秒降到了 0.5 秒。
嗯,这就是工具链的价值——从宏观到微观,一步步缩小范围,直到找到根因。