1
2
3
4
5
6
7
作者:李晓辉

联系方式:

1. 微信:Lxh_Chat

2. 邮箱:939958092@qq.com
  1. Linux 性能调优系列(一) 建立科学的性能分析思路
  2. Linux 性能调优系列(二) 掌握基础性能监控命令
  3. Linux 性能调优系列(三) sysstat实现性能数据持久采集
  4. Linux 性能调优系列(四) 别只会用 sysstat,PCP 才是 Linux 性能监控的进阶玩法
  5. Linux 性能调优系列(五) 给你的服务器做一次全面体检
  6. Linux 性能调优系列(六) 想调内核参数?先搞懂 /proc、sysctl 和 sysfs
  7. Linux 性能调优系列(七) 还在手动改 sysctl?TuneD 智能性能调优实战来了
  8. Linux 性能调优系列(八) 进程太能吃资源怎么办?用 ulimit 和 systemd 给它加个"上限"
  9. Linux 性能调优系列(九) cgroup v2实战:防止一个烂进程干崩整台服务器
  10. Linux 性能调优系列(十) CPU 100% 到底是谁在忙?用 perf 把"元凶"揪出来
  11. Linux 性能调优系列(十一) 程序为什么卡住了?strace 和 ltrace 带你找到答案
  12. Linux 性能调优系列(十二) 服务器又卡了?SystemTap 带你杀到内核看看为啥
  13. Linux 性能调优系列(十三) 从 SystemTap 到 eBPF,Linux 性能追踪进入新时代
  14. Linux 性能调优系列(十四) CPU 这么忙,到底是谁决定“让谁先跑”?一文搞懂 Linux 调度器
  15. Linux 性能调优系列(十五) CPU 到底该先给谁?搞懂 Linux 实时调度
  16. Linux 性能调优系列(十六) Linux CPU 隔离实战,为什么实时线程绑核后延迟还会抖?
  17. Linux 性能调优系列(十七) CPU 占用不高却跑的慢?深挖 CPU 缓存性能瓶颈
  18. Linux 性能调优系列(十八) 程序申请了1GB内存,为什么系统却说它没用?
  19. Linux 性能调优系列(十九) 服务器硬盘慢得像蜗牛?先别急着换硬盘,这四招先把瓶颈找出来
  20. Linux 性能调优系列(二十) 磁盘 I/O 调度器怎么选?一文搞懂 mq-deadline、kyber、bfq 和 none
  21. Linux 性能调优系列(二十一) 磁盘为什么慢?从文件系统到 I/O 调优,一次讲透
  22. Linux 性能调优系列(二十二) Linux 网络调优别乱改 sysctl!先找瓶颈再调 TCP 缓冲区
  23. Linux 性能调优系列(二十三) MTU 从 1500 到 9000,巨帧真的能提升性能吗?
  24. Linux 性能调优系列(二十四) 从 CPU 省电到网卡卸载,聊聊 Linux 网络调优
  25. Linux 性能调优系列(二十五) 聊聊VirtIO、TuneD、virtio-mem 调优

上一篇我们讲了 ulimit、systemd Limit* 和 cgroup v2。这些工具解决的是:

“一个进程或者一个业务,最多可以使用多少资源?”

但是性能排查还有一个非常经典的问题:

“CPU 已经 100% 了,到底是谁在忙?”

比如我们执行:

1
top

看到:

1
2
PID    USER    %CPU    COMMAND
1234 root 99.8 myapp

我们只能知道:

myapp 很忙。

但是不知道:

1
它到底在忙什么?

可能是:

1
2
3
4
5
6
7
8
9
10
11
12
13
用户态代码疯狂计算
↓
某个函数出现死循环
↓
频繁系统调用
↓
CPU 缓存命中率很差
↓
分支预测失败
↓
大量上下文切换
↓
内核代码消耗大量 CPU

这时候,top 就有点力不从心了。我们需要一个能够继续“往程序里面钻”的工具。这就是今天的主角:perf

这个工具得先安装才行,顺便也安装一下装内核调试软件包

如果需要深入分析内核态性能,尤其是希望将性能数据带到另一台机器进行离线分析,那么对应版本的内核调试符号非常重要。否则 perf 可能只能显示内核地址,或者只能解析出有限的内核符号。

如果缺少对应的调试包,perf 很可能只能告诉你“某个地址很忙”,最终看到的是一串类似 0xffffffff... 的十六进制地址,而不是 do_syscall_64、schedule 这样的函数名称,分析起来基本就是“两眼一抹黑”。

安装的适合要注意用uname -r匹配当前的内核版本哦

1
dnf install perf kernel-debuginfo-$(uname -r) kernel-debuginfo-common-x86_64-$(uname -r) -y

