bcc/libbpf-tools 里有许多使用 bcc 实现的小工具。

咱们也来实现一个类似的工具吧。

raw tracepoint

不同于 tracepoint,raw tracepoint 的资料较少。

关于 eBPF 进行 raw tracepoint 的资料更少:

syscalldist

学习完毕,亲手实践一下吧。

syscalldist 工具,用于分析某个进程进行某个系统调用的耗时分析。

  • --pid 进程 ID
  • --syscall 系统调用号(常用系统调用列表:syscall names

用法如下:

 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