bpftrace 是基于BPF和BCC的开源系统跟踪工具. bpftrace 自带了许多性能工具,同时还提供一个高级编程语言环境,用于创建自定义的工具.
一般Linux发行版都可直接通过安装包安装使用, 我自己的环境由于升级了KERNEL导致不能正常使用, 只能通过源码重新构建使用.
环境准备:
$ uname -a
Linux fc29 5.12.7-300.fc29.x86_64 #1 SMP Fri May 28 13:45:39 CST 2021 x86_64 x86_64 x86_64 GNU/Linux
KENREL配置需确保以下功能模块是开启状态:
CONFIG_BPF=y
CONFIG_BPF_SYSCALL=y
CONFIG_BPF_JIT=y
CONFIG_HAVE_EBPF_JIT=y
CONFIG_BPF_EVENTS=y
CONFIG_FTRACE_SYSCALLS=y
CONFIG_FUNCTION_TRACER=y
CONFIG_HAVE_DYNAMIC_FTRACE=y
CONFIG_DYNAMIC_FTRACE=y
CONFIG_HAVE_KPROBES=y
CONFIG_KPROBES=y
CONFIG_KPROBE_EVENTS=y
CONFIG_ARCH_SUPPORTS_UPROBES=y
CONFIG_UPROBES=y
CONFIG_UPROBE_EVENTS=y
CONFIG_DEBUG_FS=y
$ sudo dnf install -y bison flex cmake make git gcc-c++ elfutils-libelf-devel zlib-devel llvm-devel clang-devel bcc-devel systemtap-sdt-devel binutils-devel libbpf-devel gtest-devel gmock-devel
$ cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo -DBUILD_TESTING:BOOL=OFF -DBUILD_SHARED_LIBS:BOOL=OFF .
$ make
$ make install
$ bpftrace--info
System
OS: Linux 5.12.7-300.fc29.x86_64 #1 SMP Fri May 28 13:45:39 CST 2021
Arch: x86_64
Build
version: v0.12.1
LLVM: 7.0.1
foreach_sym: yes
unsafe uprobe: no
bfd: yes
bpf_attach_kfunc: yes
bcc_usdt_addsem: yes
bcc bpf_attach_uprobe refcount: yes
libbpf: yes
libbpf btf dump: yes
libbpf btf dump type decl: yes
Kernel helpers
probe_read: yes
probe_read_str: yes
probe_read_user: yes
probe_read_user_str: yes
probe_read_kernel: yes
probe_read_kernel_str: yes
get_current_cgroup_id: yes
send_signal: yes
override_return: no
get_boot_ns: yes
dpath: yes
Kernel features
Instruction limit: 1000000
Loop support: yes
btf (depends on Build:libbpf): yes
map batch (depends on Build:libbpf): yes
uprobe refcount (depends on Build:bcc bpf_attach_uprobe refcount): yes
Map types
hash: yes
percpu hash: yes
array: yes
percpu array: yes
stack_trace: yes
perf_event_array: yes
Probe types
kprobe: yes
tracepoint: yes
perf_event: yes
kfunc: yes
iter:task: yes
iter:task_file: yes
基本语法:
{...}
: Action 执行代码块
/.../
: Filtering 过滤条件
//
, /*
: 注释
Alias | Type | Description |
---|---|---|
t | tracepoint | 内核静态探针 |
U | usdt | 用户态静态定义探针 |
k | kprobe | 内核态动态函数探针 |
kr | kretprobe | 内核态动态函数返回值探针 |
f | kfunc | 基于BPF的内核态动态函数探针 |
fr | kretfunc | 基于BPF的内核态动态函数返回值探针 |
u | uprobe | 用户态函数探针 |
ur | uretprobe | 用户态函数返回值探针 |
s | software | 内核软件事件 |
h | hardware | 基于硬件计数器的探针 |
w | watchpoint | 基于内存的监测点事件 |
p | profile | 对所有CPU进行时间采样 |
i | interval | 周期性报告(从一个CPU) |
iter | 遍历跟踪内核对象 | |
BEGIN | bpftrace 启动执行动作 | |
END | bpftrace 退出执行动作 |
列出bpftrace支持的探针
$ bpftrace -l
$ bpftrace -l "k:*net*"
$ bpftrace -l "k:tcp*"
$ bpftrace -l "kprobe:*net*"
$ bpftrace -l "f:tcp*"
$ bpftrace -l "kfunc:tcp*"
$ bpftrace -l "t:*"
$ bpftrace -l "tracepoint:*"
$ bpftrace -l "tracepoint:net:*"
$ bpftrace -l "tracepoint:syscalls:*"
$ bpftrace -l "tracepoint:syscalls:sys_enter_*"
$ bpftrace -l 'tracepoint:sock:*'
tracepoint:sock:inet_sock_set_state
tracepoint:sock:sock_exceed_buf_limit
tracepoint:sock:sock_rcvqueue_full
列出 tracepoint
函数调用参数
$ bpftrace -lv tracepoint:net:netif_receive_skb
tracepoint:net:netif_receive_skb
void * skbaddr
unsigned int len
__data_loc char[] name
$ bpftrace -lv tracepoint:net:net_dev_xmit
tracepoint:net:net_dev_xmit
void * skbaddr
unsigned int len
int rc
__data_loc char[] name
列出内核 struct
结构体数据:
$ bpftrace -lv "struct path"
struct path {
struct vfsmount *mnt;
struct dentry *dentry;
};
$ bpftrace -lv "struct sock"
$ bpftrace -lv "struct sock_common"
列出 kprobe/kfunc
函数调用参数
通过内核 vmlinux 检查 kprobe 函数调用参数,
# gdb -q ~/rpmbuild/BUILD/kernel-5.12.7/linux-5.12.7-300.fc29.x86_64/vmlinux --ex 'p tcp_set_state'
Reading symbols from /root/rpmbuild/BUILD/kernel-5.12.7/linux-5.12.7-300.fc29.x86_64/vmlinux...done.
$1 = {void (struct sock *, int)} 0xffffffff81a40730 <tcp_set_state>
(gdb) q
# grep tcp_set_state /usr/src/kernels/5.12.7-300.fc29.x86_64/include/* -r
/usr/src/kernels/5.12.7-300.fc29.x86_64/include/net/tcp.h:void tcp_set_state(struct sock *sk, int state);
$ bpftrace -lv "kfunc:tcp_set_state"
kfunc:tcp_set_state
struct sock * sk
int state
$ bpftrace -e 'kprobe:tcp_set_state { printf("%p %d\n", arg0, arg1); }'
可对内核函数开始或结束位置进行动态跟踪(插桩).
示例1:
$ cat ./vfs_open.bt
#!/usr/bin/env bpftrace
#include <linux/path.h>
#include <linux/dcache.h>
kprobe:vfs_open
{
printf("open path: %s\n", str(((struct path *)arg0)->dentry->d_name.name));
}
跟踪 kprobe:vfs_open()
函数,将vfs_open
第一个参数打开的目录路径arg0
转换为(struct path *)
并输出.
$ bpftrace -lv 'f:vfs_open'
kfunc:vfs_open
const struct path * path
struct file * file
int retval
$ sudo ./vfs_open.bt
Attaching 1 probe...
open path: cat
open path: ld-2.28.so
open path: ld.so.cache
open path: libc-2.28.so
open path: locale-archive
open path: tcpstates.bt
open path: ls
open path: ld-2.28.so
open path: ld.so.cache
open path: libselinux.so.1
open path: libcap.so.2.25
open path: libc-2.28.so
open path: libpcre2-8.so.0.8.0
open path: libdl-2.28.so
open path: libpthread-2.28.so
open path: locale-archive
open path: tools
open path: interrupts
open path: stat
^C
示例2:
统计 vfs_read
每次执行耗时分布:
$ 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[NetworkManager]:
[2K, 4K) 1 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@|
@ns[pmdaxfs]:
[512, 1K) 3 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@|
[1K, 2K) 1 |@@@@@@@@@@@@@@@@@ |
[2K, 4K) 2 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ |
[4K, 8K) 0 | |
[8K, 16K) 0 | |
[16K, 32K) 0 | |
[32K, 64K) 0 | |
[64K, 128K) 1 |@@@@@@@@@@@@@@@@@ |
@ns[pmcd]:
[512, 1K) 1 |@@@@@@@@@@ |
[1K, 2K) 5 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@|
[2K, 4K) 1 |@@@@@@@@@@ |
[4K, 8K) 0 | |
[8K, 16K) 0 | |
[16K, 32K) 1 |@@@@@@@@@@ |
@ns[cat]:
[512, 1K) 6 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@|
[1K, 2K) 2 |@@@@@@@@@@@@@@@@@ |
[2K, 4K) 2 |@@@@@@@@@@@@@@@@@ |
[4K, 8K) 1 |@@@@@@@@ |
[8K, 16K) 0 | |
[16K, 32K) 0 | |
[32K, 64K) 0 | |
[64K, 128K) 0 | |
[128K, 256K) 0 | |
[256K, 512K) 1 |@@@@@@@@ |
@ns[in:imjournal]:
[512, 1K) 2 |@@@ |
[1K, 2K) 31 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@|
[2K, 4K) 1 |@ |
@ns[pmdakvm]:
[1K, 2K) 40 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@|
[2K, 4K) 2 |@@ |
@ns[bash]:
[512, 1K) 8 |@@@@@@@@@@ |
[1K, 2K) 5 |@@@@@@ |
[2K, 4K) 39 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@|
[4K, 8K) 1 |@ |
[8K, 16K) 2 |@@ |
[16K, 32K) 6 |@@@@@@@@ |
[32K, 64K) 0 | |
[64K, 128K) 0 | |
[128K, 256K) 1 |@ |
[256K, 512K) 1 |@ |
[512K, 1M) 1 |@ |
@ns[ls]:
[512, 1K) 58 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@|
[1K, 2K) 31 |@@@@@@@@@@@@@@@@@@@@@@@@@@@ |
[2K, 4K) 7 |@@@@@@ |
@ns[sshd]:
[1K, 2K) 46 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@ |
[2K, 4K) 84 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@|
[4K, 8K) 4 |@@ |
[8K, 16K) 11 |@@@@@@ |
[16K, 32K) 2 |@ |
@ns[pmdalinux]:
[256, 512) 25 |@@@@@@@@@@@@@@@@@ |
[512, 1K) 58 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ |
[1K, 2K) 23 |@@@@@@@@@@@@@@@@ |
[2K, 4K) 17 |@@@@@@@@@@@@ |
[4K, 8K) 21 |@@@@@@@@@@@@@@ |
[8K, 16K) 73 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@|
[16K, 32K) 4 |@@ |
[32K, 64K) 9 |@@@@@@ |
[64K, 128K) 4 |@@ |
[128K, 256K) 1 | |
@ns[irqbalance]:
[256, 512) 7 |@@@ |
[512, 1K) 9 |@@@@ |
[1K, 2K) 52 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@ |
[2K, 4K) 4 |@@ |
[4K, 8K) 78 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ |
[8K, 16K) 94 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@|
[16K, 32K) 6 |@@@ |
[32K, 64K) 5 |@@ |
[64K, 128K) 5 |@@ |
@ns[pmdaproc]:
[512, 1K) 3 | |
[1K, 2K) 0 | |
[2K, 4K) 389 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@|
[4K, 8K) 42 |@@@@@ |
[8K, 16K) 3 | |
@start[1905]: 1922797996393161
@start[1907]: 1922797996743168
@start[1904]: 1922798001240070
@start[1906]: 1922798017500978
@start[5624]: 1922821038865193
@start[19276]: 1922821050726967
示例:
1. Listing probes
bpftrace -l 'tracepoint:syscalls:sys_enter_*'
2. Hello world
bpftrace -e 'BEGIN { printf("hello world\n"); }'
3. File opens
bpftrace -e 'tracepoint:syscalls:sys_enter_open { printf("%s %s\n", comm, str(args->filename)); }'
4. Syscall counts by process
bpftrace -e 'tracepoint:raw_syscalls:sys_enter { @[comm] = count(); }'
5. Distribution of read() bytes
bpftrace -e 'tracepoint:syscalls:sys_exit_read /pid == 18644/ { @bytes = hist(args->retval); }'
6. Kernel dynamic tracing of read() bytes
bpftrace -e 'kretprobe:vfs_read { @bytes = lhist(retval, 0, 2000, 200); }'
7. Timing read()s
bpftrace -e 'kprobe:vfs_read { @start[tid] = nsecs; }
kretprobe:vfs_read /@start[tid]/ { @ns[comm] = hist(nsecs - @start[tid]); delete(@start[tid]); }'
8. Count process-level events
bpftrace -e 'tracepoint:sched:sched* { @[name] = count(); } interval:s:5 { exit(); }'
9. Profile on-CPU kernel stacks
bpftrace -e 'profile:hz:99 { @[stack] = count(); }'
10. Scheduler tracing
bpftrace -e 'tracepoint:sched:sched_switch { @[stack] = count(); }'
11. Block I/O tracing
bpftrace -e 'tracepoint:block:block_rq_complete { @ = hist(args->nr_sector * 512); }'
tracepoint:sock:inet_sock_set_state
:
$ bpftrace -lv t:sock:inet_sock_set_state
tracepoint:sock:inet_sock_set_state
const void * skaddr
int oldstate
int newstate
__u16 sport
__u16 dport
__u16 family
__u16 protocol
__u8 saddr[4]
__u8 daddr[4]
__u8 saddr_v6[16]
__u8 daddr_v6[16]
$ bpftrace -e 'tracepoint:sock:inet_sock_set_state { printf("%d %d\n", args->oldstate, args->newstate); }'
Attaching 1 probe...
7 2
2 1
10 3
3 1
7 2
2 1
1 4
4 11
11 7
1 4
1 8
8 9
4 5
5 7
9 7
$ bpftrace -e 'tracepoint:sock:inet_sock_set_state { printf("%-15s %-15s %d %d\n", ntop(2, args->saddr), ntop(2, args->daddr), args->oldstate, args->newstate); }'
uprobe/uretprobe
分别为用户态函数调用和函数返回探针:
uprobe: arg0, arg1, ..., argN
uretprobe: retval
以下示例演示跟踪用户态主函数main
:
# cat /root/wrks/test.c
#include <stdio.h>
int main(int argc, char **argv)
{
printf("hello world!\n");
return 0;
}
# gcc -g test.c -o test
# objdump -tT test |grep main
0000000000000000 F *UND* 0000000000000000 __libc_start_main@@GLIBC_2.2.5
0000000000401126 g F .text 0000000000000020 main
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 __libc_start_main
$ bpftrace -e 'uprobe:/root/wrks/test:0x401126 { printf("test\n");}'
Attaching 1 probe...
# /root/wrks/test
hello world!
$ bpftrace -e 'uprobe:/root/wrks/test:0x401126 { printf("test\n");}'
Attaching 1 probe...
test
^C
# objdump -d test
...
0000000000401126 <main>:
401126: 55 push %rbp
401127: 48 89 e5 mov %rsp,%rbp
40112a: 48 83 ec 10 sub $0x10,%rsp
40112e: 89 7d fc mov %edi,-0x4(%rbp)
401131: 48 89 75 f0 mov %rsi,-0x10(%rbp)
401135: bf 10 20 40 00 mov $0x402010,%edi
40113a: e8 f1 fe ff ff callq 401030 <puts@plt>
40113f: b8 00 00 00 00 mov $0x0,%eax
401144: c9 leaveq
401145: c3 retq
401146: 66 2e 0f 1f 84 00 00 nopw %cs:0x0(%rax,%rax,1)
40114d: 00 00 00
...
$ bpftrace -e 'uprobe:/root/wrks/test:main { printf("test\n");}'
Attaching 1 probe...
test
$ bpftrace -e 'uprobe:/root/wrks/test:main { printf("arg0: %d\n", arg0);}'
Attaching 1 probe...
arg0: 1
当前系统支持的硬件计数器:
$ bpftrace -lv 'h:*'
hardware:backend-stalls:
hardware:branch-instructions:
hardware:branch-misses:
hardware:bus-cycles:
hardware:cache-misses:
hardware:cache-references:
hardware:cpu-cycles:
hardware:frontend-stalls:
hardware:instructions:
hardware:ref-cycles:
示例 - 统计30秒内cache-missed次数超过1000000的进程
bpftrace -e 'hardware:cache-misses:1000000 { @[pid] = count(); } interval:s:30 { exit(); }'
分析内核实时函数栈, 统计ip_output
调用栈:
$ bpftrace -e 'kprobe:ip_output { @[kstack()] = count(); } interval:s:10 { exit(); }'
Attaching 2 probes...
...
@[
ip_output+1
__ip_queue_xmit+349
__tcp_transmit_skb+2718
tcp_write_xmit+971
tcp_tsq_handler+57
tcp_tasklet_func+205
tasklet_action_common.isra.18+102
__do_softirq+223
irq_exit_rcu+218
common_interrupt+127
asm_common_interrupt+30
cpuidle_enter_state+219
cpuidle_enter+41
do_idle+573
cpu_startup_entry+25
start_secondary+273
secondary_startup_64_no_verify+194
]: 8
@[
ip_output+1
__ip_queue_xmit+349
__tcp_transmit_skb+2718
tcp_write_xmit+971
__tcp_push_pending_frames+50
tcp_sendmsg_locked+3206
tcp_sendmsg+39
sock_sendmsg+84
sock_write_iter+140
new_sync_write+376
vfs_write+445
ksys_write+157
do_syscall_64+51
entry_SYSCALL_64_after_hwframe+68
]: 45
...
# bpftrace -e 'kprobe:ip_output { @[kstack(3)] = count(); }'
Attaching 1 probe...
^C
@[
ip_output+1
__ip_queue_xmit+349
__tcp_transmit_skb+2718
]: 54
$ bpftrace -e 'k:tcp_sendmsg { @size = hist(arg2); } interval:s:10 { exit(); }'
Attaching 2 probes...
@size:
[32, 64) 6 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ |
[64, 128) 2 |@@@@@@@@@@ |
[128, 256) 5 |@@@@@@@@@@@@@@@@@@@@@@@@@@ |
[256, 512) 10 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@|
[512, 1K) 1 |@@@@@ |
#bpftrace -e 'kr:tcp_sendmsg { @retvals[retval > 0 ? 0 : retval] = count(); } interval:s:30 { exit(); }'
Attaching 2 probes...
@retvals[0]: 148
#!/usr/local/bin/bpftrace
#include <net/sock.h>
k:tcp_sendmsg
{
@sk[tid] = arg0;
@size[tid] = arg2;
}
kr:tcp_sendmsg
/@sk[tid]/
{
$sk = (struct sock *)@sk[tid];
$size = @size[tid];
$af = $sk->__sk_common.skc_family;
if ($af == AF_INET) {
$daddr = ntop($af, $sk->__sk_common.skc_daddr);
$saddr = ntop($af, $sk->__sk_common.skc_rcv_saddr);
$lport = $sk->__sk_common.skc_num;
$dport = $sk->__sk_common.skc_dport;
$dport = ($dport >> 8) | (($dport << 8) & 0xff00);
printf("%-15s %-5d -> %-15s %-5d: %d bytes, retval %d\n",
$saddr, $lport, $daddr, $dport, $size, retval);
} else {
printf("IPv6...\n");
}
delete(@sk[tid]);
delete(@size[tid]);
}
# ./tcp_sendmsg.bt
Attaching 2 probes...
10.0.0.65 49978 -> 52.37.243.173 443 : 63 bytes, retval 63
127.0.0.1 58566 -> 127.0.0.1 22 : 36 bytes, retval 36
127.0.0.1 22 -> 127.0.0.1 58566: 36 bytes, retval 36
[...]
监控tcp_sendmsg发送大于8192字节进程pid
bpftrace -e 'k:tcp_sendmsg /arg2 > 8192/ { printf("PID %d: %d bytes\n", pid, arg2); }'
统计调用tcp_sendmsg进程发送字节分布
bpftrace -e 'k:tcp_sendmsg { @size[pid, comm] = hist(arg2); }'
Attaching 1 probe...
^C
@size[2326, sshd]:
[32, 64) 22 |@@@@@@@@@@@@@@@@@@@ |
[64, 128) 19 |@@@@@@@@@@@@@@@@ |
[128, 256) 59 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@|
[256, 512) 33 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ |
统计tcp_sendmsg 返回值分布
bpftrace -e 'kr:tcp_sendmsg { @return[retval] = count(); }'
每秒统计tcp_sendmsg 调用次数&发送总计&平均字节
bpftrace -e 'k:tcp_sendmsg { @size = stats(arg2); } interval:s:1 { print(@size); clear(@size); }'
统计 tcp_sendmsg 调用栈分布
bpftrace -e 'k:tcp_sendmsg { @[kstack] = count(); }'
限制调用栈层次为3层, 统计 tcp_sendmsg 调用栈分布
bpftrace -e 'k:tcp_sendmsg { @[kstack(3)] = count(); }'
bpftrace -e 'k:tcp_sendmsg { @ts[tid] = nsecs; }
kr:tcp_sendmsg /@ts[tid]/ { @ns = hist(nsecs - @ts[tid]); delete(@ts[tid]); }'
Attaching 2 probes...
^C
@ns:
[512, 1K) 21 |@ |
[1K, 2K) 14 |@ |
[2K, 4K) 291 |@@@@@@@@@@@@@@@@@@@@@@@@@@ |
[4K, 8K) 574 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@|
[8K, 16K) 33 |@@ |
[16K, 32K) 5 | |
@ts[1614]: 1826887636941576
统计每个hrtimer_start
函数的参数调用频率
$ bpftrace -lv 't:timer:hrtimer_start'
tracepoint:timer:hrtimer_start
void * hrtimer
void * function
s64 expires
s64 softexpires
enum hrtimer_mode mode
$ bpftrace -e 't:timer:hrtimer_start { @[ksym(args->function)] = count(); }'
Attaching 1 probe...
^C
@[timerfd_tmrproc]: 5
@[posix_timer_fn]: 8
@[it_real_fn]: 13
@[watchdog_timer_fn]: 256
@[hrtimer_wakeup]: 431
@[tick_sched_timer]: 22141
Kernel analysis with bpftrace
bpftrace Reference Guide
bpftrace Install Guide
bpftrace Cheat Sheet
BPF Performance Tools (book)
Linux Extended BPF (eBPF) Tracing Tools
tutorial_one_liners_chinese
Tracing a packet journey using Linux tracepoints, perf and eBPF
https://github.com/yadutaf/tracepkt