要是安装不上,可能是仓库没开

1
[root@localhost ~]# dnf config-manager --set-enabled baseos-debuginfo

先从一个真实生产问题开始

假设现在有一台生产服务器:

1
2
16 CPU
32 GB RAM

上面运行着:

1
2
3
4
5
Nginx
MySQL
Redis
业务程序
监控程序

突然某天晚上,监控报警:

1
CPU 使用率:98%

我们第一反应:

1
top

看到:

1
2
PID     USER      %CPU    COMMAND
18372 app 395% order-service

好家伙。这个程序吃了接近 4 个 CPU。但是问题来了:

为什么?

我们继续看:

flowchart TB

    TOP["top<br/>发现 order-service CPU 很高"]

    TOP --> Q1{"到底哪里在耗 CPU?"}

    Q1 --> A["用户态业务代码"]
    Q1 --> B["内核态代码"]
    Q1 --> C["频繁系统调用"]
    Q1 --> D["缓存 / 分支等硬件问题"]
    Q1 --> E["上下文切换等软件事件"]

    A --> PERF["perf"]
    B --> PERF
    C --> PERF
    D --> PERF
    E --> PERF

    PERF --> RESULT["定位真正的性能热点"]

这就是 perf 的价值。它不是简单告诉你:

“CPU 很高。”

而是继续帮你回答:

“CPU 到底花在哪里了?”


perf 到底是什么?

perf 是 Linux 下非常重要的一套性能分析工具。它依赖 Linux 内核提供的性能事件机制,可以访问:

1
2
3
CPU 硬件性能监控单元 PMU
+
Linux 内核软件性能事件

从而统计和采样各种性能指标。可以简单理解成:

flowchart LR

    APP["应用程序"]

    APP --> KERNEL["Linux Kernel"]

    CPU["CPU / PMU"] --> KERNEL

    KERNEL --> PERF["perf"]

    PERF --> STAT["统计指标"]
    PERF --> RECORD["采样数据"]
    PERF --> REPORT["热点函数"]

如果把 top 比喻成:

“医院门口量体温。”

那么 perf 更像:

“拿着各种检查设备进入身体内部,看看究竟是哪儿出了问题。”


perf 能看什么?

perf 最常见的用途,可以分成两大类。

1. 统计

比如:

1
2
3
4
5
6
7
8
9
CPU 一共执行了多少周期?

执行了多少条指令?

发生了多少次缓存未命中?

发生了多少次上下文切换?

发生了多少次缺页异常?

主要使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[root@localhost ~]# perf stat

Performance counter stats for 'system wide':

67,874,908,959 cpu-clock # 4.037 CPUs utilized
3,783 context-switches # 55.735 /sec
89 cpu-migrations # 1.311 /sec
262 page-faults # 3.860 /sec
97,369,054 instructions # 0.15 insn per cycle
653,841,045 cycles # 0.010 GHz
21,112,766 branches # 311.054 K/sec
1,432,281 branch-misses # 6.78% of all branches

16.813091627 seconds time elapsed


2. 采样

如果我们想进一步知道:

“到底是哪个函数最耗 CPU?”

就需要:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
[root@localhost ~]# perf record
[ perf record: Woken up 1 times to write data ]
[ perf record: Captured and wrote 0.606 MB perf.data (4417 samples) ]


[root@localhost ~]# perf report
Samples: 4K of event 'cycles:P', Event count (approx.): 1841795976
Overhead Command Shared Object Symbol
5.42% unix_chkpwd libcrypt.so.2.0.0 [.] blockmix_xor
4.87% swapper [kernel.kallsyms] [k] default_idle
3.94% swapper [kernel.kallsyms] [k] lapic_next_deadline
2.51% unix_chkpwd [kernel.kallsyms] [k] clear_page_erms
1.85% unix_chkpwd libcrypt.so.2.0.0 [.] blockmix_xor_save
1.72% swapper [kernel.kallsyms] [k] intel_pmu_disable_all
1.68% swapper [kernel.kallsyms] [k] asm_sysvec_apic_timer_interrupt
1.00% sshd-session [kernel.kallsyms] [k] avtab_search_node
0.83% swapper [kernel.kallsyms] [k] asm_sysvec_call_function_single
0.71% sshd-session libcrypto.so.3.5.8 [.] bn_sqrx8x_internal
0.60% swapper [kernel.kallsyms] [k] x2apic_send_IPI
0.58% swapper [kernel.kallsyms] [k] sched_balance_update_blocked_averages
0.36% sshd-session sshd-session [.] mult.lto_priv.0
0.33% swapper [kernel.kallsyms] [k] irqentry_enter
0.32% (ostnamed) [kernel.kallsyms] [k] avtab_search_node
0.31% swapper [kernel.kallsyms] [k] enqueue_entity

