eBPF:一小步

(更新:我现在有一个关于 eBPF 的页面:Linux eBPF 工具)
我在 eBPF 上迈出了一小步,但它预示着 Linux 跟踪的一大飞跃。

./bitehist

跟踪块设备 I/O… 间隔 5 秒。Ctrl-C 结束。

 kbytes : 计数分布
   0 -> 1 : 3 | |
   2 -> 3 : 0 | |
   4 -> 7 : 3395 |******************************************|
   8 -> 15 : 1 | |
  16 -> 31 : 2 | |
  32 -> 63 : 738 |******* |
  64 -> 127 : 3 | |
 128 -> 255 : 1 | |

我的 bitehist 工具的输出显示,在 5 秒的跟踪期间,大多数磁盘 I/O 的大小在 4 到 7 KB 之间。32 到 63 KB 之间的数量也较小。
这使用了一个自定义的内核直方图,通过 eBPF map 实现。
地图可以提供很多东西,包括令人垂涎的延迟直方图——Sun 的 DTrace 实用程序的主要内容。而且,与 DTrace 一样,地图是高效的。(eBPF 映射可能更是如此,因为它们是 JIT 编译的。)
在此示例中,映射仅允许将直方图数据(“计数”列)从内核传递到用户级。它使用自定义 eBPF 程序填充,并使用内核函数的动态跟踪。
这是我关于 eBPF(扩展伯克利包过滤器)的第一篇文章,我将总结我们是如何到达这里的,为什么 eBPF 和映射很重要,以及我们接下来可能会去哪里。
为什么我们需要 eBPF
在过去的十年中,Linux 跟踪有了很大的改进。ftrace 和 perf_events 现在是 Linux 内核的一部分,可让您详细分析 Linux 性能,并解决困难的处理器级、内核级和应用程序级问题。他们的能力让我将一些旧的 DTrace 工具移植到 Linux。我已将这些作为perf-tools集合发布,我在LISA14上谈到过,并在lwn.net中写过。
但我有时会遇到一些限制,比如想要低开销的延迟直方图。 eBPF 使这种能力以及更多功能成为可能,eBPF 通过扩展数据包过滤功能开始了生命。 eBPF 现在正在分阶段集成到 Linux 内核中。就好像 Linux 正在获得超级大国,一次一个。
以下是内核开发人员 Ingo Molnár 在 2015 年 4 月的 lkml 帖子中描述 eBPF 的方式:
这个周期中更有趣的功能之一是将 eBPF 程序(由内核执行的用户定义的沙盒字节码)附加到 kprobes 的能力。这允许在实时内核映像上进行用户定义的检测,该映像永远不会崩溃、挂起或对内核产生负面影响。
Linux 4.1 的这一新增功能是一个巨大的飞跃,因为它允许创建新的高效内核工具。 eBPF 最终也会附加到更多的 kprobes 上。
我对 eBPF 感到很兴奋。在过去的一年里,我已经对其进行了多次测试,并与它的开发者 Alexei Starovoitov (PLUMgrid) 进行了讨论。我还在 Tracing Summit(2014 年)上对其进行了总结,并介绍了非官方的 eBPF 吉祥物:右侧的 ponycorn(由 Deirdré Straughan 创建,他还创建了 DTrace ponycorn,并编辑了这篇文章。)
eBPF 有很多特性;与其他追踪工具的区别在于它的地图。
eBPF 地图
eBPF 映射由您的自定义 eBPF 程序在内核中填充,并且可以从用户级代码异步读取。地图有很多用途,包括:
直方图:对数、线性或完全自定义。
汇总统计:计数、平均值、最小值、最大值等。
频率计数:例如,PID 的事件计数。
延迟:按事件存储和检索时间戳,用于测量延迟。
我的 bitehist 程序打印的直方图存储为数组。更复杂的数据可以存储在键值哈希中。例如,bitsize:

# ./bitesize 5 2
Tracing block device I/O... Interval 5 secs. Ctrl-C to end.

Thu May 14 23:03:17 2015

  PID: 583 UID: 0 CMD: cksum
     kbytes          : count     distribution
       4 -> 7        : 37       |                                      |
       8 -> 15       : 7        |                                      |
      16 -> 31       : 5        |                                      |
      32 -> 63       : 3        |                                      |
      64 -> 127      : 1        |                                      |
     128 -> 255      : 2152     |************************************* |

  PID: 582 UID: 0 CMD: dd
     kbytes          : count     distribution
       4 -> 7        : 70       |**                                    |
       8 -> 15       : 0        |                                      |
      16 -> 31       : 1        |                                      |
      32 -> 63       : 737      |************************************* |
[...]

这是按 PID 存储直方图。 它显示 cksum 命令正在使用 128 到 255 KB 的磁盘 I/O。
给我看代码!
我之前曾将 eBPF 的易用性描述为残酷,与其他跟踪器相比,它仍然如此。 我最初的 DTrace 版本的 bitize 大约有十几行代码,但它很容易成为单行代码。 我的 eBPF 版本是用 C 编写的,目前有超过 200 行代码。
好消息是它会变得更容易,我稍后会讨论。 但是,如果您对代码当前的样子感到好奇,我的两个程序的源代码都在 github 上:bitehist 和 bitize。 这些基于 Alexei 的 eBPF 样本,值得一试。 以下是来自bitehist的一些片段。
下面声明了一个数组类型的映射(BPF_MAP_TYPE_ARRAY),并指定了键的大小、值和元素的数量。 这将存储直方图(来自 bitehist_kern.c):

