今天给技术伙伴们分享一下 BCC 和 Ftrace 的使用方法,在开发实践过程中会遇到哪些问题,本篇文章都将一一道来~

BCC 一个用于跟踪内核和操作程序的工具集,其软件包中包含了一些有用的工具和例子,是 eBPF 技术的一个前端,集成了 ebpf/kprobe/uprobe 等工具,可以使用 Python 来封装,对一些常见功能进行了整合。

而 eBPF 是近几年比较热门的一项技术,它的前身是 BPF(Berkeley Packet Filter),也就是 tcpdump 使用过滤数据包的技术。eBPF 对 BPF 进行了扩展,它能在内核中运行自定义程序,而无需修改内核源码或者加载内核模块。

Ftrace 有两种概念,一种是广义的 Ftrace 框架,一种是狭义的追踪技术。广义的 Ftrace 框架包含了 krpobe trace event 很多追踪技术,而狭义的 Ftrace 也就是本期的重点内容,今天会对其中一种 Ftrace 的使用做着重介绍。

BCC 和 Ftrace 追踪内核网络模块实战_云计算

Tracer 的概念直译为追踪器,可以直观理解为内核提供的一些追踪技术,比如有 function tracer,function graph tracer 等等,今天将着重介绍 function graph tracer,因为 function tracer 的很多功能用 BCC 就可以实现,而 function graph 却是 BCC 没有的功能。

function graph tracer 的效果如图:

BCC 和 Ftrace 追踪内核网络模块实战_高性能_02

BCC 效果如图:

BCC 和 Ftrace 追踪内核网络模块实战_高性能_03

如何使用 BCC Ftrace?

>>>> 安装

BCC 使用 yum install bcc -y 就能安装。Ftrace 不用安装,是内核提供的,只要开启 CONFIG_DYNAMIC_FTRACE 就行,而这个选项我们是默认开启的。

内核版本越高,BCC 和 Ftrace 能使用的功能就越多。也就是说,在低内核版本上,可能一些功能并没有集成进去所以就不能使用这个功能。

BCC 安装完成后如图所示:

BCC 和 Ftrace 追踪内核网络模块实战_云计算_04

而 Ftrace 需要手动开启内核一些选项:

BCC 和 Ftrace 追踪内核网络模块实战_高性能_05

Trace 语法:

  • name 或者 p:name:对内核函数 name() 进行插桩
  • r::name:对内核函数 name() 的返回值进行插桩
  • lib:name 或者 p:lib:name:对用户态 lib 库中的函数 name() 进行插桩
  • r:lib:name:对用户态 lib 库中的函数 name() 进行插桩
  • path::name:对位于 path 路径下的用户态函数 name() 进行插桩
  • r:path:name:对位于 path 路径下的用户态函数 name() 的返回值进行插桩
  • t:system:name:对名为 system:name 的跟踪点进行插桩
  • u:lib:name:对 lib 库中名为 name 的 USDT 探针进行插桩

*: 用来匹配任意字符的通配符。-r 选项允许使用正则表达式。

详细可见《bpf之巅——洞悉linux系统和应用性能》

开发过程中的实际案例

>>>> 安装

BCC 和 Ftrace 追踪内核网络模块实战_应用程序_06

可以看到环境上有两个同网段的 ip,这个环境作为 client 向 server 发消息,server 地址是 100.1.1.21。

>>>> ifdown 测试

在 client 和 server 的正常通信中通过 ifdown 把网卡 down 掉,会发生什么呢?

BCC 和 Ftrace 追踪内核网络模块实战_文件存储_07

BCC 和 Ftrace 追踪内核网络模块实战_文件存储_08

我们可以看到,client 在 down 掉网卡后,返回值还是正常的,但是 server 阻塞在 read(),就已经接收不到数据。

>>>> ip link set down 测试

在 client 和 server 的正常通信中通过 ip link set ens192 down 把 link down 掉,会发生什么呢?

BCC 和 Ftrace 追踪内核网络模块实战_高性能_09

BCC 和 Ftrace 追踪内核网络模块实战_高性能_10

可以看到,client 和 server 都还可以正常收发!

