动态跟踪工具BCC的使用分享
动态跟踪工具BCC的使用分享前言在Linux下调试软件需要用到一些系统工具来帮助开发人员对软件进行分析调试,例如 GDB就是经常使用的一个CORE分析工具,其主要用来分析非法内存访问、堆栈跟踪、线程调试等问题,GDB一般只能用来进行用户态的调试,其它的诸如内存泄漏、 缓存分析、死锁等问题则需要借助跟踪工具进行分析调试。虽然valgrind也提供了内存泄漏的分析功能,但其使用上存在不便,valgri
动态跟踪工具BCC的使用分享
- 前言
在Linux下调试软件需要用到一些系统工具来帮助开发人员对软件进行分析调试,例如 GDB就是经常使用的一个CORE分析工具,其主要用来分析非法内存访问、堆栈跟踪、线程调试等问题,GDB一般只能用来进行用户态的调试,其它的诸如内存泄漏、 缓存分析、死锁等问题则需要借助跟踪工具进行分析调试。
虽然valgrind也提供了内存泄漏的分析功能,但其使用上存在不便,valgrind需要主动拉起进程进行跟踪调试,而对于我们服务器上运行的通用组件,如果此时观察到了可疑的内存泄漏或者死锁等问题,valgrind则显得束手无策了。
本案例汇总Linux下的跟踪调试工具,并对各种工具做个简要的分类介绍,最后分享如何使用动态追踪工具BCC来分析内存泄漏、缓存等问题。
跟踪分为动态跟踪和静态跟踪,所谓动态跟踪是利用内存中的CPU指令并在这些指令之上动态构建监测数据,动态跟踪技术把所有软件变的可以监控,而且能用在真实的生产环境中,使得从前不易观测而无法处理的问题变得可以解决,从前可以解决而难以解决的问题,现在也往往可以得以简化。
跟踪工具分为进程级别和系统级别,下面分别介绍。
-
- 系统级别
- tcpdump:网络包跟踪(用libpcap库)。
- snoop:为基于Solaris的系统打造的网络包跟踪工具。
- blktrace:块I/O跟踪(Linux)。
- iosoop:块I/O跟踪(基于DTrace)。
- execsnoop:跟踪新进程(基于DTrace)。
- dtruss:系统级别的系统调用缓冲跟踪(基于DTrace)。
- DTrace:跟踪内核的内部活动和所有资源的使用情况(不仅仅是网络和块I/O),支持静态和动态的跟踪。
- SystemTap: 跟踪内核的内部活动和所有资源的使用情况,支持静态和动态的跟踪。
- eBPF:跟踪内核的内部活动和所有资源的使用情况,支持静态和动态的跟踪。
- perf:Linux性能事件,跟踪静态和动态的探针。
DTrace、 SystemTap和BPF都是可编程环境,在它们之上可以构建系统级别的跟踪工具,上面列表中的iosoop、execsnoop和dtruss即是基于DTrace的,本文中将要分享的bcc工具是基于eBPF的。
-
- 进程级别
-
- strace:基于linux系统的系统调用跟踪。
- truss:基于Solaris系统的系统调用跟踪。
- gdb:源代码级别的调试器,广泛应用于Linux系统。
- mdb: Solaris系统的一个具有可扩展的调试器。
- 跟踪工具的使用
动态跟踪工具的能力非常强大,本文档介绍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的强大功能。
-
- 下载安装
- BCC的下载地址:GitHub - iovisor/bcc: BCC - Tools for BPF-based Linux IO analysis, networking, monitoring, and more
- 下载后将bcc解压,打开安装文档INSTALL.md,根据操作系统类型选择合适的安装方法,可以源码安装,也可以二进制安装。本文使用环境是centos,通过命令yum install bcc-tools可直接安装二进制安装包,安装后所在路径为 /usr/share/bcc/。
- 使用案例
工具安装完成后,/usr/share/bcc/tools目录下是用于系统分析的各种工具,不同工具的功能根据工具的名称即可辨识,以memleak为例,此工具用于分析进程内存泄漏,使用方法可通过memleak –h知悉,工具集如下。
Memleak –h 查询帮助信息如下。
- 内存泄漏
- 示例代码
$ 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:
- 缓存分析
缓存是现在所有高并发系统必需的核心模块,主要作用就是把经常访问的数据(也就是热点数据),提前读入到内存中。这样,下次访问时就可以直接从内存读取数据,而不需要经过硬盘,从而加快应用程序的响应速度,命中率越高,表示使用缓存带来的收益越高,应用程序的性能也就越好。通用组件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%
- 慢函数查询
执行 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 一款即可,低版本的内核更多的时候需要同时使用多个工具来互相辅助追踪分析。
- 附录
argdist.py
统计指定函数的调用次数、调用所带的参数等等信息,打印直方图bashreadline.py
获取正在运行的bash命令所带的参数biolatency.py
统计block IO请求的耗时,打印直方图biosnoop.py
打印每次block IO请求的详细信息biotop.py
打印每个进程的block IO详情bitesize.py
分别打印每个进程的IO请求直方图bpflist.py
打印当前系统正在运行哪些BPF程序btrfsslower.py
打印btrfs 慢于某一阈值的 read/write/open/fsync 操作的数量cachestat.py
打印Linux页缓存 hit/miss状况cachetop.py
分别打印每个进程的页缓存状况capable.py
跟踪到内核函数cap_capable()
(安全检查相关)的调用,打印详情ujobnew.sh
跟踪内存对象分配事件,打印统计,对研究GC很有帮助cpudist.py
统计task on-CPU time,即任务在被调度走之前在CPU上执行的时间cpuunclaimed.py
跟踪CPU run queues length,打印idle CPU (yet unclaimed by waiting threads) 百分比criticalstat.py
跟踪涉及内核原子操作的事件,打印调用栈dbslower.py
跟踪 MySQL 或 PostgreSQL 的慢查询dbstat.py
打印MySQL或PostgreSQL的查询耗时直方图dcsnoop.py
跟踪目录缓存(dcache)查询请求dcstat.py
打印目录缓存(dcache)统计信息deadlock.py
检查运行中的进行可能存在的死锁execsnoop.py
跟踪新进程创建事件ext4dist.py
跟踪ext4文件系统的 read/write/open/fsyncs 请求,打印耗时直方图ext4slower.py
跟踪ext4慢请求filelife.py
跟踪短寿命文件(跟踪期间创建然后删除)fileslower.py
跟踪较慢的同步读写请求filetop.py
打印文件读写排行榜(top),以及进程详细信息funccount.py
跟踪指定函数的调用次数,支持正则表达式funclatency.py
跟踪指定函数,打印耗时funcslower.py
跟踪唤醒时间(function invocations)较慢的内核和用户函数gethostlatency.py
跟踪hostname查询耗时hardirqs.py
跟踪硬中断耗时inject.py
javacalls.sh
javaflow.sh
javagc.sh
javaobjnew.sh
javastat.sh
javathreads.sh
killsnoop.py
跟踪kill()
系统调用发出的信号llcstat.py
跟踪缓存引用和缓存命中率事件mdflush.py
跟踪md driver level的flush事件memleak.py
检查内存泄漏mountsnoop.py
跟踪mount和unmount系统调用mysqld_qslower.py
跟踪MySQL慢查询nfsdist.py
打印NFS read/write/open/getattr 耗时直方图nfsslower.py
跟踪NFS read/write/open/getattr慢操作nodegc.sh
跟踪高级语言(Java/Python/Ruby/Node/)的GC事件offcputime.py
跟踪被阻塞的进程,打印调用栈、阻塞耗时等信息offwaketime.py
跟踪被阻塞且off-CPU的进程oomkill.py
跟踪Linux out-of-memory (OOM) killeropensnoop.py
跟踪open()
系统调用perlcalls.sh
perlstat.sh
phpcalls.sh
phpflow.sh
phpstat.sh
pidpersec.py
跟踪每分钟新创建的进程数量(通过跟踪fork()
)profile.py
CPU profilerpythoncalls.sh
pythoonflow.sh
pythongc.sh
pythonstat.sh
reset-trace.sh
rubycalls.sh
rubygc.sh
rubyobjnew.sh
runqlat.py
调度器run queue latency直方图,每个task等待CPU的时间runqlen.py
调度器run queue使用百分比runqslower.py
跟踪调度延迟很大的进程(等待被执行但是没有空闲CPU)shmsnoop.py
跟踪shm*()
系统调用slabratetop.py
跟踪内核内存分配缓存(SLAB或SLUB)sofdsnoop.py
跟踪unix socket 文件描述符(FD)softirqs.py
跟踪软中断solisten.py
跟踪内核TCP listen事件sslsniff.py
跟踪OpenSSL/GnuTLS/NSS的 write/send和read/recv函数stackcount.py
跟踪函数和调用栈statsnoop.py
跟踪stat()
系统调用syncsnoop.py
跟踪sync()
系统调用syscount.py
跟踪各系统调用次数tclcalls.sh
tclflow.sh
tclobjnew.sh
tclstat.sh
tcpaccept.py
跟踪内核接受TCP连接的事件tcpconnect.py
跟踪内核建立TCP连接的事件tcpconnlat.py
跟踪建立TCP连接比较慢的事件,打印进程、IP、端口等详细信息tcpdrop.py
跟踪内核drop TCP 包或片(segment)的事件tcplife.py
打印跟踪期间建立和关闭的的TCP sessiontcpretrans.py
跟踪TCP重传tcpstates.py
跟踪TCP状态变化,包括每个状态的时长tcpsubnet.py
根据destination打印每个subnet的throughputtcptop.py
根据host和port打印throughputtcptracer.py
跟踪进行TCP connection操作的内核函数tplist.py
打印内核tracepoint和USDT probes点,已经它们的参数trace.py
跟踪指定的函数,并按照指定的格式打印函数当时的参数值ttysnoop.py
跟踪指定的tty或pts设备,将其打印复制一份输出vfscount.py
统计VFS(虚拟文件系统)调用vfsstat.py
跟踪一些重要的VFS函数,打印统计信息wakeuptime.py
打印进程被唤醒的延迟及其调用栈xfsdist.py
打印XFS read/write/open/fsync 耗时直方图xfsslower.py
打印XFS慢请求zfsdist.py
打印ZFS read/write/open/fsync 耗时直方图zfsslower.py
打印ZFS慢请求
更多推荐
所有评论(0)