0%

使用eBPF得到内核执行过程的时间分布

具体例子介绍

在SVA的使用场景中,IO缺页比较影响系统的性能。为此,我们需要观测在一个段程序执行
的时候,系统中发生IO缺页的次数, 以及IO缺页的统计特征,比如IO缺页的平均时间、方差
和分布情况。我们可以在内核代码中加tracepoint点,然后用perf或者是ftrace得到trace
信息,然后用脚本处理得到上面的信息。但是,本文介绍的是用eBPF的方法得到这样的
信息。

使用eBPF的方法,可以在把一段用户态写的代码放到内核执行,这段用户态写的代码可以
在已有的内核tracepoint点上附着。就是每次内核里跑到tracepoint点的时候,都可以跑
一下用户态写的代码,用户可以自由编程统计自己想要的信息。

eBPF在内核中提供里各种help函数,方便统计代码调用。同时现在也有关于eBPF的用户态
库(e.g. https://github.com/iovisor/bcc.git),方便在用户态编写代码,记录和处理
得到的信息。

内核配置

我们使用https://github.com/Linaro/linux-kernel-uadk branch: uacce-devel-5.11
的分支测试,需要打上如下第3节中的内核补丁。

如下是需要打开的内核编译选项:

make ARCH=arm64 defconfig
CONFIG_UACCE=y
CONFIG_DEV_HISI_ZIP=y
CONFIG_DEV_HISI_QM=y
CONFIG_ARM_SMMU_V3=y (默认)
CONFIG_ARM_SMMU_V3_SVA=y (默认)
CONFIG_IOMMU_SVA_LIB=y (默认)
CONFIG_PCI_PASID=y (默认)
CONFIG_FTRACE=y
(以上是业务相关的)

CONFIG_BPF_SYSCALL=y
CONFIG_BPF_KPROBE_OVERRIDE=y
CONFIG_KPROBE=y

CONFIG_SQUASHFS_XZ=y
CONFIG_CGROUP_FREEZER=y
(以上是ebpf相关的)

用户态环境搭建

本文测试所使用的环境是Ubuntu 16.04, 这个版本的ubuntu没有bcc相关的仓库,外部仓库
现在也无法使用了(http://www.brendangregg.com/blog/2016-06-14/ubuntu-xenial-bcc-bpf.html)。

所以本文测试使用了snap来安装bcc相关的环境,snap是ubuntu apt之外的另一个包管理器。

1
2
sudo apt-get install snap
sudo snap install bcc

如上会把snap管理下的bcc包安装到/snap/*下。 ls -al /snap

1
2
3
4
5
6
7
8
total 28
drwxr-xr-x 6 root root 4096 2月 20 02:34 .
drwxr-xr-x 22 root root 4096 10月 1 15:48 ..
drwxr-xr-x 3 root root 4096 2月 20 02:34 bcc
drwxr-xr-x 2 root root 4096 2月 20 05:16 bin
drwxr-xr-x 3 root root 4096 2月 20 02:33 core18
-r--r--r-- 1 root root 548 10月 17 2019 README
drwxr-xr-x 3 root root 4096 2月 20 02:32 snapd

为python增加搜索库的路径,可以看到snap管理的bcc python库都在snap自己的路劲下。

1
2
3
4
5
PYTHONPATH="/snap/bcc/146/usr/lib/python3/dist-packages:{$PYTHONPATH}"
export PYTHONPATH

LD_LIBRARY_PATH="/snap/bcc/146/usr/lib/aarch64-linux-gnu/:{$LD_LIBRARY_PATH}"
export LD_LIBRARY_PATH

注意,这里编译bcc代码的时候,会使用到内核头文件。本文尝试了各种内核头文件
导出的方式都还是一直有内核头文件找不到。索性把查找头文件的目录又重新软连接到
使用内核的源码路径上。为了简单起见,本文的测试使用了本地编译内核的方式:

1
ln -s <your kernel source> /lib/modules/<your_kernel_magic>/build

代码示例

内核补丁:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
index 8d29aa1be282..637e95c237a1 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
@@ -32,6 +32,8 @@

#include <linux/amba/bus.h>

+#include <trace/events/smmu.h>
+
#include "arm-smmu-v3.h"
#include "../../iommu-sva-lib.h"

@@ -945,6 +947,8 @@ static int arm_smmu_page_response(struct device *dev,
* forget.
*/

+ trace_io_fault_exit(dev, resp->pasid);
+
return 0;
}

@@ -1474,6 +1478,8 @@ static int arm_smmu_handle_evt(struct arm_smmu_device *smmu, u64 *evt)
struct iommu_fault_event fault_evt = { };
struct iommu_fault *flt = &fault_evt.fault;

+ trace_io_fault_entry(smmu->dev, FIELD_GET(EVTQ_0_SSID, evt[0]));
+
/* Stage-2 is always pinned at the moment */
if (evt[1] & EVTQ_1_S2)
return -EFAULT;
diff --git a/include/trace/events/smmu.h b/include/trace/events/smmu.h
index e9b648407102..4d96bfd20726 100644
--- a/include/trace/events/smmu.h
+++ b/include/trace/events/smmu.h
@@ -82,6 +82,28 @@ DEFINE_EVENT(smmu_mn, smmu_mn_free, TP_PROTO(unsigned int pasid), TP_ARGS(pasid)
DEFINE_EVENT(smmu_mn, smmu_mn_get, TP_PROTO(unsigned int pasid), TP_ARGS(pasid));
DEFINE_EVENT(smmu_mn, smmu_mn_put, TP_PROTO(unsigned int pasid), TP_ARGS(pasid));

+DECLARE_EVENT_CLASS(smmu_io_fault_class,
+ TP_PROTO(struct device *dev, unsigned int pasid),
+ TP_ARGS(dev, pasid),
+
+ TP_STRUCT__entry(
+ __string(dev, dev_name(dev))
+ __field(int, pasid)
+ ),
+ TP_fast_assign(
+ __assign_str(dev, dev_name(dev));
+ __entry->pasid = pasid;
+ ),
+ TP_printk("dev=%s pasid=%d", __get_str(dev), __entry->pasid)
+);
+
+#define DEFINE_IO_FAULT_EVENT(name) \
+DEFINE_EVENT(smmu_io_fault_class, name, \
+ TP_PROTO(struct device *dev, unsigned int pasid), \
+ TP_ARGS(dev, pasid))
+
+DEFINE_IO_FAULT_EVENT(io_fault_entry);
+DEFINE_IO_FAULT_EVENT(io_fault_exit);

#endif /* _TRACE_SMMU_H */

如上,为了在eBPF的跟踪代码里跟踪io page fault的流程,我们在SMMU io page fault
的入口和出口出增加了新的tracepoint点。增加trancepoint的方法还可以参考这里

用户态python脚本, ebpf_smmu_iopf.py。可以参考bcc里自带的tools, 看看怎么写这些
脚本: https://github.com/iovisor/bcc.git

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
#!/usr/bin/python3
#
from __future__ import print_function
from bcc import BPF
from ctypes import c_ushort, c_int, c_ulonglong
from time import sleep
from sys import argv

def usage():
print("USAGE: %s [interval [count]]" % argv[0])
exit()

# arguments
interval = 5
count = -1
if len(argv) > 1:
try:
interval = int(argv[1])
if interval == 0:
raise
if len(argv) > 2:
count = int(argv[2])
except: # also catches -h, --help
usage()

# load BPF program
b = BPF(text="""
#include <uapi/linux/ptrace.h>

BPF_HASH(start, u64);
BPF_HISTOGRAM(dist);

TRACEPOINT_PROBE(smmu, io_fault_entry)
{
u64 ts;

ts = bpf_ktime_get_ns();
start.update((unsigned int *)args->pasid, &ts);

return 0;
}

TRACEPOINT_PROBE(smmu, io_fault_exit)
{
u64 *tsp, delta;
u64 pasid;

tsp = start.lookup((unsigned int *)args->pasid);

if (tsp != 0) {
delta = bpf_ktime_get_ns() - *tsp;
dist.increment(bpf_log2l(delta));
start.delete((unsigned int *)args->pasid);
}

return 0;
}
""")

# header
print("Tracing... Hit Ctrl-C to end.")

# output
loop = 0
do_exit = 0
while (1):
if count > 0:
loop += 1
if loop > count:
exit()
try:
sleep(interval)
except KeyboardInterrupt:
pass; do_exit = 1

print()
b["dist"].print_log2_hist("nsecs")
b["dist"].clear()
if do_exit:
exit()

运行效果

1
2
3
4
sudo ./ebpf_smmu_iopf.py

另一个窗口运行:
sudo zip_perf_test -b 8192 -s 819200000

得到如下的iopf时延的分布图:

1
[...]

在过程中测试eBPF的环境是否搭建ok,也可以跑下/snap/bin自带的程序,比如跑bcc.cpudist
我们得到了这样的分布图:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Tracing on-CPU time... Hit Ctrl-C to end.

usecs : count distribution
0 -> 1 : 193 |************************* |
2 -> 3 : 49 |****** |
4 -> 7 : 35 |**** |
8 -> 15 : 41 |***** |
16 -> 31 : 37 |**** |
32 -> 63 : 116 |*************** |
64 -> 127 : 14 |* |
128 -> 255 : 2 | |
256 -> 511 : 0 | |
512 -> 1023 : 3 | |
1024 -> 2047 : 12 |* |
2048 -> 4095 : 61 |******** |
4096 -> 8191 : 96 |************ |
8192 -> 16383 : 97 |************ |
16384 -> 32767 : 162 |********************* |
32768 -> 65535 : 301 |****************************************|