37、Linux系统性能分析工具全解析

Linux系统性能分析工具全解析

1. 符号表与编译标志

在对系统进行性能分析时,虽然观察系统的自然状态很重要,但工具往往需要额外的信息来理解事件。部分工具,如 perf、Ftrace 和 LTTng,需要特殊的内核选项,这可能意味着你要构建并部署新的内核。

调试符号在将原始程序地址转换为函数名和代码行时非常有用。部署带有调试符号的可执行文件不会改变代码的执行,但需要有包含调试信息的二进制文件和内核副本。一些工具,如 perf,在目标系统上安装这些调试符号后能更好地工作。

若要工具生成调用图,可能需要启用栈帧进行编译;若要工具准确地将地址与代码行关联起来,可能需要降低优化级别进行编译。此外,一些工具需要在程序中插入检测代码来捕获样本,这就需要重新编译相关组件,例如应用程序的 gprof 以及内核的 Ftrace 和 LTTng。

需要注意的是,对观察的系统改动越多,就越难将测量结果与生产系统关联起来。因此,最好采取观望的态度,只有在需求明确时再进行更改,并意识到每次更改都会改变测量的内容。

2. 开始性能分析

分析整个系统时,像 top 这样的简单工具是很好的起点,它能快速提供系统的概览,显示内存使用情况、占用 CPU 周期的进程,以及这些情况在不同核心和时间上的分布。
- 如果 top 显示单个应用程序在用户空间占用了所有 CPU 周期,可以使用 perf 对该应用程序进行分析。
- 如果有两个或多个进程的 CPU 使用率较高,可能存在某种因素将它们耦合在一起,比如数据通信。若大量周期花在系统调用或处理中断上,可能是内核配置或设备驱动有问题。在这两种情况下,都需要使用 perf 对整个系统进行分析。
- 若想了解更多关于内核和其中事件的顺序,可以使用 Ftrace 或 LTTng。
- 对于多线程代码的锁定问题、随机数据损坏等问题,Valgrind 加上 Helgrind 插件可能会有帮助。内存泄漏也属于此类问题。

graph LR
    A[开始分析系统] --> B{top 显示情况}
    B -->|单个应用占满 CPU| C[使用 perf 分析该应用]
    B -->|多个进程高 CPU 使用率| D[使用 perf 分析整个系统]
    B -->|大量系统调用或中断| D
    B -->|其他问题| E[使用 Valgrind + Helgrind 插件]
    D --> F[若想了解内核, 使用 Ftrace 或 LTTng]

3. 使用 top 进行性能分析

top 程序是一个简单的工具,不需要特殊的内核选项或符号表。BusyBox 中有基本版本,procps 包中有功能更丰富的版本,Yocto Project 和 Buildroot 中都可以获取。也可以考虑使用 htop,它功能与 top 类似,但有些人认为其用户界面更好。

开始时,关注 top 的摘要行(使用 BusyBox 时是第二行,使用 procps 的 top 时是第三行)。以下是使用 BusyBox top 的示例:

Mem: 57044K used, 446172K free, 40K shrd, 3352K buff, 34452K cached
CPU:  58% usr   4% sys   0% nic   0% idle  37% io   0% irq   0% sirq
Load average: 0.24 0.06 0.02 2/51 105
  PID  PPID USER     STAT   VSZ %VSZ %CPU COMMAND
  105   104 root     R    27912   6%  61% ffmpeg -i track2.wav

摘要行显示了在各种状态下花费的时间百分比,具体如下表所示:
| procps | BusyBox | 描述 |
| ---- | ---- | ---- |
| us | usr | 具有默认优先级的用户空间程序 |
| sy | sys | 内核代码 |
| ni | nic | 具有非默认优先级的用户空间程序 |
| id | idle | 空闲 |
| wa | io | I/O 等待 |
| hi | irq | 硬件中断 |
| si | sirq | 软件中断 |
| st | - | 窃取时间:仅在虚拟环境中相关 |

在上述示例中,几乎所有时间(58%)都花在用户模式,只有少量(4%)在系统模式,这是一个用户空间 CPU 受限的系统。摘要后的第一行显示,ffmpeg 应用程序是罪魁祸首,任何降低 CPU 使用率的努力都应针对它。

再看另一个示例:

Mem: 13128K used, 490088K free, 40K shrd, 0K buff, 2788K cached
CPU:   0% usr  99% sys   0% nic   0% idle   0% io   0% irq   0% sirq
Load average: 0.41 0.11 0.04 2/46 97
  PID  PPID USER     STAT   VSZ %VSZ %CPU COMMAND
   92    82 root     R     2152   0% 100% cat /dev/urandom

