perf 中的 usdt

在学习 ebpf 的 usdt 探针的时候遇到了问题,看了一些网上的链接,在 gregg 大神的博客中找到了 perf 中与 usdt 相关的内容,就研究了一下。

perf 程序源码在内核源码树中的 ./tools/perf/ 目录中,进入到这个目录执行 make 进行编译即可。

编译完成后我执行 make install 后直接执行发现会报 perf_5.0 找不到的错误,make install V=1 发现 perf 被安装到了我的家目录下的 bin 目录中,先配置下环境变量凑合用吧。

上手 perf 的 usdt 追踪功能

如下命令从 Static User Tracing 中摘录。

# perf buildid-cache --add `which node`
# perf list | grep sdt_nodesdt_node:gc__done                                  [SDT event]sdt_node:gc__start                                 [SDT event]sdt_node:http__client__request                     [SDT event]sdt_node:http__client__response                    [SDT event]sdt_node:http__server__request                     [SDT event]sdt_node:http__server__response                    [SDT event]sdt_node:net__server__connection                   [SDT event]sdt_node:net__stream__end                          [SDT event]
# perf record -e sdt_node:http__server__request -a
^C[ perf record: Woken up 1 times to write data ]
[ perf record: Captured and wrote 0.446 MB perf.data (3 samples) ]
# perf scriptnode  7646 [002]   361.012364: sdt_node:http__server__request: (dc2e69)node  7646 [002]   361.204718: sdt_node:http__server__request: (dc2e69)node  7646 [002]   361.363043: sdt_node:http__server__request: (dc2e69)

它使用了 node 程序的探测点,我想偷懒就用了一个简单的 hello_usdt 程序替换了 node 命令,程序代码如下:

#include <sys/sdt.h>int main(int argc, char const *argv[]) {DTRACE_PROBE(hello_usdt, probe-main);return 0;
}

perf list 报了如下错误:

Semantic error :sdt_hello-usdt is bad for event name -it must follow C symbol-naming rule.

看上去应该是事件的名称不符合 c 的符号命令规则,于是将代码修改为如下内容:

#include <sys/sdt.h>int main(int argc, char const *argv[]) {DTRACE_PROBE(hello_usdt, probe_main);return 0;
}

修改完成后重新编译,重新执行上面的步骤,perf list 这次能够看到如下内容:

  sdt_hello_usdt:probe_main                          [SDT event]

执行 perf record 的时候报了如下错误:

[root@debian-10:10:30:33] usdt # perf record -e sdt_hello_usdt:probe_main -a
event syntax error: 'sdt_hello_usdt:probe_main'\___ unknown tracepointError:  File /sys/kernel/debug/tracing/events/sdt_hello_usdt/probe_main not found.
Hint:   SDT event cannot be directly recorded on.Please first use 'perf probe sdt_hello_usdt:probe_main' before recording it.Run 'perf list' for a list of valid eventsUsage: perf record [<options>] [<command>]or: perf record [<options>] -- <command> [<options>]-e, --event <event>   event selector. use 'perf list' to list available events

首先检查 tracing/events 子目录中确实不存在 sdt_hello_usdt,看后面的提示需要先执行一下 perf probe,就操作了一下。

操作后再次执行 perl list 并搜索 sdt 得到了如下信息:

  sdt_hello_usdt:probe_main                          [Tracepoint event]sdt_hello_usdt:probe_main                          [SDT event]

然后重新执行 perf record,这次没有报错,运行了这个 perf 命令后,在其它终端中执行一次 hello_usdt 程序,然后在 perf 所在的终端按 Ctrl+C 退出 perf。

操作记录如下:

[root@debian-10:10:37:31] usdt # perf record -e sdt_hello_usdt:probe_main -a
^C[ perf record: Woken up 1 times to write data ]
[ perf record: Captured and wrote 1.137 MB perf.data (1 samples) ]

