Linux系统性能分析神器BPF
BPF是什么?
- BPF是Berkeley packet Filter的缩写,诞生于1992年,最早用于提升网络包过滤工具的性能。
- 2013年,Alexei Starovoitov重新实现了BPF,经过他和Daniel Borkmann的共同完善,在2014年正式并入Linux内核主线。
- BPF提供了一种在各种内核事件和应用程序事件发生时运行一小段程序的机制。这就使得内核变得可编程。允许用户定制和控制他们的系统。
- BPF由指令集,存储对象和辅助函数等几部分组成。由于它采用了虚拟指令集规范,因此可将它视作一种虚拟机实现,这些指令由Linux内核的BPF运行时模块执行。(可类比Java虚拟机)
- 应用领域:网络,可观测性,安全。
BCC, bpftrace
由于直接通过BPF指令编写BPF程序是非常繁琐的,因此社区开发了可以提供高级语言编程的BPF前端。其中BCC和bpftrace都是属于Github上的一个名为IO Visor的Linux基金会项目。
- BCC(BPF Compiler Collection)是最早用于开发BPF跟踪程序的高级框架,它提供了一个编写内核BPF程序的C语言环境,同时还提供了其他高级语言(如python,lua,C++)环境来实现用户端接口。
- bpftrace提供了专门用于创建BPF工具的高级语言支持。且bpftrace的源代码非常简洁。
- BCC和bpftrace具有互补性,bpftrace在编写功能强大的单行程序,短小的脚本方面甚为理想;BCC则更适合开发复杂的脚本和作为后台进程使用,它还可以调用其他库的支持。
- libbcc和libbpf库提供了使用BPF程序对事件进行观测的库函数。
- 另外,还有一个叫ply的BPF前端,目前处于开发阶段。它的设计目标是尽可能轻量化并且依赖最小化,因此尤其适合在嵌入式Linux环境下使用。
BPF跟踪的能见度
BPF跟踪可以在整个软件栈范围内提供能见度,在生产环境中可以立刻部署BPF程序,不需要重启系统,也不需要以特殊方式重启应用软件。这有点像医学检查使用的X光影像。
图解BPF
早期的经典BPF
BPF最初是为BSD操作系统开发的,其工作方式十分有趣:用户使用BPF虚拟机的指令集定义过滤器表达式,然后传递给内核,由解释器执行。 这使得包过滤可以在内核中直接进行,避免了向用户态进程复制每个数据包,从而提升了数据包过滤的性能,著名的网络抓包工具tcpdump就是这样工作的。
最初的BPF现在被成为经典BPF,它是一个功能有限的虚拟机。它由两个寄存器,一个由16个内存槽位组成的临时存储区域和一个程序计数器。经典BPF于1997年进入Linux内核版本2.1.75
现在的扩展版BPF
扩展版BPF增加了更多寄存器,字长从32位增至64位,创建了灵活的BPF映射型存储,并允许调用一些受限制的内核功能。同时,BPF被设计成可以使用即时编译(JIT),机器指令和寄存器可以一对一映射。支持的事件类型也更加丰富,除了网络数据包,还包括kprobes(内核函数)、uprobes(用户态函数)、tracepoints(跟踪点),USDT(用户态标记), PMC,perf_events等。
BPF运行时(runtime)的各模块架构如下图所示,它展示了BPF指令如何通过BPF验证器验证,然后通过JIT编译器编译成机器码,最后由BPF虚拟机执行。
- 为什么性能工具需要BPF技术
BPF与众不同之处在于,它同时具备高效率和生产环境安全性的特点,并且它已经被内置在内核中。 下图对比了“用直方图的形式展示磁盘I/O的尺寸分布”的处理过程。使用BPF避免了将事件复制到用户空间并再次对其处理的成本。
BPF跟踪的事件源
- 动态插桩 kprobes
- kprobes 提供了针对内核的动态插桩支持。2004年,kprobes正式加入内核2.6.9版本。
- kprobes 可以对任何内核函数进行插桩,它可以实时在生产环境系统启用,不需要重启系统,也不需要以特殊方式重启内核。
- kprobes 技术还有另外一个接口,即kretprobes, 用来对内核函数返回时进行插桩以获取返回值。
- 使用举例:下面这个bpftrace单行程序通过匹配
vfs_
开头的函数,统计了所有VFS函数的调用次数。
$ sudo bpftrace -e 'kprobe:vfs_* { @[probe] = count() }' @[kprobe:vfs_statx]: 28 @[kprobe:vfs_write]: 49 @[kprobe:vfs_getattr_nosec]: 60 @[kprobe:vfs_read]: 143 @[kprobe:vfs_open]: 168
- 动态插桩uprobes
- uprobes 提供了用户态程序的动态插桩。于2012年7月被合并到Linux 3.5内核中。
- uprobes 可以在用户态程序的以下位置插桩:函数入口,特定便宜处,以及函数返回处。
- uprobes 是基于文件的,当一个可执行文件的一个函数被跟踪时,所有使用到这个文件的进程都会被插桩。
- 使用举例:下面的bpftrace单行程序对readline()进行插桩
bpftrace -e 'uprobe:/bin/bash:readline { @ = count() }' Attaching 1 probe...
- 静态插桩tracepoint
- 跟踪点(tracepoint)可以用来对内核进行静态插桩。内核开发者在内核函数的特定逻辑位置处,有意放置了这些插桩点;
- 该技术正式出现在2009年发布的Linux 2.6.32内核中。
- 跟踪点的格式是“子系统:事件名”,例如:
kmem:kmalloc
- 静态插桩USDT
- 用户态预定义静态跟踪(user-level statically defined tracing, USDT)提供了一个用户空间版的跟踪点机制。
- 动态USDT
- PMC性能监控计数器
- performance monitoring counter, 指的是:处理器上的硬件可编程计数器。
- 只有通过PMC才能测量CPU指令执行的效率,CPU缓冲命中率,内存/数据互联和设备总线利用率,以及阻塞的指令周期等。
- perf_events
- perf_events是
perf
命令所依赖的采样和跟踪机制,它于2009年被加入Linux 2.6.31版本。
- perf_events是
安装
- 安装bcc
以ubuntu系统为例:
$ sudo apt-get install bpfcc-tools linux-headers-$(uname -r)
这会把BCC工具安装到/sbin目录下,并带有-bpfcc
后缀。
$ ls /sbin/*-bpfcc
- 安装bpftrace
以ubuntu系统为例:
$ sudo apt-get install bpftrace
$ which bpftrace
/usr/bin/bpftrace
性能分析方法论
方法论是一个可以遵循的过程:它指导从哪里开始,中间步骤有哪些和到哪里结束。
业务负载画像
业务负载画像的目的是理解实际运行的业务负载。“消除不必要的工作”是在性能优化结果中收益最显著的一种。通过研究业务负载的构成就可以找到这样的优化点。
推荐步骤如下:
- 负载是谁产生的?(比如,进程ID,用户ID,IP地址)
- 负载为什么会产生?
- 负载的组成是什么?
- 负载怎样随着事件发生变化?
下钻分析
下钻分析的工作过程是从一个指标开始,然后将这个指标拆分成多个组成部分,再将最大的组件进一步拆分为更小的组件,不断重复这个过程直到定位出一个或多个根因。
推荐步骤如下:
- 从业务最高层级开始分析
- 检查下一个层级的细节
- 挑出最感兴趣的部分或线索
- 如果问题还没有解决,跳转至第2步
USE方法论
USE方法论用来对资源的使用情况进行分析,针对每一个资源,分别去检查:
- 使用率
- 饱和度
- 错误
这些资源包括CPU,内存,磁盘,网络等。
检查清单法
检查清单法可以列出一系列工具和指标,用于对照运行和检查。 这个方法论适用于指导公司各个层次的工程师实施操作,将个人技能应用于更广的范围内。
Linux 60秒分析
-
uptime
快速检查平均负载 -
dmesg | tail
显示过去10条系统日志,寻找可能导致性能问题的错误 -
vmstat 1
显示CPU,内存,IO等系统指标 -
mpstat -P All 1
将每个CPU分解到各个状态下的事件打印出来 -
pidstat 1
按每个进程展示CPU的使用情况 -
iostat -xz 1
显示存储设备的IO指标 -
free -m
显示以MB为单位的可用内存 -
sar -n DEV 1
查看网络设备指标 -
sar -n TCP, ETCP 1
查看TCP指标和TCP错误信息(比如retrans/s 每秒TCP重传的数量) -
top
对相关结果进行二次确认,浏览系统和进程的摘要信息。
BCC工具检查清单
-
execsnoop
通过跟踪每次execve
系统调用,监控系统新进程的创建 -
opensnoop
通过跟踪每次open
系统调用,跟踪进程打开文件信息 -
ext4slower
跟踪ext4文件系统常见操作,把耗时超过某个阈值的操作打印出来 -
biolatency
跟踪磁盘IO延迟, 并且以直方图显示 -
biosnoop
将每一次磁盘IO请求打印出来,包含调用进程,延迟之类的细节信息 -
cachestat
显示文件系统缓冲的统计信息,可发现缓存命中率较低的问题 -
tcpconnect
跟踪每次主动的TCP连接建立,寻找不寻常的连接请求 -
tcpaccept
跟踪每次被动的TCP连接建立时 -
tcpretrans
跟踪TCP重传数据包,TCP重传会导致延迟和吞吐量方面的问题 -
runqlat
对线程等待CPU运行的事件进行统计,并打印为一个直方图 -
profile
CPU剖析器,用来理解哪些代码路径消耗了CPU资源