struct bpf_map_def SEC("maps") hist_map = {
        .type = BPF_MAP_TYPE_ARRAY,
        .key_size = sizeof(u32),
        .value_size = sizeof(long),
        .max_entries = 64,
};

The following populates the map. Yes, the kprobe interface is unstable. This should use tracepoints instead, when they are supported by eBPF (from bitehist_kern.c):

/* kprobe is NOT a stable ABI
 * kernel functions can be removed, renamed or completely change semantics.
 * Number of arguments and their positions can change, etc.
 * In such case this bpf+kprobe example will no longer be meaningful.
 */
SEC("kprobe/blk_start_request")
int bpf_prog1(struct pt_regs *ctx)
{
        长 rq = ctx->di;
        结构请求 *req = (结构请求 *)ctx->di;
        长*值;
        u32 索引 = log2l(_(req->__data_len) / 1024);

        值 = bpf_map_lookup_elem(&hist_map, &index);
        如果(值)
                __sync_fetch_and_add(值, 1);

这会从用户级别(来自 bitehist_user.c)获取地图数据:
静态无效 print_log2_hist(int fd, const char *type)
[…]
for (key = 0; key < MAX_INDEX; key++) {
bpf_lookup_elem(fd, &key, &value);
数据[键] = 值;
bitize 程序使用哈希而不是数组 (BPF_MAP_TYPE_HASH),其中键是包含进程凭据和千字节索引的 C 结构,值是计数。
未来
在编写这些程序的自然过程中,Alexei 和我创建了一些可以重复使用的常用函数和程序模板,例如打印直方图、计算 log2、清除地图等。这意味着编写 eBPF C 将走下坡路从这里。
但是,它的难度应该不是采用的主要问题。正如我在 DTrace 中发现的那样,很多人只想使用我的程序,而不是自己编写程序。eBPF 也应该如此。只要 Alexei、我和其他人编写和发布有用的程序,eBPF 就应该成功使用。
也就是说,可能有一个 eBPF 前端(或多个前端),以提供更高级的语言以方便使用。这是ktap开发者 Jovi Zhangwei 一直在考虑的事情。华为的开发人员也一直在研究perf 的 bpf 子命令。另一个系统监控项目Shark 已经声称支持eBPF(尽管我还没有尝试过)。
我希望在内核中包含 eBPF 以触发对所有跟踪器的支持。我们还应该看到 eBPF 在可观察性和功能方面的新用途。
顺便说一句,我们也知道“eBPF”不是一个说明性的名称。有更好的主意吗?(邪恶的)Black Pony Flying,以匹配“eBPF”和吉祥物,已被建议!(但他并不是真正的邪恶——只是邪恶。)
更多示例
Alexei 的eBPF 示例包括用于将磁盘 I/O 延迟显示为热图的 tracex3:
时间流逝在 y 轴上(向下),延迟在 x 轴上。颜色深度显示有多少 I/O 落入时间和延迟范围:越深越深。这个特定的示例显示大多数 I/O 花费了大约 9 毫秒,但分布范围很广。我注意到图像上的一些其他特征(原始)。
延迟热图不仅显示了延迟的分布,包括多种模式和异常值,而且还显示了随时间的变化。有关介绍,请参阅我的延迟热图页面(它的 x 和 y 轴被翻转)。
tracex3 程序使用一个映射来存储发出磁盘 I/O 时的时间戳,另一个映射将延迟存储为直方图,每秒从用户级程序读取一次。
结论
eBPF(增强型 BPF)正在集成到 Linux 内核中,并允许创建新的可观察性工具以及其他用途。特别是,它允许创建低开销延迟直方图和热图,以进行详细的性能分析。
我的 bitehist 程序可能是 eBPF 的一小步,但它对自定义内核映射的使用是 Linux 跟踪的一大飞跃。
更多阅读
https://lwn.net/Articles/437981/包过滤器的 JIT,2011 年 4 月 12 日
https://lwn.net/Articles/575444/使用 BPF 跟踪过滤器,2013 年 12 月 2 日
BPF tracing filters [LWN.net] BPF 跟踪过滤器,2013 年 12 月 4 日
https://lkml.org/lkml/2014/2/5/772 [RFC PATCH v2 tip 0/7] 64-bit BPF insn set and tracking filters,2014 年 2 月 5 日
LKML: Alexei Starovoitov: [RFC PATCH v2 tip 7/7] tracing filter examples in BPF [RFC PATCH v2 tip 7/7] 在 BPF 中跟踪过滤器示例,2014 年 2 月 5 日
LKML: Alexei Starovoitov: [PATCH RFC net-next 00/14] BPF syscall, maps, verifier, samples [PATCH RFC net-next 00/14] BPF 系统调用,映射,验证器,示例,2014 年 6 月 27 日
The BPF system call API, version 14 [LWN.net] BPF 系统调用 API,版本 14,2014 年 9 月 24 日
https://github.com/torvalds/linux/tree/master/samples/bpf eBPF 示例
另请查看此博客;请注意,未来的帖子可能会删除“e”并将这些描述为“BPF”增强功能,因为它们经常在 lkml 上进行描述。

转载翻译自: https://www.brendangregg.com