eBPF入门-2: bpftrace

本文由我翻译自The bpftrace One-Liner Tutorial.

安装

sudo apt install -y bpftrace

1. 列出可用的追踪

sudo bpftrace -l 'tracepoint:syscalls:sys_enter_*'

bpftrace能够跟踪很多系统调用,这里是可以用 * 和 ? 查询的。

2. Hello World

ethan@ethan-kali:~$ sudo bpftrace -e 'BEGIN {printf("hello world\n"); }'
Attaching 1 probe...
hello world
^C

3. 跟踪打开的文件

ethan@ethan-kali:~$ sudo bpftrace -e 'tracepoint:syscalls:sys_enter_openat { printf("%s %s\n", comm, str(args->filename)); }'
Attaching 1 probe...
code /proc/6972/cmdline
ksysguardd /proc/stat
ksysguardd /proc/vmstat
ksysguardd /proc/meminfo

查看 sys_enter_openat 的args包含了哪些成员:

ethan@ethan-kali:~$ sudo bpftrace -vl tracepoint:syscalls:sys_enter_openat 
tracepoint:syscalls:sys_enter_openat
    int __syscall_nr;
    int dfd;
    const char * filename;
    int flags;
    umode_t mode;

4. 统计进程系统调用数

ethan@ethan-kali:~$ sudo bpftrace -e 'tracepoint:raw_syscalls:sys_enter { @[comm] = count(); }'
Attaching 1 probe...
^C

@[haveged]: 1
@[kglobalaccel5]: 2
@[org_kde_powerde]: 2
@[DiscoverNotifie]: 2
@[kdeconnectd]: 2
@[EventHandler]: 2

当bpftrace结束(比如按下Ctrl+C),就会自动打印出所有的map。

5. 打印 read() 读取字节数的分布

ethan@ethan-kali:~$ sudo bpftrace -e 'tracepoint:syscalls:sys_exit_read /pid == 6615/ { @bytes = hist(args->ret); }'
Attaching 1 probe...
^C

@bytes: 
(..., 0)               9 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@|
[0]                    1 |@@@@@                                               |
[1]                    0 |                                                    |
[2, 4)                 0 |                                                    |
[4, 8)                 0 |                                                    |
[8, 16)                1 |@@@@@                                               |
[16, 32)               0 |                                                    |
[32, 64)               2 |@@@@@@@@@@@                                         |
[64, 128)              0 |                                                    |
[128, 256)             2 |@@@@@@@@@@@                                         |
[256, 512)             1 |@@@@@                                               |
[512, 1K)              3 |@@@@@@@@@@@@@@@@@                                   |
[1K, 2K)               2 |@@@@@@@@@@@                                         |

以上命令,统计了PID为6615的程序在此期间调用 sys_read() 的返回值, 并打印出统计柱形图。

6. 动态跟踪内核中 read() 的字节数

ethan@ethan-kali:~$ sudo bpftrace -e 'kretprobe:vfs_read { @bytes = lhist(retval, 0, 2000, 200); }'
Attaching 1 probe...
^C

@bytes: 
(..., 0)              44 |@                                                   |
[0, 200)            1415 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@|
[200, 400)             0 |                                                    |
[400, 600)             0 |                                                    |
[600, 800)             0 |                                                    |
[800, 1000)            0 |                                                    |
[1000, 1200)          18 |                                                    |
[1200, 1400)           0 |                                                    |
[1400, 1600)           0 |                                                    |
[1600, 1800)           0 |                                                    |
[1800, 2000)           0 |                                                    |
[2000, ...)            3 |                                                    |

ET: 这里使用了上一节提到的lhist, 即线性统计,与hist的2幂次统计区间不同, lhist 可以指定统计的起始及终止区间, 以及步长:

lhist(value, min, max, step)

7. 统计 read() 耗时

ethan@ethan-kali:~$ sudo bpftrace -e 'kprobe:vfs_read { @start[tid] = nsecs; } kretprobe:vfs_read /@start[tid]/ { @ns[comm] = hist(nsecs - @start[tid]); delete(@start[tid]); }'
Attaching 2 probes...
^C

@ns[kactivitymanage]: 
[4K, 8K)               1 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@|

@ns[xembedsniproxy]: 
[4K, 8K)               1 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@|

@ns[systemd-journal]: 
[4K, 8K)               1 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@|

@ns[code]: 
[4K, 8K)               2 |@@@@@@@@@@@@@                                       |
[8K, 16K)              3 |@@@@@@@@@@@@@@@@@@@                                 |
[16K, 32K)             8 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@|                       |

@start[23580]: 91727748589219
@start[1385]: 91727748883680
@start[1212]: 91727749317256
@start[23582]: 91727749318410
@start[1235]: 91727749799433
@start[30483]: 91727781856565

8. 统计进程级事件

ethan@ethan-kali:~$ sudo bpftrace -e 'tracepoint:sched:sched* { @[probe] = count(); } interval:s:5 { exit(); }'
Attaching 25 probes...


