前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >透视QAPM Android新卡顿&新启动分析的技术方案

透视QAPM Android新卡顿&新启动分析的技术方案

原创
作者头像
010101011001
修改2021-02-09 15:47:05
1.5K0
修改2021-02-09 15:47:05
举报

春节快乐,干货来袭。QAPM团队已服务于公司内外包括国有大行的50+产品,声音大呀,但是之前的卡顿与启动个例是真心不好用,也让不少团队憋着对我们的吐槽来推广他们的新方案。何苦呢?我们怎么能站着茅坑不XX!新方案其实一直在路上,一直在路上,现在终于来了。本文,我们会描述两个“在路上”的曲折与思考,也透视出新方案的技术核心与优势。

切肤之痛,个例单堆栈根本无法定位准确

例一:堆栈显示正常创建一个对象,这个地方怎么会卡呢

例二:堆栈显示的是个空方法

类似的反馈我们收到了太多了,卡顿方案的缺陷也一直是我们的痛点。一个事件下存在大量的执行函数,而方案是基于阈值满足的前提下才执行的堆栈抓取,这样会产生堆栈偏移,有可能真真实实捕获到了卡顿所在的函数,但更多的是一些不卡的函数,只是刚好被捕获到了~

一直在路上之一,方法插桩,稳定+可靠+性能好

通过在方法的开始和结束点插入方法,统计并记录方法名与方法耗时。微信和闪现社区用的都是这个方案。经过微信的打磨,在其开源的Matrix中已经非常完善,稳定可靠,性能好。这么好,是不是直接用微信的方案吧,但是我们很犹豫。而这犹豫源自于一个我们执迷不悟的坚持。

我们团队都是专项测试出身,跨产品做性能分析与优化是家常便饭,也因此对于我们来说,可以泛化的分析方法,价值更大。系统方法调用栈就是构成这个分析方法的核心,例如文件与数据库的主线程I/O,IPC/RPC的调用导致的卡顿/ANR,其实都有可以总结的优化方法和思路。而这个美好的插桩方案让我们犹豫的核心原因就是,没有系统方法调用。然后我们就一直犹豫,不想放弃原来的方案。

一直在路上之二,一个“无效”的突破

正当我们犹豫之际,我们的nicky同学在受到闪现社区的冲击(技术人的冲击就是这么地简单纯粹),又一个团队选择了插桩。这时,nicky开始深入研究JVMTI的黑科技,希望可以有个完美方案。最终我们成功绕过了debug包的限制,在不侵入的前提下,在release包下可以拿到各个方法的进出及耗时。原本以为胜利就在前方,但却被现实泼了冷水。在JVMTI开启的情况下,性能消耗是原来的100%。。。一番考虑下,APM这边暂时不将该方案合入,但JVMTI的突破,的的确确给了我们更多的发展空间,如线程监控、内存分配监控等等。

终于来了,谁说采堆栈不能优化

撞了南墙也要破个洞穿过去!采集堆栈的方法真的无解吗?在这之前,我们来总结下,目前主流的实时获取耗时信息的方案:插桩法、定时堆栈法、jvmti法。其优缺点和典型的代表如下:

JVMTI法:由于性能问题,APM暂时不做考虑;定时堆栈法:精准度低,性能消耗大;插桩法:无系统和应用层的调用关系,侵入性强且后续有包体优化的成本。这样看来,好像没有一个方法是合适的。但我们的内心是喜欢定时堆栈的,无他,为了那个具备问题解决方案泛化价值的系统调用栈。

"方法总比问题多",执迷不悟的我们还是尝试从问题着手,去尝试优化那个别人抛弃的定时堆栈。而核心就是要破除这个看似无解的问题,性能差-精度低。精准度低:事件内多函数执行,达到阈值才去抓取堆栈只能靠运气的抓到真耗时函数,大部分抓到的可能是耗时短的函数;性能消耗大:堆栈转换成字符串时容易造成太多GC,而为了提升精度,还会要增加捕获的堆栈,也增大了性能的消耗。下面来看看,针对这两个问题,我们是如何突破的。

