3. 性能分析工具:Linux perf、火焰图、strace、top、vmstat、iostat 等工具的使用

说实话,性能优化这件事,工具用对了,问题就解决了一半。我见过太多人一上来就对着代码瞎猜,折腾半天找不到根因。其实 Linux 自带的那些工具,个个都是宝贝,关键是你得知道什么时候该用哪个。

这一章,我就带你把这些工具捋一遍。不是罗列参数,而是告诉你——我遇到瓶颈时,第一反应会摸哪个工具。

3.1 先看全局:top、vmstat、iostat

接到一个性能问题,我个人的习惯是:先看全局,再定焦点。别一上来就 strace 挂上去,那是最后一步。

3.1.1 top —— 系统负载的第一眼

top 是我每天敲得最多的命令。没什么花哨,但信息密度极高。

$ top
top - 14:23:11 up 3 days,  2:15,  3 users,  load average: 2.15, 1.89, 1.45
Tasks: 245 total,   1 running, 244 sleeping,   0 stopped,   0 zombie
%Cpu(s): 45.2 us, 12.8 sy,  0.0 ni, 41.5 id,  0.2 wa,  0.0 hi,  0.3 si
MiB Mem :  15984.3 total,   2345.6 free,   8912.3 used,   4726.4 buff/cache
MiB Swap:   2048.0 total,   2048.0 free,      0.0 used.   5678.9 avail Mem

  PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND
 4567 root      20   0 12.5g 2.3g  45000 S 120.3  14.7 345:23.45 java
 1234 mysql     20   0  1.8g 890m  12000 S  45.2   5.6 120:12.33 mysqld

我一般先看三行:

  • load average:三个数分别代表 1/5/15 分钟平均负载。如果 1 分钟远高于 15 分钟,说明问题正在发生。
  • %Cpu(s):us 是用户态,sy 是内核态,wa 是 IO 等待。wa 高了,别折腾 CPU,去看磁盘。
  • %MEM 和 RES:RES 是物理内存占用,VIRT 是虚拟内存,别被 VIRT 吓到。
我的小技巧:按 P 键按 CPU 排序,按 M 键按内存排序。排查问题时,我习惯先按 CPU 排,看看谁在吃资源。

3.1.2 vmstat —— 系统瓶颈的快速扫描仪

top 看的是快照,vmstat 看的是趋势。我通常这样用:

$ vmstat 1 5
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 2  0      0 2345600 45000 4678000    0    0    12    34 1200 4500 45 13 41  1  0
 3  1      0 2341000 45000 4679000    0    0   890    56 1500 5200 48 15 35  2  0

重点看这几列:

  • r:运行队列。如果持续大于 CPU 核数,说明 CPU 不够用。
  • b:阻塞队列。b 大于 0 且持续增长,大概率是磁盘 IO 在拖后腿。
  • si/so:swap 换入换出。这两个数不为 0,说明内存不够了,系统在疯狂换页。
  • bi/bo:磁盘读写块数。bi 高说明读磁盘多,bo 高说明写磁盘多。
我曾经踩过的坑:有一次线上服务响应变慢,我看 top 发现 CPU 空闲 80%,就以为是代码问题。后来用 vmstat 一看,b 列一直大于 5,bi 飙到 2000+,才发现是磁盘 IO 打满了。白白浪费了半天查代码。

3.1.3 iostat —— 磁盘到底行不行

vmstat 告诉你磁盘有压力,iostat 告诉你压力在哪块盘、有多严重。

$ iostat -x 1 3
Device            r/s     w/s     rkB/s     wkB/s  await  svctm  %util
sda             45.2   120.3    1800.5    4800.2   12.3   2.1   35.2
sdb            890.0    12.0   35600.0     480.0  120.5   8.9   99.8

我重点关注:

  • %util:接近 100% 说明磁盘已经满负荷了。但注意,SSD 和 HDD 的 %util 含义不同,SSD 可以承受更高的并发。
  • await:IO 请求的平均等待时间。超过 100ms 基本可以断定磁盘是瓶颈。
  • svctm:IO 服务时间。如果 svctm 远小于 await,说明请求在排队,磁盘忙不过来了。

3.2 深入进程:perf 与火焰图

全局看完了,锁定了一个进程(比如那个吃 CPU 的 Java 进程),接下来就要钻进去看它到底在干什么。

3.2.1 perf —— 采样分析的王牌

perf 是 Linux 内核自带的性能剖析工具。它的原理很简单:以固定频率采样 CPU 正在执行的指令,然后统计每个函数出现的次数。

# 采样 10 秒,记录 CPU 时钟事件
$ perf record -F 99 -p 4567 -- sleep 10

# 生成报告
$ perf report

输出会告诉你,CPU 时间花在了哪些函数上。比如:

Samples: 9K of event 'cpu-clock', Event count (approx.): 9000000
Overhead  Command  Shared Object      Symbol
  45.2%  java     libjvm.so          [.] G1ParScanThreadState::trim_queue
  12.8%  java     libjvm.so          [.] InstanceKlass::oop_size
   8.5%  java     libzip.so          [.] inflate

