动态跟踪工具BCC的使用分享

  1. 前言

在Linux下调试软件需要用到一些系统工具来帮助开发人员对软件进行分析调试,例如 GDB就是经常使用的一个CORE分析工具,其主要用来分析非法内存访问、堆栈跟踪、线程调试等问题,GDB一般只能用来进行用户态的调试,其它的诸如内存泄漏、 缓存分析、死锁等问题则需要借助跟踪工具进行分析调试。

虽然valgrind也提供了内存泄漏的分析功能,但其使用上存在不便,valgrind需要主动拉起进程进行跟踪调试,而对于我们服务器上运行的通用组件,如果此时观察到了可疑的内存泄漏或者死锁等问题,valgrind则显得束手无策了。

本案例汇总Linux下的跟踪调试工具,并对各种工具做个简要的分类介绍,最后分享如何使用动态追踪工具BCC来分析内存泄漏、缓存等问题。

  1. 跟踪工具

跟踪分为动态跟踪和静态跟踪,所谓动态跟踪是利用内存中的CPU指令并在这些指令之上动态构建监测数据,动态跟踪技术把所有软件变的可以监控,而且能用在真实的生产环境中,使得从前不易观测而无法处理的问题变得可以解决,从前可以解决而难以解决的问题,现在也往往可以得以简化。

跟踪工具分为进程级别和系统级别,下面分别介绍。

    1. 系统级别
  1. tcpdump:网络包跟踪(用libpcap库)。
  2. snoop:为基于Solaris的系统打造的网络包跟踪工具。
  3. blktrace:块I/O跟踪(Linux)。
  4. iosoop:块I/O跟踪(基于DTrace)。
  5. execsnoop:跟踪新进程(基于DTrace)。
  6. dtruss:系统级别的系统调用缓冲跟踪(基于DTrace)。
  7. DTrace:跟踪内核的内部活动和所有资源的使用情况(不仅仅是网络和块I/O),支持静态和动态的跟踪。
  8. SystemTap: 跟踪内核的内部活动和所有资源的使用情况,支持静态和动态的跟踪。
  9. eBPF:跟踪内核的内部活动和所有资源的使用情况,支持静态和动态的跟踪。
  10. perf:Linux性能事件,跟踪静态和动态的探针。

DTrace、 SystemTap和BPF都是可编程环境,在它们之上可以构建系统级别的跟踪工具,上面列表中的iosoop、execsnoop和dtruss即是基于DTrace的,本文中将要分享的bcc工具是基于eBPF的。

    1. 进程级别
    1. strace:基于linux系统的系统调用跟踪。
    2. truss:基于Solaris系统的系统调用跟踪。
    3. gdb:源代码级别的调试器,广泛应用于Linux系统。
    4. mdb: Solaris系统的一个具有可扩展的调试器。
  1. 跟踪工具的使用

动态跟踪工具的能力非常强大,本文档介绍eBPF的使用,其它工具DTrace、SystemTap的使用参考对应工具使用文档,也可参考由Brendan Gregg编写的《性能之巅》这本书。

eBPF(extended Berkeley Packet Filter)相当于一个内核虚拟机,以JIT(Just In Time)的方式运行事件相关的追踪程序,同时eBPF也支持对ftrace,perf_events等机制的处理。另外eBPF在传统的包过滤器进行很大的变革,其在内核追踪,应用性能追踪,流控等方面都做了很大的改变,不过在接口的易用性方面还有待提高。

第三方的 bpftrace 实现了对 eBPF 的封装,支持 python,lua 等接口,用起来方便了很多,还有其提供的 bcc 工具集在 > Linux 4.1+ 的系统中被广泛应用。由于bcc工具集基于eBPF提供了各种系统分析工具,在服务器环境上直接下载安装bcc即可体验eBPF的强大功能。

    1. 下载安装
  1. BCC的下载地址:GitHub - iovisor/bcc: BCC - Tools for BPF-based Linux IO analysis, networking, monitoring, and more
  2. 下载后将bcc解压,打开安装文档INSTALL.md,根据操作系统类型选择合适的安装方法,可以源码安装,也可以二进制安装。本文使用环境是centos,通过命令yum install bcc-tools可直接安装二进制安装包,安装后所在路径为 /usr/share/bcc/。
    1. 使用案例

