bcc/libbpf-tools 里有许多使用 bcc
实现的小工具。
咱们也来实现一个类似的工具吧。
raw tracepoint
不同于 tracepoint,raw tracepoint 的资料较少。
关于 eBPF 进行 raw tracepoint 的资料更少:
syscalldist
学习完毕,亲手实践一下吧。
syscalldist
工具,用于分析某个进程进行某个系统调用的耗时分析。
用法如下:
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
|
# ./syscall-profiler --pid 54786 --syscall 7
2022/09/08 16:35:09 Attached raw_tracepoint(sys_enter)
2022/09/08 16:35:09 Attached raw_tracepoint(sys_exit)
2022/09/08 16:35:09 Hit Ctrl-C to end.
^C
Histogram for syscall(7) (sum 11):
usecs : count distribution
0 -> 1 : 0 | |
2 -> 3 : 0 | |
4 -> 7 : 0 | |
8 -> 15 : 0 | |
16 -> 31 : 0 | |
32 -> 63 : 0 | |
64 -> 127 : 0 | |
128 -> 255 : 0 | |
256 -> 511 : 0 | |
512 -> 1023 : 0 | |
1024 -> 2047 : 0 | |
2048 -> 4095 : 0 | |
4096 -> 8191 : 0 | |
8192 -> 16383 : 0 | |
16384 -> 32767 : 0 | |
32768 -> 65535 : 0 | |
65536 -> 131071 : 0 | |
131072 -> 262143 : 1 |**** |
262144 -> 524287 : 10 |****************************************|
|
sys_enter
学习那些资料后,已知:
- raw tracepoint 对系统调用入口进行 trace 的是
raw_tracepoint/sys_enter
sys_enter
的第二个参数就是系统调用号
所以,raw_tracepoint/sys_enter
的时候,过滤 PID 和系统调用号,并记录一个时间戳:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
SEC("raw_tracepoint/sys_enter")
int sys_enter(struct bpf_raw_tracepoint_args *ctx)
{
__u64 syscall_id = ctx->args[1];
if (syscall_id != filter_syscall_id)
return 0;
__u32 pid = (__u32)bpf_get_current_pid_tgid();
if (pid != filter_pid)
return 0;
__u64 ts = bpf_ktime_get_ns();
bpf_map_update_elem(&clocks, &pid, &ts, BPF_ANY);
return 0;
}
|
sys_exit
对应 sys_enter
,raw tracepoint 对系统调用出口进行 trace 的时候 raw_tracepoint/sys_exit
。
对于系统调用号,需要一定手段去获取,参考 TRACE_EVENT_FN(sys_exit)。
而后,取出时间戳,并对耗时进行 histogram 统计。
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
|
SEC("raw_tracepoint/sys_exit")
int sys_exit(struct bpf_raw_tracepoint_args *ctx)
{
struct pt_regs *args = (struct pt_regs *)ctx->args[0];
__u64 syscall_id = BPF_CORE_READ(args, orig_ax);
if (syscall_id != filter_syscall_id)
return 0;
__u32 pid = (__u32)bpf_get_current_pid_tgid();
if (pid != filter_pid)
return 0;
__u64 *tsp = bpf_map_lookup_and_delete(&clocks, &pid);
if (!tsp)
return 0;
struct hist initial_hist = {};
__u32 index = 0;
struct hist *hp = bpf_map_lookup_or_try_init(&hists, &index, &initial_hist);
if (!hp)
return 0;
__u64 delta = bpf_ktime_get_ns() - *tsp;
delta /= 1000; // micro-second
__u64 slot = log2l(delta);
if (slot >= MAX_SLOTS)
slot = MAX_SLOTS - 1;
__sync_fetch_and_add(&hp->slots[slot], 1);
return 0;
}
|
小结
syscalldist
的核心代码如上,Go 代码就比较简单了。
完整代码:syscalldist。