LWN:简单又高效!利用bpftrace分析kernel行为

640点击上方蓝色字关注我们~



Kernel analysis with bpftrace

July 18, 2019

本文由 Brendan Gregg 提供


在2019 Linux Storage, Filesystem, and Memory-Management Summit (LSFMM)峰会里,我有一个keynote介绍了我使用bpftrace在Netflix server上debug过程中,利用BPF查看各种信息如何方便。本文我会针对kernel开发者介绍利用bpftrace分析的过程,帮助他们掌握更便利的代码分析方法。

最近我在跟另一位开发者讨论tcp_sendmsg(),他比较担心过大的message size(例如超过100MB)可能会导致出错。100MB?? 我不太相信Netflix会在实际server上使用这么大的网络包。大家应该都很熟悉这个函数的定义,来自net/ipv4/tcp.c:

   int tcp_sendmsg(struct sock *sk, struct msghdr *msg, size_t size);

bpftrace在Netflix的在线服务器上已经安装好了(很多公司都自带安装好了),所以我直接ssh到一个繁忙的服务器上,想看看每10秒时间内tcp消息包的长度的分布:

 
 

最大在128~256KB之间。这里使用kproble(“k”)监控tcp_sendmsg(),然后保存参数2(size)的柱状图统计信息到名为@size的BPF map中,这里名字其实不重要,只是用来在输出时能更加易懂。并且配置了10秒钟的监控时长,时间到了后会退出,此时把所有BPF map都打印出来。


会不会有出错?:

 
 

可以看出没有出错过。这里我使用了kretprobe("kr"),统计retval各种值的发生次数,它要么是一个负数的错误之,要么是size。因为我不关心返回的size,所以使用了一个三元操作把所有正数都设为0。


bpftrace可以让你快速回答这些问题,并且是在真正运行的生产系统server上,不需要使用debuginfo(Netflix通常不会安装)。这个例子表明Netflix这种workload场景下不太可能碰到这里所说的tcp_sendmsg()过大的问题https://github.com/iovisor/bcc/issues/2440 


Other one-liners

此前关于tcp_sendmsg()分析的一行命令已经展示了bpftrace对kernel分析的能力。还有一些其他一行命令能完成很多功能。仍以tcp_sendmsg()为例(你使用的时候可以用在其他kernel function上)。


列出tcp_sendmsg() size超过8192字节的情况,每次都打印出来:

 
 


列出一个直方图来展示每个进程(包括PID和进程名comm)发出的size:

 
 


统计各种返回值的发生频次:

 
 


展示每秒钟的统计信息,包括调用次数,平均size,总字节数:

 
 


统计各种类型stack trace的次数:

 
 


统计各种类型的stack trace,并且回溯3层stack:

 
 


统计函数调用延迟,展示为纳秒数据的柱状图:

 
 

最后一个例子会对每个probe(利用thread ID做索引)保存时间戳,并且在另一次probe触发时提取前面的时间戳做计算。这种用法可以用在各种延迟测算上。


struct data


上述的一行命令例子里面都缺少一个重要的功能:查看struct。这里再列一下函数原型:

 
 

bpftrace可以利用arg0-argN用来指示kprobe函数参数,简单来说它们就是指调用函数的时候的参数寄存器(例如arg2就是x86_64平台上的%rdx寄存器的值)。

bpf有能力读取kernel头文件,这些头文件在生产系统上也都是安装好的,这样只要能包含正确的头文件并且转换好参数,就能够访问struct的成员数据了:

 
 

这个例子就是使用bpftrace打印出tcp_sendmsg()的地址信息,大小,返回值。相应的输出会是类似这样:

 
 

tcp_sendmsg.bt文件的源代码如下:

 
 

这里的kprobe中,sk和size都保存在一个per-thread-ID的maps里面,这样当tcp_sendmsg()返回时就可以从kretprobe里面访问到这些信息。kretprobe解析sk并且打印其中的信息。如果是IPv4包,就用bpftrace的ntop()函数来把地址转换成字符串。目标端口则会把网络字节序转换成主机字节序(译者注:这里应该是指big endian和little endian转换)。为了简化起见,这里我没有考虑IPv6,不过你应该可以自己添加IPv6支持(ntop()支持IPv6地址)。

现在有人在做工作让bpftrace支持BPF Type Format (BTF)信息,这样就能带来更多好处,例如kernel头文件里看不到的struct结构也能支持了。


Advance example


目前演示的都是tracing调试方法。在这个更高级的例子里面我会演示off-CPU profiling(指的是统计每次进程释放CPU切换出去再切换回来的时候的过程)。

对某个进程进行CPU profiling通常很容易:我只要对stack进行采样,检查performance-monitor counter(PMC)以及model相关的寄存器(MSRs),就能看到CPU上在运行的是什么东西,以及为什么这么慢了。不过Off-CPU profiling也有不少麻烦之处。我能在context switch(上下文切换)的时候显示stack被阻塞在哪个调用上,不过通常只能看到是阻塞在一些常见的调用API上(例如select(), epoll(), 或者某个锁),但我们其实想要更进一步的信息。

BPF最终也提供出了一个解决方案:保存stack trace以及后续提取出来分析的能力(我很希望DTrace有这个功能可惜一直没有)。下面就是bpftrace实现方案,能显示进程的名字,被block时的stack信息,唤醒者的stack,以及毫秒为单位的阻塞时间柱状图:

 
 

