==战地分析==
性能分析常常是一种战地分析,所以,在我们可以端起咖啡慢慢想怎么进行分析之前,我们要先说说我们在战地上的套路。
战地分析是说在实用环境中发现问题,我们真正需要进行性能分析的场合,通常都没有机会让你反复运行程序,重试等等的。几千万用户,几百万在线,几百万个Socket连接,上T的数据库记录,这些场景通常你回家以后就再也没有机会建出来了。而且客户现场的运维工程师可能很Nice,但通常他们的领导都不Nice。领导说,再弄一个小时,他们再搞不定就让他们回家,我们上备份系统……你就得抱着你的便携,手忙脚乱地滚出人家的实验室。
所以现场的机会很宝贵, 针对不同的现场,你最好手上有一套脚本,上去不管三八二十四,先把什么dmesg啦,dpkg -l啦, /proc/cpu, /proc/ingterrupts, /proc/mem啦,ifconfig啦,ps -ef -L啦,/var/log啦,统统先给他扯一套出来,这样你后面怎么都好分析,现场数据对于后面的分析非常重要。你连那个系统有多少内存都不知道,你分析条毛的性能啊?
第二步就是性能了,我们通常先看top,不要用什么交互模式了,直接用top -b -n 3取一个结果出来再说,至少你可以备份。但这个方法有个缺点,它不会显示每个CPU的分布,我的方法是先用交互模式进去(直接运行top),然后按1,展开CPU,然后W,把当前配置写进去,然后再运行top -b -n 3即可。
对top有谱了,我们大概就能知道问题主要出现在哪里了,如果整个系统都闲得很,通量还是上不去,那就是在什么地方丢包或者入口通道带宽不足了了,开始找丢包点把。
如果只是时延太大,就要回去画整个包的调度流程图,看看包括那些步骤,然后用ftrace跟踪这些步骤吧。
(补充一句,在现场的话,如果要定位的是启动速度问题,读者可以考虑一下使用strace或者ltrace attach来跟踪启动效率的问题)
如果有CPU占用率很高,这时就要靠perf来画像了,先查基于时间的perf分布。看看问题,顺便最好把perf-archive打包带回去。
这样,现场的工作就差不多了,出去和开始请客户的运维人员吃饭喝酒套近乎吧。
==离线分析==
离线分析的第一件事是——点杯咖啡?
好吧,那个不重要,对我来说,最重要最重要的事情是:他妈的给老子写份文档出来!这个实在太重要的,我不知道遇到多少次,有人在现场搞不定了,找我出马,然后屁颠屁颠跑过来,说“Kenneth我给你讲讲我们的进展”……讲你老母!!!
交分析报告!
交分析报告!!
交分析报告!!!
他么老子不是你秘书好不好。整个性能分析的工作,就是建立模型,猜测瓶颈,和数据对照,再采样,再分析瓶颈,修正设计,再采样……这样的一个个循环。不写文档不断整合我们看到的现象,整个分析就像建在沙子上一样。你来给我讲讲?讲完我给你写报告是吧?
所以,我们的整个分析过程,应该是一个不断记录我们对模型的修正的过程,文档是整个工作中最重要的一环。
同时,写文档也是提醒我们保存数据。很多人很不在乎数据的记录,在工作环境上这个运行一下,那个运行一下,然后就完事了。 浪费了不知道多少东西,我每次接触工作环境,第一件事是创建一个目录,放一个BRIEF文件,写上当前时间,测试人,环境,原因,然后才开始数据收集,过程中不覆盖任何“稍有点用的”原始数据。这是基本的工作技巧,很多工程师不肯学这种基本素养,不能守弱,工作起来乱七八糟的,这样写出来的报告一钱不值。
怎么写文档这个不是我这里要教的东西,这是你中学语文应该学好的东西,但我还是提一句很多工程师经常犯的错误:判断这种分析报告写得好不好的一个基本原则是,你是否始终围绕着“瓶颈的证据是什么”来表述观点。这个原则非常简单,但很多报告写出来就会忘掉这个。他们写着写着就喜欢收集各种很好看的分布图,趋势图,然后彻底忘掉到底现在系统到底到达瓶颈没有,以及到达瓶颈的理由是什么。很多人给我夸夸奇谈半天,我问一句,“你根据什么判断现在压力不能上去了?”,然后他就傻了。这个说到底是个守弱的问题,我们还是少点想建功立业,先做点基本的东西比较好。
==示例==
我们还是用我的cs程序为例(我晚点注册个新的github帐号共享出来),这个例子很简单,它模拟了一组线程产生数据,写入队列,然后另一组线程把数据取出来,完成整个计算的过程,计算用heavy_cal函数来模拟。我们的目标是尽量提高计算的通量。所以,我们首先看4线程的一般运行的结果:
这个每秒处理175K的任务。但CPU还有空闲。可能是因为我们在每个线程计算的时候有IO,导致效率上不去,我们用更多的线程(40个)来填掉这些IO的等待,结果提升非常有限:
简单解决不了这个问题了,我们看看ftrace的数据:
看见没有,cs的线程执行不到5个微秒就休眠了,搞什么飞机?
这个函数这样写的:
void * pro_routin(void * arg) {
struct task * tsk = arg;
int ret;
srand((intptr_t)tsk->arg);
while(1) {
ret = heavy_cal(rand(), n_p_cal);
en_q(ret);
marker("yield here");
yield_method_f();
}
}
heavy_cal是纯计算,不会引起无意义的休眠,marker在内核中是用spin_lock保护的,不会引起休眠,唯一有可能休眠的是yield和en_q()(写入队列),我们清掉yield试试,发现没有效果。那就只有怀疑en_q()了,我们预期provider en_q可以写上几十个,然后才切换给consumer再处理几十个。
但实际上根据Linux的调度算法,consumer会因此被逐步提权为interactive线程(Linux调度算法总是把总用不完时间片的进程的调度优先级提高,让他们成为interactive线程,这样那些用来处理鼠标,键盘的任务可以优先得到调度,从而提高响应速度。
这样修改以后,单核CPU占用率提升到92%以上,处理效率就提升到311K了。这时我们再看ftrace数据,它是这样的:
这个跟踪我们还跟踪了futex的调用,我们可以看到大量的pthread_mutex_unlock的调用,但都没有引起调度,整体性能就提上去了。
我们还有办法可以把剩下的那些时间用起来,不过这只是个例子,就到此为止吧。
==总结==
本文介绍了最基本的性能分析流程,后面我们会具体讨论一些常见的分析模型,加深对这些模型的理解。