最后的 1 samples 表示捕获到了一次事件,执行 perf script 来查看记录的内容,操作记录如下:

[root@debian-10:10:38:10] usdt # perf scripthello_usdt  5457 [005] 25700.337067: sdt_hello_usdt:probe_main: (563b1d927130)

能够看到成功捕获到了!但是 USDT 真正生效了吗?

检查 USDT 是否生效

为了验证这点,我将上面的 hello_usdt 的代码修改为如下内容:

#include <sys/sdt.h>
#include <unistd.h>
#include <stdio.h>int add(int a, int b) {DTRACE_PROBE(hello_usdt, probe_main);return a + b;
}
int main(int argc, char const *argv[]) {while (1) {printf("result is %d\n", add(1, 2));sleep(1);}return 0;
}

添加跟踪点的过程与上面一致,这里我在 perf record 程序执行后,执行 hello_usdt 程序,在终端中打印了 result is 3 后我使用 gdb -p 跟踪 hello_usdt 程序,反汇编 add 函数,得到了如下信息:

(gdb) disass add
Dump of assembler code for function add:0x0000565285243145 <+0>: push   %rbp0x0000565285243146 <+1>:  mov    %rsp,%rbp0x0000565285243149 <+4>: mov    %edi,-0x4(%rbp)0x000056528524314c <+7>:   mov    %esi,-0x8(%rbp)0x000056528524314f <+10>:  int3   0x0000565285243150 <+11>: mov    -0x4(%rbp),%edx0x0000565285243153 <+14>:  mov    -0x8(%rbp),%eax0x0000565285243156 <+17>:  add    %edx,%eax0x0000565285243158 <+19>:    pop    %rbp0x0000565285243159 <+20>: retq
End of assembler dump.

可以看到 10 偏移量处的指令被替换为了 int3,这表示 usdt 生效。

perf 的其它功能

1. 追踪内核函数中某变量的值

[root@debian-10:23:36:18] perf # ./perf probe -a 'dev_ethtool ifr->ifr_ifrn.ifrn_name[0]'
Added new event:probe:dev_ethtool    (on dev_ethtool with ifrn_name=ifr->ifr_ifrn.ifrn_name[0])You can now use it in all perf tools, such as:perf record -e probe:dev_ethtool -aR sleep 1[root@debian-10:23:36:31] perf # ./perf record -e probe:dev_ethtool -aR sleep 100
^C[ perf record: Woken up 1 times to write data ]
[ perf record: Captured and wrote 1.041 MB perf.data (25 samples) ][root@debian-10:23:36:48] perf # ./perf report -n
# To display the perf.data header info, please use --header/--header-only options.
#
#
# Total Lost Samples: 0
#
# Samples: 25  of event 'probe:dev_ethtool'
# Event count (approx.): 25
#
# Overhead       Samples  Trace output
# ........  ............  ................................
#100.00%            25  (ffffffff9f5ed470) ifrn_name=119

上述示例打印出了 dev_ethtool 函数中 ifr->ifr_ifrn.ifrn_name[0] 的值,这个数字为 119,表示的是小写字母 w,而我执行的命令就是 ethtool wlp0s20f3,ifrn_name 中存储的就是 wlp0s20f3 这个字符串

2. perf 查看内核堆栈

这个功能可以用于研究内核,查看内核中某个函数的调用关系,示例如下:

[root@debian-10:23:41:21] perf # ./perf record -e probe:dev_ethtool -a -g -- sleep 100
^C[ perf record: Woken up 1 times to write data ]
[ perf record: Captured and wrote 1.094 MB perf.data (5 samples) ]
[root@debian-10:23:41:56] perf # ./perf report --stdio
# To display the perf.data header info, please use --header/--header-only options.
#
#
# Total Lost Samples: 0
#
# Samples: 5  of event 'probe:dev_ethtool'
# Event count (approx.): 5
#
# Children      Self  Trace output
# ........  ........  ..................
#100.00%   100.00%  (ffffffff9f5ed470)|---0x3d4c4c45485300330x3ioctlentry_SYSCALL_64do_syscall_64__x64_sys_ioctlksys_ioctldo_vfs_ioctlsock_ioctlsock_do_ioctldev_ioctldev_ethtool
..........

