终端性能监控 Pro 支持全面的 ANR 分析能力,支持 Android 与 iOS ANR 异常的搜集与上报,帮助用户更好地优化应用质量。本文主要介绍终端性能监控 Pro 的 ANR 分析能力及功能详情。
Android
ANR 概览
ANR 问题列表
ANR 问题详情
个例详情
GC 详情
查看 ANR 发生前的 GC 执行时间以及 Java 内存分配信息。
GC:Garbage Collection,Android 虚拟机(如 Dalvik、ART)自动回收无用对象内存的机制。当对象不再被引用时,GC 会标记并释放其占用的内存,避免内存泄漏。
Block GC:Block Garbage Collection,指 GC 执行期间会暂停应用线程(Stop-The-World),导致主线程(UI 线程)卡顿的现象。这是 Android 性能优化的重点问题。
heapAllocated:表示当前进程已分配的堆内存大小,单位KB,对应 Runtime.totalMemory() - Runtime.freeMemory() 的计算结果。
heapMaxMemory:表示当前进程可分配的最大堆内存大小,单位KB,对应 Runtime.getMaxMemory() 接口返回值。
调度时序图
调度时序图以时序图的方式展示 ANR 发生时的消息调度情况。单击消息旁的箭头可以展开 ANR 发生前后的消息调度详情。
其中,长耗时消息以黄色的卡片展示,鼠标 hover 至消息卡片上,将展示对应消息的 Wall Time、CPU Time、Message Handler 和 Callback 等信息。
说明:
Wall Time:实际耗时,指消息从开始执行到完成所经历的实际物理时间(包括线程阻塞、等待 I/O、CPU 调度延迟等所有时间)。
CPU Time:CPU 执行时间,消息执行期间线程实际占用 CPU 进行计算的时间(不包括等待时间)。
Message Handler:消息处理器,Message Handler 是消息的发送者和处理者,负责将 Message 投递到 Looper 的消息队列,并在消息被执行时调用对应的处理逻辑。
Callback:回调接口,附着在 Message 上的可执行逻辑,可以是 Runnable 或 Handler.Callback 接口的实现。
在分析 ANR 问题时,可以勾选模块右上角仅显示长耗时消息来过滤筛选出长耗时消息。如果想查看长耗时消息中的详细堆栈,可以单击指定长耗时消息卡片中的 ANR 之前60秒卡顿堆栈追踪,将跳转至卡顿页面查询该设备的卡顿个例上报,结合卡顿个例中的堆栈来综合分析导致 ANR 的原因。

进程信息
进程信息包含 Maps 信息、Maps 归类、Meminfo 信息、进程状态、线程状态和线程统计数据。
Maps 信息:取自 /proc/[pid]/maps,简称 Maps 信息,用于记录指定进程的内存映射详情。它展示了该进程的虚拟内存布局,包括加载的库、堆栈、共享内存区域等关键信息。

Maps 归类:对 Maps 信息的归类处理,通过树状结构将相同类型的 Maps 信息归在一起。

进程状态:取自 /proc/[pid]/status 文件,详细可参考 Linux 内核官方文档。

线程状态:取自 /proc/[pid]/task/[tid]/stat 文件,记录了线程的运行统计信息。

线程统计数据:根据线程名,对线程进行分类统计,以树状结构呈现。

ANR_INFO
ANR_INFO 信息是应用程序在 ANR 发生时生成的一项系统报告,记录了 ANR 发生时所在的 Activity 组件、发生 ANR 的进程 ID,以及发生 ANR 的原因。
其中,Load 表示 ANR 发生时系统在1分钟、5分钟和15分钟的平均负载情况,平均负载的单位是进程数,代表在 ANR 发生时系统中正在运行或等待 CPU 资源的进程数的平均值。CPU Usage 代表在 ANR 发生的时间范围内 CPU 的详细占用情况,根据这些信息可以辅助进行 ANR 问题的分析。

ANR Trace
ANR Trace 是一个包含应用程序在 ANR 期间发生的事件和线程堆栈跟踪的日志文件,主要由两部分组成,如下图所示。
第一部分是 ANR 时间和 GC 信息,可以了解当前 ANR 问题发生了多长时间,系统进行了哪些 GC 操作。第二部分是线程堆栈信息,列出了所有在 ANR 期间运行线程的堆栈跟踪信息。每个线程除堆栈信息外,还包含线程 ID(tid)、线程优先级(prio)、线程状态(state)、线程调度统计信息(schedstat)、线程使用的 CPU 时间(utm 和 stm)等线程的状态信息。结合上述信息,尤其是线程的堆栈跟踪信息,可以快速帮助定位分析造成 ANR 问题的原因。