这个系统几乎所有时间(99%)都花在内核空间,这是由于 cat 从 /dev/urandom 读取数据导致的。在这种情况下,单独分析 cat 没有帮助,但分析 cat 调用的内核函数可能有用。

top 的默认视图只显示进程,CPU 使用率是进程中所有线程的总和。按 H 键可以查看每个线程的信息。同样,它会汇总所有 CPU 的时间。如果使用 procps 版本的 top,按 1 键可以查看每个 CPU 的摘要。

4. 简易性能分析器

你可以使用 GDB 以任意间隔停止应用程序,查看其正在执行的操作,这就是简易性能分析器。它设置简单,是收集性能数据的一种方法。具体步骤如下:
1. 使用 gdbserver(用于远程调试)或 GDB(用于本地调试)连接到进程,进程会停止。
2. 观察进程停止的函数,可以使用 GDB 的 backtrace 命令查看调用栈。
3. 输入 continue 使程序继续运行。
4. 过一段时间后,按 Ctrl + C 再次停止程序,然后回到步骤 2。

多次重复步骤 2 到 4,你能快速了解程序是在循环还是在前进,如果重复足够多次,还能找出代码中的热点。在 http://poormansprofiler.org 上有专门介绍这个方法的页面,还有一些脚本可以让操作更简单。

这是统计性能分析的一个例子,即按间隔对程序状态进行采样。经过多次采样后,你会了解函数执行的统计可能性,实际上所需的采样次数比想象中少。其他统计性能分析工具还有 perf record、OProfile 和 gprof。不过,使用调试器采样会干扰程序,因为在收集样本时程序会停止较长时间,而其他工具的开销要小得多。

5. 介绍 perf

perf 是 Linux 性能事件计数器子系统 perf_events 的缩写,也是与 perf_events 交互的命令行工具的名称。自 Linux 2.6.31 起,它们就成为了内核的一部分。在 Linux 源代码树的 tools/perf/Documentation 以及 https://perf.wiki.kernel.org 上有很多有用的信息。

开发 perf 的初衷是提供一种统一的方式来访问性能测量单元(PMU)的寄存器,PMU 是大多数现代处理器核心的一部分。API 定义并集成到 Linux 后,将其扩展以涵盖其他类型的性能计数器就变得顺理成章。

perf 本质上是一组事件计数器,带有关于何时主动收集数据的规则。通过设置规则,你可以捕获整个系统、仅内核、单个进程及其子进程的数据,并且可以在所有 CPU 或单个 CPU 上进行操作,非常灵活。使用这个工具,你可以先查看整个系统,然后聚焦于似乎有问题的设备驱动、运行缓慢的应用程序或执行时间比预期长的库函数。

perf 命令行工具的代码位于内核的 tools/perf 目录中。该工具和内核子系统是协同开发的,这意味着它们必须来自同一版本的内核。perf 功能强大,本文仅将其作为性能分析器进行探讨,其他功能可查看 perf 的手册页和前面提到的文档。

6. 为 perf 配置内核

要使用 perf,需要配置支持 perf_events 的内核,并将 perf 命令交叉编译以在目标系统上运行。相关的内核配置是 CONFIG_PERF_EVENTS,可在“General setup | Kernel Performance Events And Counters”菜单中找到。

若要使用跟踪点进行性能分析(后面会详细介绍),还需启用 Ftrace 部分描述的选项,同时启用 CONFIG_DEBUG_INFO 也很有价值。

perf 命令有很多依赖项,交叉编译比较复杂。不过,Yocto Project 和 Buildroot 都有针对它的目标包。此外,目标系统上需要有你感兴趣的二进制文件的调试符号,否则 perf 无法将地址解析为有意义的符号。理想情况下,整个系统,包括内核,都应该有调试符号,内核的调试符号在 vmlinux 文件中。

6.1 使用 Yocto Project 构建 perf

如果你使用的是标准的 linux - yocto 内核,perf_events 已经启用,无需额外操作。

要构建 perf 工具,可以将其明确添加到目标镜像的依赖项中,或者添加 tools - profile 特性,该特性还会引入 gprof。如前文所述,你可能希望目标镜像和内核 vmlinux 镜像都有调试符号。在 conf/local.conf 中需要添加以下内容:

EXTRA_IMAGE_FEATURES = "debug-tweaks dbg-pkgs tools-profile" 
IMAGE_INSTALL_append = "kernel-vmlinux" 

