原创 宋宝华 Linux阅码场 2019-12-22
在《宋宝华:火焰图:全局视野的Linux性能剖析》一文中,我们主要看了on-cpu火焰图,理解了系统的CPU的走向的分析。但是,很多时候,单纯地看on-cpu的情况(什么代码在耗费CPU),并不能解决性能问题,因为有时候性能差的原因瓶颈不一定在CPU上面,而是在off-cpu的时间,比如:
- 进程进入系统调用执行io动作,io动作的延迟
- 进程等待mutex锁的时间
- 内存被交换,swap的时间
- 内存不够的时候,执行直接内存回收的时间
- 进程被抢占调度走、或者时间片用完被调度走的时间(runqueue太大)
等等等。
基本上,off-cpu的状态图如下(图片来自:
http://www.brendangregg.com/offcpuanalysis.html)
比如一个http服务器,登录的用户多了后,如果普遍觉得上网慢,瓶颈可能出现在网络慢、硬盘读取慢、mutex竞争等。这种情况下,on-cpu可能不是问题,主要的问题可能是在off-cpu的部分了。off-cpu分析,对性能问题的调优也至关重要。
下面我们写一个最简单的程序
gcc编译它,可以获得a.out。假设我们追求的性能目标是:每秒钟打印地hello world越多越好,证明这个进程越能服务更多的打印请求。当然现实生活中的程序比这个要复杂1万倍,但是这个例子不妨碍我们说明原理。
实验环境:
Ubuntu-18.10,内核版本4.18,apt install安装bpfcc-tools - tools for BPF Compiler Collection (BCC)工具包,以及git clone了
https://github.com/brendangregg/FlameGraph
下面我们采集一个它的off-cpu时间:
barry@barryUbuntu:~$ sudo offcputime-bpfcc -K -p `pgrep -nx a.out`
Tracing off-CPU time (us) of PID 5593 by kernel stack... Hit Ctrl-C to end.
按下ctrl-c停下来后,我们看到2个主要的off-cpu的栈回溯是:
一个发生在usleep()调用的hrtimer_nanosleep -> do_nanosleep系统调用;一个发生在printf()的时候,进入sys_write系统调用后,tty_write的n_tty_write等待一个mutex的代码上面。
这个时候,我们可以进一步查看Linux内核的代码
https://lxr.missinglinkelectronics.com/linux+v4.18/drivers/tty/n_tty.c#L2285
我们认为延迟应该是出现在这个地方:
....
所以,如果我们想实现每秒打印hello world尽可能多的目标,显然应该删除那个usleep,以及分析为什么这个mutex_lock要这么久,看看内核里面有无优化的空间。
如果我们想绘制系统在运行上述a.out进程的时候的off-cpu火焰图,我们可以先采集调度数据30秒,得到out.stacks:
sudo offcputime-bpfcc -df -p pgrep -nx a.out` 30 > out.stacks
接下来,我们进入clone下来的FlameGraph项目目录,用flamegraph.pl绘制火焰图:
./flamegraph.pl --color=io --title="Off-CPU Time Flame Graph" --countname=us ~/out.stacks > output.svg
用看图片的工具打开output.svg:
从图上也可以看到,off-cpu的2个主要原因一个是nanosleep,一个是write系统调用进入后n_tty_write里面要拿mutex。
点击write的路径,可以局部放大这部分栈回溯:
与我们前面的文本分析的结果是一致的。如果我们想优化性能,一个是可以消除usleep,第二个是分析为什么mutex_lock要等这么久,有什么空间可以提高。
本文是一篇关于off-cpu火焰图的入门引导性文章,如果您对此感兴趣,可以进一步参考:
http://www.brendangregg.com/offcpuanalysis.html
(完)