@[tracepoint:sched:sched_process_wait]: 11
@[tracepoint:sched:sched_migrate_task]: 1575
@[tracepoint:sched:sched_wake_idle_without_ipi]: 29166
@[tracepoint:sched:sched_wakeup]: 37333
@[tracepoint:sched:sched_stat_runtime]: 37754
@[tracepoint:sched:sched_waking]: 37911
@[tracepoint:sched:sched_switch]: 73204

9. 统计CPU上的内核栈性能

ethan@ethan-kali:~$ sudo bpftrace -e 'profile:hz:99 { @[kstack] = count(); }'
Attaching 1 probe...
^C

@[
    pcibios_pm_ops+409080112
]: 1
@[
    run_timer_softirq+64
    __softirqentry_text_start+230
    irq_exit+166
    smp_apic_timer_interrupt+118
    apic_timer_interrupt+15
]: 1
@[
    find_busiest_group+227
    load_balance+370
    rebalance_domains+684
    __softirqentry_text_start+230
    irq_exit+166
    smp_apic_timer_interrupt+118
    apic_timer_interrupt+15
    cpuidle_enter_state+201
    cpuidle_enter+41
    do_idle+484
    cpu_startup_entry+25
    start_secondary+351
    secondary_startup_64+164
]: 1

以99Hz频率分析内核栈性能, 打印出频率统计。

10. 跟踪调度器

ethan@ethan-kali:~$ sudo bpftrace -e 'tracepoint:sched:sched_switch { @[kstack] = count(); }'
[sudo] password for ethan: 
Attaching 1 probe...
^C

@[
    __sched_text_start+866
    __sched_text_start+866
    schedule+74
    schedule_timeout+350
    intel_pipe_update_start+337
    intel_update_crtc+161
    skl_commit_modeset_enables+437
    intel_atomic_commit_tail+779
    process_one_work+436
    worker_thread+80
    kthread+249
    ret_from_fork+53
]: 1
@[
    __sched_text_start+866
    __sched_text_start+866
    schedule+74
    schedule_timeout+350
    rtR0SemEventLnxWait.isra.0+835
    supR0SemEventWaitEx+123
    supdrvIOCtl+10757
    VBoxDrvLinuxIOCtl_6_1_10+362
    ksys_ioctl+135
    __x64_sys_ioctl+22
    do_syscall_64+82
    entry_SYSCALL_64_after_hwframe+68
]: 1

统计导致上下文切换事件的内核栈。

11. 阻塞性 I/O 追踪

ethan@ethan-kali:~$ sudo bpftrace -e 'tracepoint:block:block_rq_issue { @ = hist(args->bytes); }'
Attaching 1 probe...
^C

@: 
[0]                   27 |@@@@@@                                              |
[1]                    0 |                                                    |
[2, 4)                 0 |                                                    |
[4, 8)                 0 |                                                    |
[8, 16)                0 |                                                    |
[16, 32)               0 |                                                    |
[32, 64)               0 |                                                    |
[64, 128)              0 |                                                    |
[128, 256)             0 |                                                    |
[256, 512)             0 |                                                    |
[512, 1K)            117 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@                        |
[1K, 2K)              25 |@@@@@@                                              |
[2K, 4K)               7 |@                                                   |
[4K, 8K)             215 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@|
[8K, 16K)             51 |@@@@@@@@@@@@                                        |
[16K, 32K)            32 |@@@@@@@                                             |
[32K, 64K)             2 |                                                    |
[64K, 128K)            3 |                                                    |
[128K, 256K)          21 |@@@@@                                               |

追踪阻塞性I/O请求,统计字节数量。

这个探查的上下文很重要: 当阻塞性I/O请求指派到设备时, 发生该事件。 这一般发生在进程上下文中, 这时comm能够提供给你进程的名字。 但也可能发生在内核上下文中, 例如 readahead, 这时 pid/comm 都不会提供给你符合预期的应用程序。

12. 内核结构追踪

创建并编辑文件path.bt:

#include <linux/path.h>
#include <linux/dcache.h>

kprobe:vfs_open
{
        printf("open path: %s\n", str(((struct path *)arg0)->dentry->d_name.name));
}

随后运行命令:

ethan@ethan-kali:~$ sudo bpftrace path.bt 
Attaching 1 probe...
open path: cmdline
open path: cmdline
open path: status

这里动态追踪了 vfs_open() 函数,该函数地一个参数为 (struct path*)。

内核结构体的支持与bcc相同, 都是使用linux 的内核头文件。 这意味着许多结构提都可用,不过不是全部, 有时可能需要手动的include一个结构体。 对此, 可以参考 dcsnoop tool, 包含了一部分手动引入的结构提 nameidata, 因为在头文件中找不到它。 如果内核有BTF (BPF Type Format)数据, 所有内核结构都始终可用。

现在你已经对bpftrace有了较多的了解, 也可以开始使用和写自己的一行命令了。 要了解更多内容, 参考 Reference Guide