上面我只保留了一对stack输出信息。这里能看出ssh进程阻塞在select()上,而唤醒者的stack能看出它在等一个网络报文。柱状图可以看出这条切换出去再到被唤醒切换回CPU的过程中,通常都花费了多少毫秒的时间。

其中offwake.bt的源代码如下:

 
 

这里try_to_wake_up()的kernel stack会按照它的进程ID来保存起来,后面在finish_task_switch()的时候会提取这些信息来做计算和统计。这其实就是BCC里的offwaketime工具以及kernel代码里的samples/bpf/offwaketime*的简化实现,只要使用bpftrace就能做到了。

我之前所做的Performance@Scale talk演讲里面介绍过这个问题以及相应的BPF解决方案,当时还演示了这些stack信息如何被展示成火焰图(flame graph)。

有时你需要知道谁唤醒那个唤醒者,以及再之前是谁唤醒的,这么一串的过程。通过遍历wakeup chain,就能建立一个“chain graph”把这一路上的latency来自哪里都展示出来(通常是来自中断)。


Tracepoints


此前的例子都是使用kprobe,而kprobe都会随着kernel升级来更新的。

tracepoint比起kprobe来说更适合bpftrace程序,尽管它们其实也会随着kernel版本而改变。反正还是比kprobe要好,因为kprobe可能会被随意更改,甚至可能因为被inline处理了所以直接消失。不过有些kprobe要比其他的kprobe更加稳定一些,特别是kernel内部接口相关的,例如VFS,struct file_operations,struct proto等等。

下面给个简单的例子,如下是timer:hrtimer_start tracepoint能提供的参数:

 
 

那我们也来统计一下函数参数各个值的使用次数:

 
 

tracepoint的参数可以通过args来访问,这个例子里面我使用ksym() bpftrace函数来返回kernel里这个地址对应的符号名称。


More examples and information


我在LSFMM主题演讲里面逐步演示了Netflix系统服务器上的调试例子。大家还可以从bpftrace的git仓库里tools目录下找到更多的例子。在LSFMM会议上我还展示了很多我为后面Addison-Wesley出版社要出版的BPF分析书准备的示例工具,也都可以在这本书的网页上看到 http://www.brendangregg.com/bpf-performance-tools-book.html


建议你参照这里(https://github.com/iovisor/bpftrace/blob/master/INSTALL.md  )的安装文档,获取最新的0.9.1或更新版本。针对各种Linux发行版也有相应的安装包。像Netflix和Facebook等公司都有他们自己的安装包,你也可以自己从代码编译。bpftrace目前使用LLVM和Clang来做依赖检测(BCC也是这么做的),不过今后版本里面可能就不是必须的了。

也请查看bpftrace Cheat Sheet(速查表,http://www.brendangregg.com/BPF/bpftrace-cheat-sheet.html  )了解此语言的总结,还有完整的bpftrace Reference Guide参考手册(https://github.com/iovisor/bpftrace/blob/master/docs/reference_guide.md  )。


What about perf, ftrace?


工作中我会一直选择最好用的工具,并不会局限于bpftrace一个。很多时候会用perf或者ftrace。

举例来说:

  • 要统计很多个函数的调用次数的时候:使用ftrace,因为它能快速初始化,BPF kprobe加载的时候也会更加快,特别是能用上perf_event_open()的multi-attach功能的时候。

  • 函数流程tracing:使用ftrace function graphing功能。

  • PMC statistics:使用perf,统计CPU所提供的performance monitor counter。

  • 对CPU周期采样并提供时间戳:使用perf。因为它的perf.data输出格式优化的非常棒。


就最后一个例子来说,有时候我需要每个采样点都要有时间戳,不过大多数时候并不需要,这样我就使用bpftrace。例如按照99Hz的频率对kernel stack进行采样:


Conclusion


Linux随着时间发展,变化非常大。现在它有了很强大的tracer,也就是bpftrace,它具有从底层开发出来的extend PBF和Linux支持,也能解决Netflix和其他公司在生产系统上的真正问题。使用仅仅一行命令,或者很短小的工具,就可以用各种特有方法来查看你的代码。本文里面已经提供了很多样例。


如果你想知道你的代码在Netflix生产环境服务器上运行的如何,可以给我发个email(译者注:这句话是针对kernel开发者说的),提供你的bpftrace program,我可以直接把相应的命令输出发回给你(必须是在不会暴露公司或者客户的具体信息的情况下)。如果能帮你查出来Linux执行我们Netflix的workload的时候有哪些低效的地方,并且你又能提供一个fix,那对Netflix更加有好处了。我的邮件是bgregg@netflix.com。


[感谢Alastair Robertson 创建了 bpftrace, 感谢bpftrace, BCC, and BPF 社区过去5年的成果和贡献]


全文完

LWN文章遵循CC BY-SA 4.0许可协议。

极度欢迎将文章分享到朋友圈 
热烈欢迎转载以及基于现有协议上的修改再创作~


长按下面二维码关注:Linux News搬运工,希望每周的深度文章以及开源社区的各种新近言论,能够让大家满意~


640?wx_fmt=jpeg

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值