Linux 性能分析三件套:perf、火焰图、strace 实战
系统讲解 Linux 性能分析的三个核心工具:perf 采样 CPU 热点、FlameGraph 可视化调用栈、strace/ltrace 追踪系统调用,通过真实案例演示如何定位 CPU 飙升、进程卡死、系统调用异常等生产级性能问题。
CPU 使用率高是运维最常见的告警之一,但 top 只能告诉你“谁在用 CPU”,不能告诉你“为什么用”。perf 可以深入到函数级别采样,火焰图把采样结果可视化,strace 则能追踪进程的每一个系统调用。三者组合,覆盖了 80% 的 Linux 性能问题诊断场景。
一、perf:CPU 性能采样
安装
# Ubuntu/Debian
apt install linux-tools-common linux-tools-$(uname -r) linux-tools-generic
# CentOS/RHEL
yum install perf
# 验证
perf --version
# perf version 6.5.13
基础用法
# 采样系统整体 CPU 使用(30 秒,99Hz)
sudo perf top -g -F 99
# 采样特定进程(-p PID)
sudo perf top -p $(pidof nginx) -g -F 99
# 录制采样数据(30秒)
sudo perf record -F 99 -a -g -- sleep 30
# -F 99:99 Hz 采样频率(避免与系统时钟 100Hz 共振)
# -a:采集所有 CPU
# -g:记录调用栈(call graph)
# 采样特定进程
sudo perf record -F 99 -g -p $(pidof java) -- sleep 30
# 分析采样结果
sudo perf report -g
# 交互式界面,按 Enter 展开调用链
# 按 q 退出
# 文本输出(适合脚本处理)
sudo perf report --stdio -g | head -100
perf stat:统计硬件性能计数器
# 统计程序运行期间的 CPU 事件
sudo perf stat -p $(pidof nginx) -- sleep 10
# 输出示例:
# Performance counter stats for process id '12345':
# 10,234,567,890 cycles # 2.89 GHz
# 8,145,234,567 instructions # 0.80 insn per cycle ← 低说明 cache miss 多
# 2,345,678,901 cache-misses # 22.89% of cache refs ← 高是问题
# 234,567 page-faults ← 缺页
# 常用事件
sudo perf stat -e cache-misses,cache-references,instructions,cycles -p 12345 sleep 5
# 分析特定命令
sudo perf stat -d make -j8 # 编译项目,分析硬件事件
二、火焰图:可视化 CPU 热点
火焰图(Flame Graph)由 Brendan Gregg 发明,x 轴是函数名(按字母排序),y 轴是调用栈深度,宽度代表采样次数(≈ CPU 时间占比)。
生成火焰图
# 安装 FlameGraph 工具
git clone https://github.com/brendangregg/FlameGraph
cd FlameGraph
# 采集数据(perf record 后生成)
sudo perf record -F 99 -a -g -- sleep 30
sudo perf script > out.perf
# 折叠调用栈
./stackcollapse-perf.pl out.perf > out.folded
# 生成 SVG 火焰图
./flamegraph.pl out.folded > flamegraph.svg
# 用浏览器打开
firefox flamegraph.svg
# 可以点击各函数放大,Ctrl+F 搜索函数名
读懂火焰图
┌──────────────────────────────────┐
│ main() │ ← 顶部宽 = 这个函数 CPU 占比高 = 热点
├──────────┬───────────────────────┤
│ func_a() │ func_b() │ ← 宽度表示 CPU 占比
├──────────┴────────┬──────────────┤
│ func_c() │ func_d() │
└───────────────────┴──────────────┘
CPU 时间 →
解读原则:
- 顶部宽的函数 = CPU 热点,优先优化
- 调用栈很深但顶部窄 = 只是调用路径,不是热点
- 平顶(plateau)= 函数本身消耗 CPU,而不是它的子函数
不同类型火焰图
# CPU 热点火焰图(默认)
./flamegraph.pl out.folded > cpu.svg
# off-CPU 火焰图(分析阻塞:IO等待、锁等待)
# 需要 perf 的 sched 事件
sudo perf record -e 'sched:sched_switch' -a -g -- sleep 30
sudo perf script > out.perf
./stackcollapse-perf.pl --pid out.perf | \
awk 'BEGIN{p=0} /off-cpu/{p=1} /on-cpu/{p=0} p' > off-cpu.folded
./flamegraph.pl --color=io --title="Off-CPU Time" off-cpu.folded > off-cpu.svg
# 内存分配火焰图(分析堆分配热点)
sudo perf record -e 'kmem:kmem_cache_alloc' -a -g -- sleep 30
sudo perf script > out.perf
./stackcollapse-perf.pl out.perf > mem.folded
./flamegraph.pl --color=mem --title="Memory Allocations" mem.folded > mem.svg
实战案例:定位 Java 应用 CPU 飙升
# Java 应用 CPU 100%,用火焰图定位
# 方法1:使用 async-profiler(比 perf 对 JVM 更友好)
wget https://github.com/async-profiler/async-profiler/releases/download/v3.0/async-profiler-3.0-linux-x64.tar.gz
tar -xzf async-profiler-3.0-linux-x64.tar.gz
# 采样 30 秒,生成火焰图
./asprof -d 30 -f flamegraph.html $(pidof java)
# 在浏览器打开 flamegraph.html
# 方法2:perf + FlameGraph(适合 Native 代码)
sudo perf record -F 99 -p $(pidof java) -g -- sleep 30
sudo perf script | ./stackcollapse-perf.pl | \
grep -v "^#" | ./flamegraph.pl > java-cpu.svg
三、strace:追踪系统调用
基础用法
# 追踪进程的所有系统调用(实时输出)
strace -p $(pidof nginx)
# 只看特定系统调用(-e)
strace -e trace=open,read,write,connect -p 12345
# 统计各系统调用次数和时间(-c)
strace -c -p 12345
# % time seconds usecs/call calls errors syscall
# ------ ----------- ----------- --------- --------- --------
# 45.32 0.032145 321 100 epoll_wait
# 30.12 0.021345 21 1000 read
# 15.67 0.011123 11 987 write
# 追踪子进程(-f)
strace -f -p 12345 # 跟踪 fork 出的子进程
# 时间戳(-t 相对时间,-tt 绝对时间,-T 每次调用耗时)
strace -T -p 12345 # 显示每次系统调用耗时
# 输出到文件
strace -o /tmp/trace.log -p 12345
实战案例 1:定位进程卡死
# 现象:Python 进程 CPU 0% 但没有任何输出,像卡住了
# 先看进程状态
cat /proc/$(pidof python3)/status | grep State
# State: S (sleeping) ← S 状态是阻塞等待
# strace 看它在等什么
strace -p $(pidof python3) -e trace=all
# 输出:
# futex(0x7f8a4c002a90, FUTEX_WAIT_PRIVATE, 0, NULL
# 一直停在这里,说明在等锁(futex = fast mutex)
# 分析锁竞争
strace -p $(pidof python3) -e trace=futex 2>&1 | head -50
# futex(addr, FUTEX_WAIT, ...) = 0 ← 等了 0 秒(锁释放了)
# futex(addr, FUTEX_WAIT, ...) <unfinished ...> ← 还在等
# 更深入:用 gdb 查看线程状态
gdb -p $(pidof python3)
(gdb) thread apply all bt # 打印所有线程调用栈
实战案例 2:定位文件 IO 问题
# 现象:应用写文件很慢,磁盘 IO 看起来没问题
# 追踪文件相关系统调用,显示耗时
strace -e trace=openat,read,write,fsync,fdatasync -T -p 12345
# 输出:
# openat(AT_FDCWD, "/data/app/log.txt", O_WRONLY|O_CREAT|O_APPEND, 0666) = 5 <0.000123>
# write(5, "log entry...", 1024) = 1024 <0.000045>
# fsync(5) = 0 <0.285000> ← fsync 每次 285ms!问题在这里
# write(5, "log entry...", 1024) = 1024 <0.000045>
# fsync(5) = 0 <0.312000>
# 发现:每次 write 后都 fsync,fsync 触发磁盘刷盘,极慢
# 解决:改用 O_DSYNC 或改写策略,批量 fsync
实战案例 3:追踪网络连接问题
# 现象:服务启动后无法连接外部数据库,日志只显示 connection refused
strace -e trace=socket,connect,send,recv -p $(pidof myapp) 2>&1 | head -30
# socket(AF_INET, SOCK_STREAM, IPPROTO_TCP) = 5
# connect(5, {sa_family=AF_INET, sin_port=htons(5432), sin_addr=inet_addr("10.0.0.1")}, 16)
# = -1 ECONNREFUSED (Connection refused)
# ← 清楚看到:连接 10.0.0.1:5432 被拒绝
# 进一步确认
strace -e trace=connect -p $(pidof myapp) 2>&1
# 发现连接的 IP 不对(配置文件写错了 IP)
四、综合诊断流程
# 标准性能诊断流程
# ================================================
# Step 1:确认现象
top -b -n 1 | head -20 # 谁在用 CPU?
iostat -xz 1 3 # 磁盘 IO?
ss -s # 网络连接数?
# Step 2:确定目标进程
pidof myapp
# 12345
# Step 3:快速看系统调用分布(10秒统计)
sudo strace -c -p 12345 &
sleep 10 && kill %1
# Step 4:perf 采样调用栈
sudo perf record -F 99 -g -p 12345 -- sleep 30
# Step 5:生成火焰图
sudo perf script | ~/FlameGraph/stackcollapse-perf.pl | \
~/FlameGraph/flamegraph.pl > /tmp/$(date +%Y%m%d_%H%M).svg
# Step 6:分析(结合火焰图和 strace 结果)
# - 火焰图顶部最宽的函数 = CPU 热点
# - strace -c 中耗时最多的系统调用 = IO/网络瓶颈
常用一行命令速查
# 实时看进程在做什么(类似 strace 但更轻量)
sudo cat /proc/$(pidof nginx)/syscall
# 7 0x5 0x7f... 0x1000 ... ← syscall ID 7 = poll,说明在等IO
# 查看进程打开的文件描述符
ls -la /proc/$(pidof nginx)/fd | head -20
# 统计进程的系统调用(不挂起进程)
sudo perf stat -e 'syscalls:sys_enter_*' -p 12345 sleep 5 2>&1 | \
grep -v "0 " | sort -k1 -rn | head -20
# 快速生成火焰图(一行脚本)
sudo perf record -F 99 -a -g -- sleep 20 && \
sudo perf script | ~/FlameGraph/stackcollapse-perf.pl | \
~/FlameGraph/flamegraph.pl > /tmp/flame.svg && \
echo "火焰图已生成:/tmp/flame.svg"
# 只追踪 errno(找系统调用错误)
strace -e trace=all -qq -p 12345 2>&1 | grep "= -1"
小结
perf + FlameGraph + strace 是 Linux 性能诊断的黄金组合。使用顺序通常是:先用 strace -c 快速确认瓶颈在 CPU 还是 IO/网络;CPU 问题用 perf record 采集 + 火焰图定位热点函数;IO/网络问题用 strace -T -e trace=... 追踪耗时系统调用。火焰图的关键读法:顶部最宽的方块是性能热点,平顶意味着这个函数本身消耗 CPU 而不是调用其子函数。