6.2 使用 Buildroot 构建 perf

许多 Buildroot 内核配置不包含 perf_events,因此首先要检查内核是否包含前面提到的选项。

要交叉编译 perf,运行 Buildroot 的 menuconfig 并选择以下选项:
- 在“Kernel | Linux Kernel Tools”中选择 BR2_LINUX_KERNEL_TOOL_PERF。

要构建带有调试符号的包并将其未剥离地安装在目标系统上,选择以下两个设置:
- 在“Build options | build packages with debugging symbols”中选择 BR2_ENABLE_DEBUG。
- 在“Build options | strip command for binaries on target”中选择 BR2_STRIP = none。

然后,先运行 make clean,再运行 make。构建完成后,需要手动将 vmlinux 复制到目标镜像中。

7. 使用 perf 进行性能分析

你可以使用 perf 通过事件计数器对程序状态进行采样,并在一段时间内累积样本以创建性能分析报告。这是统计性能分析的另一个例子,默认的事件计数器是 cycles,它是一个通用的硬件计数器,映射到表示核心时钟频率周期数的 PMU 寄存器。

使用 perf 创建性能分析报告分两个阶段:
1. 使用 perf record 命令捕获样本并将其写入名为 perf.data 的文件(默认情况下)。例如,对一个搜索字符串 linux 的 shell 脚本进行性能分析:

# perf record sh -c "find /usr/share | xargs grep linux > /dev/null"
[ perf record: Woken up 2 times to write data ]
[ perf record: Captured and wrote 0.368 MB perf.data (~16057 samples) ]
# ls -l perf.data
-rw-------    1 root     root      387360 Aug 25  2015 perf.data
  1. 使用 perf report 命令分析结果。有三种用户界面可供选择:
    • –stdio:纯文本界面,无用户交互,每次查看跟踪信息都需要启动 perf report 并进行注释。
    • –tui:简单的基于文本的菜单界面,可在屏幕间切换。
    • –gtk:图形界面,功能与 –tui 相同。

默认使用 TUI 界面。

perf 能够记录代表进程执行的内核函数,因为它在内核空间收集样本。列表按函数的活跃程度排序,在上述示例中,除了一个函数外,其他都是在 grep 运行时捕获的。有些函数在库(如 libc - 2.20)中,有些在程序(如 busybox.nosuid)中,有些在内核中。由于所有二进制文件都安装了带有调试信息的版本,并且从 /boot/vmlinux 读取内核符号,所以我们能看到程序和库函数的符号名。如果 vmlinux 位于不同位置,可在 perf report 命令中添加 -k 。你也可以使用 perf record -o 将样本保存到不同的文件,然后使用 perf report -i 进行分析。

默认情况下,perf record 使用 cycles 计数器以 1000 Hz 的频率进行采样。但 1000 Hz 的采样频率可能过高,会产生观察者效应,建议尝试降低采样率,根据经验,100 Hz 对大多数情况就足够了,可使用 -F 选项设置采样频率。

8. 调用图

仅查看函数列表可能并不容易理解,列表顶部的函数大多是低级内存操作,而且可以确定它们已经过优化。这时,了解这些函数的调用位置会很有帮助。你可以使用 perf record 的 -g 选项捕获每个样本的回溯信息,这样 perf report 会在函数属于调用链的位置显示加号(+),你可以展开跟踪信息查看链中更低层的函数。

生成调用图依赖于从栈中提取调用帧的能力,就像 GDB 中生成回溯信息一样。展开栈所需的信息编码在可执行文件的调试信息中,但并非所有架构和工具链的组合都能做到这一点。

9. perf annotate

知道要查看哪些函数后,你可能想深入查看代码并了解每条指令的命中次数,这就是 perf annotate 的作用。它通过调用目标系统上安装的 objdump 来实现,你只需用 perf annotate 代替 perf report 即可。

perf annotate 需要可执行文件和 vmlinux 的符号表。如果你想查看源代码与汇编代码交错显示的内容,可以将相关源文件复制到目标设备。如果你使用 Yocto Project 并使用 extra image feature dbg - pkgs 进行构建,或者安装了单独的 - dbg 包,源文件会被安装在 /usr/src/debug 中。否则,你可以检查调试信息来查看源代码的位置,例如:

$ arm-buildroot-linux-gnueabi-objdump --dwarf lib/libc-2.19.so  | 
 grep DW_AT_comp_dir
