Linux性能分析与追踪工具指南
1. BPF简介
BPF最初源于网络领域,如今已发展成为运行在Linux内核中的通用虚拟机。它能让用户轻松地在特定的内核和应用事件上运行小程序,迅速成为Linux最强大的追踪器。就像cgroups对容器化部署的变革一样,BPF有潜力通过让用户全面监控生产系统来革新可观测性。像Netflix和Facebook等公司,在其微服务和云基础设施中广泛使用BPF进行性能分析和抵御分布式拒绝服务(DDoS)攻击。
围绕BPF的工具也在不断发展,BPF Compiler Collection(BCC)和bpftrace已成为最突出的两个前端工具。Brendan Gregg深度参与了这两个项目,并在他的书中对BPF进行了广泛的介绍。虽然BPF的可能性众多、范围广泛,但就像cgroups一样,我们无需深入了解其工作原理就能开始使用它。BCC提供了一些现成的工具和示例,可直接从命令行运行。
2. 为BPF配置内核
BCC要求Linux内核版本为4.1或更高。目前,BCC仅支持少数64位CPU架构,这在很大程度上限制了BPF在嵌入式系统中的应用。不过,其中一种64位架构是aarch64,因此我们仍可以在树莓派4上运行BCC。以下是为树莓派4配置支持BPF的内核的步骤:
$ cd buildroot
$ make clean
$ make raspberrypi4_64_defconfig
$ make menuconfig
BCC使用LLVM来编译BPF程序。由于LLVM是一个大型的C++项目,因此构建它需要一个支持wchar、线程等特性的工具链。
小贴士:一个名为ply(https://github.com/iovisor/ply)的软件包于2021年1月23日合并到Buildroot中,并将包含在Buildroot的2021.02 LTS版本中。ply是一个轻量级的Linux动态追踪器,它利用BPF将探针附加到内核的任意点。与BCC不同,ply不依赖于LLVM,除了libc之外没有其他外部依赖项,这使得它更容易移植到arm和powerpc等嵌入式CPU架构上。
在为BPF配置内核之前,我们需要选择一个外部工具链并修改
raspberrypi4_64_defconfig
以适应BCC:
1. 导航到
Toolchain | Toolchain type | External toolchain
并选择该选项,启用外部工具链的使用。
2. 退出
External toolchain
,打开
Toolchain
子菜单,选择最新的ARM AArch64工具链作为外部工具链。
3. 退出
Toolchain
页面,进入
System configuration | /dev management
,选择
Dynamic using devtmpfs + eudev
。
4. 退出
/dev management
,选择
Enable root login with password
,打开
Root password
并在文本字段中输入非空密码。
5. 退出
System configuration
页面,进入
Filesystem images
,将精确大小值增加到2G,以便为内核源代码留出足够的空间。
6. 退出
Filesystem images
,进入
Target packages | Networking applications
,选择
dropbear
软件包,以启用对目标的scp和ssh访问。注意,dropbear不允许无密码的root用户进行scp和ssh访问。
7. 退出
Network applications
,进入
Miscellaneous target packages
,选择
haveged
软件包,以防止程序在目标上等待
/dev/urandom
初始化时阻塞。
8. 保存更改并退出
menuconfig
。
接下来,用
menuconfig
的更改覆盖
configs/raspberrypi4_64_defconfig
,并准备配置Linux内核源代码:
$ make savedefconfig
$ make linux-configure
make linux-configure
命令将下载并安装外部工具链,构建一些主机工具,然后获取、提取和配置内核源代码。目前,Buildroot 2020.02.9 LTS版本的
raspberrypi4_64_defconfig
仍然指向Raspberry Pi Foundation的GitHub分支中的自定义4.19内核源代码tarball。检查
raspberrypi4_64_defconfig
的内容,以确定你使用的内核版本。当
make linux-configure
完成内核配置后,我们可以为BPF重新配置内核:
$ make linux-menuconfig
在交互式菜单中搜索特定的内核配置选项时,按
/
并输入搜索字符串,搜索结果将返回一个编号列表,输入相应的编号即可直接跳转到该配置选项。
要启用内核对BPF的支持,至少需要选择以下选项:
CONFIG_BPF=y
CONFIG_BPF_SYSCALL=y
对于BCC,还需要添加以下选项:
CONFIG_NET_CLS_BPF=m
CONFIG_NET_ACT_BPF=m
CONFIG_BPF_JIT=y
Linux内核版本4.1到4.6需要以下标志:
CONFIG_HAVE_BPF_JIT=y
Linux内核版本4.7及更高版本需要以下标志:
CONFIG_HAVE_EBPF_JIT=y
从Linux内核版本4.7开始,需要添加以下选项,以便用户可以将BPF程序附加到kprobe、uprobe和tracepoint事件:
CONFIG_BPF_EVENTS=y
从Linux内核版本5.2开始,需要添加以下选项以支持内核头文件:
CONFIG_IKHEADERS=m
BCC需要读取内核头文件来编译BPF程序,因此选择
CONFIG_IKHEADERS
可以通过加载
kheaders.ko
模块来访问这些头文件。
要运行BCC网络示例,还需要以下模块:
CONFIG_NET_SCH_SFQ=m
CONFIG_NET_ACT_POLICE=m
CONFIG_NET_ACT_GACT=m
CONFIG_DUMMY=m
CONFIG_VXLAN=m
退出
make linux-menuconfig
时,务必保存更改,以便在构建支持BPF的内核之前将其应用到
output/build/linux-custom/.config
。
3. 使用Buildroot构建BCC工具包
在完成内核对BPF的必要支持配置后,我们需要将用户空间库和工具添加到镜像中。目前,Jugurtha Belkalem等人正在努力将BCC集成到Buildroot中,但他们的补丁尚未合并。虽然Buildroot已经包含了LLVM软件包,但没有选项可以选择BCC编译所需的BPF后端。新的bcc和更新后的llvm软件包配置文件可以在
MELP/Chapter20/
目录中找到。将它们复制到Buildroot 2020.02.09 LTS安装目录的步骤如下:
$ cp -a MELP/Chapter20/buildroot/* buildroot
现在,将bcc和llvm软件包添加到
raspberrypi4_64_defconfig
:
$ cd buildroot
$ make menuconfig
如果你使用的是Buildroot 2020.02.09 LTS版本,并且正确复制了
MELP/Chapter20
中的buildroot覆盖文件,那么在
Debugging, profiling and benchmark
下应该可以找到bcc软件包。要将bcc软件包添加到系统镜像,请执行以下步骤:
1. 导航到
Target packages | Debugging, profiling and benchmark
并选择bcc。
2. 退出
Debugging, profiling and benchmark
,进入
Libraries | Other
,确保
clang
、
llvm
和LLVM的BPF后端都已选中。
3. 退出
Libraries | Other
,进入
Interpreter languages and scripting
,确保
python3
已选中,以便运行BCC附带的各种工具和示例。
4. 退出
Interpreter languages and scripting
,在
Target packages
页面的
BusyBox
下选择
Show packages that are also provided by busybox
。
5. 进入
System tools
,确保
tar
已选中,用于提取内核头文件。
6. 保存更改并退出
menuconfig
。
再次用
menuconfig
的更改覆盖
configs/raspberrypi4_64_defconfig
,并构建镜像:
$ make savedefconfig
$ make
LLVM和Clang的编译将需要很长时间。镜像构建完成后,使用Etcher将生成的
output/images/sdcard.img
文件写入micro SD卡。最后,将内核源代码从
output/build/linux-custom
复制到micro SD卡根分区的
/lib/modules/<kernel version>/build
目录。这一步非常关键,因为BCC需要访问内核源代码来编译BPF程序。
将完成的micro SD卡插入树莓派4,用以太网线将其连接到本地网络,然后给设备通电。使用
arp-scan
定位树莓派4的IP地址,并使用之前设置的密码以root用户身份通过SSH登录。现在,我们就可以开始亲身体验BPF的实验了。
4. 使用BPF追踪工具
几乎所有与BPF相关的操作,包括运行BCC工具和示例,都需要root权限,这就是我们启用通过SSH进行root登录的原因。另一个前提条件是挂载
debugfs
:
# mount -t debugfs none /sys/kernel/debug
BCC工具所在的目录不在
PATH
环境变量中,为了方便执行,导航到该目录:
# cd /usr/share/bcc/tools
让我们从一个以直方图形式显示任务在CPU上运行时间的工具开始:
# ./cpudist
cpudist
显示任务在被调度出CPU之前在CPU上花费的时间。如果没有看到直方图,而是看到以下错误信息,则说明你忘记将内核源代码复制到micro SD卡:
modprobe: module kheaders not found in modules.dep
Unable to find kernel headers. Try rebuilding kernel with
CONFIG_IKHEADERS=m (module) or installing the kernel
development package for your running kernel version.
chdir(/lib/modules/4.19.97-v8/build): No such file or directory
[…]
Exception: Failed to compile BPF module <text>
另一个有用的系统级工具是
llcstat
,它可以追踪缓存引用和缓存未命中事件,并按PID和CPU进行汇总。并非所有BCC工具都需要按
Ctrl + C
来结束,有些工具(如
llcstat
)可以接受采样周期作为命令行参数。
我们可以使用
funccount
等工具更具体地追踪特定函数,该工具接受一个模式作为命令行参数。例如,我们可以追踪所有名称中包含
tcp
后跟
send
的内核函数。许多BCC工具也可用于追踪用户空间的函数,但这需要调试符号或在源代码中插入用户静态定义的追踪点(USDT)探针。
对于嵌入式开发人员来说,
hardirqs
工具特别有用,它可以测量内核处理硬中断所花费的时间。用Python编写自己的通用或自定义BCC追踪工具比你想象的要容易,你可以在BCC附带的
/usr/share/bcc/examples/tracing
目录中找到一些示例进行参考。
5. 使用Valgrind
Valgrind是一个用于识别内存问题的工具,除了
memcheck
工具外,它还有其他用于应用程序性能分析的有用工具,这里我们将介绍Callgrind和Helgrind。由于Valgrind通过在沙箱中运行代码来工作,因此它可以在代码运行时进行检查并报告某些行为,这是原生追踪器和分析器无法做到的。
5.1 Callgrind
Callgrind是一个生成调用图的分析器,它还可以收集有关处理器缓存命中率和分支预测的信息。Callgrind仅在瓶颈是CPU限制时有用,如果涉及大量I/O或多个进程,则它的作用不大。
Valgrind不需要内核配置,但需要调试符号。它在Yocto Project和Buildroot中都作为目标软件包提供(
BR2_PACKAGE_VALGRIND
)。在目标设备上使用Valgrind运行Callgrind的命令如下:
# valgrind --tool=callgrind <program>
这将生成一个名为
callgrind.out.<PID>
的文件,你可以将其复制到主机上,使用
callgrind_annotate
进行分析。默认情况下,所有线程的数据将捕获到一个文件中。如果在捕获时添加
--separate-threads=yes
选项,则每个线程的分析结果将保存在以
callgrind.out.<PID>-<thread id>
命名的文件中。
Callgrind可以模拟处理器的L1/L2缓存并报告缓存未命中情况。使用
--simulate-cache=yes
选项捕获跟踪信息。由于L2缓存未命中的代价远高于L1缓存未命中,因此要注意D2mr或D2mw计数较高的代码。
Callgrind的原始输出可能会让人不知所措,难以理解。像KCachegrind(https://kcachegrind.github.io/html/Home.html)这样的可视化工具可以帮助你处理Callgrind收集的大量数据。
5.2 Helgrind
Helgrind是一个线程错误检测器,用于检测包含POSIX线程的C、C++和Fortran程序中的同步错误。它可以检测三类错误:
- 检测API的错误使用,例如解锁已经解锁的互斥锁、解锁由不同线程锁定的互斥锁或不检查某些pthread函数的返回值。
- 监控线程获取锁的顺序,以检测可能导致死锁的循环。
- 检测数据竞争,即两个线程在没有使用适当的锁或其他同步机制的情况下访问共享内存位置。
使用Helgrind很简单,只需执行以下命令:
# valgrind --tool=helgrind <program>
它会在发现问题和潜在问题时打印出来。你可以通过添加
--log-file=<filename>
将这些消息定向到一个文件中。
Callgrind和Helgrind依赖于Valgrind的虚拟化进行性能分析和死锁检测,这种重量级的方法会减慢程序的执行速度,增加观察者效应的可能性。
6. 使用strace
strace是一个非常简单的追踪器,它可以捕获程序及其子进程(可选)进行的系统调用。你可以使用它来完成以下任务:
- 了解程序进行了哪些系统调用。
- 找出失败的系统调用及其错误代码,这在程序无法启动但不打印错误消息或消息过于笼统时非常有用。
- 找出程序打开了哪些文件。
- 了解正在运行的程序正在进行哪些系统调用,例如检查它是否陷入了循环。
获取跟踪信息的最简单方法是将命令作为参数传递给strace:
# strace ./helloworld
跟踪信息大部分显示了运行时环境的创建过程,特别是库加载器如何寻找
libc.so.6
,最终在
/lib
中找到它。最后,程序开始运行
main()
函数,打印消息并退出。
如果你希望strace跟踪原始进程创建的任何子进程或线程,可以添加
-f
选项。如果使用strace跟踪创建线程的程序,建议使用
-f
选项,更好的做法是使用
-ff
和
-o <filename>
,这样每个子进程或线程的输出将写入一个以
<filename>.<PID | TID>
命名的单独文件中。
一个常见的用途是发现程序在启动时尝试打开哪些文件,你可以通过
-e
选项限制跟踪的系统调用,并使用
-o
选项将跟踪信息写入文件而不是标准输出:
# strace -e open -o ssh-strace.txt ssh localhost
你甚至可以将strace用作基本的性能分析工具,使用
-c
选项,它会累积系统调用花费的时间并打印摘要:
# strace -c grep linux /usr/lib/* > /dev/null
strace非常通用,我们只是触及了它的功能的冰山一角。建议下载Julia Evans的免费电子杂志《Spying on your programs with strace》(https://wizardzines.com/zines/strace/)以了解更多信息。
综上所述,这些工具(BPF、Valgrind和strace)在不同的场景下都有各自的优势,可以帮助我们更好地分析和调试Linux系统和应用程序。在实际使用中,我们可以根据具体需求选择合适的工具。例如,当需要对内核和应用事件进行详细追踪时,BPF是一个很好的选择;当需要进行内存和性能分析时,Valgrind可以提供丰富的信息;而当需要快速了解程序的系统调用情况时,strace则是一个简单有效的工具。
Linux性能分析与追踪工具指南(续)
7. 工具对比总结
为了更清晰地了解BPF、Valgrind和strace这三款工具的特点和适用场景,我们可以通过以下表格进行对比:
| 工具名称 | 主要功能 | 适用场景 | 优点 | 缺点 |
| — | — | — | — | — |
| BPF | 运行小型程序在特定内核和应用事件上进行追踪和分析,可用于性能分析和抵御DDoS攻击 | 需要深入内核和应用事件追踪,如微服务和云基础设施性能分析 | 功能强大,可全面监控生产系统;有丰富的工具和示例 | 配置复杂,对内核版本和CPU架构有要求 |
| Valgrind | 包含Callgrind和Helgrind等工具,用于内存问题检测、调用图生成、缓存分析和线程错误检测 | 内存和性能分析,特别是CPU限制的瓶颈分析和线程同步错误检测 | 能在沙箱中运行代码进行检查,提供多种分析功能 | 重量级方法,减慢程序执行速度,增加观察者效应 |
| strace | 捕获程序及其子进程的系统调用 | 了解程序系统调用情况,如查找失败的系统调用、打开的文件等 | 简单通用,能快速获取系统调用信息 | 只能提供系统调用层面的信息,不够深入 |
通过这个表格,我们可以根据具体的需求和场景,更准确地选择合适的工具。
8. 操作流程总结
下面是使用这些工具的整体操作流程mermaid流程图:
graph LR
classDef startend fill:#F5EBFF,stroke:#BE8FED,stroke-width:2px
classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px
classDef decision fill:#FFF6CC,stroke:#FFBC52,stroke-width:2px
A([开始]):::startend --> B{选择工具}:::decision
B -->|BPF| C(配置内核支持BPF):::process
C --> D(构建BCC工具包):::process
D --> E(使用BPF追踪工具):::process
B -->|Valgrind| F(安装Valgrind):::process
F --> G{选择工具}:::decision
G -->|Callgrind| H(运行Callgrind分析):::process
G -->|Helgrind| I(运行Helgrind检测):::process
B -->|strace| J(运行strace追踪):::process
E --> K([结束]):::startend
H --> K
I --> K
J --> K
9. 实际应用案例分析
为了更好地理解这些工具的实际应用,我们来看几个具体的案例。
9.1 BPF在微服务性能分析中的应用
假设我们有一个由多个微服务组成的系统,在高并发情况下出现了性能瓶颈。我们可以使用BPF的BCC工具来进行分析。
- 首先,按照前面的步骤配置内核和构建BCC工具包。
- 然后,使用
cpudist
工具查看各个微服务任务在CPU上的运行时间分布,找出哪些任务占用CPU时间过长。
# cd /usr/share/bcc/tools
# ./cpudist
-
接着,使用
funccount工具追踪特定函数,例如微服务中处理请求的关键函数,查看其调用频率。
# ./funccount 'function_pattern'
通过这些分析,我们可以定位到性能瓶颈所在的微服务和具体函数,从而进行针对性的优化。
9.2 Valgrind在内存泄漏检测中的应用
在一个C++编写的应用程序中,我们怀疑存在内存泄漏问题。可以使用Valgrind的
memcheck
工具进行检测。
# valgrind --tool=memcheck <program>
memcheck
会详细报告内存分配和释放的情况,指出可能存在内存泄漏的位置。如果还需要进行性能分析,可以使用Callgrind工具。
# valgrind --tool=callgrind <program>
生成的
callgrind.out.<PID>
文件可以使用
callgrind_annotate
进行分析,或者使用KCachegrind进行可视化分析。
9.3 strace在程序启动问题排查中的应用
当一个程序无法正常启动,且没有明确的错误信息时,我们可以使用strace来找出问题所在。
# strace -e open -o program-strace.txt <program>
通过查看
program-strace.txt
文件,我们可以了解程序在启动过程中尝试打开的文件,可能会发现文件缺失或权限问题导致程序无法启动。
10. 总结与建议
在Linux系统和应用程序的性能分析和调试中,BPF、Valgrind和strace这三款工具都发挥着重要的作用。它们各自有其独特的功能和适用场景,我们需要根据具体的问题和需求来选择合适的工具。
- 对于复杂的内核和应用事件追踪 :BPF是首选工具。虽然配置和使用相对复杂,但它提供了强大的功能和丰富的工具集,可以深入了解系统的运行情况。
- 对于内存和性能分析 :Valgrind是一个很好的选择。它可以帮助我们检测内存问题、分析调用图和线程同步错误,但要注意其对程序执行速度的影响。
- 对于快速了解程序的系统调用情况 :strace是简单有效的工具。它可以快速定位程序启动问题、文件访问问题等。
在实际使用中,我们可以结合多个工具进行综合分析,以更全面地了解系统和程序的运行状况。同时,不断学习和掌握这些工具的使用技巧,将有助于我们更高效地进行性能优化和问题排查。
希望通过本文的介绍,你对这些工具的使用有了更深入的了解,并能在实际工作中灵活运用它们。
超级会员免费看

2075

被折叠的 条评论
为什么被折叠?