其实这个示例与第一个示例操作过程大致相同,只是在 perf record 的时候添加了一个 -g 参数,执行 perf record -h 得到了如下描述信息:

    -g                    enables call-graph recording

我觉得这个功能挺实用的,我以前用过 dump_stack 来打印内核堆栈信息,这种方式需要修改源码,有了 perf 的这个功能几行命令就可以看函数调用信息了!

3. perf vs strace

gregg 的博客中提到了 perf 与 strace 的对比,这个内容我有点兴趣。strace 命令的原理我是比较清楚的,但是 perf 的原理我还一知半解。不过对于两者对程序性能的影响这个问题倒是已经有些研究了。

strace 的原理在 《LINUX 内核源代码情景分析》中有非常详细的描述,它与 gdb 类似,都是使用了 ptrace 系统调用来完成工作。

strace 跟踪的子进程会在系统调用执行前后都停下来,并向 strace 进程发送信号,然后 strace 执行 ptrace 获取相关的信息,再发信号到子进程恢复其执行,这意味着 strace 将会对程序的性能带来直接的影响。

下面的测试方法来自于 Linux perf Examples 这篇博客。

正常状态下的性能测试

[root@debian-10:23:51:36] perf # dd if=/dev/zero of=/dev/null bs=512 count=10000k
记录了10240000+0 的读入
记录了10240000+0 的写出
5242880000 bytes (5.2 GB, 4.9 GiB) copied, 3.77995 s, 1.4 GB/s

总共花了不到 4s 的时间,速度在 1.4GB/s

使用 strace 跟踪下的性能测试

strace -c 选项告诉 strace 在跟踪系统调用的同时统计不同类型系统调用的执行次数。

[root@debian-10:23:47:54] perf #  strace -c dd if=/dev/zero of=/dev/null bs=512 count=10000k
^Cstrace: Process 16264 detached
% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------50.90   13.133820           2   5162749           read49.10   12.671348           2   5162748           write0.00    0.000000           0         6           close0.00    0.000000           0         4           fstat0.00    0.000000           0         1           lseek0.00    0.000000           0         9           mmap0.00    0.000000           0         4           mprotect0.00    0.000000           0         2           munmap0.00    0.000000           0         3           brk0.00    0.000000           0         3           rt_sigaction0.00    0.000000           0         1           access0.00    0.000000           0         2           dup20.00    0.000000           0         1           execve0.00    0.000000           0         1           arch_prctl0.00    0.000000           0         6           openat
------ ----------- ----------- --------- --------- ----------------
100.00   25.805168              10325540           total
记录了5162748+0 的读入
记录了5162748+0 的写出
2643326976 bytes (2.6 GB, 2.5 GiB) copied, 110.966 s, 23.8 MB/s

上述命令已经执行了快 2 分钟还没有结束,因此我就按了 Ctrl + C 终止了。可以看到它的性能急剧下降,拷贝速度只有 23.8 MB/s。

使用 perf 跟踪下的性能测试

[root@debian-10:23:51:18] perf # ./perf stat -e 'syscalls:sys_enter_*' dd if=/dev/zero of=/dev/null bs=512 count=10000k
记录了10240000+0 的读入
记录了10240000+0 的写出
5242880000 bytes (5.2 GB, 4.9 GiB) copied, 4.62303 s, 1.1 GB/sPerformance counter stats for 'dd if=/dev/zero of=/dev/null bs=512 count=10000k':0      syscalls:sys_enter_socket                                   0      syscalls:sys_enter_socketpair                                   0      syscalls:sys_enter_bind                                     0      syscalls:sys_enter_listen                                   ..........