工具安装完成后,/usr/share/bcc/tools目录下是用于系统分析的各种工具,不同工具的功能根据工具的名称即可辨识,以memleak为例,此工具用于分析进程内存泄漏,使用方法可通过memleak –h知悉,工具集如下。

 

Memleak –h 查询帮助信息如下。

 

  1. 内存泄漏

  • 示例代码

$ docker exec app cat /app.c

...

long long *fibonacci(long long *n0, long long *n1)

{

   //分配1024个长整数空间方便观测内存的变化情况

    long long *v = (long long *) calloc(1024, sizeof(long long));

    *v = *n0 + *n1;

    return v;

}

void *child(void *arg)

{

    long long n0 = 0;

    long long n1 = 1;

    long long *v = NULL;

    for (int n = 2; n > 0; n++) {

        v = fibonacci(&n0, &n1);

        n0 = n1;

        n1 = *v;

        printf("%dth => %lld\n", n, *v);

        sleep(1);

    }

}

...

  • 针对可疑的内存泄漏进程,只需执行memleak –p  进程号。

$ docker cp app:/app /app

$ /usr/share/bcc/tools/memleak -p $(pidof app) -a

Attaching to pid 12512, Ctrl+C to quit.

[03:00:41] Top 10 stacks with outstanding allocations:

    addr = 7f8f70863220 size = 8192

    addr = 7f8f70861210 size = 8192

    addr = 7f8f7085b1e0 size = 8192

    addr = 7f8f7085f200 size = 8192

    addr = 7f8f7085d1f0 size = 8192

    40960 bytes in 5 allocations from stack

        fibonacci+0x1f [app]

        child+0x4f [app]

start_thread+0xdb [libpthread-2.27.so]

上面memleak输出可以看出,应用在不停的分配内存,并且这些分配的地址没有被回收,fibonacci函数内部申请的内存没有被释放,修改代码释放内存重新执行memleak跟踪。

  • 重新执行 memleak工具检查内存泄漏情况

$ /usr/share/bcc/tools/memleak -a -p $(pidof app)

Attaching to pid 18808, Ctrl+C to quit.

[10:23:18] Top 10 stacks with outstanding allocations:

[10:23:23] Top 10 stacks with outstanding allocations:

  1. 缓存分析

缓存是现在所有高并发系统必需的核心模块,主要作用就是把经常访问的数据(也就是热点数据),提前读入到内存中。这样,下次访问时就可以直接从内存读取数据,而不需要经过硬盘,从而加快应用程序的响应速度,命中率越高,表示使用缓存带来的收益越高,应用程序的性能也就越好。通用组件redis可通过tools目录下的cachestat和cachetop命令跟踪内核中管理的缓存,并输出缓存的使用和命中情况。

  • $ cachestat 1 3

   TOTAL   MISSES     HITS  DIRTIES   BUFFERS_MB  CACHED_MB

       2        0        2        1           17        279

       2        0        2        1           17        279

       2        0        2        1           17        279

TOTAL ,表示总的 I/O 次数;

MISSES ,表示缓存未命中的次数;

HITS ,表示缓存命中的次数;

DIRTIES, 表示新增到缓存中的脏页数;

BUFFERS_MB 表示 Buffers 的大小,以 MB 为单位;

CACHED_MB 表示 Cache 的大小,以 MB 为单位。

  • $ cachetop

11:58:50 Buffers MB: 258 / Cached MB: 347 / Sort: HITS / Order: ascending

PID      UID      CMD      HITS   MISSES   DIRTIES   READ_HIT%   WRITE_HIT%

13029    root     python   1      0        0        100.0%       0.0%

  1. 慢函数查询

