我们采用高频采集的方案来获取一段卡顿时间内的多个堆栈,而不再是只有一个点的堆栈。这样的方案的优点是保证了监控的完备性,整个卡顿过程的堆栈都得以采样、收集和落地。
具体做法是在子线程监控的过程中,每一轮log输出或是每一帧开始启动monitor时,我们便已经开启了高频采样收集主线程堆栈的工作了。当下一轮log或者下一帧结束monitor时,我们判断是否发生卡顿(计算耗时是否超过阈值),来决定是否将内存中的这段堆栈集合落地到文件存储。也就是说,每一次卡顿的发生,我们记录了整个卡顿过程的多个高频采样堆栈。由此精确地记录下整个凶案发生的详细过程,供上报后分析处理(后文会阐述如何从一次卡顿中多个堆栈信息中提取出关键堆栈)。
采样频率与性能消耗
目前我们的策略是判断一个卡顿是否发生的耗时阈值是80ms(5*16.6ms),当一个卡顿达80ms的耗时,采集1~2个堆栈基本可以定位到耗时的堆栈。因此采样堆栈的频率我们设为52ms(经验值)。
当然,高频采集堆栈的方案,必然会导致app性能上带来的影响。为此,为了评估对App的性能影响,在上述默认设置的情况下,我们做一个简单的测试实验观察。实验方法:ViVoX9 上运行微信读书App,使用卡顿监控与高频采样,和不使用卡顿监控的情况下,保持两次的操作动作相同,分析性能差异,数据如下:
关闭监控 打开监控 对比情况(上涨)CPU 1.07% 1.15% 0.08%
Memory Native Heap 38794 38894 100 kB
Dalvik Heap 25889 26984 1095 kB
Dalvik Other 2983 3099 116 kB
.so mmap 38644 38744 100 kB
没有线程快照 加上线程快照
性能指标 2.4.5.368.91225 2.4.8.376.91678 上涨
CPU CPU 63 64 0.97%
流量KB Flow 28624 28516
内存KB NativeHeap 59438 60183 1.25%
DalvikHeap 7066 7109 0.61%
DalvikOther 6965 6992 0.40%
Sommap 22206 22164
日志大小KB file size 294893 1561891 430%
压缩包大小KB zip size 15 46 206%
从实验结果可知,高频采样对性能消耗很小,可以不影响用户体验。
监控使用Choreographer.FrameCallback, 采样频率设52ms),最终结果是性能消耗带来的影响很小,可忽略:
1)监控代码本身对主线程有一定的耗时,但影响很小,约0.1ms/S;
2)卡顿监控开启后,增加0.1%的CPU使用;
3)卡顿监控开启后,增加Davilk Heap内存约1MB;
4)对于流量,文件可按天写入,压缩文件最大约100KB,一天上传一次
痛点2:海量卡顿堆栈后该如何处理?
卡顿堆栈上报到平台后,需要对上报的文件进行分析,提取和聚类过程,最终展示到卡顿平台。前面我们提到,每一次卡顿发生时,会高频采样到多个堆栈信息描述着这一个卡顿。做个最小的估算,每天上报收集2000个用户卡顿文件,每个卡顿文件dump下了用户遇到的10个卡顿,每个卡顿高频收集到30个堆栈,这就已经产生20001030=60W个堆栈。按照这个量级发展,一个月可产生上千万的堆栈信息,每个堆栈还是几十行的函数调用关系。这么大量的信息对存储,分析,页面展示等均带来相当大的压力。很快就能撑爆存储层,平台无法展示这么大量的数据,开发更是没办法处理这些多的堆栈问题。因而,海量卡顿堆栈成为我们另外一个面对的难题。