<3f>   DW_AT_comp_dir : /home/chris/buildroot/output/build/host-
 gcc-initial-4.8.3/build/arm-buildroot-linux-gnueabi/libgcc  

目标系统上的路径应与 DW_AT_comp_dir 中看到的路径完全相同。

10. 其他性能分析工具 - OProfile 和 gprof

OProfile 和 gprof 是早于 perf 的统计性能分析工具,它们提供了 perf 部分功能的子集,但仍然很受欢迎,下面简要介绍一下。

10.1 OProfile

OProfile 是一个内核性能分析器,始于 2002 年。最初它有自己的内核采样代码,但最近的版本使用 perf_events 基础设施来实现这一目的。更多信息可查看 http://oprofile.sourceforge.net。

OProfile 由内核空间组件、用户空间守护进程和分析命令组成。它需要启用以下两个内核选项:
- 在“General setup | Profiling support”中启用 CONFIG_PROFILING。
- 在“General setup | OProfile system profiling”中启用 CONFIG_OPROFILE。

如果你使用 Yocto Project,用户空间组件会作为 tools - profile 镜像特性的一部分安装。如果你使用 Buildroot,通过 BR2_PACKAGE_OPROFILE 启用该包。

你可以使用以下命令收集样本:

# operf <program>

等待应用程序完成,或按 Ctrl + C 停止性能分析,性能分析数据会存储在 /oprofile_data/samples/current 中。使用 opreport 生成性能分析摘要,具体选项可查看 OProfile 手册。

10.2 gprof

gprof 是 GNU 工具链的一部分,是最早的开源代码性能分析工具之一。它结合了编译时检测和采样技术,采样率为 100 Hz,优点是不需要内核支持。

要使用 gprof 对程序进行性能分析,需要在编译和链接标志中添加 -pg,这会在函数前言中注入收集调用树信息的代码。程序运行时,样本会被收集并存储在缓冲区中,程序终止时会写入名为 gmon.out 的文件。

例如,要对 BusyBox 的 grep 小程序进行性能分析,需要使用 -pg 选项重新构建 BusyBox,运行命令,然后查看结果:

# busybox grep "linux" *
# ls -l gmon.out
-rw-r--r-- 1 root root   473 Nov 24 14:07 gmon.out

然后,在目标系统或主机上使用以下命令分析捕获的样本:

# gprof busybox
Flat profile:
Each sample counts as 0.01 seconds.
 no time accumulated
  %   cumulative   self              self     total
 time   seconds   seconds    calls  Ts/call  Ts/call  name
 0.00     0.00     0.00      688     0.00     0.00  xrealloc
 0.00     0.00     0.00      345     0.00     0.00  bb_get_chunk_from_file
 0.00     0.00     0.00      345     0.00     0.00  xmalloc_fgetline
 0.00     0.00     0.00       6      0.00     0.00  fclose_if_not_stdin
 0.00     0.00     0.00       6      0.00     0.00  fopen_for_read
 0.00     0.00     0.00       6      0.00     0.00  grep_file
[...]
    Call graph
granularity: each sample hit covers 2 byte(s) no time propagated
index  % time    self  children    called     name
                 0.00    0.00      688/688  bb_get_chunk_from_file [2]
[1]      0.0     0.00    0.00      688         xrealloc [1]
----------------------------------------------------------
                 0.00    0.00      345/345  xmalloc_fgetline [3]
[2]      0.0     0.00    0.00      345      bb_get_chunk_from_file [2]
                 0.00    0.00      688/688  xrealloc [1]
---------------------------------------------------------
                 0.00    0.00      345/345  grep_file [6]
[3]      0.0     0.00    0.00     345       xmalloc_fgetline [3]
                 0.00    0.00     345/345   bb_get_chunk_from_file [2]
--------------------------------------------------------
                 0.00    0.00       6/6     grep_main [12]
[4]      0.0     0.00    0.00       6       fclose_if_not_stdin [4]
[...]

由于大部分时间花在系统调用上,而 gprof 不跟踪系统调用,所以执行时间都显示为零。此外,gprof 只能捕获多线程进程主线程的样本,也不采样内核空间,这限制了它的实用性。

11. 性能分析工具对比