解决精准度低的问题:

在事件进入时,开启一个延时的定时任务,如果任务在规定时间内完成则取消掉,否则开始间隔30ms抓取堆栈对象,最多抓取3秒数据的堆栈,即100个堆栈对象。

解决性能消耗大的问题:

一个小小的细节:

一个堆栈对象,真的没有可以利用的空间了吗?一个堆栈由包名、文件名、方法名和行号组成,但其实我们还忽略了一个比较重要的信息,是该栈距栈底的距离。而通过行号及栈深的计算,基本可以确定栈的唯一性

抓取到的堆栈对象中的每一行栈,转成字符串做map存储计数,就能大概分析出卡顿的点了,但如果使用字符串去匹配,那内存的消耗则会大大的上升,那如何不使用字符串匹配又能知道出现次数最多的栈呢?

整型的计算,远远会比字符串的操作消耗要低。通过行号及栈深来记录该栈的出现次数,应该就能初步解决消耗大的问题。

为什么说是初步?试想一下,极端情况下,我们抓到了100个堆栈数组,且每个堆栈数组的深度有50行+,在每个堆栈都需要计算存储的前提下,我们至少得需要5000+次的计算任务。这里其实也是有不小的消耗的。那能不能减少这里的计算和存储成本呢?其实是可以的。APM对于一个堆栈数组的处理是这样的,从找到第一个非系统栈开始,保留业务栈的上层系统栈,从当前栈开始,往下追五层,如果连续超过5层还有业务栈,则不再处理新的业务栈,且当再次碰到系统栈或者遇到handleCallback等方法时,结束处理当前堆栈数组。

将堆栈归类后,现在就要找到大头卡顿的栈,怎样算大头卡顿的栈呢?这个其实也没有一个太好的标准。目前卡顿阈值是200ms,30ms取栈一次,大于3次则认为是卡顿的源头了。在寻找大头卡点时,又碰到了另一个问题,就是怎么去除重复的栈。举个例子:如果连续出现多个同样的堆栈数组且堆栈数组内有多个业务栈,就会发现会有多个key指向同一个堆栈,如下图

这种情况多出现于该函数发生长卡顿的,对于这种情况,我们采用的是有序的map来进行存储,栈存储时是由上往下遍历存储,所以读取时我们从下往上遍历。如上图的情况,我们会先找到了行号为410的栈,发现他的儿子栈为70,于是往上追,再发现他的儿子栈为613,以此类推,最终找到了618的栈顶类,这个时候再来做处理,就可以避开另外三个的栈处理。

最后找到最大头的卡点进行上报

最终我们借助真实的App测试了性能消耗

这里特别感谢手Q和视频团队提供的测试环境。

通过WeTest和PerfDog的性能测试工具,分别对带有新卡顿和旧卡顿的包进行了多场景下的性能测试,在获取更多堆栈,更多逻辑处理的基础上,大部分数据与旧卡顿相差无几。而且普通的系统方法获取堆栈,绝对没有任何兼容性问题,安全可靠,而且还有那个我们梦寐以求的系统调用在其中。

最后

小小的细节,大大的改变。如对方案有异议或有更好的建议,欢迎批评指正(QAPM是个很开放的团队哦~)

--

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 切肤之痛,个例单堆栈根本无法定位准确
  • 一直在路上之一,方法插桩,稳定+可靠+性能好
  • 一直在路上之二,一个“无效”的突破
    • 终于来了,谁说采堆栈不能优化
      • 解决精准度低的问题:
        • 解决性能消耗大的问题:
        • 一个小小的细节:
        • 最终我们借助真实的App测试了性能消耗
    • 这里特别感谢手Q和视频团队提供的测试环境。
    • 最后
    相关产品与服务
    测试服务
    测试服务 WeTest 包括标准兼容测试、专家兼容测试、手游安全测试、远程调试等多款产品,服务于海量腾讯精品游戏,涵盖兼容测试、压力测试、性能测试、安全测试、远程调试等多个方向,立体化安全防护体系,保卫您的信息安全。
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档