0. bpftrace
Alastair 与2016.12创建了bpftrace。bpftrace 是一款基于BPF和BCC的开源跟踪器。其自带了许多多性能工具和支持文档,同时提供了一个高级编程语言环境,可以用来创建强大的单行程序和小工具。
0.1 bpftrace组件
在安装bpftrace的时候,其会存在以下的目录树。其中提供了工具的文档、man帮助文档、示例文件等等。
├── docs # 参考手册 单行小程序指引
├── libbpf # 库文件
├── src # 前端
│ └── ast #中间代码生成
├── man
│ ├── adoc
│ └── man8 # 帮助文档
├── README
├── INSTALL
└── tools # 工具和实例
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
0.2 bpftrace 帮助信息
使用-h
参数以获取帮助信息
[root@bogon bpftrace]# bpftrace -h
USAGE:
bpftrace [options] filename
bpftrace [options] - <stdin input>
bpftrace [options] -e 'program'
OPTIONS:
-B MODE output buffering mode ('full', 'none')
-f FORMAT output format ('text', 'json')
-o file redirect bpftrace output to file
-d debug info dry run
-dd verbose debug info dry run
-e 'program' execute this program
-h, --help show this help message
-I DIR add the directory to the include search path
--include FILE add an #include file before preprocessing
-l [search] list probes
-p PID enable USDT probes on PID
-c 'CMD' run CMD and enable USDT probes on resulting process
--usdt-file-activation
activate usdt semaphores based on file path
--unsafe allow unsafe builtin functions
-q keep messages quiet
-v verbose messages
--info Print information about kernel BPF support
-k emit a warning when a bpf helper returns an error (except read functions)
-kk check all bpf helper functions
-V, --version bpftrace version
--no-warnings disable all warning messages
ENVIRONMENT:
BPFTRACE_STRLEN [default: 64] bytes on BPF stack per str()
BPFTRACE_NO_CPP_DEMANGLE [default: 0] disable C++ symbol demangling
BPFTRACE_MAP_KEYS_MAX [default: 4096] max keys in a map
BPFTRACE_CAT_BYTES_MAX [default: 10k] maximum bytes read by cat builtin
BPFTRACE_MAX_PROBES [default: 512] max number of probes
BPFTRACE_LOG_SIZE [default: 1000000] log size in bytes
BPFTRACE_PERF_RB_PAGES [default: 64] pages per CPU to allocate for ring buffer
BPFTRACE_NO_USER_SYMBOLS [default: 0] disable user symbol resolution
BPFTRACE_CACHE_USER_SYMBOLS [default: auto] enable user symbol cache
BPFTRACE_VMLINUX [default: none] vmlinux path used for kernel symbol resolution
BPFTRACE_BTF [default: none] BTF file
EXAMPLES:
bpftrace -l 'sleep'
list probes containing "sleep"
bpftrace -e 'kprobe:do_nanosleep { printf("PID %d sleeping...\n", pid); }'
trace processes calling sleep
bpftrace -e 'tracepoint:raw_syscalls:sys_enter { @[comm] = count(); }'
count syscalls by process name
- 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
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
0.3 bpftrace 工具速览表
应用场景 | 工具名称 | 使用场景及用法 |
---|---|---|
CPU | execsnoop.bt |
0.4 bpftrace 探针
类型 | 缩写 | 描述 |
---|---|---|
tracepoint | t | 内核静态插桩点 |
usdt | U | 用户静态插桩点 |
kprobe | k | 内核动态函数插桩点 |
kretprobe | kr | 内核动态函数返回值插桩点 |
uprobe | u | 用户动态函数插桩点 |
uretprobe | ur | 用户动态函数返回值插桩点 |
software | s | 内核软件事件 |
hardware | h | 硬件基于计数器的插桩 |
profile | p | 对全部cpu进行采样 |
interval | i | 一个cpu上周期性报告 |
BEGIN | 启动 | |
END | 退出 |
0.4.1 tracepoint
tracepoint 探针类型会对内核跟踪点进行插桩,格式如下:
# tracepoint_name 是跟踪点全名,包括用来将跟踪点所在列和事件名字分割开的冒号
tracepoint:tracepoint_name
- 1
- 2
跟踪点通常是带有参数的:bpftrace 可以通过内置变量args来访问这些参数信息。假如说我们访问的跟踪点有一个代表数据包长度的参数,名称为len,我们可以用agrs->len
来访问这个变量。而一个跟踪点具体具有哪些参数则是可以bpftrace的-lv
参数来进行查看:
[root@bogon ik]# bpftrace -lv tracepoint:syscalls:sys_enter_read
tracepoint:syscalls:sys_enter_read
int __syscall_nr
unsigned int fd
char * buf
size_t count
- 1
- 2
- 3
- 4
- 5
- 6
我们可以看到这里跟man参数文档描述的其实是不一致的,这里会多一个系统调用号__syscall_nr
。
ssize_t read(int fd,void *buf,size_t count)
- 1
0.4.2 usdt
udst探针对用户态静态探针点进行插桩。格式如下:
usdt:binary_path:probe_name
usdt:libary_path:probe_name
usdt:binary_path:namespace:probe_name
usdt:libary_path:namespace:probe_name
- 1
- 2
- 3
- 4
比如说我们定义了如下的函数:
// 会编译成可执行文件test
namespace bpf_method{
void bpf_test();
}
- 1
- 2
- 3
- 4
那么对这个函数来说,其探针可以表述usdt:./test:bpf_method:bpf_test
。另外,如果是在不确定的话,我们可以使用-l
参数列出当前二进制文件之中所有可用的usdt探针。
# 这里存疑,笔者并没有实验成功
bpftrace -l 'usdt:/usr/local/cpython/python'
- 1
- 2
除此之外,我们还可以使用 -p pid
的方式列出正在运行的程序其支持usdt类型探针:
bpftrace -lp 2266 | grep usdt
usdt:/proc/2266/root/usr/lib64/libc-2.28.so:libc:memory_sbrk_more
usdt:/proc/2266/root/usr/lib64/libc-2.28.so:libc:memory_tcache_double_free
usdt:/proc/2266/root/usr/lib64/libc-2.28.so:libc:memory_tunable_tcache_count
usdt:/proc/2266/root/usr/lib64/libc-2.28.so:libc:memory_tunable_tcache_max_bytes
usdt:/proc/2266/root/usr/lib64/libc-2.28.so:libc:memory_tunable_tcache_unsorted_limit
usdt:/proc/2266/root/usr/lib64/libc-2.28.so:libc:setjmp
[...]
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
0.4.3 kprobe和kretprobe
这种探针类型用于内核的动态插桩,格式如下:
kprobe:function_name
kretprobe:function_name
- 1
- 2
kprobe对函数的开始进行插桩,其参数arg0,arg1,arg2…是进入函数时的参数,类型均为64位无符号整数。如果其是指向C结构体的指针,可以进行强制转换; kretprobe对函数的结束进行插桩,其内置参数retval是函数的返回值,类型永远是64位无符号整型,如果和上述整型不一致,需要通过类型强制转换为对应的类型。
0.4.4 uprobe和uretprobe
这种探针类型用于用户态的动态插桩,格式如下:
uprobe:binary_path:function_name
uprobe:libary_path:function_name
uretprobe:binary_path:function_name
uretprobe:libary_path:function_name
- 1
- 2
- 3
- 4
uprobe对函数的开始进行插桩,其参数arg0,arg1,arg2…是进入函数时的参数,类型均为64位无符号整数。如果其是指向C结构体的指针,可以进行强制转换; uretprobe对函数的结束进行插桩,其内置参数retval是函数的返回值,类型永远是64位无符号整型,如果和上述整型不一致,需要通过类型强制转换为对应的类型。
0.4.5 software和hardware
这些探针的类型是预先定义好的软件事件和硬件事件,类型如下:
software:event_name
software:event_name:count
hardware:event_name
hardware:event_name:count
- 1
- 2
- 3
- 4
这些事件跟跟踪点是类似的,不过更适合于基于计数器的指标和基于采样的探测。由于事件的频闭可能很高,所以这里存在一个count
字段,当每发生count
此事件之后,才会触发一次探针。如果没有指定count,则会采用默认的频率。
0.4.6 profile和interval
这些是基于定时器的事件,格式如下:
profile:hz:rate
profile:s:rate
profile:ms:rate
profile:us:rate
interval:s:rate
interval:ms:rate
- 1
- 2
- 3
- 4
- 5
- 6
profile类型会在全部cpu之上进行激活,可以用作对CPU的使用进行采样;interval类型只在单个CPU上激活,可以用于周期性的打印输出。比如说profile:hz:100
就表示100hz周期会激活一次对CPU使用的采样。
1. 安装bpftrace
- centos
curl https://repos.baslab.org/bpftools.repo --output /etc/yum.repos.d/bpftools.repo
sudo yum install bpftrace bpftrace-tools
- 1
- 2
- 3
- ubuntu
sudo apt-get update
sudo apt-get install bpftrace
- 1
- 2
2. bpftrace 工具
2.1 bpftrace 编程
下面是个bpftrace编程的例子,其展示了bpftrace 编程的基本结构:
# 指定解释器
#!/usr/local/bin/bpftrace
# 定义的探针1
kprobe:vfs_read
{
# 将当前进程调用vfs_read 的时间放入一个名为start的map之中
@start[tid] = nsecs;
}
# 定义的探针2
kretprobe:vfs_read
# 过滤start 这个map,获取tid这一项
/@start[tid]/
{
# 获取执行时间
\(duration_us</span> <span class="token operator">=</span> <span class="token punctuation">(</span>nsecs - @start<span class="token punctuation">[</span>tid<span class="token punctuation">]</span><span class="token punctuation">)</span> / <span class="token number">1000</span><span class="token punctuation">;</span>
<span class="token comment"># 执行时间放入直方图的bucket之中</span>
@us <span class="token operator">=</span> hist<span class="token punctuation">(</span><span class="token variable">\)duration_us);
# 删除map中这个元素
delete(@start[tid]);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
下面是上面这个文件的输出展示,vfs_exec_time.bt
是存放上面代码的文件,bt后缀只是为了辨别:
[root@bogon bpftrace]# bpftrace vfs_exec_time.bt
Attaching 2 probes...
^C
@start[6165]: 6928640480723
@us:
[0] 185 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@|
[1] 56 |@@@@@@@@@@@@@@@ |
[2, 4) 91 |@@@@@@@@@@@@@@@@@@@@@@@@@ |
[4, 8) 44 |@@@@@@@@@@@@ |
[8, 16) 1 | |
[16, 32) 3 | |
[32, 64) 2 | |
[64, 128) 0 | |
[128, 256) 0 | |
[256, 512) 0 | |
[512, 1K) 0 | |
[1K, 2K) 1 | |
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
2.1.1 bpftrace 用法
bpftrace主要有单行程序和批处理两种用法:
# bpftrace 单行程序
bpftrace -e 'program'
- 1
- 2
-e
参数表示bpftrace会执行program,并开始跟踪其中定义的所有时间。程序会持续运行,直达Ctrl+C
组合键被按下或者程序显示的调用exit()
为止。
bpftrace file.bt
- 1
程序还可被保存到一个文件之中,然后使用bpftrace来执行。或者我们也可以使用chmod +x file.bt
来赋予文件可执行权限,这样就可以像其他任何程序一样运行。
2.1.2 程序结构
bpftrace程序的结构是一系列探针+对应的动作,当探针被激活后,相应的动作就会被执行。
probe { actions }
- 1
注: 动作
动作既可以是单条语句,也可以是使用分号分割的多条语句{ action1,action2、action3... }
probe /filter_pattern/ { actions }
- 1
注: 过滤器
filter_pattern
是个布尔表达式,其会决定一个动作是否被执行,比如说pid==123
表示只有进程123触碰探针才会执行动作。同时,在布尔表达式中也可以运用&&
、||
等布尔运算符
如果我们希望对多个探针实行同一动作处理,可以在探针之间加上,
来分割,如下:
probe1,probe2,... { actions }
- 1
同时,探针也支持通配符,如下:
# 匹配probe1、probeabc等等...
probe* { actions }
- 1
- 2
2.1.3 探针格式
探针以类型名字开始,然后是一系列以:
为分隔的标识符:
# ...表示可选
type:identifier1[:identifier2[...]]
- 1
- 2
一般来说,内核探针kprobe探针类型对内核态函数进行插桩,只需要一个标识符:内核函数名。uprobe探针类型对用户态函数进行插桩,需要两个标识符: 二进制文件的路径和函数名
2.1.4 变量
在bpftrace之中有三种类型的变量:
- 内置变量:由bpftrace定义,通常是个只读的信息源,主要有以下几种:
变量 | 作用 |
---|---|
pid | 进程id |
comm | 进程名称 |
nsecs | 时间戳(纳秒) |
curtask | 当前线程的 task_struct 结构体 |
- 临时变量:可以用于临时的计算,以
$
作为前缀
# 定义变量x并赋值1
$x = 1
- 1
- 2
- 映射表变量:使用BPF映射表来存储对象,名字带有
@
前缀,可以用于全局存储,在不同动作之间传递数据。
# 定义全局变量a 并赋值1
probe1 { @a = 1;}
# 定义array这个映射表,其为数组,给数组中对应的元素赋值
probe2 { @array[tid] = nsecs}
# 复合数组映射表
probe2 { @muti_array[pid,tid] = nsecs}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
2.1.5 控制流
bpftrace支持三种控制流:filter、ternarg和if语句,这些都依靠布尔表达式来有条件的改变对程序执行的流向。
2.1.5.1 filter
probe /filter_pattern/ { actions }
- 1
注: 过滤器
filter_pattern
是个布尔表达式,其会决定一个动作是否被执行,比如说pid==123
表示只有进程123触碰探针才会执行动作。同时,在布尔表达式中也可以运用&&
、||
等布尔运算符
2.1.5.2 if 语句
bpftrace的 if 语法如下:
if (test) {
actions
}
if (test) {
actions
} else {
other_actions
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
2.1.5.3 unroll 循环语句
由于bpftrace运行在受限的环境中,所以这里使用了unroll
语句来保证循环时有限的。
2.1.5 函数
bpftrace 的内置函数主要如下:
函数 | 类型 | 作用 |
---|---|---|
exit() | 普通函数 | 退出bpftrace |
str(char*) | 普通函数 | 输出一个指针,返回字符串 |
system(format[,arguments …]) | 普通函数 | 在shell之中运行命令 |
count() | 映射表函数 | 累计统计 |
sum($var) | 映射表函数 | 求和 |
hist($var) | 映射表函数 | 直方图展示 |
delete($var) | 映射表函数 | 删除映射表元素 |
2.2 bpftrace 示例
2.2.1 单行程序示例
- hello world:
bpftrace -e 'BEGIN { printf("hello world!\n"); }'
- 1
[root@bogon bpftrace]# bpftrace -e 'BEGIN { printf("hello world!\n"); }'
Attaching 1 probe...
hello world!
- 1
- 2
- 3
- 展示系统谁在执行什么命令:
bpftrace -e 'tracepoint:syscalls:sys_enter_execve { printf("%s -> %s\n", comm, str(args->filename)); }'
- 1
# 在这里,笔者打开了另外一个中断,执行了ls 命令,也就是这里最后一行。
[root@bogon bpftrace]# bpftrace -e 'tracepoint:syscalls:sys_enter_execve { printf("%s -> %s\n", comm, str(args->filename)); }'
Attaching 1 probe...
ksmtuned -> /usr/bin/awk
ksmtuned -> /usr/bin/pgrep
ksmtuned -> /usr/bin/awk
ksmtuned -> /usr/bin/sleep
bash -> /usr/bin/ls
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
注: 什么是ksmtuned?
KSM 是一种节省内存的重复数据删除功能,由 CONFIG_KSM=y 启用,在 2.6.32 中添加到 Linux 内核中。
KSM 最初是为与 KVM(在那里称为内核共享内存)一起使用而开发的,通过共享它们之间的公共数据,将更多虚拟机放入物理内存中。但它对于生成相同数据的许多实例的任何应用程序都非常有用。
- 展示新进程的创建:
bpftrace -e 'tracepoint:syscalls:sys_enter_execve { join(args->argv); }'
- 1
# 下面代码中的第一行展示了笔者在另外的终端运行ls的过程,其余代码则是KSM机制所带来的
[root@bogon bpftrace]# bpftrace -e 'tracepoint:syscalls:sys_enter_execve { join(args->argv); }'
Attaching 1 probe...
ls --color=auto
awk /^(MemFree|Buffers|Cached):/ {free += $2}; END {print free} /proc/meminfo
pgrep -d -- ^qemu(-(kvm|system-.+)|:.{1,11})$
awk { sum += $1 }; END { print 0+sum }
sleep 60
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 按照进程统计文件的开启情况:
bpftrace -e 'tracepoint:syscalls:sys_enter_openat { printf("%s %s\n", comm, str(args->filename)); }'
- 1
[root@bogon bpftrace]# bpftrace -e 'tracepoint:syscalls:sys_enter_openat { printf("%s %s\n", comm, str(args->filename)); }'
Attaching 1 probe...
in:imjournal /run/log/journal/29545612d2ce4e319e6a9edaa0cff1d3/system.journa
systemd-journal /
systemd-journal sys
systemd-journal bus
- 1
- 2
- 3
- 4
- 5
- 6
注: 什么是systemd-journal?
systemd-journald 是一个收集并存储各类日志数据的系统服务。 它创建并维护一个带有索引的、结构化的日志数据库, 并可以收集来自各种不同渠道的日志:
- 通过 kmsg 收集内核日志
- 通过 libc 的 syslog(3) 接口收集系统日志
- 通过 本地日志接口 sd_journal_print(3) 收集结构化的系统日志
- 捕获服务单元的标准输出(STDOUT)与标准错误(STDERR)
- 通过内核审计子系统收集审计记录
- 按照程序统计系统调用次数:
bpftrace -e 'tracepoint:raw_syscalls:sys_enter { @[comm] = count(); }'
- 1
[root@bogon bpftrace]# bpftrace -e 'tracepoint:raw_syscalls:sys_enter { @[comm] = count(); }'
Attaching 1 probe...
^C
@[NetworkManager]: 19
@[tuned]: 24
@[sshd]: 24
@[irqbalance]: 40
@[bpftrace]: 67
@[gdbus]: 93
@[in:imjournal]: 673
@[systemd-journal]: 7752
...
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 按照探针名字统计系统调用次数:
bpftrace -e 'tracepoint:syscalls:sys_enter_* { @[probe] = count(); }'
- 1
# 下面的代码会统计运行期间符合‘tracepoint:syscalls:sys_enter_*’ 正则匹配的所有探针的调用次数
[root@bogon bpftrace]# bpftrace -e 'tracepoint:syscalls:sys_enter_* { @[probe] = count(); }'
Attaching 341 probes...
^C
@[tracepoint:syscalls:sys_enter_waitid]: 1
@[tracepoint:syscalls:sys_enter_getpid]: 1
@[tracepoint:syscalls:sys_enter_timerfd_create]: 1
@[tracepoint:syscalls:sys_enter_socket]: 1
@[tracepoint:syscalls:sys_enter_rt_sigreturn]: 1
@[tracepoint:syscalls:sys_enter_munmap]: 2
@[tracepoint:syscalls:sys_enter_mmap]: 2
@[tracepoint:syscalls:sys_enter_sendmsg]: 5
@[tracepoint:syscalls:sys_enter_writev]: 5
@[tracepoint:syscalls:sys_enter_ftruncate]: 5
@[tracepoint:syscalls:sys_enter_inotify_add_watch]: 5
...
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 按照进程统计系统调用数量:
bpftrace -e 'tracepoint:raw_syscalls:sys_enter { @[pid, comm] = count(); }'
- 1
[root@bogon bpftrace]# bpftrace -e 'tracepoint:raw_syscalls:sys_enter { @[pid, comm] = count(); }'
Attaching 1 probe...
^C
@[3126, gpg-agent]: 1
@[3171, gpg-agent]: 1
@[3139, gpg-agent]: 1
@[985, sssd]: 1
@[1084, tuned]: 2
@[2848, gmain]: 3
@[2751, gmain]: 4
@[698, systemd-journal]: 2178
...
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 按照进程展示运行期间内总的读取字节数:
bpftrace -e 'tracepoint:syscalls:sys_exit_read /args->ret/ { @[comm] = sum(args->ret); }'
- 1
[root@bogon bpftrace]# bpftrace -e 'tracepoint:syscalls:sys_exit_read /args->ret/ { @[comm] = sum(args->ret); }'
Attaching 1 probe...
^C
@[sshd]: 54
@[in:imjournal]: 63
@[gmain]: 188
@[sedispatch]: 756
@[systemd-journal]: 18056
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 按照进程展示read 返回结果大小的分布:
bpftrace -e 'tracepoint:syscalls:sys_exit_read { @[comm] = hist(args->ret); }'
- 1
[root@bogon ik]# bpftrace -e 'tracepoint:syscalls:sys_exit_read { @[comm] = hist(args->ret); }'
Attaching 1 probe...
^C
@[sshd]:
[2, 4) 1 |@@@@@@@@@@@@@@@@@@@@@@@@@@ |
[4, 8) 0 | |
[8, 16) 0 | |
[16, 32) 1 |@@@@@@@@@@@@@@@@@@@@@@@@@@ |
[32, 64) 2 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@|
...
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 展示进程的磁盘IO尺寸:
bpftrace -e 'tracepoint:block:block_rq_issue { printf("%d %s %d\n", pid, comm, args->bytes); }'
- 1
[root@bogon ik]# bpftrace -e 'tracepoint:block:block_rq_issue { printf("%d %s %d\n", pid, comm, args->bytes); }'
Attaching 1 probe...
# pid name bytes
251 kworker/2:1H 4096
284 kworker/4:1H 0
251 kworker/2:1H 0
288 kworker/7:1H 14848
288 kworker/7:1H 0
3684 kworker/2:0 8
3684 kworker/2:0 8
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 按照进程展示页换入数量:
bpftrace -e 'software:major-faults:1 { @[comm] = count(); }'
- 1
[root@bogon ik]# bpftrace -e 'software:major-faults:1 { @[comm] = count(); }'
Attaching 1 probe...
^C
@[sssd_nss]: 2
- 1
- 2
- 3
- 4
- 按照进程展示缺页中断数量:
bpftrace -e 'software:faults:1 { @[comm] = count(); }'
- 1
[root@bogon ik]# bpftrace -e 'software:faults:1 { @[comm] = count(); }'
Attaching 1 probe...
^C
@[systemd-journal]: 5
@[in:imjournal]: 5
- 1
- 2
- 3
- 4
- 5
- 对pid为xxx的进程以yyhz抓取其用户态的调用栈信息:
bpftrace -e 'profile:hz:yy /pid == xxx/ { @[ustack] = count(); }'
- 1