可以理解成:

flowchart LR

    STAT["perf stat"] --> NUMBER["得到数字"]

    RECORD["perf record"] --> SAMPLE["采集性能样本"]

    SAMPLE --> REPORT["perf report"]

    REPORT --> FUNCTION["定位热点函数"]

所以记住:

1
2
3
4
5
6
7
8
9
10
11
perf stat
↓
看整体指标

perf record
↓
采集样本

perf report
↓
分析热点函数

这三个命令,是学习 perf 最重要的基础。


perf 里面有两类重要事件

perf 可以观察大量事件。

大体可以分成:

1
2
硬件事件
软件事件

1. 硬件事件

硬件事件来自 CPU 的性能监控能力。常见的包括:

事件含义
cyclesCPU 时钟周期
instructions执行的指令数量
cache-missesCPU Cache 未命中次数
branches分支指令数量
branch-misses分支预测失败次数

2. 软件事件

软件事件主要由 Linux 内核提供。例如:

事件含义
context-switches上下文切换
page-faults缺页异常
cpu-clockCPU 时钟采样
task-clock任务运行时间

所以可以简单理解:

flowchart TB

    PERF["perf"]

    PERF --> HW["硬件事件"]

    HW --> CYCLES["cycles"]
    HW --> INST["instructions"]
    HW --> CACHE["cache-misses"]
    HW --> BRANCH["branch-misses"]

    PERF --> SW["软件事件"]

    SW --> CS["context-switches"]
    SW --> PF["page-faults"]
    SW --> CLOCK["cpu-clock"]

先看看你的机器到底支持哪些事件

学习 perf 的第一个命令:

它会列出当前系统支持的性能事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[root@localhost ~]# perf list

List of pre-defined events (to be used in -e or -M):

branch-instructions OR branches [Hardware event]
branch-misses [Hardware event]
bus-cycles [Hardware event]
cache-misses [Hardware event]
cache-references [Hardware event]
cpu-cycles OR cycles [Hardware event]
instructions [Hardware event]
ref-cycles [Hardware event]

cache:
L1-dcache-loads OR cpu/L1-dcache-loads/
L1-dcache-load-misses OR cpu/L1-dcache-load-misses/
L1-dcache-stores OR cpu/L1-dcache-stores/
L1-icache-load-misses OR cpu/L1-icache-load-misses/
...

这里有一个非常重要的知识点:

不同 CPU 支持的硬件 Performance Monitoring Unit(性能监控单元) 事件可能不同。

所以不要看到网上某篇文章写:

1
perf stat -e xxx

就认为自己的服务器一定支持。正确习惯是:

1
perf list

先看看自己的机器支持什么。


perf stat:先做一次“体检”

如果你现在刚接触 perf,我最建议先掌握:

1
perf stat

它做的事情很简单:

统计一段程序运行期间发生了多少性能事件。

这里最重要的是:

perf stat 主要告诉你“发生了多少”,而不是“哪个函数发生了”。


案例一:统计一个命令的 CPU 情况

我们先做一个非常简单的实验。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[root@localhost ~]# perf stat ls -l /etc

Performance counter stats for 'ls -l /etc':

5,951,317 task-clock # 0.618 CPUs utilized
29 context-switches # 4.873 K/sec
0 cpu-migrations # 0.000 /sec
168 page-faults # 28.229 K/sec
13,722,648 instructions # 0.68 insn per cycle
20,166,351 cycles # 3.389 GHz
2,905,193 branches # 488.160 M/sec
68,925 branch-misses # 2.37% of all branches

0.009632427 seconds time elapsed

0.000000000 seconds user
0.006322000 seconds sys

你可以把它理解成:

flowchart LR

    CMD["ls -l /etc"]

    CMD --> CPU["CPU cycles"]
    CMD --> INST["instructions"]
    CMD --> CS["context switches"]
    CMD --> PF["page faults"]

    CPU --> PERF["perf stat"]
    INST --> PERF
    CS --> PERF
    PF --> PERF

    PERF --> RESULT["统计结果"]

这个实验的价值不是为了研究 ls。而是先熟悉:

perf stat 到底是怎么工作的。


案例二:统计一个程序运行期间的硬件事件

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[root@localhost ~]# perf stat -e cycles,instructions,cache-misses \
dd if=/dev/zero of=/dev/null bs=1M count=1000
1000+0 records in
1000+0 records out
1048576000 bytes (1.0 GB, 1000 MiB) copied, 0.02423 s, 43.3 GB/s

Performance counter stats for 'dd if=/dev/zero of=/dev/null bs=1M count=1000':

102,355,372 cycles
210,967,302 instructions # 2.06 insn per cycle
44,798 cache-misses

0.026605730 seconds time elapsed