可以看到它花了快 5s 的时间就拷贝完了,速度在 1.1 GB/s。

性能损耗总结

使用 strace 跟踪比正常情况慢了 60.2 倍左右,使用 perf 跟踪比正常情况慢了 1.2 倍左右。

当系统调用不太频繁时 strace 与 perf 跟踪程序对程序性能的影响不会这样大,系统调用越频繁差距将会拉的越大。

这里性能测试的结果反映出了 perf 与 strace 执行过程的不同。

strace 会导致子进程在执行每一个系统调用的前后都停下来,并且 strace 自身调用 ptrace 获取信息也有很大的损耗,而 perf 使用了内核中的 tracepoint 来跟踪系统调用,其信息的采集完全在内核中完成,只在最后完成的时候才会输出到用户态中,对程序性能的影响相较 strace 要低很多!

perf 支持多少 tracepoint 点呢

执行如下程序统计我使用的 5.0 内核中 tracepoing 的数目,得到了如下信息:

[root@debian-10:23:59:51] perf # ./perf list | awk -F: '/Tracepoint event/ { lib[$1]++ } END {> for (l in lib) { printf "  %-16.16s %d\n", l, lib[l] } }' | sort | columnalarmtimer     4       fib            1        intel-sst      10       mmc            2        probe          3        sdt_hello_usdt 1        udp            1asoc           13       fib6           1        iommu          7        module         5        probe_hello_us 1        signal         2        v4l2           6block          18       filelock       11       irq            5        mpx            5        probe_libc     1        skb            3        vb2            4bridge         4        filemap        4        irq_matrix     12       msr            3        qdisc          1        smbus          4        vmscan         16btrfs          65      fs_dax         14       irq_vectors    34       napi           1        random         15       sock           3        vsyscall       1cfg80211       164      ftrace         2        jbd2           16       net            17       ras            6        spi            7        wbt            4cgroup         9        gpio           2        kmem           12       nfsd           23       raw_syscalls   2        sunrpc         36       workqueue      4clk            16       hda            5        kvm            70       nmi            1        rcu            1        swiotlb        1        writeback      29compaction     14      hda_controller 6        kvmmmu         14       nvme           4        regmap         15       syscalls       640      x86_fpu        12cpuhp          3       hda_intel      4        libata         6        oom            8        regulator      7        task           2        xdp            8devlink        1        huge_memory    4        mac80211       123      page_isolation 1        rpm            4        tcp            7        xen            27dma_fence      7       hwmon          3        mce            1        pagemap        2        rseq           2        thermal        5        xhci-hcd       48drm            3       i2c            4        mdio           1        percpu         5        rtc            12       timer          13exceptions     2       i915           36       mei            3        power          23       sched          24       tlb            1ext4           105      initcall       3        migrate        1        printk         1        scsi           5        ucsi           7

可以看到它基本上覆盖了内核代码中的主要子模块,支持的系统调用的 tracepoint 的数目是 320 个左右(一个系统调用有 enter 与 exit 两个,不确定是否有不成对存在的项目)。

总结

perf 的功能比我们所了解的还要多出许多,很多功能是非常实用滴,下一次需要某些功能时就执行下 perf xx -h 命令吧!

