
USDT 是一种探针技术,让用户态程序也支持使用 perf/ebpf 性能分析工具进行 tracing/profiling。做性能分析通常听过 perf/ebpf 这些工具,这些工具通常用在内核上,用于分析内核的一些性能问题,如果是用户态程序也遇到性能问题了,我们希望用户态程序也能使用 perf/ebpf 工具进行 tracing/profiling。那么就需要 USDT 技术了。
简单来说,USDT 就是探针技术,我们在源码里插入一个固定的探针。当我们 tracing 这个探针的时候,就获取我们埋点的数据了。
USDT 如何插入探针?
插入探针,有时候也叫做埋点。只需要包含 <sys/sdt.h> 头文件,然后利用 DTRACE_PROBE 系列函数埋点即可。
DTRACE_PROBE 系列函数定义在 /usr/include/sys/sdt.h
/* DTrace compatible macro names. */ |
第一次看到DTRACE_PROBE1, 2, 3, 4 … 可能会有些懵,这都是啥呀?!
实际上很简单,而且用法也很简单,我用 DPDK l2fwd 举个例子,
https://git.dpdk.org/dpdk/tree/examples/l2fwd
|
上面的代码是一个利用 DPDK l2 fwd 里的示例代码, rte_eth_rx_burst 函数从网卡收包,我们想知道本次收包收了多少个,于是我们打了一条 log。
这样实现很不优雅,首先是在 datapath 打 log 是错误的决定。其次,我们只希望在 tracing 的时候打印结果,不 tracing 的时候不打印结果。使用 打 log 的方式是无法实现这种效果的,我们需要一个开关,能动态打开和关闭 tracing 的开关,当我们 attach 到这个探针时候,我们可以观测到数据,当我们 detach 的时候,就去掉这个探针,不增加额外的观测消耗。
USDT 可以实现动态开关的功能。我们只需要添加 <sys/sdt.h> 头文件,将 log 语句替换为 DTRACE_PROBE3 语句即可。DTRACE_PROBE5 的数字 3 代表 3 个观测参数,前两个是标识参数,第一个通常用 appname 填充,第二个通常用函数名称填充。当我们使用 perf、ebpf 等观测工具时,通过这两个参数定位我们预先埋下的观测点。
|
查看可用的探针
查看可用探针
bpftrace -l "usdt:/usr/bin/l2fwd:*" |
查看正在运行的进程的可用探针,会同时展示其链接的动态库的可用探针
bpftrace -lp $(pidof l2fwd) "usdt:*" |
USDT 使用
bpftrace -e 'usdt:/usr/bin/l2fwd:l2fwd_main_loop { printf("%d,%d,%d\n",arg0,arg1,arg2); }' |
bpftrace 内置许多参数,比如 comm, pid, cpu, elapsed,利用这些参数可以实现更高级的 tracing。
还可以通过 bcc/libbpf 将一段时间的数据收集起来,进行统计,得出更宏观的指标,这样有一定的代码量。bpftrace 的优势就是一行,很简单。
USDT 原理
包含 USDT 的代码经过编译后生成的二进制文件将有一个名为 .stapsdt.base 的 ELF 段。这个 .stapsdt.base 有助于 tracing 工具在二进制文件加载到内存后计算探针的内存地址。
readelf -S l2fwd | grep sdt |
还有一个 ELF note,note 记录了探针的相关信息(名称、地址、信号量、参数)。用户态进程可以读取这个段获得探针的信息。
Displaying notes found in: .note.stapsdt |
USDT 动态开关
反汇编二进制,发现 DTRACE_PROBE 被编译为一条 nop 指令,当不进行 probe 时候,就是执行一条 nop 指令,当进行 probe 的时候替换为 int3 进行跳转到 hook 函数。