执行 funcslower命令捕获内核收包函数 net_rx_action 耗时大于 100us 的情况,并打印内核调用栈(注意,视机器的网络和工作负载状况,这里的打印可能没有,也可能会非常多。建议先设置一个比较大的阈值(例如 -u 200),如果没有输出 ,再将阈值逐步改小)。

root@# funcslower -u 100 -f -K net_rx_action

Tracing function calls slower than 100 us... Ctrl+C to quit.

COMM           PID    LAT(us)             RVAL FUNC

swapper/1      0       158.21                0 net_rx_action

    kretprobe_trampoline

    irq_exit

    do_IRQ

    ret_from_intr

    native_safe_halt

    __cpuidle_text_start

    arch_cpu_idle

    default_idle_call

    do_idle

    cpu_startup_entry

    start_secondary

verify_cpu

具体地,上面的输出表示,这次 net_rx_action() 花费了 158us ,是从内核进程 swapper/1调用过来, /1 表示进程在CPU 1上,并且打印出当时的内核调用栈。在进行系统网络性能优化分析时该工具非常有用

其它的死锁分析命令deadlock,短时进程分析命令execsnoop等也是常用的系统分析工具。可以说 eBPF 能够监控所有想监控的,在 Linux 4.1+ 系统中,动态追踪工具使用 eBPF 一款即可,低版本的内核更多的时候需要同时使用多个工具来互相辅助追踪分析。

  1. 附录
  2. argdist.py 统计指定函数的调用次数、调用所带的参数等等信息,打印直方图
  3. bashreadline.py 获取正在运行的bash命令所带的参数
  4. biolatency.py 统计block IO请求的耗时,打印直方图
  5. biosnoop.py 打印每次block IO请求的详细信息
  6. biotop.py 打印每个进程的block IO详情
  7. bitesize.py 分别打印每个进程的IO请求直方图
  8. bpflist.py 打印当前系统正在运行哪些BPF程序
  9. btrfsslower.py 打印btrfs 慢于某一阈值的 read/write/open/fsync 操作的数量
  10. cachestat.py 打印Linux页缓存 hit/miss状况
  11. cachetop.py 分别打印每个进程的页缓存状况
  12. capable.py 跟踪到内核函数 cap_capable() (安全检查相关)的调用,打印详情
  13. ujobnew.sh 跟踪内存对象分配事件,打印统计,对研究GC很有帮助
  14. cpudist.py 统计task on-CPU time,即任务在被调度走之前在CPU上执行的时间
  15. cpuunclaimed.py 跟踪CPU run queues length,打印idle CPU (yet unclaimed by waiting threads) 百分比
  16. criticalstat.py 跟踪涉及内核原子操作的事件,打印调用栈
  17. dbslower.py 跟踪 MySQL  PostgreSQL 的慢查询
  18. dbstat.py 打印MySQLPostgreSQL的查询耗时直方图
  19. dcsnoop.py 跟踪目录缓存(dcache)查询请求
  20. dcstat.py 打印目录缓存(dcache)统计信息
  21. deadlock.py 检查运行中的进行可能存在的死锁
  22. execsnoop.py 跟踪新进程创建事件
  23. ext4dist.py 跟踪ext4文件系统的 read/write/open/fsyncs 请求,打印耗时直方图
  24. ext4slower.py 跟踪ext4慢请求
  25. filelife.py 跟踪短寿命文件(跟踪期间创建然后删除)
  26. fileslower.py 跟踪较慢的同步读写请求
  27. filetop.py 打印文件读写排行榜(top),以及进程详细信息
  28. funccount.py 跟踪指定函数的调用次数,支持正则表达式
  29. funclatency.py 跟踪指定函数,打印耗时
  30. funcslower.py 跟踪唤醒时间(function invocations)较慢的内核和用户函数
  31. gethostlatency.py 跟踪hostname查询耗时
  32. hardirqs.py 跟踪硬中断耗时
  33. inject.py
  34. javacalls.sh
  35. javaflow.sh
  36. javagc.sh
  37. javaobjnew.sh
  38. javastat.sh
  39. javathreads.sh
  40. killsnoop.py 跟踪 kill() 系统调用发出的信号
  41. llcstat.py 跟踪缓存引用和缓存命中率事件
  42. mdflush.py 跟踪md driver levelflush事件
  43. memleak.py 检查内存泄漏
  44. mountsnoop.py 跟踪mountunmount系统调用
  45. mysqld_qslower.py 跟踪MySQL慢查询
  46. nfsdist.py 打印NFS read/write/open/getattr 耗时直方图
  47. nfsslower.py 跟踪NFS read/write/open/getattr慢操作
  48. nodegc.sh 跟踪高级语言(Java/Python/Ruby/Node/)的GC事件
  49. offcputime.py 跟踪被阻塞的进程,打印调用栈、阻塞耗时等信息
  50. offwaketime.py 跟踪被阻塞且off-CPU的进程
  51. oomkill.py 跟踪Linux out-of-memory (OOM) killer
  52. opensnoop.py 跟踪 open() 系统调用
  53. perlcalls.sh
  54. perlstat.sh
  55. phpcalls.sh
  56. phpflow.sh
  57. phpstat.sh
  58. pidpersec.py 跟踪每分钟新创建的进程数量(通过跟踪 fork() 
  59. profile.py CPU profiler
  60. pythoncalls.sh
  61. pythoonflow.sh
  62. pythongc.sh
  63. pythonstat.sh
  64. reset-trace.sh
  65. rubycalls.sh
  66. rubygc.sh
  67. rubyobjnew.sh
  68. runqlat.py 调度器run queue latency直方图,每个task等待CPU的时间
  69. runqlen.py 调度器run queue使用百分比
  70. runqslower.py 跟踪调度延迟很大的进程(等待被执行但是没有空闲CPU
  71. shmsnoop.py 跟踪 shm*() 系统调用
  72. slabratetop.py 跟踪内核内存分配缓存(SLABSLUB
  73. sofdsnoop.py 跟踪unix socket 文件描述符(FD
  74. softirqs.py 跟踪软中断
  75. solisten.py 跟踪内核TCP listen事件
  76. sslsniff.py 跟踪OpenSSL/GnuTLS/NSS write/sendread/recv函数
  77. stackcount.py 跟踪函数和调用栈
  78. statsnoop.py 跟踪 stat() 系统调用
  79. syncsnoop.py 跟踪 sync() 系统调用
  80. syscount.py 跟踪各系统调用次数
  81. tclcalls.sh
  82. tclflow.sh
  83. tclobjnew.sh
  84. tclstat.sh
  85. tcpaccept.py 跟踪内核接受TCP连接的事件
  86. tcpconnect.py 跟踪内核建立TCP连接的事件
  87. tcpconnlat.py 跟踪建立TCP连接比较慢的事件,打印进程、IP、端口等详细信息
  88. tcpdrop.py 跟踪内核drop TCP 包或片(segment)的事件
  89. tcplife.py 打印跟踪期间建立和关闭的的TCP session
  90. tcpretrans.py 跟踪TCP重传
  91. tcpstates.py 跟踪TCP状态变化,包括每个状态的时长
  92. tcpsubnet.py 根据destination打印每个subnetthroughput
  93. tcptop.py 根据hostport打印throughput
  94. tcptracer.py 跟踪进行TCP connection操作的内核函数
  95. tplist.py 打印内核tracepointUSDT probes点,已经它们的参数
  96. trace.py 跟踪指定的函数,并按照指定的格式打印函数当时的参数值
  97. ttysnoop.py 跟踪指定的ttypts设备,将其打印复制一份输出
  98. vfscount.py 统计VFS(虚拟文件系统)调用
  99. vfsstat.py 跟踪一些重要的VFS函数,打印统计信息
  100. wakeuptime.py 打印进程被唤醒的延迟及其调用栈
  101. xfsdist.py 打印XFS read/write/open/fsync 耗时直方图
  102. xfsslower.py 打印XFS慢请求
  103. zfsdist.py 打印ZFS read/write/open/fsync 耗时直方图
  104. zfsslower.py 打印ZFS慢请求
Logo

更多推荐