Nov 21

客户端Crash率的定义与可衡量性的建模讨论

Lrdcq , 2020/11/21 02:05 , 程序 , 閱讀(3178) , Via 本站原創
我们做客户端包括并不限于Android/iOS/Windows/嵌入式程序等,核心指标之首均Crash率,我们通过Crash率来衡量咱们工程的可用性高低。目前业界Crash率的定义的定义有两种:

- Crash次数/启动次数,这样能测算出每次启动后到底有多少概率是正常离开的,多少概率是闪退的,非常理论化的一个数字。
- Crash次数/用户数,比如每日Crash次数除以日活即今日Crash率,这样可以准确评估出用户感知到的可用性闪退率到底是多少。


但是如果我们要非常理论的衡量不同/所有的客户端之间的可用性,并且可进行比较,以上两种度量方式足够么?还需要补充什么参数么?

我们遇到的问题

我们一个业务有多个客户端,如果按特点来描述,有三种:

a. 第一种就是我们常见的偏C端的电商客户端,用户特点是买完即走,没啥逛的需求,平均客户端启动时间不到十分钟,每天平均用户使用一次即日活数和订单数接近。因此它的Crash率采用上面两种方式看起来都还行也都能统计准确。

b. 另一个客户端是内部人员工作用的,类似于钉钉的客户端,它的特点是用户使用时间超长,并且由于一直在使用/一直在不断被打开,好一点的手机这个应用就常驻了,而差一点的手机会变成app在不断被重启。当然,用户使用时间长,也意味着假如每分钟遇到crash的概率是相等的,那么使用时长更长的app用户遇到crash的数量就越多。因此这个app的数字用/启动次数去计算,Crash率就很离谱了,而通过日活去看crash率,就简直不能看。

c. 还有一个windows上的客户端,它的作用是在本地建立一个常驻服务去承接一些打印/硬件相关的请求转驱动。当然这个客户端有附带watchdog做crash后拉取,但是实际上它的Crash率确实无论怎么衡量,都很低。

当我们把这个三个app放在一起比较的时候,就炸了。内部人员app表示有自己的苦衷,windows客户端则无法解释为啥自己crash率这么低。如果大家都说自己有自己的特点,crash无法横向比较,衡量的可用性就没法拉出绝对性了。因此显然,目前的Crash率建模是不够用的。

为何可用性差

要找到Crash率异常率怎么度量更合适,即可用性怎么度量更合适,需要找到引起可用性降低,发生异常的根因。

- 总使用时长:直观的第一层判断是,使用时长肯定和异常率正相关。因为传统Crash率建模的核心就是假设程序单位时间可用性是一致的。也就是说,如果我们简单解决上面这个问题的话,Crash率定义为单位使用时长Crash率即可,即Crash次数/使用时长分钟数or秒速。

当然,实际上并没有这么简单。如果去review除了代码真的写坏了,其余的长尾异常,明显那些奇怪的Crash可以分为:a. 内存相关,内存不够了野指针。b. io/跨进程通信相关,和别人交互发生异常了。 c. 高阶组件相关,比如webview,相机相关的crash。d. 极端异步操作相关,快速或高强度ui操作导致的偶发异步问题。

- 内存积累:因此使用时长对应着内存积累,会逐渐导致Crash率上升。主要体现在内外两层,在内部,内存占用变高虽然不完全代表内存泄漏,但是对应的各种单例,监听器,轮训变多,也就是说常驻执行的代码覆盖率变高,对应着crash率变高。在外部,系统可用内存降低可能导致整体内存接近阈值,触发各种内存分配异常野指针,oom类型的可用性问题。
- 设备性能:有一个特征是在很多移动设备上,如果设备处于高内存压力的状态,客户端非常容易出现异常。根因是各种系统跨进程通信部分直接失败or超时,包括程序加载不完全这样的错误。不过这样的异常有可能不会进行上报。
- 操作密度:如果用户进行高操作密度的行为,对应着一个同时执行的代码量大,这是crash率上升的主因。同时也有异步逻辑风险高的因素。

这里还可以解释为什么那个windows客户端crash率低:那个客户端的特点是常驻但是实际交互不多,并且作为一个中转server本身代码量也不大,对应着:

- 每单位时钟热代码量:作为常驻客户端,如果单位时钟的热代码越少,当然crash率也就越低。这是对上面三条的二次解析。
- 应用总代码量:作为上面这一条的补充,应用的总代码量决定了热代码的上限和绝对值。

可用性变量抽象与建模

结合以上讨论的信息,我们显然可以知道,最小Crash率抽象单位为:

- 单位时钟/单位代码量/异常率

其中“单位代码量/异常率”看起来其实就是类似于千行bug率一样,只是作为统计线上可用性的话,加上了线上的时间限定即“单位时钟”与所谓热代码。但是很显然,如上这个信息,即单位时钟里到底有多少热代码是正在运行的这一点,是不可统计的。因此只能按特点进行转化。

根据上面的讨论,最简单的转化是,将crash率转化为:

- 程序基准Crash率:程序单位时钟最低的Crash,以衡量程序的基础可用性。
- 程序单位时间增长Crash率:对应着程序持续运行的可用性表现。

对应着程序运行抽线为:每次启动Launch的Crash率为初始值,然后在前台阶段,随着固定数率上升。到重启后恢复。如下图:
點擊在新視窗中瀏覽此圖片
- 这样抽象的好处主要是简单,如果从大数据上看,只需要采集用户的每日平均启动次数/单次启动时长,就可以对这个数字进行计算了,并且这个模型可以涵盖绝大部分问题。只有基准热代码量问题无法解决(即这张图运行情况完全一致的两个应用,代码逻辑简单的那个应用Crash率低)。
- 同时,这个数字计算的结果可以对应用优化方向带来参考意义。

但是如何解决代码复杂度即热代码带来的问题呢?根据上文讨论,只能从性能结果上表现,即将当前是否有大量的hot代码的问题转换为当前程序的运行状态是否hot的问题。因此我们可以把程序运行时定义为三个状态:

- Wait态:程序无交互,looper几乎空转。实际可以用程序cpu占用测量,这个状态cpu占用应该<=0.5%才对。
- Hot态:程序处于高密度逻辑运行状态,假设是cpu占用应该>=15%。
- Normal态:即正常使用的状态。

结合第一个抽象即程序的异常率会以某个恒定速率上升,那么如果假设hot态的异常率会上升一个台阶的话,单个启动流程的曲线可以理解为:
點擊在新視窗中瀏覽此圖片
整个时段即WHN三个状态。HN均以另一个恒定速率异常概率上升,启动H会整体高一段。而W状态假设和处于后台一致,异常率保持不变。

- 因此这里需要采集更细致的时间,即单位时间内平均W时长,H时长,N时长,通过性能采集工具进行。
- 同时也重新定义出新的应用基准Crash率和增长Crash率,与另一个热状态Crash率diff。这些数字应根据上一个模型有对应关系,假设热状态Crash率diff始终=基准Crash率 x 20%的话,这个等式就有唯一解了,可以直接换算。

因此结合以上讨论,会得到换算公式:
點擊在新視窗中瀏覽此圖片

总的来说,对于单个客户端回话时间来说,他们的积分即总的Crash率应该是恒定的,只是我们对这个数据依据不同模型做了更细致的拆分与拟合。

设计转化与落地

因此,我们的Crash率建模重新拆分为三层:

- 基础Crash率法:单位时间Crash率。
- 运行时Crash率法:基准Crash率+单位时间增长Crash率。
- 运行时Crash率法(状态优化版):基准Crash率2+单位时间增长Crash率2。

着三层都有查看的意义并且可以对咱们各个App进行可用性比较与衡量了。在代码上的todo,包括:

- 同一定义:目前所有采集单位均以天为单位计算。
- 应用用户行为采集:主要包括用户前后台切换,用户主动离开应用(Android返回到无Activity,PC关闭到无窗口)。
- 应用运行状态采集:建议包括并不限于CPU占用/内存占用,并且同时根据实时状态按天上报应用WHN状态时长结论与占比。
- 正常的Crash采集。

这样,我们就可以离线的计算出应用重新建模的可用性指标,并进行横向对比了。
关键词:crash , crash率 , 建模
logo