下钻分析
iOS
ANR (Deadlock)
ANR(Application Not Responding)是 Android 系统中的一个术语,用于描述应用程序在一段时间内未能响应用户输入的情况。在 iOS 系统中,表现为因为错误码为0x8badf00d 的 SIGKILL,在平台中,因为其本质为同一种异常表现,故这里统称为 ANR,方便平台和开发者统一概念。
ANR 指标分析
ANR 问题的本质是主线程卡顿,导致 App 响应用户输入超时,触发系统 WatchDog 机制,从而停止进程。SDK 通过检查主线程 Runloop 执行周期,判定主线程是否发生了卡顿,对于超过阈值(默认5s)的卡顿,则标记为可能发生 ANR 事件。结合 App 进程退出判定,若进程最后退出状态时,依然为可能发生 ANR 事件的状态,则判定上一次进程退出为 ANR。
综上,平台将 App 前台运行期间进程被 SIGKILL 时,处于长卡顿状态的退出场景,判定归类为 ANR 异常。
注意:
由于 SIGKILL 信号是直接停止进程,不会有任何信号或通知到进程内部,因此平台将已知的退出(例如用户手动停止进程、其他 Crash 等退出)排除后的退出当作 SIGKILL 处理,在一定的处理上,与 FOOM 判定方式类似。
为了方便衡量 ANR 事件的严重程度,平台提供了 ANR 率(ANR 退出次数与进程启动次数)的比值。同时加入了从设备角度和用户角度衡量的指标,即设备 ANR 率和用户 ANR 率,二者均是有设备 ID 和用户 ID 去重后计算所得。
ANR 率 = 退出次数 / 启动次数
设备 ANR 率 = 设备 ID 去重后的退出次数 / 设备 ID 去重后的启动次数
用户 ANR 率 = 用户 ID 去重后的退出次数 / 用户 ID 去重后的启动次数

与其他指标分析类似,支持按照不同的条件进行筛选分析不同条件下的指标变化和对比。
ANR 问题列表
平台将上报的 ANR 个例按造成其异常的调用栈的特征进行聚类,将同类问题作为一个 issue 进行统计,方便业务跟进和定位问题。对于每一个 issue,问题列表中会统计其影响设备数、最近上报时间等信息,与其他问题列表的表现类似。

调用栈特征提取
如前所述,SDK 会将造成 ANR 的调用栈上报到后台。为了有效聚类个例问题,平台通过提取关键耗时方法,使用关键耗时方法中的前三层函数作为其特征。
由于调用栈采集的为连续时间内的执行情况,因此按照栈底往上,将相邻相同的帧进行合并,便可以将调用栈作为调用树,其中每个节点表示对应方法的执行耗时。
在堆栈树中,找到耗时较大的一条路径,作为关键耗时方法,之后提取这条路径中的前三层作为其特征进行聚类。
问题列表 -- 无堆栈问题
在问题列表中,会存在一个特殊的 issue 类,其没有关键耗时特征栈:“无堆栈问题”。无堆栈问题的出现是由于 平台 采样机制导致的。
对于没有开启堆栈采样的个例,由于其无法提供对应的诊断堆栈信息,但在某些情况下,需要查看对应的用户或设备的 ANR 发生情况,故此问题列表中会展示这部分发生 ANR 的个例,并将其都归类在“无堆栈问题”中。
注意:
在堆栈采样率开启到100%时,依然会有无堆栈问题上报,属于平台已知问题,在早期版本中表现更加明显。这是因为 SDK 在堆栈持久化时有一定延迟,导致堆栈还未持久化,进程就已被杀导致,平台目前正在逐步优化以减少此类情况的出现。
ANR 个例分析
个例分析是解决 ANR 问题的重要依据,其提供了造成 ANR 的详细原因,提供有效的诊断数据定位 ANR 问题。通过问题列表中的 issue 进入到其对应的个例列表中,与其他问题列表及个例分析类似,同样提供基本的个例信息,例如发生时间、上报时间、用户/设备 ID 等,同时也提供对应的筛选条件。下钻分析也是类似,提供了该 issue 下的个例上报趋势、版本分布等信息。
个例问题的消息详情中,提供了出错堆栈、全线程堆栈、操作日志、符号表、现场数据等信息,其中操作日志、符号表、现场数据等都与其他问题的消息详情一致,可参考 崩溃问题详情,此处重点解释出错堆栈和全线程堆栈。
出错堆栈
出错堆栈即为前述中提到的周期性采集的主线程调用栈,提供了三种展示方式:时间片、堆栈树、火焰图。这三种方式都是用同一份数据生成的,只是以不同的方式呈现,方便查看。
时间片:采集的原始数据,以采集的时间区间的顺序依次展示,其中 TimeSlice: xx~xx 标识对应的采集区间内的调用栈详情,点击查看即可。在采集和上报时,会将相同时间片的相同调用栈进行合并,展示时亦是如此。例如 TimeSlice: 10~20 表示采集区间为10 - 20时刻都是一样的调用栈,即这里一直在执行同样的逻辑。这里的时间区间为相对值,表示在最后采集时间范围内的第几次采集。由于上报堆栈均为判定疑似 ANR 发生时刻的最近5s内采集,序号将按“由远及近”的规则表示对应的采集周期。例如序号1,即表示发生 ANR 判定时保留的最早一次的采集数据,后续序号以此类推。
堆栈树:堆栈树为更为有效的一种表现方式,其通过对时间片数据从栈底依次往上聚合,生成的调用树,其每一个节点表示对应方法的执行时间,可以更加直观的发现卡顿方法及其时间。
火焰图:堆栈树的图形化表示方式。

出错堆栈直接展示了卡顿的时间和对应的方法,对于一般问题而言,基本可以定位到导致异常的方法。出错堆栈只包含主线程的状态,但有些 ANR 问题可能是由于主线程等待锁导致的,此时需要知道锁被何处持有,才能更好的定位具体原因。此时就需要用到全线程堆栈了。
全线程堆栈
全线程堆栈是在判定疑似 ANR 时捕获的所有线程的调用栈,包括主线程自己,其目的是方便业务查看锁竞争的问题。
一般而言,若主线程因为锁等待造成卡顿,按照调用栈找相同的锁使用逻辑,可以查看其他线程是否持有对应锁或等待相同的锁,便可以知道是否发生了死锁或长时间的锁等待等问题。