0.000000000 seconds user
0.025568000 seconds sys

这里:-e表示指定需要统计的事件。我们一次统计:

1
2
3
cycles
instructions
cache-misses

如何理解 IPC?

perf 经常会给出:

1
insn per cycle   老李备注:平均每个 CPU 时钟周期执行了多少条指令。

也就是:

IPC(Instructions Per Cycle)

简单来说:

1
IPC = 执行的指令数量 / CPU 周期数量

比如:

1
2
instructions = 10 亿
cycles = 5 亿

那么:

1
IPC ≈ 2

可以理解成:

平均每个 CPU cycle 完成约 2 条指令。


但是这里有一个非常容易踩的坑,不要看到:

1
IPC 很低

就直接得出:

“缓存一定有问题。”

这不严谨。IPC 低只能说明:

CPU 每个周期完成的指令数量比较少。

具体原因可能包括:

1
2
3
4
5
6
缓存访问延迟
分支预测失败
内存访问等待
流水线停顿
指令依赖
资源竞争

所以正确的分析方式应该是:

flowchart TB

    IPC["IPC 较低"]

    IPC --> CACHE["检查 cache-misses"]
    IPC --> BRANCH["检查 branch-misses"]
    IPC --> STALL["进一步分析流水线停顿"]
    IPC --> MEMORY["检查内存访问特征"]

    CACHE --> RESULT["综合判断"]
    BRANCH --> RESULT
    STALL --> RESULT
    MEMORY --> RESULT

perf 是用来提供证据的,不是让我们看到一个数字就直接下结论。


-a:统计整台机器

如果我们执行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[root@localhost ~]# perf stat -a sleep 10

Performance counter stats for 'system wide':

40,011,584,774 cpu-clock # 4.000 CPUs utilized
2,408 context-switches # 60.183 /sec
32 cpu-migrations # 0.800 /sec
82 page-faults # 2.049 /sec
94,333,797 instructions # 0.22 insn per cycle
438,224,175 cycles # 0.011 GHz
20,517,375 branches # 512.786 K/sec
1,037,761 branch-misses # 5.06% of all branches

10.002953997 seconds time elapsed

这里-a表示:

system-wide,统计整台机器。

也就是说:

flowchart TB

    HOST["整台服务器"]

    HOST --> CPU1["CPU 0"]
    HOST --> CPU2["CPU 1"]
    HOST --> CPU3["CPU 2"]
    HOST --> CPU4["..."]
    HOST --> CPUN["CPU N"]

    CPU1 --> PERF["perf stat -a"]
    CPU2 --> PERF
    CPU3 --> PERF
    CPU4 --> PERF
    CPUN --> PERF

如果你只想统计某个命令:

1
perf stat command

如果想观察整个系统:

1
perf stat -a command

不过这里有一个细节:

1
perf stat -a sleep 10

并不是说 sleep 自己占用了 CPU。

而是:

在 sleep 10 这段时间窗口内,统计整台机器的性能事件。

这个区别一定要理解。sleep 10 在这里主要用于提供 10 秒的统计时间窗口,而 -a 决定了统计范围是所有 CPU,因此最终得到的是这 10 秒内整个系统的性能事件,而不是 sleep 进程自身的 CPU 使用情况。


perf record与perf report

perf record 用于采集程序或系统运行过程中的性能数据,将 CPU 使用、调用栈、进程/线程等信息记录到 perf.data 文件中,供后续通过 perf report、perf script 等命令进行分析。

简单来说:

perf record 负责“采集数据”,perf report 负责“分析数据”。

perf record:我不只想知道“多少”,还想知道“谁”

到这里,我们已经可以统计:

1
2
3
4
CPU cycles
instructions
cache-misses
context-switches

但是生产环境真正让人头疼的问题通常是:

“到底哪个函数最耗 CPU?”

例如:

1
myapp CPU:400%

我们想知道:

1
2
3
4
5
6
7
main()
↓
process_request()
↓
calculate()
↓
???

这时候:

1
perf stat

就不够了。需要:

1
perf record

perf record 是怎么工作的?

它不是不停地记录程序的每一条指令。而是:

周期性进行采样。

可以想象一下:

1
2
3
4
5
6
7
8
程序运行:

████████████████████████████████

perf:

↑ ↑ ↑ ↑
采样 采样 采样 采样

每次采样记录:

1
当时 CPU 正在执行什么函数

采样次数足够多以后,就可以通过统计:

某个函数被采到的次数占全部样本的多少

来估算 CPU 时间主要花在哪里。


案例三:使用 perf record 采集性能样本

例如我们执行:

