技术博客

Linux 性能分析三件套:perf、火焰图、strace 实战

系统讲解 Linux 性能分析的三个核心工具:perf 采样 CPU 热点、FlameGraph 可视化调用栈、strace/ltrace 追踪系统调用,通过真实案例演示如何定位 CPU 飙升、进程卡死、系统调用异常等生产级性能问题。

perf火焰图straceLinux性能分析故障排查

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 而不是调用其子函数。