bpftrace简介
概述
bpftrace是一个基于eBPF的高级跟踪工具, 用于动态追踪Linux系统的行为和性能.
用于可以用简单的一行命令或者简洁的脚本,实现监控分析内核和用户空间程序的运行,而无需修改或者重新编译
安装
sudo apt-get install bpftrace |
基本语法
bpftrace
脚本由 probe 和 action 组成:
probe filter { action } |
- probe:定义事件触发点(例如内核函数、系统调用、定时器等)。
- action:当事件触发时执行的代码块(例如打印信息、统计计数等)。
- filter: 定义事件的过滤器(例如map中key存在,变量值比较)
打印信息
使用 printf
打印信息:
用法与c语言的printf一致
tracepoint:syscalls:sys_enter_open { printf("File opened by %s (PID: %d)\n", comm, pid);} |
统计计数
使用 @
定义映射(map)来统计事件:
tracepoint:syscalls:sys_enter_open { @[comm] = count(); } |
过滤条件
使用 /条件/
过滤事件:
tracepoint:syscalls:sys_enter_open /pid == 1234/ { printf("File opened by PID 1234\n"); } |
调用栈
打印内核或用户空间调用栈:
kprobe:do_sys_open { printf("Kernel stack: %s\n", kstack); } |
# sudo bpftrace -e 'kprobe:kernel_clone { printf("Kernel stack: %s\n", kstack); }’ |
如果调用栈深度不足,可以尝试增加调用栈深度。bpftrace
默认的调用栈深度可能不足以捕获完整的调用栈。
条件语句
bpftrace
支持条件语句(if
/else
):
if (条件) { |
示例
tracepoint:syscalls:sys_enter_open { |
映射(Map)
bpftrace
使用 @
定义映射(map),用于存储和统计数据。
- 统计事件次数
tracepoint:syscalls:sys_enter_open { @[comm] = count(); } |
- 统计唯一值
tracepoint:syscalls:sys_enter_open { @files[str(args->filename)] = 1; } |
- 打印映射
在脚本结束时打印映射:
END { print(@); } |
传递参数
$1 $2 $3之类的都是内置变量,可以直接引用,用法如以下示例
sudo bpftrace -e 'tracepoint:tlb:tlb_flush { printf("TLB flushed by %s (pid: %d)\n", comm, $1); }' 1 |
这条命令是过滤所有的pid=1的TLB刷新事件
内置变量和函数
进程相关的变量
pid
: 当前进程的进程 ID(PID)。tid
: 当前线程的线程 ID(TID)。uid
: 当前进程的用户 ID(UID)。gid
: 当前进程的组 ID(GID)。comm
: 当前进程的命令名称(进程名,最多 16 个字符)。nsecs
: 当前时间戳(纳秒级别,从系统启动开始计算)。cgroup
: 当前进程的 cgroup ID(需要内核支持)。args
: 用于访问 tracepoint 或 kprobe 的参数(具体参数取决于 tracepoint 或 probe)。
CPU 相关的变量
cpu
: 当前 CPU 的编号。curtask
: 当前 CPU 上正在运行的任务的task_struct
指针。kstack
: 当前的内核调用栈。ustack
: 当前的用户空间调用栈。
时间和计时相关的变量
nsecs
: 当前时间戳(纳秒级别,从系统启动开始计算)。elapsed
: 从 bpftrace 脚本启动开始经过的时间(纳秒级别)。
上下文相关的变量
retval
: 函数的返回值(通常用于 kretprobe)。func
: 当前函数的名称(用于 kprobe 或 uprobe)。probe
: 当前 probe 的名称。arg0
,arg1
,arg2
, …: 函数或 tracepoint 的参数(具体含义取决于 probe 类型)。
其他常用变量
bpftrace
: 当前 bpftrace 脚本的 PID。$1
,$2
,$3
, …: 脚本的命令行参数(例如$1
表示第一个参数)。@
: 用于定义和访问映射(map)的变量。
特殊变量
kstack
: 内核调用栈。ustack
: 用户空间调用栈。args
: 用于访问 tracepoint 的参数(例如args->filename
)。
map函数
函数 | 功能 | 适用场景 |
---|---|---|
hist() |
生成 2 的幂次方直方图 | 分析数据分布(非线性区间) |
lhist() |
生成线性直方图 | 分析数据分布(自定义区间) |
count() |
统计事件次数 | 统计事件发生的总次数 |
sum() |
累加数值 | 计算总和 |
avg() |
计算平均值 | 分析数据的平均水平 |
min() |
返回最小值 | 查找数据的最小值 |
max() |
返回最大值 | 查找数据的最大值 |
hist()
- 功能: 生成一个直方图,用于统计数据的分布情况。
- 参数: 一个数值(通常是整数或指针)。
- 输出: 将数据分布划分为 2 的幂次方区间(如 0-1, 2-3, 4-7, 8-15, …),并统计每个区间的计数。
lhist()
- 功能: 生成一个线性直方图,用于统计数据的分布情况。
- 参数:
- 第一个参数是数值。
- 第二个参数是区间的最小值。
- 第三个参数是区间的最大值。
- 第四个参数是区间的步长。
- 输出: 将数据分布划分为线性区间,并统计每个区间的计数。
# 统计 pid 在 0 到 1000 之间的分布,步长为 100。
bpftrace -e 'profile:hz:99 { @ = lhist(pid, 0, 1000, 100); }'count()
功能: 统计事件发生的次数。
参数: 无(直接调用)。
输出: 统计事件的总次数。
示例:
bpftrace -e 'tracepoint:syscalls:sys_enter_open { @ = count(); }'
这会统计
open
系统调用的总次数。
sum()
功能: 对某个数值进行累加。
参数: 一个数值。
输出: 返回所有事件的累加值。
示例:
bpftrace -e 'tracepoint:syscalls:sys_enter_read { @ = sum(args->count); }'
这会统计所有
read
系统调用中count
参数的总和。
avg()
功能: 计算某个数值的平均值。
参数: 一个数值。
输出: 返回所有事件的平均值。
示例:
bpftrace -e 'tracepoint:syscalls:sys_enter_read { @ = avg(args->count); }'
这会计算所有
read
系统调用中count
参数的平均值。
min()
功能: 返回某个数值的最小值。
参数: 一个数值。
输出: 返回所有事件中的最小值。
示例:
bpftrace -e 'tracepoint:syscalls:sys_enter_read { @ = min(args->count); }'
这会返回所有
read
系统调用中count
参数的最小值。
max()
功能: 返回某个数值的最大值。
参数: 一个数值。
输出: 返回所有事件中的最大值。
示例:
bpftrace -e 'tracepoint:syscalls:sys_enter_read { @ = max(args->count); }'
这会返回所有
read
系统调用中count
参数的最大值。
probe类型
bpftrace
支持多种 probe 类型,用于捕获不同的事件。常见的 probe 类型包括:
内核动态跟踪
**
kprobe
**:跟踪内核函数的入口。kprobe:do_sys_open { printf("File opened by %s\n", comm); }
**
kretprobe
**:跟踪内核函数的返回值。kretprobe:do_sys_open { printf("File opened, return value: %d\n", retval); }
静态跟踪点
**
tracepoint
**:跟踪内核静态跟踪点。tracepoint:syscalls:sys_enter_open { printf("File: %s\n", str(args->filename)); }
用户空间动态跟踪
**
uprobe
**:跟踪用户空间函数的入口。uprobe:/bin/bash:readline { printf("Bash readline: %s\n", str(arg0)); }
**
uretprobe
**:跟踪用户空间函数的返回值。uretprobe:/bin/bash:readline { printf("Bash readline returned: %s\n", str(retval)); }
定时器
**
interval
**:按固定时间间隔触发。interval:s:1 { printf("1 second passed\n"); }
其他
**
BEGIN
**:脚本开始时触发。BEGIN { printf("bpftrace started\n"); }
**
END
**:脚本结束时触发。END { printf("bpftrace ended\n"); }
编写脚本
shell脚本中嵌入bt脚本
方便动态传递探针名字之类的复杂场景
|
sudo bash demo.sh /xxx/xxx/bin/xxx |
编写bt文件执行
写成文件有时候可读性要比单行命令更好一些
|
chmod +x ./demo.bt |
注意:后边的符号尽量加引号,防止解析失败的问题; 这个常用于go的符号Cannot find go symbols · Issue #1098 · bpftrace/bpftrace (github.com)
用例
Counts
利用map对象, 输出按指定key的统计计数
bpftrace -e 'tracepoint:raw_syscalls:sys_enter { @[comm] = count(); }' |
Distribution
输出指定字段的值的分布
# bpftrace -e 'tracepoint:syscalls:sys_exit_read /pid == 18644/ { @bytes = hist(args.ret); }' |
- @: 这个和map是相似的, 但是没有 ([]) , 他的作用是设置输出图标的字段为 “bytes”
- hist(): 这是一个map函数,用于总结数据为一个2的幂次的直方图
- 其他类似的map函数有 lhist() (linear hist), count(), sum(), avg(), min(), and max().
在内核栈上执行Profile On-CPU
# bpftrace -e 'profile:hz:99 { @[kstack] = count(); }' |
- 99是采样频率,这个数字既不会干扰性能,又不会因为是整数而跟定时任务同步了
记录Off-CPU 时间分布
|
使用kprobe追踪
# cat path.bt |
用户空间动态追踪
用户空间程序可以使用uretprobe和uprobe进行追踪, 支持多种编程语言
注意事项
- 对go程序使用uretprobe可能会导致崩溃,因为栈可能发生移动(参考文档种如此解释:It is important to note that for
uretprobe
s to work the kernel runs a special helper on user-space function entry which overrides the return address on the stack. This can cause issues with languages that have their own runtime like Golang:
参考资料
bpftrace/man/adoc/bpftrace.adoc at master · bpftrace/bpftrace (github.com)