1
2
3
4
5
6
7
8
9
10
11
[root@localhost ~]# perf record -g \
-o perf-report.data \
-e cpu-clock,context-switches \
--buildid-mmap \
dd if=/dev/zero of=/dev/null bs=2048 count=1000000
1000000+0 records in
1000000+0 records out
2048000000 bytes (2.0 GB, 1.9 GiB) copied, 0.311058 s, 6.6 GB/s
[ perf record: Woken up 1 times to write data ]
[ perf record: Captured and wrote 0.048 MB perf-report.data ]

这里逐个解释。


  1. -o
1
-o perf-report.data

指定输出文件,默认情况下:

1
perf.data

这里我们指定:

1
perf-report.data

  1. -e
1
-e cpu-clock,context-switches

指定采集的事件。这里采集:

1
2
cpu-clock
context-switches

  1. --buildid-mmap

--buildid-mmap 用于在记录映射信息时关联二进制文件的 Build ID。Build ID 可以帮助 perf 在后续分析和归档时确认采样数据对应的具体二进制版本。对于需要将 perf.data 带到其他机器进行离线分析的场景,Build ID 尤其重要。特别是在:

1
2
3
4
5
生产服务器采集
↓
数据拷贝到分析服务器
↓
离线分析

这种场景下,要特别注意符号和二进制文件的对应关系。


为什么 perf report 有时候全是地址?

这是初学 perf 非常容易遇到的问题。例如你执行:

1
perf report

结果看到:

1
2
3
0x0000000000401234
0x0000000000405678
0xffffffff8103831

一脸懵。你肯定会问:

“这到底是什么函数?”

因为 perf 需要符号信息才能把地址转换成人类可读的函数名。

可以理解成:

flowchart LR

    SAMPLE["采样数据"]

    SAMPLE --> ADDRESS["内存地址"]

    SYMBOL["符号信息"] --> RESOLVE["地址解析"]

    ADDRESS --> RESOLVE

    RESOLVE --> FUNCTION["函数名称"]

所以生产环境做性能分析时,要特别注意:

1
2
3
4
5
perf 数据
+
对应的二进制
+
符号信息

三者之间必须能够对应起来。如果你需要分析内核或者带调试符号的程序,可以根据具体环境安装对应的 debuginfo。

先检查自己系统的环境:

1
uname -r

然后根据当前系统和内核版本配置对应的 Debug 仓库。

例如:

1
[root@localhost ~]# dnf config-manager --set-enabled baseos-debuginfo

然后安装与运行内核匹配的调试包。这里不要简单地认为:

1
kernel-debuginfo

装上就万事大吉。真正重要的是:

调试符号版本要和你正在分析的内核 / 二进制匹配。

生产环境尤其要注意这一点。


perf report:看看 CPU 到底耗在哪里

采样结束以后:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[root@localhost ~]# perf report --stdio -i perf-report.data
# To display the perf.data header info, please use --header/--header-only options.
#
#
# Total Lost Samples: 0
#
# Samples: 645 of event 'cpu-clock'
# Event count (approx.): 161250000
#
# Overhead Command Shared Object Symbol
# ........ ....... ................. .....................................
#
29.15% dd [kernel.kallsyms] [k] do_syscall_64
9.46% dd [kernel.kallsyms] [k] rep_stos_alternative
9.15% dd libc.so.6 [.] read
8.68% dd libc.so.6 [.] __GI___libc_write
5.27% dd [kernel.kallsyms] [k] fdget_pos
4.96% dd [kernel.kallsyms] [k] __fsnotify_parent
3.41% dd [kernel.kallsyms] [k] read_zero

--stdio 的意思是:

不进入交互界面,直接输出文本报告。

这里最值得看的就是:

1
Overhead

是不是很容易看出谁占的最多了


Overhead 到底是什么意思?

例如:

1
29.15% do_syscall_64

可以理解成:

在这次采样中,大约 29.15% 的样本落在 do_syscall_64 这个函数上。

它是一个采样开销占比,不要机械理解成:

“这个函数精确占用了 29.15% 的 CPU 时间。”

perf 的采样分析是统计意义上的估算。


[k] 和 [.] 是什么?

这是 perf report 里面非常重要的标记。

例如:

1
[k] do_syscall_64

这里[k]表示:内核态函数。

而:

1
[.] read

通常表示:用户态代码。

所以看到报告以后,我们可以先粗略判断:

flowchart TB

    REPORT["perf report"]

    REPORT --> KERNEL["[k] 内核态"]

    REPORT --> USER["[.] 用户态"]

    KERNEL --> K1["系统调用"]
    KERNEL --> K2["内核处理"]
    KERNEL --> K3["驱动等"]

    USER --> U1["业务代码"]
    USER --> U2["glibc"]
    USER --> U3["其他共享库"]

这就比:

1
top

只告诉你:

1
myapp 400%

深入很多了。


