卡顿监控使用指引
概述
应用卡顿是导致用户流失的重要原因之一,卡顿监控模块致力于协助用户治理卡顿问题。卡顿治理的一个重要思路是,通过提取有效的指标衡量应用当前的状况,抓取足够的信息提供优化方向。卡顿监控模块通过FPS及挂起率两个指标来衡量应用的流畅度情况,通过卡顿问题监控,直接抓取卡顿堆栈,协助用户定位卡顿原因,为用户提供优化方向。
- 卡顿指标通过稳定、轻量的采集技术,收集应用在整个运行过程中的流畅度数据。
- 卡顿问题监控,通过高频抓栈技术,提供丰富的现场信息,实现准确归因。
- Android平台自研快速抓栈技术,对比传统抓栈实现,性能提升6倍。
- 无论是卡顿指标,还是卡顿问题,都支持用户自定义采样率,同时提供了丰富的数据分析能力。
FPS
FrameRate:帧率是GPU和CPU合作,在应用运行时,可产生的图像的数量,计量单位是帧/秒(FramesPerSecond,缩写FPS),通常是评估硬件性能与应用体验流畅度的指标。
Bugly专业版的卡顿监控统计的是包含真实UI刷新的FPS,剔除了页面静止期间的数据。这样统计的数据反应了用户使用应用的真实感受。
我们举例说明一下,相册页面,如果应用没有处理好异步加载的话,在用户滑动时,可能特别卡。用户安静观看照片时,由于没有触发耗时操作,可能看起来一切正常。如果以用户停留页面总时长来统计FPS,则页面无刷新的时间可能将滑动时的卡顿表现给平均掉了。使用Bugly专业版的卡顿指标监控,则没有这个问题,我们只统计包含页面真实刷新时FPS,不会因为页面无刷新的时长变化而导致FPS变化。
除此之外,Bugly充分考虑了不同屏幕刷新率的情况。不同的屏幕刷新率最终影响VSYNC信号的产生频率(见下图)。当前市面上存在各种屏幕刷新率的手机,常见的是60Hz, 90Hz, 120Hz。简单来讲,如果一个应用性能非常好,UI更新不耗时,那么其帧率是受屏幕刷新率限制的,其帧率不可能高于屏幕刷新率。也就是说,即使一个性能非常好的应用,在不同屏幕刷新率下,其帧率是不一样的。这也是一些游戏手机,为了提升体验,屏幕刷新率一般比较高,超过传统的60Hz。 Bugly的帧率监控是希望直接运行在线上的,需要兼容不同的屏幕刷新率。经过多方考量后,我们决定采用归一化的思想,将不同屏幕刷新率统一归一到60Hz的情况。也就是说,即使两台不同刷新率的手机,Bugly统计到的一款应用的FPS基本一致。
当前Bugly是按场景来统计FPS,在应用运行过程中,卡顿指标统计模块,统计一个个场景的数据,用户切换场景后,统计模块会保存上一个场景的数据。在应用下次启动后,上报模块会提取上次运行期间所采集的数据,合并相同场景的数据,最终得到一个个不同场景的数据记录。这些数据记录上报到服务器,服务器会保存这些原始记录,同时统计平均值,分位值(P50、P90、P99)。
- 卡顿监控统计包含UI刷新的FPS,反应了用户使用应用的真实感受。
- Bugly通过对FPS数据进行归一化操作,兼容各种刷新频率。
- Bugly按场景统计FPS,一次运行期间相同场景的数据会合并上报。
- 除了FPS平均值,Bugly还统计了多种分位值,P50, P90, P99,帮助用户更好理解用户真实体验。
- 通过实验数据,以及上线观察对比,Bugly的FPS统计方式对应用的性能影响非常低。
趋势分析
用户可以在趋势分析中,查看一天,或者一段时间的FPS变化趋势。Bugly提供了平均值,分位值(P50、P90、P99)多种统计方式,帮助用户更好理解应用FPS的整体状况。
假如,A应用的某次运行,用户体验了10个场景,产生10条记录上报到服务器。共有10个用户上报,总共产生了100条记录。每条记录都有FPS,Bugly服务器将这100条记录按FPS的大小排序,从大到小排序。已排序数据中的第50个,即这批数据中的P50。已排序数据中的第90个,即为这批数据中的P90。已排序数据中的第99个,即为这批数据中的P99。
在FPS中,分位值是按从大到小排序的,因此 P50 >= P90 >= P99 。
用户可以查询指定条件下的FPS数据,如下图所示的,指定场景的FPS:
对比分析
在趋势分析中,我们可以将一组查询结果添加到对比列表中,添加多组查询结果后,后续可以在对比列表中,对比分析这两组数据。
如下图所示,我们期望对比分析两个不同场景的数据情况,分别查询这两个场景的数据,添加到对比列表中,点击 趋势预览 即可查看不同统计维度的对比结果。
多维分析
有些情况下,我们想查看一段时间,所有APP版本,或者所有场景的数据情况,通过多维分析,可以解决这个问题。
我们还有可能想分析指定版本,在某些场景下的数据情况。
挂起率
Bugly卡顿监控,对于挂起率的统计是,如果应用两帧之间的刷新延时超过200ms,则认为此时应用不能很好地响应用户的交互,并且累加到挂起时间中。一个设备的挂起率,是指这个设备在一天中,总的挂起时间除以设备的前台总时长。也就是说,卡顿监控的挂起率,是以设备,按天聚合统计的。
设备挂起率 = 设备一天的累计挂起时间(单位是秒)/ 设备一天的前台总时长 (单位是小时)
对于一个应用而言,我们关注的是设备挂起率的分位值,如P50, P90, P99等。例如,某个应用A,在某天中,有100台设备上报了挂起率数据,则后台按设备ID聚合,得到100条记录,每条记录统计挂起率。这100条记录,从小到大排序。在已排序的数据中,第50个为45.23s/h,则P50 = 45.23s/h。依次类推,第90个数据为134.23s/h,则P90=134.23s/h。
跟FPS类似,挂起率也支持趋势分析,对比分析以及多维分析,交互参考前面的FPS指标。
- FPS数据以场景为粒度,直接统计用户上报的原始记录,在原始记录的基础上统计平均值和分位值。
- 挂起率则以用户上报的原始记录为基础,以设备ID进行按天聚合,一个设备一天中产生的多条记录被聚合成一条记录。挂起率是在这些聚合后的数据的基础上进行统计平均值和分位值。
问题监控
卡顿问题监控核心专注于帮助用户找到导致应用卡顿的原因,通过监控UI线程消息执行耗时,结合高频、连续抓栈策略,将耗时超过阈值的情况上报到服务器。服务器基于卡顿堆栈树,提取关键耗时特征,将个例聚合成Issue(问题),得到卡顿问题列表。
问题列表
- 通过左侧菜单,卡顿/问题列表进入卡顿问题监控的数据页;
- 卡顿问题列表支持丰富的搜索条件,用户可以通过 添加字段 来自由设置期望展示的搜索字段;
- 用户还可以通过 展示 与 收起 来调整搜索条件区域的展示效果,网页会记住用户的选择;
- 选定搜索条件后,点击 查询 提交查询任务,得到查询结果后,页面会自动刷新;
- 卡顿问题列表的头部包含查询结果的摘要信息,用户可以看到满足条件的问题有多少个,以及Top N问题的占比情况;
- 查询结果默认按Issue的上报时间排序,一个Issue包含问题特征,最近上报时间,卡顿顿耗时的平均值及分位值,关键堆栈耗时平均值及分位值,叶子结点最大耗时的分位值等等,用户可以结合实际需要,通过 设置 自由选择字段展示;
- 用户点击Issue的问题ID,可进入问题详情查看一个Issue的上报详情;
- 列表展示的统计字段比较多时,结果列表的内容比较宽,用户可以左右滑动来查看各部分内容;
- 用户还可以将本页的内容直接以表格的方式导出数据;
问题详情
问题详情分为三部分,问题头部,详情分析以及下钻分析。
- 问题头部,包含Issue维度的描述,例如Issue的版本标签,用户给Issue打的自定义标签,处理状态,处理人,问题ID,问题特征 等等。
- 详情分析,侧重对卡顿个例进行分析,用户可以利用搜索条件,查询满足指定条件的个例。
- 下钻分析,侧重对整个Issue进行分析,包含趋势分析,统计分布,出错堆栈分析等等。
个例分析
查看某个卡顿个例,我们核心关注的是以下信息:
- 卡顿总耗时,关键堆栈耗时,以及叶子结点最大耗时,这些信息要以帮助我们快速判断,这是一个什么类型的卡顿。
- 接着,我们开始分析卡顿的堆栈详情。当前卡顿的堆栈现场通过三种不同的方式呈现,时间片,堆栈树以及火焰图。
- 通常情况下,我们会先分析火焰图,它比较直观呈现了卡顿现场的全貌。
- 接着利用时间片或者火焰图分析堆栈详情。
- 火焰图以及堆栈树中的耗时是通过抓栈间隔估算的结果。
- 卡顿耗时是准确的,表示一条UI线程的消息执行耗时。
虽然卡顿堆栈的耗时是根据抓栈次数以及抓栈间隔估算得到,但是卡顿耗时却是准确的,表示一个UI线程的消息执行的耗时。当前卡顿监控通过监控UI线程的消息执行耗时来判断应用是否发生了卡顿。
以下图示例,这个堆栈连续6帧都被抓中,当前的抓栈间隔是52ms,堆栈的耗时估算为 52 x 6 = 312 ms。
Android 与 iOS,甚至iOS的不机型,抓栈间隔可能都有差异,大家可以结合时间片中显示的抓栈次数,再结合推算的抓栈耗时推算出本个例的抓栈间隔。
另外,用户还可以结合操作日志以及现场数据来进一步分析卡顿现场。
下钻分析
用户可以通过下钻分析来查看Issue的上报趋势,以及在一些字段的分布情况。
- 下钻分析支持用户设置搜索条件,查看指定条件下的统计结果。
- 上报趋势支持查看不同统计维度的数据,当前支持发生次数,影响用户数(按用户ID去重),影响设备数(按设备ID去重)。
- 上报趋势支持查看不同聚合粒度下的数据,最小支持分钟级别。
- 统计分布是指查看搜索条件下,在指定字段的统计分布情况。
指这个字段包含有效值的上报情况。有些情况下,某些字段可能存在取不到值,或者取的值不合法的情况。因此虽然搜索条件一致,有可能不同字段的有效上报次数不一致。
治理技能
结合业务的卡顿治理经验,我们总结了一些通用的技能,以提升分析卡顿问题效率。
技能1:卡顿治过程中,分阶段调整卡顿阈值。
卡顿治理往往最先遇到的问题是,开启卡顿监控后,发现上报了几万个问题,分析成本巨大。根据应用的实际情况,阶段阶段调整卡顿阈值,先解决一些比较明显的卡顿问题,例如卡顿耗时大于500ms的。通过分阶段调整卡顿阈值,既可以专注有效的精力在重要问题上,又可以降低上报成本。
技能2:聚焦Top问题,根据查询结果的Top推荐,选择合适的Top值。
根据二八原则,80%的上报集中在20%的问题上,所以你只需要聚焦Top问题。这些Top问题首先从上报量表现这是一个用户容易遇到的问题,而不会受个例特殊环境的影响。根据搜索结果的摘要,我们可以快速了解问题的大致情况,再根据实际情况来选择合适的Top问题来分析。
如下图所示:满足条件的问题共有5590个,但是Top500的问题包含了80%以上的上报个例。也就是说,我们只需要聚集这Top500的问题,而不需要分析所有的问题。
技能3:优先解决对用户体验影响大的,影响面广的问题。
如何评估一个问题是否对用户体验影响大,以及其影响面是不是比较广呢?需要结合卡顿问题列表的卡顿耗时P50,关键堆栈耗时P50,叶子结点耗时P50,以及触发次数。
如下图所示:在资源有限的情况,如果只能解决一个问题,选择第3个问题来解决的性价比会比较高。首次其上报次数比较高,占Top3的问题,其次其卡顿耗时比较大,并且叶子结点最大耗时P50比较大,说明是一个单一节点耗时问题,修复的成本通常会比较小。
技能4:理解问题列表的统计维度。
问题列表包含卡顿耗时,关键堆栈耗时,以及叶子结点最大耗时等特性的平均值,以及分位值。
下图展示了,在一个卡顿个例中,卡顿耗时,关键堆栈耗时以及叶子结点最大耗时的含义。
分位值,亦称分位点,是指将一个随机变量的概率分布范围分为几个等份的数值点,常用的有中位数(即二分位数)、四分位数、百分位数等。这里Bugly平台使用的是百分位数。百分位数是统计学术语,如果将一组数据从小到大排序,并计算相应的累计百分位,则某一百分位所对应数据的值就称为这一百分位的百分位数。以卡顿耗时来说,一个问题包含多个例上报,每个个例上报的耗时都不是完全相同的。我们可以将这一个个的上例上报的卡顿耗时,从小到大进行排序,如下图所示,共30个上报,P50 = 排序15对应的卡顿耗时468ms。
技能5:按叶子结点最大耗时P50排序,分析单点耗时问题。
卡顿问题分为三大类:单点耗时,多点耗时,复杂或频繁执行。
单点耗时
多点耗时
- 复杂或频繁执行
不同类型的问题,治理的成本是有差异的。一般来说,单点耗时问题,比较容易推动解决。其次就是多点耗时问题。而复杂或者频繁执行的问题,往往需要做一些比较大范围的重构,治理的成本也是最高的。
如下图所示:满足条件的问题有27个,其中Top9的问题包含81.48%的上报个例,说明我们只需要关注Top9的问题即可。在这Top9的问题中,通过叶子节点耗时P50排序,我们找到单点耗时问题,优先解决性价比最高的问题。
技能6:优先防止新问题产生,逐步推动历史问题解决。
应用的历史问题可能特别多,推动这些问题解决不太容易,特别有一些是历史代码,调整的成本比较高,也容易引发质量问题。但是,新问题就不一样,一般是近期开发所导致的问题,这类问题比较容易推动开发解决,而且解决的成本也往往比较低。通过条件对比功能,可以帮助我们快速定位到新增问题。
如下图所示,1.4.4版本为我们最新开发版本,在发布前,我们通过条件对比功能,我们可以快速找到1.4.4版本的新增问题。
FAQ
1. 挂起率是怎么统计的?
挂起率是以设备为单位,按天统计,该设备在一天中,应用在前台时,两帧耗时超过200ms的挂起时长,以及应用在前台的时长。设备挂起率 = 设备一天的累计挂起时间(单位是秒)/ 设备一天的前台总时长 (单位是小时),挂起率的单位是 秒/小时(s/h)。
2. FPS是怎么统计的?
FPS按场景统计,当前统计的是应用包含UI真实刷新的FPS,剔除了页面无UI刷新时的数据。Bugly当前展示的FPS数据通过了归一化处理,以兼容不同屏幕刷新率。
3. 卡顿的方法耗时是怎么统计的?
卡顿问题监控中,方法耗时是通过连续抓栈,结合抓栈间隔,估算得到。例如一个堆栈被连续抓中6次,抓栈间隔是52ms,则这个堆栈的执行耗时是 6 x 52 = 320 ms 。这种估算方式存在误差,误差为抓栈间隔。但是卡顿耗时却是准确的,表示一个UI线程的消息执行的耗时。当前卡顿监控通过监控UI线程的消息执行耗时来判断应用是否发生了卡顿。
4. 堆栈树或者火焰图中,耗时为52ms的堆栈代表什么?
如果一个堆栈耗时为52ms,表示这个堆栈在连续抓栈过程中,只被抓中了一次,并不代表其真正耗时为52ms。如果一个堆栈被抓中N次,其中N大于1,则表示该堆栈耗时一定大于(N-1)*52ms。
Android 与 iOS,甚至iOS的不机型,抓栈间隔可能都有差异,大家可以结合时间片中显示的抓栈次数,再结合推算的抓栈耗时推算出本个例的抓栈间隔。
5. 什么是关键堆栈耗时?
根据前面内容可知,每一个个例包含一棵堆栈树。为了聚合问题,需要抽取特征。导致卡顿的主要原因是不是就是关键堆栈。如下所示,以下就是这个个例的关键堆栈了。
- 关键堆栈
com.tencent.demo.buglyprodemo.TestActivityLeakActivity$1.onClick(TestActivityLeakActivity.java)
com.tencent.demo.buglyprodemo.TestActivityLeakActivity.access$000(TestActivityLeakActivity.java)
com.tencent.demo.buglyprodemo.TestActivityLeakActivity.triggerLeak(TestActivityLeakActivity.java)
6. 叶子结点最大耗时有什么作用?
叶子结点最大耗时是指一个堆栈树,所有叶子结点中耗时最大的。如果一个卡顿问题,其叶子结点最大耗时比较大,甚至接近于关键堆栈耗时,说明叶子结点的堆栈就是卡顿的主要原因。像死锁,UI线程执行IO操作,UI线程访问DB,往往叶子结点最大耗时会比较大。