为了更清晰地了解各个性能分析工具的特点和适用场景,下面对 top、简易性能分析器、perf、OProfile 和 gprof 进行对比:
| 工具名称 | 特点 | 适用场景 | 操作步骤 | 局限性 |
| ---- | ---- | ---- | ---- | ---- |
| top | 简单易用,无需特殊内核选项或符号表,能快速提供系统概览 | 初步了解系统整体性能,查看内存使用、CPU 占用等情况 | 直接运行 top 命令,关注摘要行信息;按 H 查看线程信息;使用 procps 版本时按 1 查看每个 CPU 摘要 | 只能提供宏观信息,难以深入分析具体函数性能 |
| 简易性能分析器 | 设置简单,通过 GDB 按间隔采样 | 快速了解程序是否循环或前进,找出代码热点 | 1. 使用 gdbserver 或 GDB 连接进程;2. 观察停止函数,用 backtrace 查看调用栈;3. 输入 continue 继续;4. 按 Ctrl + C 再次停止并重复操作 | 干扰程序运行,收集样本时程序停止时间长 |
| perf | 功能强大,可灵活配置,能记录内核函数,支持多种用户界面 | 全面分析系统、进程、内核等性能,生成调用图和进行代码注释 | 1. 配置内核支持 perf_events;2. 构建 perf 工具;3. 使用 perf record 捕获样本;4. 使用 perf report 分析结果 | 交叉编译复杂,依赖调试符号 |
| OProfile | 内核性能分析器,利用 perf_events 基础设施 | 专门分析内核性能 | 1. 启用相关内核选项;2. 使用 operf 收集样本;3. 使用 opreport 生成摘要 | 需要内核支持,配置相对复杂 |
| gprof | 结合编译时检测和采样技术,无需内核支持 | 对单线程程序进行性能分析 | 1. 在编译和链接标志中添加 -pg;2. 运行程序生成 gmon.out 文件;3. 使用 gprof 分析文件 | 不支持多线程和内核空间采样 |

graph LR
    A[性能分析需求] --> B{初步概览}
    B -->|是| C[使用 top]
    B -->|否| D{深入分析}
    D -->|关注内核| E[使用 OProfile]
    D -->|关注应用程序| F{单线程或多线程}
    F -->|单线程| G[使用 gprof]
    F -->|多线程| H[使用 perf]
    C --> I[进一步分析]
    I --> H
    G --> J[查看结果]
    H --> J
    E --> J

12. 性能分析的最佳实践

在进行性能分析时,遵循以下最佳实践可以提高效率和准确性:
1. 从简单工具入手 :先使用 top 等简单工具对系统进行初步观察,了解系统的整体状态,确定是否存在明显的性能问题以及问题的大致范围。
2. 按需配置工具 :根据具体需求为工具配置合适的选项,如为 perf 启用调试符号、调整采样频率等。避免过度配置导致系统状态改变,影响测量结果与生产系统的关联性。
3. 逐步深入分析 :在初步了解问题后,使用更强大的工具(如 perf)进行深入分析,从整体系统到具体进程、函数,逐步定位性能瓶颈。
4. 结合多种工具 :不同工具具有不同的特点和优势,结合使用可以更全面地了解系统性能。例如,先用 top 发现问题,再用 perf 进行详细分析,必要时使用简易性能分析器进行快速验证。
5. 注意采样频率 :过高的采样频率可能会产生观察者效应,影响程序的正常运行。根据实际情况选择合适的采样频率,一般 100 Hz 对大多数情况足够。
6. 保存和分析数据 :使用工具生成的性能分析数据(如 perf.data、gmon.out 等)要妥善保存,以便后续深入分析和对比。可以使用相应的分析命令(如 perf report、gprof)查看详细结果。

13. 总结

性能分析是优化 Linux 系统性能的重要手段,通过合理使用各种性能分析工具,可以准确找出系统中的性能瓶颈,为优化提供依据。

top 作为简单易用的工具,是性能分析的良好起点,能让我们快速了解系统的整体状况。简易性能分析器虽然简单但能在一定程度上帮助我们发现代码热点。perf 功能强大且灵活,是进行全面性能分析的首选工具,它可以记录内核函数、生成调用图和进行代码注释。OProfile 专注于内核性能分析,而 gprof 则适用于单线程程序的性能分析。

在实际应用中,我们应根据具体需求选择合适的工具,并遵循性能分析的最佳实践,逐步深入分析问题。同时,要注意工具的配置和使用方法,避免因不当操作影响分析结果。通过不断实践和总结经验,我们能够更好地掌握性能分析技巧,提高 Linux 系统的性能和稳定性。

希望本文介绍的性能分析工具和方法能帮助你在 Linux 系统性能优化的道路上取得更好的成果。如果你在使用过程中遇到问题或有其他疑问,欢迎在评论区留言讨论。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值