perf 程序追踪 USDT 用户态程序静态跟踪点相关推荐

  1. LibcarePlus用户态程序热补丁

    LibcarePlus https://gitee.com/openeuler/docs/edit/stable2-20.03_LTS_SP1/docs/zh/docs/Virtualization/ ...

  2. windows用户态程序的Dump

    熟悉Linux的开发人员都知道,在Linux下开发程序,如果程序崩溃了,可以通过配置Core Dump,来让程序崩溃的瞬间产生一个Dump文件,然后通过dump文件来调试程序为什么崩溃.但是windo ...

  3. USDT用户态静态跟踪介绍

    一 功能 提供用户空间版本的跟踪点支持 二 添加USDT探针 (1)通过systemtap-sdt-dev包提供的头文件和工具,这个应该是用在发行版linux上的 (2)使用Facebook的Foll ...

  4. 用户态程序调用系统态程序-快速系统调用

    在调试程序中,经常发现程序最后会调用到系统态的程序.这个过程是怎样的?用户空间的程序怎样进行系统调用,在此过程中是怎样进入和退出内核的. 根据运行状态和执行代码所在的内存空间的不同,CPU既可以运行于 ...

  5. 用windbg内核模式调试用户态程序

    使用内核调试会话也可以执行一些用户态调试任务,比如向位于用户态的模块设置断点.但这样做与使用用户态调试器有什么不同呢?我们就以向NTDLL.dll模块的ZwTerminateProcess函数(Stu ...

  6. 使用Windbg内核调试连接调试用户态程序的方法

    1. 中断目标计算机: 2. kd>!process 0 0 3. 找到要调试的程序对应进程: 4. kd>.process /i TARGETPROCESSADDRESS 5. kd&g ...

  7. 用户态程序阻塞原因_进程阻塞 操作系统某种情况进行进程的阻塞和唤醒操作...

    进程阻塞 1)请求系统服务.例如打印服务,进程在提出打印请求后,系统创建打印服务进程为该进程服务,而提出打印请求可能被阻塞起来等待打印的完成. 2)启动某种操作.例如,进程发出启动I/O或启动打印机的 ...

  8. linux收发包内核进程名称,Linux内核IP Queue机制的分析(一)——用户态接收数据包...

    序 笔者将会通过包括本文在内的三篇文章,对IP Queue机制从用户态的应用到内核态的模块程序设计进行分析.三篇文章的题目分别是: Linux内核IP Queue机制的分析(一)­--用户态接收数据包 ...

  9. 进程用户态和内核态及其切换过程

    1.进程的堆栈 内核在创建进程的时候,会为进程创建相应的堆栈.每个进程会有两个栈,一个用户栈,存在于用户空间,一个内核栈,存在于内核空间.当进程在用户空间运行时,CPU堆栈指针寄存器里面的内容是用户堆 ...

最新文章

  1. find命令 -- 之查找指定时间内修改过的文件
  2. javascript中组合使用构造函数模式和原型模式创建对象
  3. 95-30-020-Channel-NioSocketChannel
  4. python sys与shutil模块
  5. android虚拟摇杆
  6. wordpress字体_如何在WordPress中使用网络字体
  7. __attribute__ 关键字小结
  8. Maven整合SSM项目(七)
  9. 网易云信发送短信工具类
  10. war包里面文件的修改方式
  11. 【证明题】(一)微分中值定理
  12. 半导体的基础-三极管的工作原理,史上绝无仅有的理解方式
  13. 真空灾难:物理学最著名的未解难题之一
  14. 期刊列表及发表文章的相关事项
  15. 【正则表达式】从字符串中提取数字
  16. android 权限管理 主动防御,基于SEAndroid的敏感数据主被动防御机制分析与设计
  17. 分段函数的复合函数求解
  18. Firefox设置中键点击书签菜单不关闭
  19. html网页div框架代码,div层仿网页框架布局特效代码
  20. 技术沙龙之Ruby 命令行选项介绍

热门文章

  1. DUT——同相运算放大器
  2. 《商业模式新生代(个人篇)》书摘
  3. 山东科技大学OJ题库 1219 体重正常吗?
  4. 蒙牛为什么这么猛?---《蒙牛内幕》读书笔记
  5. H1标签有用吗?怎么使用呢
  6. iOS-UILabel多行显示文本内容、根据文本长短自动计算文本框高度
  7. 第十二届蓝桥杯省赛c++b组—杨辉三角形
  8. Excel无法输入中文
  9. 七十三、Impala的常用操作
  10. Spring 全家桶BeanDefinition资源定位解析-立哥技术