布景介绍
本文在 2024 年初最新一期『抖音客户端根底技能大揭秘』技能沙龙活动中已做过专题共享,本次将内容重新整理文章进行共享。
抖音作为一个超大型的运用,咱们在 ANR 问题管理上面对着很大的应战。首要关于存量问题的优化,因为短少有用的归因手段,一些长时刻的疑难问题一直难以打破处理,例如长时刻位于 Top 1 的 nativePollOnce 问题。一起咱们在防劣化上也面对很大的压力,版别快速迭代引入的新增劣化,以及线上改变导致的激增劣化,都需求投入许多的人力去排查定位,无法在第一时刻快速修复止损。
ANR 原理简介
已然咱们要建造的是 ANR 归因渠道,首要需求了解下什么是 ANR ?它是 Android 系统定义的一种“运用程序无呼应”的反常问题,目的是为了监控发现运用程序是否存在交互呼应慢或卡死的问题。从用户的视角来看,产生 ANR 时设备上会呈现提示运用无呼应的弹窗,甚至在一些机型上或许就直接闪退了。所以 ANR 与其他溃散问题一样,是一种会对用户体会形成严峻打断的反常问题。
接下来咱们从系统的规划原理来看下为什么会产生 ANR ?以一种常见的播送超时引起的 ANR 为例,首要系统 AMS 服务会经过 IPC 办法将一个有序播送发送给运用进程,并在一起发动一个超时监控。运用进程在 Binder 线程接收到播送之后,会将其封装成一个音讯 Message 参加到主线程的音讯行列里等候履行。正常状况下,播送音讯在运用内都会得到及时呼应,然后通知系统 AMS 服务取消超时监控。可是在一些反常的状况下,假如在系统设置的超时到来之前,目标音讯还没有调度履行完结的话,系统就会断定呼应超时并触发 ANR。
归因计划现状
当时业界针对 ANR 问题的归因手段有哪些?第一种是传统归因计划:依据系统生成的 ANR Trace 和 ANR Info 来定位问题原因。这儿的 ANR Trace 是在问题产生时系统通知运用本身 dump 收集的各个线程的仓库以及状况信息,而 ANR Info 中则包含了 ANR 原因、系统以及运用进程的 CPU 运用率 和 IO 负载等信息。关于像下图中这类因为当时音讯严峻耗时或卡死引起的 ANR,这种系统原生的计划能够协助咱们快速的定位到问题仓库。
可是它也存在一个显着的问题,早年面的原理剖析能够知道,播送等系统组件音讯在参加到主线程之后,是按照它在音讯行列中的先后顺序来履行的,所以有或许是之前的前史音讯存在严峻耗时然后引起的问题。这种状况下在 ANR 实践产生时系统抓取的仓库很有或许就现已错过了问题的现场,依据这样的数据进行归因得到的效果也是不准确的。
第二种是慢音讯归因计划:经过监控主线程音讯的履行状况,并结合耗时音讯的采样抓栈来定位问题原因。这个计划处理了前面传统归因计划中存在的问题,提供了一种更细粒度的监控和归因才能,能够一起发现当时和前史的耗时音讯,以及其间或许存在的耗时问题仓库。
但这个计划也相同存在的一些不足之处, 因为关于像抖音这样的大型运用来说,ANR 一般是因为各种复杂的归纳性要素导致的,包含子线程 / 子进程 CPU 抢占、运用 / 系统内存不足等也都会对主线程的履行功率形成影响,直接导致主线程全体都变慢了。在这种状况下,主线程的慢音讯或仓库或许并不是问题的根本原因,相同以此得到的归因效果也是不全面的。
所以总结一下现在的现状,现有的 ANR 归因计划存在以下几个痛点问题:首要是归因不准确,归因效果难以消费,不能真实处理问题。其次是归因才能少,关于复杂问题难以定位根本原因。终究是归因功率低,人工排查周期长。
建造思路
接下来要点介绍下 ANR 归因渠道的建造思路,渠道归因系统首要环绕以下三个方向进行建造:
- 单点问题归因:首要需求对单个 ANR 问题完结精准的归因,这也是咱们整个归因系统建造的要点和根底。
- 聚合问题归因:其次是线上大数据的聚合问题归因,协助咱们聚集 Top 要点问题。
- 劣化问题归因:终究线上灰度以及全量版别劣化问题的主动归因,提升新增 / 激增问题的处理功率,现在正在建造中,本次共享就不展开介绍了。
单点归因思路
单点 ANR 问题的归因能够分为三个步骤,首要需求从原理出发清晰 ANR 问题区间;接下来对问题进行粗归因,也便是一种定性的剖析,比方说是主线程堵塞卡死、仍是 CPU 抢占或是内存反常导致的问题;终究便是进一步进行细归因,也便是需求定位到详细的问题代码,能实践指导咱们消费并处理问题。
问题区间
首要从 ANR 问题的原理出发,来剖析一下如何大致确认 ANR 问题区间。咱们相同仍是以上面的播送音讯超时引起的 ANR 为例,问题产生的时刻顺序为:从系统 AMS 服务发送有序播送并发动超时监控开端,到运用进程将该播送音讯参加到主线程音讯行列,并按照行列中的先后顺序等候调度履行。当系统进程的超时时刻完毕前,对应的播送音讯还没有履行完结并通知系统,系统就会断定呼应超时并触发 ANR。这儿咱们能够以 ANR 实践产生时刻为完毕点,往前回溯对应的超时时刻(这儿不同的 ANR 原因会对应不同的超时时刻设置),也便是后续需求确诊剖析 ANR 问题区间范围。
粗归因
在清晰 ANR 问题的时刻范围后,咱们需求从技能视点来拆解下,如何进行粗归因的定性剖析。从上面的原理剖析能够推导出,引起 ANR 的根本原因便是系统组件音讯(包含 input 事件)在运用侧没有得到及时的履行。而咱们知道这些要害目标音讯都是在主线程中进行消费处理的,所以这儿的要害点便是 ANR 区间内之前的这些音讯为什么履行耗时 ?从系统的视点来看,一切代码逻辑的履行能够分为 On-CPU 和 Off-CPU 两种状况:
- On-CPU:对应 Running 状况,即当时使命正在占用 CPU 资源进行核算处理。已知 核算耗时 = 核算量 / 核算速度,这儿核算速度会遭到 CPU 硬件本身的限制,比方 CPU 中心频率以及当时运转在大核或小核上。另一个跟运用本身联系比较严密的便是核算量,例如主线程在履行 CPU 密集型的操作,比方 JSON 序列化 / 反序列化,或是在处理许多的高频事务音讯。
- Off-CPU:包含 Runnable 和 Sleep 两种状况。Runnable 代表使命所需的资源已安排妥当,正在 CPU 运转行列上等候调度履行,这儿除了遭到系统本身的调度策略的影响之外,也跟当时相同已安排妥当并等候调度的使命数量有关。假如子线程或子进程有许多 CPU 耗时使命在等候履行的话,因为总的核算资源是有限的,相互之间频频的抢占也会影响主线程的履行功率。Sleep 代表使命在堵塞等候资源,比方等候 Lock、IO、同步 Binder 以及内存 Block GC 等。一般状况下咱们应该避免在主线程产生这类 Block 堵塞问题,一起这也是比较常见的一类处理卡顿或 ANR 的优化手段。
下面咱们来看下第一种主线程反常音讯直接引起的 ANR,它或许是因为当时音讯严峻耗时或卡死导致的,也或许是因为之前的一个或多个前史耗时音讯引起的 ANR。
第二种便是后台使命 CPU 资源抢占引起的ANR,它会直接影响主线程的履行功率。从下图中能够看到在子线程 CPU 负载变高之后,主线程的全体功能开端下降变慢,这种状况下就会更容易产生 ANR。最典型的便是在冷发动的场景下,经过降级或打散 CPU 耗时的后台使命,咱们现已验证能够有用的下降 ANR 率以及缩短发动首刷耗时。
终究一种内存等资源反常问题,例如虚拟机 Java 内存不足时,GC 线程就会开端变得活泼并进行频频 GC,这相同也会抢占主线程 CPU 资源或 Block GC 等候,然后导致 ANR 问题的产生。关于抖音这样的视频类大型运用,线上内存问题导致的卡顿或 ANR 问题的占比较高,现在也在专项管理优化中。
依据以上的演绎推理,总结一下咱们的归因思路首要包含以下几类:第一,主线程本身的反常问题,例如存在严峻的堵塞等候或者 CPU 繁忙等问题。第二,后台使命抢占 CPU 资源导致的反常问题。终究,内存 / IO 等系统资源不足导致的反常问题,现在正在探索中,本次共享就不展开介绍了。
细归因
主线程音讯反常
首要来看主线程音讯反常的归因思路:咱们需求先对主线程音讯进行监控,这儿包含三种状况,现已履行完结和正在履行中的音讯以及音讯行列中待履行的音讯。关于现已或正在履行的音讯,咱们首要重视它们的耗时状况,经过剖析系统源码咱们能够知道,主线程音讯行列里处理的音讯总共包含三种:Java 音讯、Native 音讯和 Idle Handler。而关于待履行的音讯,咱们首要重视其间的数量,然后判断是否存在许多音讯堆积的反常问题。
关于主线程反常音讯的问题类型,第一类便是耗时音讯,即在问题区间内存在一个或多个耗时大于阈值的慢音讯。另一种是高频音讯,也便是存在呈现次数以及累计耗时超越必定阈值的高密度音讯。这儿经过对事务音讯的 target、callback 和 what 等信息进行聚合剖析,有时也能够帮忙定位到导致问题的事务方。
可是只是找到反常音讯并不能直接协助咱们处理问题,还需求对音讯的耗时原因做进一步的归因剖析,找到其间引起音讯耗时的问题函数。接下来介绍一下线上 Trace 数据收集计划,这儿咱们采取类似 Matrix 计划,经过 ASM 字节码东西,在编译时对运用内的事务代码进行插桩,也便是在函数的进口和出口插入一行统计代码,来记载当时办法的运转耗时。为了下降收集时的功能损耗,将办法 begin / end 状况标识、当时办法 ID 以及时刻戳的 Diff 相对值,兼并运用一个 64 位的变量来记载。在 ANR 等反常问题产生时,再将 Ring Buffer 中记载的数据进行上报,在后端数据链路处理生成对应的 Trace 仓库,并提供给后续的确诊算法进行主动化剖析,以及 Perfetto 人工可视化剖析的需求。
但关于抖音这样的大型运用来说,插桩计划也会有一些弊端,当插桩的函数过多时,会对包体积以及功能产生负面的影响。为了尽或许下降监控东西对线上用户体会的影响,咱们提出了精准插桩的计划!因为结合关于线上问题的剖析诉求来看,咱们要点重视的是上层履行的事务函数,比方页面生命周期、事务音讯等进口办法,以及底层那些或许耗时的事务函数。所以咱们依据静态代码剖析的根底才能,剖析提取出带有耗时特征的函数来进行插桩,例如下面表格中带有锁要害字的函数、存在 Native / IO 等调用的函数以及特别复杂的大办法等。在这个精准插桩的优化策略之下,大幅减少了约 90% 的插桩数量!
在大幅精简插桩数量之后,咱们在消费线上数据时又面对到一个问题,便是仅有插桩的仓库信息太少,有时难以协助咱们实践定位到产生问题的代码。为了处理这一痛点问题,咱们又规划了插桩和抓栈数据拟合的优化计划,其原理便是经过对耗时超越必定阈值的慢函数进行抓栈上报,然后在服务端再将插桩与抓栈数据依据时刻点进行拟合,补齐其间缺失的事务和系统仓库信息。如下图中所示,当插桩仓库中连续两个节点能在抓栈数据中找到最小间距的数据并对齐时,抓栈数据中对应层之间的数据将会被补全到到插桩数据中,生成新的拟合仓库数据,图中的 D 便是被补全的数据。
在获取到线上问题产生时的详细 Trace 数据后,咱们需求进一步找到其间引起耗时的问题函数,常见的问题函数类型包含以下两种:
- 慢函数:是指函数的履行耗时超越必定的阈值;而且从实践可消费性的视点来看,咱们预期是要能找到更接近叶子节点的事务慢函数。所以需求依据实践调用仓库的状况,进一步剔除底层的根底库或东西类办法,以及存在相同调用链路的亲缘父子节点,来找到最合适的慢函数问题。
- 高频函数:对应便是单个尽管并不耗时,可是因为次数许多,累计履行耗时超越阈值的函数;依据咱们以往的经验来看,关于高频函数的优化一般也能带来不错的收益。
当然只是知道函数慢也是不够的!咱们结合一个线上实践的示例来看,经过 Trace 能够发现符号红框的这儿有一个事务函数履行比较耗时,可是这儿为什么会耗时呢?咱们要如何进行优化呢?现在仅有的仓库信息并不能满足咱们的归因需求。之后经过剖析事务代码并补齐了缺失的“要害”信息之后,咱们就能够清晰知道这儿耗时的原因是因为锁竞赛导致的,而且咱们还进一步弥补了当时持有锁的线程以及仓库等重要信息。
为了更好的对慢函数的耗时进行归因,除了前面收集的 Trace 仓库数据之外,咱们还需求弥补一些要害的上下文信息,这儿就统称为精细化数据。比方上面说到的锁,还有函数的 CPU-Time、Binder 调用的名称、IO 读写的文件路径和大小、制作渲染相关的 RenderNode,以及内存 Block GC 等相关信息。
所以回顾总结一下主线程音讯反常的归因流程,首要需求清晰当时 ANR 的问题区间,然后找到其间的反常音讯(耗时音讯或高频音讯),进一步下钻找到其间引起耗时的问题函数(慢函数或高频函数),终究再结合精细化数据对其耗时原因进行归因。
后台使命反常
接下来再看下后台使命 CPU 反常的归因思路:首要咱们需求清晰是否存在后台使命对主线程 CPU 资源产生抢占的问题,这儿能够结合主线程的非自愿上下文切换以及调度状况的信息,来观测主线程是否有较多的时刻都花费在等候系统调度上。假如存在显着的反常状况,再结合系统和运用的 CPU 运用率信息,能够进一步先定位到是运用的子线程 / 子进程,或是要害系统进程(如 dex2oat 进程等),仍是其他运用进程形成的 CPU 资源抢占。
关于运用本身形成的 CPU 抢占问题,咱们需求进一步定位到详细的问题代码。所以咱们在之前的 Trace 收集计划的根底上,进行了严重的晋级改造,扩展支撑了全线程的 Trace 数据收集。
单线程 Trace | 多线程 Trace | 阐明 | |
---|---|---|---|
Flag 状况位 | 2 bit | 2 bit | 代表函数开端/完毕: 3(二进制0b11)= catch, // 预留 2(二进制0b10)= throw,// 预留 1(二进制0b01)= begin, 0(二进制0b00)= end |
Method ID | 20 bit | 20 bit | 代表插桩的办法 ID,最大支撑 1048575 个函数 |
Thread ID | – | 15 bit | 代表当时线程的 TID |
Timestamp | 42 bit | 27 bit | 代表当时函数履行时与基准时刻的相对时刻,多线程形式下最大支撑 134,217,727 ms = 约 1.5 天 |
因为后台使命咱们要点重视的是 CPU-Time 耗时,所以在收集函数的 Wall-Time 履行耗时之外,一起也支撑函数粒度的 CPU-Time 耗时收集,并在后端进行处理相关。这儿出于功能损耗上的考虑,咱们会进一步精简控制一起需求收集 CPU 时刻的插桩函数数量,例如仅对系统的生命周期办法,以及子线程的 Runnable、Callable 以及二方 / 三方的使命结构进口办法,以及少量的要害特征办法才敞开,而且会设置最小的采样间隔时刻。
关于 CPU-Time 的获取一般有两种办法:一种是经过定时读取 proc 文件系统下的文件来解析获取,这种办法假如想要准确到办法等级需求适当高的读取频率,这种高频率读取文件并解析的功能损耗很高,不适合在线上办法等级的 CPU-Time 收集;另一种则是经过 Android 提供的 SystemClock.currentThreadTimeMillis()
办法或者 Native 层的 clock_gettime(CLOCK_THREAD_CPUTIME_ID)
办法获取当时线程的 CPU-Time,这种办法比较适合收集办法等级的 CPU 耗时,在办法开端和完毕时别离调用前述办法再核算差值即可,因而咱们线上收集挑选的也是这个计划。
相同回顾总结一下后台使命反常的归因流程,在 ANR 的问题区间内,首要需求清晰是否存在对主线程履行功率产生显着影响的 CPU 资源抢占,假如是运用本身的问题,先找到运用内 CPU 负载较高的线程或进程,进一步定位到对应反常阶段里的后台使命代码,终究再结合精细化数据对其 CPU 耗时原因进行归因。
聚合归因思路
依据以上对 ANR 单点问题进行确诊剖析后产出的归因定论,咱们能够进一步结合线上大数据进行聚合归因,然后协助咱们更好的聚集到 Top 要点问题的优化上。
归因标签
首要是对归因标签的聚合剖析,首要包含以下几类:
- 粗归因标签:针对 ANR 问题定性的归因分类标签,包含主线程堵塞、高频音讯、CPU 抢占、堆内存不足等。
- 细归因标签:针对细归因定位到的问题代码的精细化归因标签,包含主线程锁、IO、Binder 或者 Block GC 堵塞耗时等。
- 事务归因特征:产生 ANR 时用户所在场景页面等事务维度的特征标签,有时也能够辅助快速定位到问题相关的事务方。
如下图所示,经过对以上不同归因标签的多维聚合剖析,能够协助咱们对线上 ANR 问题的特征散布有一个全局的了解和认知,一起也能指导咱们在归因才能上下一步需求要点攻坚的方向。
反常问题
其次是对细归因产出的反常问题进行聚合剖析, 现在首要包含主线程反常函数、后台使命以及内存这三个维度。聚合后的问题列表支撑渗透率、耗时均值、PCT 50 / 90 耗时以及场景等维度的统计数据,能够协助咱们辨认出线上全体占比较高或耗时特别严峻的这类问题。
在进入反常函数的归因概况页之后,能够检查当时问题函数在线上大数据聚合后的火焰图,其间 Caller 仓库代表上层不同事务方的调用次数散布状况,而 Callee 仓库则是一切子函数的耗时散布状况。
终究,依据以上的归因标签、反常问题以及事务归因信息,渠道会产出一个对 ANR 问题终究的归因定论以及对应的归纳置信度评分。
落地效果
接下来再介绍一下渠道现在的落地效果:首要这个事例是一个发动阶段的 ANR 问题,咱们从主线程 Trace 中能够剖析定位到主线程的耗时函数,而且经过细归因标签的效果,能够清晰知道是一个锁耗时的问题。进一步结合锁的概况信息进行下钻剖析,经过当时子线程持有锁的聚合仓库,发现是因为某个后台使命的履行机遇改变提早了,然后与主线程某个使命产生了锁竞赛抵触,导致主线程长时刻的堵塞等候引起了 ANR。
第二个事例是一个主线程高频音讯问题,从定位到的问题函数能够发现其调用的十分高频,平均在一次 ANR 里会呈现了上千次!经过进一步剖析发现是因为某个事务的逻辑 Bug,导致在特定场景下会发送许多的重复音讯,导致主线程音讯行列堵塞引起的 ANR 劣化。
第三个事例是一个子线程高频使命问题,经过定位到的后台使命能够发现其在多个子线程的呈现次数都十分高频,而且累计的 CPU 耗时也比较高。进一步剖析发现也是某个事务的 Bug 问题,在特定场景下会向子线程发送许多的重复使命,而且因为这些异步使命内部还会给主线程 Handler 发送或删除音讯,所以除了会抢占 CPU 资源之外,还会直接导致主线程音讯行列在遍历取音讯时会产生高频的锁竞赛耗时,两个要素叠加之下引起的 ANR。
终究总结下渠道过去一年的阶段性效果,总共累计发现了有用问题 88 个,修复并优化其间 56 个,一起帮忙抖音 / 抖极的大盘 ANR 率别离下降了 -13.06% 和 -8.70% ,并取得了不错的事务收益。
总结展望
抖音 ANR 主动归因渠道未来的规划首要包含以下三个方面:
- 归因系统:继续打磨监控才能和归因算法,包含探索完善 Java / Native 内存、制作渲染以及 Native Trace 等方向上的精细化归因才能。
- 防劣化系统:继续优化线上劣化归因和消费流程,提升线上主动归因准确率,以及劣化问题的消费处理功率。
- 专家系统:沉积专家经验,并测验结合大模型等新技能,经过对技能特征和事务特征进行精细化聚合剖析,进一步提升问题发现和处理功率。
参加咱们
抖音根底技能客户端团队是一个深度追求极致的团队,咱们专注于 Android / iOS 的体会、稳定性、架构、编译构建、工程功率等方向的深耕,保障超大规模团队的研发功率和数亿用户的运用体会。现在北京、上海、深圳等地都有人才需求,欢迎有志之士与咱们一起建造亿级用户全球化 APP! 你能够进入字节跳动招聘官网查询「抖音根底技能客户端」相关职位,也可邮件联系 chenjiawei.kisson@bytedance.com 咨询相关信息或者直接发送简历内推!