看到这个结果,我就能判断:45% 的 CPU 花在了 GC 的扫描队列上,说明 GC 压力大。8.5% 花在解压缩上,可能是日志或数据传输的瓶颈。

关键参数:
  • -F 99:每秒采样 99 次,避免采样频率与程序周期同步产生偏差。
  • -p PID:只采样指定进程。
  • -g:记录调用栈,生成火焰图必备。

3.2.2 火焰图 —— 一眼看穿热点路径

perf report 的输出是文本,不够直观。火焰图把调用栈可视化,一眼就能看出哪条路径最热。

生成火焰图的步骤:

  1. 用 perf 采集带调用栈的数据:perf record -F 99 -p PID -g -- sleep 30
  2. 生成折叠栈文件:perf script > out.perf
  3. 用 FlameGraph 工具生成 SVG:stackcollapse-perf.pl out.perf > out.folded,然后 flamegraph.pl out.folded > flame.svg
读火焰图的要点:
  • 每个矩形代表一个函数调用,宽度代表它在采样中出现的比例。
  • 从下往上看,最底层是入口函数,顶层是叶子函数。
  • 找最宽的「平顶山」——那就是最耗 CPU 的地方。
  • 如果顶层函数是系统调用(如 sys_read、sys_write),说明程序在频繁做 IO。

下面是我用 SVG 画的一张火焰图结构示意,帮你理解它的布局逻辑:

火焰图结构示意(CPU 热点路径) main (100%) handle_request (45%) process_data (55%) parse_input (20%) db_query (25%) compress (30%) encrypt (25%) deflate (30%) 如何读这张图: • 最宽的矩形 compress/deflate (30%) 就是热点,需要优先优化 • 从下往上看调用链:main → process_data → compress → deflate

3.3 追踪系统调用:strace

perf 看 CPU 热点,strace 看系统调用。当程序表现异常——比如卡住、慢、报奇怪的错误——strace 就是我的第一把手术刀。

# 追踪指定进程的系统调用
$ strace -p 4567 -c

# 只看 write 和 read 调用
$ strace -p 4567 -e trace=write,read

# 统计系统调用次数和耗时
$ strace -p 4567 -c -S time

输出示例:

% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
 45.23    2.345678        12.34    190000           write
 32.15    1.678901         8.90    188000           read
 12.34    0.654321         5.67    115000           poll
  5.67    0.298765         3.21     93000           epoll_wait
注意:strace 有性能开销,生产环境慎用 -p 长时间追踪。我一般先用 -c 做统计模式,确认有问题再用 -e trace= 缩小范围。

我遇到过最典型的场景:一个服务每隔几秒就卡顿一次。用 strace 一看,发现每隔 5 秒就有一个 write 调用耗时 800ms。顺藤摸瓜找到日志框架,发现它在每次写日志时都会刷新磁盘。改成异步刷盘后,卡顿消失。

3.4 工具选择矩阵

这么多工具,到底先用哪个?我整理了一个选择矩阵,帮你快速定位:

现象 首选工具 看什么指标 下一步
CPU 高 top → perf us/sy 比例、热点函数 火焰图定位代码
内存高 top → vmstat RES、si/so 检查 GC 或内存泄漏
IO 慢 iostat → vmstat %util、await、b 列 strace 追踪读写调用
响应卡顿 strace -c 系统调用耗时分布 缩小到具体调用
负载高但 CPU 空闲 vmstat → iostat b 列、bi/bo 检查磁盘或锁竞争

3.5 实战案例:一次完整的排查流程

光讲理论没意思,我拿一个真实案例串一遍:

现象:线上 API 服务,平时响应 20ms,最近涨到 200ms。

第一步:top 看全局

发现 CPU 空闲 70%,但 load average 高达 8。wa 列 25%。嗯,问题不在 CPU,在 IO。

第二步:iostat 看磁盘

iostat -x 1 一看,sdb 的 %util 99.8%,await 150ms。磁盘打满了。

第三步:iotop 找进程

用 iotop 看到是 Java 进程在疯狂写磁盘。每秒写 200MB。

第四步:strace 看写什么

strace -p PID -e trace=write -c 发现 90% 的 write 调用来自日志框架。再细看,日志文件在 sdb 上。

第五步:解决问题

把日志目录移到 sda(SSD),调整日志刷盘策略为每秒批量刷一次。响应时间降回 25ms。

复盘:整个排查过程用了不到 10 分钟。如果不用工具,靠猜代码,可能一整天都找不到根因。工具链的价值就在这里——用数据说话,而不是靠直觉。

3.6 本章小结

工具不在多,在于会用。我个人的工具箱里,最常用的就是这六个:

  • top:第一眼,看系统整体健康度。
  • vmstat:第二眼,看 CPU/内存/IO 的平衡。
  • iostat:第三眼,看磁盘到底行不行。
  • perf + 火焰图:钻进去,看 CPU 花在哪。
  • strace:最后一步,看系统调用细节。

记住一个原则:从外到内,从粗到细。别一上来就 strace,也别只看 top 就下结论。工具链是串起来的,不是单点作战。