案例四:发现 CPU 高以后,到底应该怎么排?

这才是 perf 最有价值的地方。

假设:

1
top

发现:

1
order-service  CPU 380%

我们不要马上:

1
systemctl restart

而应该:

flowchart TB

    A["top 发现 CPU 很高"]

    A --> B["确认目标进程"]

    B --> C["perf stat"]

    C --> D{"整体指标有什么异常?"}

    D --> E["IPC / Cache"]
    D --> F["Context Switch"]
    D --> G["Page Fault"]
    D --> H["其他事件"]

    B --> I["perf record"]

    I --> J["perf report"]

    J --> K["找到热点函数"]

    E --> L["结合代码 / 系统继续分析"]
    F --> L
    G --> L
    H --> L
    K --> L

这才是比较完整的性能分析思路。


perf 统计单个进程

如果我们已经知道:

1
PID = 3831

可以使用:

1
perf stat -p 3831

它会附着到指定进程进行统计。

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[root@localhost ~]# perf stat -p 3831
Performance counter stats for process id '3831':

9,918,512 task-clock # 0.002 CPUs utilized
89 context-switches # 8.973 K/sec
1 cpu-migrations # 100.822 /sec
0 page-faults # 0.000 /sec
3,982,284 instructions # 0.20 insn per cycle
19,814,847 cycles # 1.998 GHz
838,129 branches # 84.501 M/sec
72,345 branch-misses # 8.63% of all branches

4.137022881 seconds time elapsed

如果想指定事件:

1
2
3
4
5
6
7
8
9
10
[root@localhost ~]# perf stat -p 3831 -e cycles,instructions,cache-misses

Performance counter stats for process id '3831':

21,065,652 cycles
4,324,785 instructions # 0.21 insn per cycle
58,862 cache-misses

8.064291846 seconds time elapsed

这样就可以专门观察:PID 3831 而不是整个服务器。


perf record 也可以针对指定 PID

例如:

1
2
3
4
5
6
7
[root@localhost ~]# perf record \
-p 3831 \
-e cpu-clock \
-o perf-report.data
[ perf record: Woken up 1 times to write data ]
[ perf record: Captured and wrote 0.023 MB perf-report.data (27 samples) ]

这样 perf 就会针对这个进程采样。

然后:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[root@localhost ~]# perf report --stdio -i perf-report.data
# To display the perf.data header info, please use --header/--header-only options.
#
#
# Total Lost Samples: 0
#
# Samples: 27 of event 'cpu-clock'
# Event count (approx.): 6750000
#
# Overhead Command Shared Object Symbol
# ........ ............ ................ ......................
#
100.00% sshd-session [vmxnet3] [k] 0x00000000000038b3


#
# (Tip: Boolean options have negative forms, e.g.: perf report --no-children)
#

分析。


为什么生产环境更推荐“先短时间采样”?

很多新人第一次接触 perf 会想:

“既然 perf 能分析,那我采一个小时。”

没必要。

性能分析首先要控制分析成本。

例如:

1
2
3
4
5
先采 10 秒
↓
看看有没有明显热点
↓
再决定是否延长

而不是:

1
2
3
4
5
perf record
↓
跑 2 小时
↓
服务器磁盘爆了

尤其生产环境更要谨慎。


perf archive:把生产现场带回来分析

这也是Linux 性能分析中非常实用的一个场景。

假设:

1
生产服务器

没有:

1
2
3
图形界面
开发环境
完整调试环境

而你的办公环境:

1
分析工具齐全

那么完全可以:

flowchart LR

    PROD["生产服务器"]

    PROD --> RECORD["perf record"]

    RECORD --> DATA["perf 数据"]

    DATA --> ARCHIVE["perf archive"]

    ARCHIVE --> PACKAGE["数据 + 相关文件"]

    PACKAGE --> SCP["scp"]

    SCP --> HOST["分析服务器"]

    HOST --> REPORT["perf report"]

这就是:

线上采集,线下分析。


二十四、生产服务器打包 perf 数据

如果采集时已经做好 Build ID 相关信息记录,可以使用:

1
perf archive

然后把:

1
perf-report.data

以及生成的归档文件一起带走。

例如:

1
2
3
4
scp \
perf-report.data \
perf-report.data.tar.bz2 \
root@hostA:/root/

把带回来的数据进行分析

把归档文件解压到对应的调试目录:

1
2
3
4
mkdir -p ~/.debug

tar -xf perf-report.data.tar.bz2 \
-C ~/.debug

然后:

1
2
perf report --stdio \
-i perf-report.data

这样就可以在分析服务器上查看报告。

解压后的目录结构需要能够被 perf 用于查找对应的 Build ID 对象。实际离线分析时,还需要保证分析环境能够找到这些对象;如果归档过程中存在缺失的 Build ID,perf archive 生成的压缩包也可能无法覆盖全部采样对象。