这里有两个问题:

  1. 为什么 ifdown 之后,client 还是可以发送成功?
  2. 为什么 ifdown 之后,server 收不到消息,而 ip link set down 测试中 server 可以接收到消息?

接下来,我们将通过 BCC 和 Ftrace 追踪工具解答这两个问题。

BCC Trace 追踪 __sk_dst_check()

根据排查定位,我们定位到了路由这一块。而路由是在 ip 层做的事情,所以我们来看 ip 层的代码:

ip_queue_xmit() -> ip_queue_xmit() ip_queue_xmit() 是 ip 层的入口函数。

BCC 和 Ftrace 追踪内核网络模块实战_云计算_11

可以看到,ip 层的第一步就是一个检查,那么我们需要看一下对两种测试来说,检查的结果是什么,有什么不同?

我们需要用 BCC 自带的 Trace 工具来查看 sk_dst_check() 的返回值。

>>>> ip link set down 测试

BCC 和 Ftrace 追踪内核网络模块实战_高性能_12

>>>> ifdown

BCC 和 Ftrace 追踪内核网络模块实战_高性能_13

可以看到,在 ifdown 测试中,后面的返回值全部变为了 NULL ,而 ip link set down 测试中,只有一次变为了 NULL ,而后面就恢复了正常。

经过了解后,变为 NULL 的原因很简单。因为 struct sock 里缓存了上次成功的路由,而这 sk_dst_check() 检查路由缓存是否还可用, ifdown 和 ip link set down 后肯定就不可用了。

梳理代码逻辑后可以观察到,如果路由缓存不可用的话,会通过 ip_route_output_ports() -> ip_route_output_flow() 来查找路由表,如果查找成功会再次将其缓存起来。所以这里发生的事情是, ifdown 路由缓存失效后,查找路由表找不到有效路由。而 ip link set down 后,查找路由表可以找到有效路由。

那么要通过 Ftrace 看一下,在两种测试中,ip_route_output_flow() 的逻辑到底有什么不同?

Ftrace 追踪 ip_route_output_flow()

Ip link set down后,路由查找的过程:

BCC 和 Ftrace 追踪内核网络模块实战_高性能_14

ifdown 后,路由查找的过程:

BCC 和 Ftrace 追踪内核网络模块实战_高性能_15

对比两种输出的不同和内核代码,可以发现,ifdown 后会直接查找设备失败,进而直接返回给上层 (tcp)-EHOSTUNREAD。而 ip link down 后,会查找设备成功,进而查找路由成功。

再对比内核代码中的查找路由过程,发现在 fib_table_lookup() 中,有以下逻辑:

BCC 和 Ftrace 追踪内核网络模块实战_云计算_16

然后发现 /proc/sys/net/ipv4/conf/ens40/ignore_routes_with_linkdown 这个选项,控制了 link down 后,原来路由是否还会生效,而这个值默认是 0,也就是不忽略原来的路由项。

至此,终于可以解答前面的两个疑问了:

  1. 为什么 ifdown 之后,client 还是可以发送成功?
  2. 因为 ip 层向 tcp 返回了 EHOSTUNREACH,而 tcp 进行了重试,再次收到 EHOSTUNREACH,再达到重试 /proc/sys/net/ipv4/tcp_retries2 这么多次之前,tcp 是不会返回失败的。
  3. 为什么 ifdown 之后,server 收不到消息,而 ip link set down 测试中 server 可以接收到消息。
  4. 因为 ifdown 后,设备被删除了,直接向 tcp 层返回了 EHOSTUNREACH,而下一次重试又会失败。所以消息发布出去, sever 收不到消息。
  5. 而 ip link set down 后,设备没有被删除,而 /proc/sys/net/ipv4/conf/ens40/ignore_routes_with_linkdown 默认为 0,也就是不忽略已经 down 掉 Link 的路由,所以会查找路由成功,进而发送成功,server 接收成功。

脚本

BCC 和 Ftrace 追踪内核网络模块实战_高性能_17

通过本期文章的分享,大家应该对 BCC 和 Ftrace 的使用方法有了一定的了解,我们后续还会有更多技术文章的分享,可以持续关注。