ebpf性能剖析之StackCount
发布时间:2026-08-10 10:04:38.286 文章来源:AiSoftCloud 浏览次数:45 下载次数:1 

StackCount

安装

安装bpfcc工具

  1. sudo apt update
  2. sudo apt install bpfcc-tools

安装perf工具

参考:手动编译安装Linux RT实时补丁

  1. sudo apt update
  2. sudo apt install -y libdw-dev systemtap-sdt-dev libslang2-dev libperl-dev libcap-dev libbabeltrace-dev libpfm4-dev libtraceevent-dev
  3. # 进入内核源码中的perf目录,编译perf工具
  4. cd linux-6.8.2/tools/perf
  5. make -j 10
  6. sudo make install
  7. sudo cp perf /usr/bin/perf

示例

统计tcp_sendmsg调用

  1. sudo stackcount-bpfcc tcp_sendmsg -D 10

含义:用 eBPF kprobe 钩住内核函数 tcp_sendmsg,统计 10 秒钟内,都是哪些调用路径走到了 tcp_sendmsg,统计每条调用栈出现的次数。

如果看到一堆b'[unknown]'的堆栈,是因为用户栈符号解析失败导致的,这一般是因为用户程序编译时没有保留帧指针-fomit‑frame‑pointer(这也是gcc默认编译选项),省略 rbp 栈基址寄存器,eBPF 无法做栈回溯。

可以:
1、不看用户栈,只看内核栈(设置成-K表示内核栈,设置为-U表示用户栈)

  1. sudo stackcount-bpfcc tcp_sendmsg -K -D 10

2、bpftrace 使用 dwarf 基于调试信息做用户栈(不需要帧指针)
stackcount‑bpfcc 底层用 bpf_get_stackid(),依赖帧指针;bpftrace 可以开启 dwarf 栈展开,代价 CPU 开销更大。

  1. sudo bpftrace -e 'kprobe:tcp_sendmsg { @[kstack + ustack(dwarf)] = count(); } interval:s:10 { exit(); }'

3、虽然拿不到完整用户栈,但可以看到哪个进程触发 tcp_sendmsg:

  1. sudo bpftrace -e 'kprobe:tcp_sendmsg { printf("pid=%-6d comm=%s\n", pid, comm); @[kstack] = count(); } interval:s:10 { exit(); }'

统计内存分配

统计调用__kmalloc的次数,并打印内核栈

  1. sudo bpftrace -e '
  2. kprobe:__kmalloc
  3. {
  4. $size = arg1;
  5. printf("__kmalloc size=%u\n", $size);
  6. @bytes[kstack] = sum($size);
  7. @cnt[kstack] = count();
  8. }
  9. interval:s:10 {
  10. print(@cnt);
  11. print(@bytes);
  12. exit();
  13. }
  14. '

统计调用__kmalloc的次数,并打印用户栈

  1. sudo bpftrace -e '
  2. kprobe:__kmalloc
  3. {
  4. $size = arg1;
  5. printf("__kmalloc size=%u\n", $size);
  6. @bytes[ustack] = sum($size);
  7. @cnt[ustack] = count();
  8. }
  9. interval:s:10 {
  10. print(@cnt);
  11. print(@bytes);
  12. exit();
  13. }
  14. '

用户栈追踪

先获取要追踪的符号在动态库的偏移:
如要追踪内存分配函数malloc(符号在/usr/lib/x86_64-linux-gnu/libc.so.6):

  1. objdump -T `realpath /usr/lib/x86_64-linux-gnu/libc.so.6` | awk -v s="malloc" '$7==s {print "0x"$1}';
  1. #!/bin/bash
  2. set -e
  3. TRACEFS=/sys/kernel/tracing
  4. LIBC=$(realpath /usr/lib/x86_64-linux-gnu/libc.so.6)
  5. # 这个是偏移
  6. OFFSET=0x000a50a0
  7. echo "LIBC=$LIBC"
  8. echo "OFFSET=$OFFSET"
  9. # ==========关键清理顺序==========
  10. # 1. 关闭全部已经enable的事件组
  11. for e in $TRACEFS/events/mygrp/*/enable; do
  12. [ -f "$e" ] && echo 0 > "$e"
  13. done
  14. # 2. 关闭全局trace
  15. echo 0 > $TRACEFS/tracing_on
  16. # 3. 清空uprobe_events,释放资源
  17. echo > $TRACEFS/uprobe_events
  18. sleep 0.1
  19. # ==============================
  20. # 拼装探针命令
  21. cmd1="p:mygrp/malloc_entry ${LIBC}:${OFFSET} %di"
  22. cmd2="r:mygrp/malloc_ret ${LIBC}:${OFFSET} %ax"
  23. echo "write cmd1: $cmd1"
  24. echo "$cmd1" > $TRACEFS/uprobe_events
  25. echo "write cmd2: $cmd2"
  26. echo "$cmd2" >> $TRACEFS/uprobe_events
  27. echo -e "\n==== uprobe_events content ===="
  28. cat $TRACEFS/uprobe_events
  29. if [ -z "$(cat $TRACEFS/uprobe_events)" ];then
  30. echo "ERROR: uprobe create failed! check dmesg"
  31. dmesg -T | grep -i uprobe
  32. exit 1
  33. fi
  34. # enable 事件
  35. echo 1 > $TRACEFS/events/mygrp/malloc_entry/enable
  36. echo 1 > $TRACEFS/events/mygrp/malloc_ret/enable
  37. echo 1 > $TRACEFS/tracing_on
  38. echo -e "\n正在采集,10秒..."
  39. sleep 10
  40. cat $TRACEFS/trace
  41. # 退出前再次完整清理
  42. for e in $TRACEFS/events/mygrp/*/enable; do
  43. [ -f "$e" ] && echo 0 > "$e"
  44. done
  45. echo 0 > $TRACEFS/tracing_on
  46. echo > $TRACEFS/uprobe_events
  47. echo "cleanup done"
更多文章可关注公众号
aisoftcloud