为什么这个方法特别适合生产环境?

因为我们可以把:

1
采集

和:

1
分析

分开。

生产环境只负责:

1
抓样本

分析服务器负责:

1
2
3
解析
查看
研究

可以理解成:

生产服务器负责“取证”,办公服务器负责“破案”。

这样就不会为了分析一个性能问题,把一堆工具和调试环境全部装进生产服务器。


perf stat 和 perf record 到底怎么选?

可以记住下面这个表:

命令主要用途典型问题
perf list查看支持的事件我的 CPU 支持哪些事件?
perf stat统计性能指标程序整体运行特征怎么样?
perf record采集性能样本CPU 时间主要花在哪里?
perf report分析样本哪个函数最热门?
perf archive归档分析环境怎么把生产数据带回去分析?

一句话:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
perf list
↓
我能看什么?

perf stat
↓
整体怎么样?

perf record
↓
把现场录下来

perf report
↓
谁最耗资源?

perf archive
↓
带回去慢慢分析

一个非常实用的生产排障套路

以后你遇到:

“服务器 CPU 突然很高。”

可以按照下面的顺序排查。

第一步:先看谁在吃 CPU

1
top

或者:

1
ps -eo pid,ppid,cmd,%cpu,%mem --sort=-%cpu | head

第二步:确定目标 PID

例如:

1
3831

第三步:先做统计

1
2
perf stat -p 3831 \
-e cycles,instructions,cache-misses,context-switches

先了解:

1
2
3
4
CPU 周期
指令数
Cache Miss
上下文切换

第四步:采样

1
2
3
4
perf record \
-p 3831 \
-e cpu-clock \
-o perf-report.data

采集一个短时间窗口。


第五步:分析热点

1
2
perf report --stdio \
-i perf-report.data

然后重点观察:

1
2
3
4
Overhead
Command
Shared Object
Symbol

第六步:根据结果继续往下钻

例如发现:

1
业务函数占比很高

那么继续分析:

1
应用代码

如果发现:

1
内核函数占比很高

那么继续考虑:

1
2
3
4
5
系统调用
IO
网络
驱动
内核路径

如果发现:

1
cache-misses 很高

再进一步研究:

1
2
3
4
缓存访问模式
数据结构
内存访问
CPU 架构

这才是:

从“CPU 高”走向“为什么 CPU 高”。


perf 和 top 到底是什么关系?

不要认为:

perf 出来了,top 就没用了。

恰恰相反。它们更像:

flowchart LR

    TOP["top<br/>发现问题"]

    TOP --> PID["找到高 CPU 进程"]

    PID --> PERFSTAT["perf stat<br/>看整体特征"]

    PERFSTAT --> PERFREC["perf record<br/>采样"]

    PERFREC --> PERFREP["perf report<br/>找热点"]

    PERFREP --> ROOT["进一步定位根因"]

所以:

1
top

负责:

发现问题。

而:

1
perf

负责:

深入分析问题。


perf 最重要的不是命令,而是分析思路

很多人学 perf,最后变成了:

1
2
3
4
5
背命令

perf stat
perf record
perf report

但是一旦真正遇到生产问题:

“我应该统计什么?”

还是不会。所以真正需要建立的是:

flowchart TB

    PROBLEM["性能问题"]

    PROBLEM --> CPU["CPU 高"]
    PROBLEM --> IO["IO 慢"]
    PROBLEM --> LAT["延迟高"]

    CPU --> STAT["先统计整体指标"]

    STAT --> CYCLES["cycles"]
    STAT --> INST["instructions"]
    STAT --> CACHE["cache-misses"]
    STAT --> BRANCH["branch-misses"]
    STAT --> CS["context-switches"]

    STAT --> RECORD["必要时进行采样"]

    RECORD --> REPORT["perf report"]

    REPORT --> HOTSPOT["热点函数"]

    HOTSPOT --> ROOTCAUSE["结合业务 / 内核继续定位"]

所以:

perf 不是“CPU 高了就运行一下”的万能命令。

它真正的价值是:

帮助我们建立从“现象 → 指标 → 热点 → 根因”的分析链路。


几个非常容易踩的坑

坑一:把 IPC 低直接等同于 Cache Miss 高

错误:

1
2
3
IPC 低
↓
一定是 Cache 有问题

正确:

1
2
3
4
5
IPC 低
↓
说明单位周期完成的指令较少
↓
再结合 Cache / Branch / Stall 等指标分析

坑二:只看一个指标就下结论

比如:

1
cache-misses 很多

不代表:

“程序一定有严重性能问题。”

还需要结合:

1
2
3
4
5
cache-references
程序工作负载
内存访问模式
CPU 架构
实际业务延迟

一起判断。


坑三:生产环境长时间高频采样

perf record 不是完全没有开销。因此生产环境建议:

1
2
3
短时间
低影响
有目标

不要:

1
2
3
高频率
长时间
无脑采集

坑四:perf report 全是地址

看到:

1
0x7fxxxx

不要慌。

检查:

1
2
3
4
二进制文件
符号
debuginfo
Build ID

是否匹配。


坑五:线上采集以后直接把数据扔到另一台机器

如果分析机器缺少:

1
2
3
对应二进制
符号
调试信息

可能无法正确解析函数。

所以才需要:

1
perf archive

帮助保存分析所需要的相关文件。


知识点串起来

到这里,我们已经可以把整个过程串起来:

flowchart TB

    A["top<br/>发现 CPU 很高"]

    A --> B["找到 PID"]

    B --> C["perf stat"]

    C --> D["cycles / instructions"]
    C --> E["cache-misses"]
    C --> F["context-switches"]
    C --> G["其他事件"]

    B --> H["perf record"]

    H --> I["perf.data"]

    I --> J["perf report"]

    J --> K["热点函数"]

    K --> L{"下一步"}

    L --> M["业务代码分析"]
    L --> N["系统调用分析"]
    L --> O["内核分析"]
    L --> P["CPU / Cache 分析"]

    I --> Q["perf archive"]

    Q --> R["离线分析"]

这时候你就会发现:

上一篇我们学:

1
cgroup

解决的是:

“这个业务最多可以吃多少资源?”

这一篇学习:

1
perf

解决的是:

“它为什么要吃这么多 CPU?”

两个东西解决的问题完全不同。


perf 在整个 Linux 性能调优体系中的位置

到目前为止,我们已经学了不少工具。可以把整个 Linux 性能分析体系理解成:

flowchart TB

    SYSTEM["Linux 性能问题"]

    SYSTEM --> HARDWARE["硬件层"]
    SYSTEM --> MONITOR["监控层"]
    SYSTEM --> KERNEL["内核参数"]
    SYSTEM --> LIMIT["资源限制"]
    SYSTEM --> ANALYSIS["性能分析"]

    HARDWARE --> CPU["CPU / Memory / Disk / Network"]

    MONITOR --> TOP["top"]
    MONITOR --> VMSTAT["vmstat"]
    MONITOR --> IOSTAT["iostat"]
    MONITOR --> SAR["sar"]

    KERNEL --> SYSCTL["sysctl"]
    KERNEL --> TUNED["TuneD"]

    LIMIT --> ULIMIT["ulimit"]
    LIMIT --> SYSTEMD["systemd"]
    LIMIT --> CGROUP["cgroup v2"]

    ANALYSIS --> PERF["perf"]

    PERF --> STAT["perf stat"]
    PERF --> RECORD["perf record"]
    PERF --> REPORT["perf report"]

这样学习起来就不会变成:

“Linux 有一堆命令,我一个个背。”

而是:

每一个工具都有自己解决的问题。


最后记住这几个命令

如果今天只能记住几个命令,就记住:

查看事件

1
perf list

统计程序

1
perf stat command

统计指定进程

1
perf stat -p PID

采样

1
perf record -p PID -o perf.data

分析

1
perf report --stdio -i perf.data

查看 Build ID

1
perf buildid-list -i perf.data

归档

1
perf archive

总结

这一篇我们没有停留在:

“perf 是一个性能工具。”

而是实际建立了一套排障思路。当你看到:

1
CPU 100%

不要只停留在:

1
top

而是继续问:

1
2
3
4
5
6
7
8
9
CPU 到底在干什么?
↓
是用户态还是内核态?
↓
哪个函数最耗 CPU?
↓
是不是 Cache / Branch / Memory 等问题?
↓
下一步应该从哪里继续排?

于是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
top
↓
找到问题进程

perf stat
↓
了解整体性能特征

perf record
↓
采集性能样本

perf report
↓
找到热点函数

进一步分析
↓
定位根因

这才是 perf 真正的价值。

如果说:

cgroup 是给业务“画资源边界”。

那么:

perf 就是在 CPU 出问题以后,拿着放大镜去看“CPU 到底在忙什么”。

下一篇,我们继续往里面钻。因为 perf 虽然可以告诉我们:

“这个函数很忙。”

但是如果我们想进一步知道:

“这个函数到底调用了哪些系统调用?”

甚至:

“它为什么一直在读文件、访问网络、打开文件?”

那就需要另外一组非常经典的工具:

1
2
strace
ltrace

下一篇,我们就来看看:

Linux 性能调优系列(九)|strace + ltrace 实战:把一个进程的一举一动扒出来