背景
卡頓 & ANR 在各 APP 中都是非常影響用戶體驗(yàn)的問(wèn)題,關(guān)于其的分析和治理一直也是個(gè)老生常談的話題。過(guò)去調(diào)查卡頓 & ANR 問(wèn)題主要依賴(lài)上報(bào)的堆棧和 traceInfo 文件,通過(guò)這些信息還原問(wèn)題的現(xiàn)場(chǎng)情況。但是在實(shí)踐過(guò)程中發(fā)現(xiàn),現(xiàn)有監(jiān)控機(jī)制下堆棧的抓取時(shí)機(jī)是晚于問(wèn)題發(fā)生的,大部分情況獲取到的是問(wèn)題發(fā)生后某一瞬間的堆棧,隨機(jī)性強(qiáng),是不置信的,無(wú)法反映問(wèn)題的真實(shí)現(xiàn)場(chǎng)情況,同一個(gè)問(wèn)題可能聚合到不同堆棧中,無(wú)法清晰的歸類(lèi)和定位問(wèn)題,這就使得很多開(kāi)發(fā)者清楚原理,但到了具體 case 時(shí)無(wú)從下手,調(diào)查起來(lái)缺乏方向甚至因堆棧聚合的不置信而陷入了錯(cuò)誤的排查方向,效率低下。另一方面,大多能夠準(zhǔn)確衡量性能的工具本身會(huì)帶來(lái)嚴(yán)重耗時(shí)問(wèn)題,無(wú)法用于線上,而性能問(wèn)題大多發(fā)生于復(fù)雜的線上用戶場(chǎng)景。所以,如何對(duì)卡頓 & ANR 進(jìn)行有效的防治就是我們需要考慮的問(wèn)題。
卡頓 & ANR 治理現(xiàn)狀和痛點(diǎn)
過(guò)去幾年,在業(yè)務(wù)發(fā)展的同時(shí)積累了大量的卡頓 & ANR 問(wèn)題,對(duì)用戶的使用體驗(yàn)帶來(lái)了極大的負(fù)面影響。隨著治理工作的進(jìn)行,現(xiàn)有的監(jiān)控機(jī)制暴露出一定的問(wèn)題,堆棧不置信、聚合錯(cuò)誤、缺乏正確信息、缺乏有效防治策略,這些成了制約治理工作進(jìn)行的瓶頸。
卡頓 & ANR 現(xiàn)狀
長(zhǎng)期以來(lái),新老問(wèn)題的不斷疊加,同時(shí)沒(méi)有系統(tǒng)的進(jìn)行相關(guān)防治工作使得數(shù)據(jù)指標(biāo)常態(tài)高水位,影響的用戶以及發(fā)生次數(shù)都很不樂(lè)觀。
在治理之初,卡頓周均影響用戶比例達(dá)到 10‰ 左右,受影響的用戶平均 5 次卡頓,ANR 影響用戶則常態(tài)高水位保持在 6‰ 左右,受影響用戶平均 ANR 2 次,這些數(shù)據(jù)在公司的各大 APP 中都排名很差。
對(duì)問(wèn)題進(jìn)行篩查發(fā)現(xiàn),問(wèn)題呈現(xiàn)頭部集中,整體分散的現(xiàn)象,TOP 2 問(wèn)題占總體的 30%,其余問(wèn)題零零散散的分布在 60000 個(gè)不同的堆棧聚合上,觀察這些不同的堆棧聚合,TOP 2 問(wèn)題落在了系統(tǒng)的堆棧上,同時(shí)很多小量級(jí)聚合并非直觀上的耗時(shí)點(diǎn),這些現(xiàn)象給我們初期的治理工作帶來(lái)了很大的困擾,占比極大的 TOP 問(wèn)題優(yōu)先級(jí)最高,但是如何導(dǎo)致,需要如何優(yōu)化,分散在 60000 個(gè)堆棧聚合上的問(wèn)題應(yīng)該如何切入。
另外,長(zhǎng)期以來(lái)缺乏有效的增量問(wèn)題防治能力。在開(kāi)發(fā)、測(cè)試階段沒(méi)有專(zhuān)項(xiàng)測(cè)試,問(wèn)題很少暴露,也缺乏持續(xù)跟進(jìn)計(jì)劃和問(wèn)題復(fù)現(xiàn)定位能力,在灰度、線上等用戶場(chǎng)景下報(bào)警策略單一,只有新增堆棧聚合情況下才會(huì)觸發(fā)報(bào)警,實(shí)際運(yùn)行中發(fā)現(xiàn)報(bào)警策略很少觸發(fā),大多情況下也無(wú)法消費(fèi)。
卡頓 & ANR 檢測(cè)機(jī)制及問(wèn)題
首先,我們來(lái)看一下 TOP 2 問(wèn)題的堆棧表現(xiàn)。
TOP 2 問(wèn)題聚合到了 nativePollonce 和 nSyncAndDrawframe 系統(tǒng)堆棧上,占比分別達(dá)到了 20% 和 10%,nativePollonce 是主線程消息機(jī)制下的消息分發(fā)函數(shù),nSyncAndDrawframe 是頁(yè)面的基礎(chǔ)繪制函數(shù),直觀上看沒(méi)有問(wèn)題,對(duì)此我們?cè)诔跗谶M(jìn)行了一系列的常規(guī)分析和嘗試。以 TOP 1 的 nativePollonce 為例。首先,常規(guī)懷疑該方法本身耗時(shí),分析了方法在 Java 層和 native 層的執(zhí)行邏輯。
看到在 native 層有 epoll_wait 調(diào)用,通過(guò)在 C++ 層 hook 相關(guān)方法驗(yàn)證,沒(méi)有發(fā)現(xiàn)問(wèn)題。接著我們?cè)?Java 層構(gòu)造一個(gè)一直存在的 idleTask,使得消息隊(duì)列空閑時(shí)就執(zhí)行 idle 任務(wù)而不休眠,驗(yàn)證發(fā)現(xiàn)問(wèn)題仍然存在。再看 Java 層邏輯。
這部分是關(guān)于同步屏障的處理,異步更新 UI 可能會(huì)導(dǎo)致同步屏障出現(xiàn)多線程問(wèn)題而無(wú)法移除,驗(yàn)證后排除該可能。調(diào)查至此,并沒(méi)有找到該問(wèn)題的明確原因,排名第二的 nSyncAndDrawframe 的問(wèn)題與此類(lèi)似,經(jīng)過(guò)埋點(diǎn)調(diào)查,很多 nSyncAndDrawframe 問(wèn)題下的調(diào)用鏈路并不耗時(shí)。
此時(shí),我們回過(guò)頭來(lái)看一下目前的監(jiān)控機(jī)制。對(duì)于卡頓 & ANR 的檢測(cè)和分析,長(zhǎng)期以來(lái)我們依賴(lài)于 NPTH 工具提供的能力。對(duì)于卡頓的監(jiān)控,采用攔截消息調(diào)度流程,在消息執(zhí)行前埋點(diǎn)計(jì)時(shí),當(dāng)耗時(shí)超過(guò)閾值時(shí),則認(rèn)為是一次卡頓,會(huì)進(jìn)行堆棧抓取和上報(bào)工作。ANR 的監(jiān)控則是通過(guò)定時(shí)輪詢,在線程中每 500ms 定時(shí)和 AMS 進(jìn)行交互,通過(guò) AMS 的 Error 信息來(lái)判斷是否發(fā)生 ANR,當(dāng)確定發(fā)生 ANR 時(shí),進(jìn)行堆棧的抓取和信息的上報(bào)。
在實(shí)際分析解決問(wèn)題時(shí),以上不論卡頓還是 ANR,在現(xiàn)有檢測(cè)機(jī)制下獲取到的堆棧及其他信息都存在一定的缺陷,無(wú)法有效解決問(wèn)題。
對(duì)于卡頓,由于是以 Message 為維度進(jìn)行檢測(cè),當(dāng)檢測(cè)到 Message 超時(shí)發(fā)生卡頓時(shí),拿到的堆棧是從 Message 開(kāi)始到當(dāng)前執(zhí)行 Method 的堆棧鏈路。實(shí)際上 Message 中可能執(zhí)行了幾千個(gè) Method,耗時(shí)點(diǎn)很可能是 Message 中的另外 Method 或者多個(gè) Method 耗時(shí)堆積導(dǎo)致 Message 超時(shí),這一點(diǎn)我們無(wú)法確認(rèn)。因此知道 Message 耗時(shí)對(duì)我們排查問(wèn)題幫助很小,我們還是無(wú)法定位到具體的可消費(fèi)的耗時(shí)點(diǎn),這就導(dǎo)致當(dāng)前的卡頓數(shù)據(jù)無(wú)法快速消費(fèi)。
對(duì)于 ANR,抓棧時(shí)機(jī)是定時(shí)輪詢有 ANR 發(fā)生才進(jìn)行的。一方面從發(fā)生 ANR 到開(kāi)始抓棧到抓棧完成都有一定的時(shí)間間隔,除了少部分循環(huán)等待、鎖等待等卡住場(chǎng)景能夠相對(duì)準(zhǔn)確抓到,大部分問(wèn)題抓到堆棧和問(wèn)題現(xiàn)場(chǎng)不匹配,堆棧會(huì)落在耗時(shí)點(diǎn)之后的調(diào)用鏈路上。另一方面對(duì)于那種多次耗時(shí)累積導(dǎo)致 ANR 的情況,單點(diǎn)的堆棧也無(wú)法定位問(wèn)題。
在此基礎(chǔ)上,我們接入了調(diào)度時(shí)序圖,調(diào)度時(shí)序圖就是主線程 MessageQueue 中的 Message 執(zhí)行情況,包括已執(zhí)行 Message、當(dāng)前 Message 和待執(zhí)行 Message,可以在 ANR 發(fā)生時(shí)一起上報(bào)。我們借助調(diào)度時(shí)序圖來(lái)看 nativePollonce 聚合下的 case:
可以看到,前邊有 Message 耗時(shí) 42s,而上報(bào)的堆棧當(dāng)前 Message 耗時(shí)很少,ANR 和當(dāng)前 Message 沒(méi)有關(guān)系。
這是一個(gè)多次耗時(shí)累積導(dǎo)致 ANR 的問(wèn)題,同樣當(dāng)前 Message 耗時(shí)很少。借助調(diào)度時(shí)序圖我們可以得出結(jié)論,nativePollonce 這類(lèi)問(wèn)題很可能和當(dāng)前堆棧沒(méi)有關(guān)系,聚合在 nativePollonce 是因?yàn)橄⒄{(diào)度是執(zhí)行頻率最高的函數(shù),抓棧時(shí)堆棧落到 nativePollonce 的頻率是最高的,這個(gè)堆棧信息對(duì)于我們解決問(wèn)題是無(wú)用的,那么是否可以借助調(diào)度時(shí)序圖解決問(wèn)題呢?
很遺憾,也不可以。調(diào)度時(shí)序圖展示的是 Message 級(jí)的耗時(shí)情況,類(lèi)似卡頓,我們即使知道了哪一個(gè) Message 耗時(shí),但 Message 中執(zhí)行的 Method 非常多,而且很多都是系統(tǒng)級(jí) Message,我們無(wú)法定位具體是哪些 Method 耗時(shí)。另外,單點(diǎn)的堆棧也無(wú)法定位問(wèn)題,這種情況無(wú)論當(dāng)前 Message 是否耗時(shí)都無(wú)法定位問(wèn)題,因?yàn)閱?wèn)題的原因和已執(zhí)行的耗時(shí) Message 是息息相關(guān)的。
經(jīng)過(guò)以上從原理分析和初期的案例調(diào)研,我們確認(rèn)了基于目前的卡頓和 ANR 機(jī)制及工具,無(wú)法獲取正確的問(wèn)題堆棧聚合,對(duì)于多次耗時(shí)導(dǎo)致的問(wèn)題更是無(wú)從下手,無(wú)法有效定位問(wèn)題和解決問(wèn)題。
增量問(wèn)題的防治
在優(yōu)化工作中,新增問(wèn)題的防治和存量問(wèn)題的治理同樣重要,只有堵住新增問(wèn)題,線上的情況才會(huì)隨著存量問(wèn)題解決越來(lái)越好。
關(guān)于增量問(wèn)題,之前并無(wú)有效的防治手段,僅有的線下測(cè)試和灰度/線上新增問(wèn)題線上報(bào)警也收效甚微。線下測(cè)試主要是開(kāi)發(fā)測(cè)試階段針對(duì)功能的測(cè)試以及一系列自動(dòng)化測(cè)試,這些測(cè)試并非針對(duì)卡頓 & ANR 等性能問(wèn)題設(shè)計(jì),對(duì)相關(guān)問(wèn)題敏感度和關(guān)注度不夠,同時(shí)機(jī)型和觸達(dá)的場(chǎng)景不足使得暴露出的問(wèn)題很少,而且缺乏必要的分析能力和分析工具,現(xiàn)場(chǎng)可用信息很少。
而灰度/線上新增問(wèn)題報(bào)警策略,準(zhǔn)確率和可消費(fèi)性都不高。只有出現(xiàn)新的堆棧聚合才會(huì)觸發(fā)報(bào)警,而通過(guò)對(duì)現(xiàn)狀的分析可知,除了個(gè)別死鎖、循環(huán)等待等 case 外,大部分 case 的堆棧具有很大的隨機(jī)性,要么落到 nativePollOnce/nSyncAndDrawframe 等無(wú)法消費(fèi)的系統(tǒng)堆棧,要么分散到各類(lèi)其他業(yè)務(wù)堆棧,分析人員拿到的信息大都是不置信的,這樣很可能發(fā)生這樣的情況:
總之,抓棧隨機(jī)性決定了我們無(wú)法定位真實(shí)原因,也就無(wú)法確定新增問(wèn)題的有效性,這就使得很多新增問(wèn)題被帶入線上,我們也在這種低效的惡性循環(huán)中不斷重復(fù)。
我們的訴求
經(jīng)過(guò)以上的分析和調(diào)研,我們的痛點(diǎn)可以歸結(jié)為以下三類(lèi):
現(xiàn)有的問(wèn)題現(xiàn)場(chǎng)堆棧對(duì)于由于抓棧的不準(zhǔn)確,無(wú)論對(duì)單點(diǎn)問(wèn)題排查還是多段耗時(shí)問(wèn)題排查都意義不大,無(wú)法正確的還原現(xiàn)場(chǎng)信息,這使得我們的問(wèn)題排查優(yōu)化進(jìn)展緩慢,甚至偏離正常的方向。現(xiàn)有的卡頓及調(diào)度時(shí)序圖等工具都是以 Message 為統(tǒng)計(jì)粒度,無(wú)法提供真正可優(yōu)化的耗時(shí)定位,而現(xiàn)有的以 Method 為統(tǒng)計(jì)粒度的工具由于性能和穩(wěn)定性問(wèn)題都只能運(yùn)行在線下。為此,我們希望有一套能夠高效運(yùn)行在線上的 Method trace 工具,用于卡頓及 ANR 的檢測(cè),以 Method 耗時(shí)為統(tǒng)計(jì)粒度,獲取卡頓/ANR 時(shí)用戶當(dāng)前和之前一段時(shí)間內(nèi)的 Method 執(zhí)行耗時(shí)情況,這樣我們可以完整的呈現(xiàn)問(wèn)題發(fā)生時(shí)刻以及之前一段時(shí)間的 Method 執(zhí)行耗時(shí)情況,高效清晰的定位問(wèn)題癥結(jié)所在。
針對(duì)增量問(wèn)題的防治,由于現(xiàn)有的能力無(wú)法識(shí)別問(wèn)題是否新增,導(dǎo)致在錯(cuò)誤的方向上耗費(fèi)太多精力,而真正的問(wèn)題無(wú)法被發(fā)現(xiàn)從而帶入線上,為此我們需要搭建增量問(wèn)題的防治體系,去體系化前置化的完成增量問(wèn)題的監(jiān)控、有效信息的提供、問(wèn)題的分發(fā),前置化預(yù)防才能避免問(wèn)題被帶入線上,體系化才能更高效更全面的最大限度發(fā)現(xiàn)問(wèn)題,同時(shí)將增量問(wèn)題的防治體系建設(shè)和問(wèn)題監(jiān)控解決能力建設(shè)結(jié)合起來(lái),建立一個(gè)自動(dòng)化、前置化、發(fā)現(xiàn)問(wèn)題全面、易消費(fèi)、分發(fā)及時(shí)的的全鏈路體系。
監(jiān)控體系建設(shè)
在目前的監(jiān)控體系下,堆棧抓取不準(zhǔn)確,堆棧聚合存在問(wèn)題,大量聚合在了無(wú)意義的堆棧上,現(xiàn)有的工具體系下,分析成本極高,大多數(shù)問(wèn)題無(wú)法得到有效消費(fèi),卡頓和 ANR 指標(biāo)長(zhǎng)期高位,這就要求我們盡快找到破解之法。
誠(chéng)然,最終導(dǎo)致彈出 ANR 彈窗的誘因很多,但是歸根結(jié)底,根本原因都是執(zhí)行超時(shí),而我們最需要關(guān)注的也是那些耗時(shí)較高的 Method,當(dāng) Method 耗時(shí)減少后,相應(yīng)的觸發(fā) ANR 的幾率也會(huì)隨之減少,為此我們就需要找出那些真正耗時(shí)卡頓的地方并對(duì)其進(jìn)行優(yōu)化。
針對(duì)以上的痛點(diǎn)和訴求,我們重新梳理了思路,對(duì)比了現(xiàn)有方案的優(yōu)缺點(diǎn)后,取長(zhǎng)補(bǔ)短,開(kāi)發(fā)了基于 Method 的高性能線上 trace 工具。在此基礎(chǔ)上,我們針對(duì) ANR、卡頓進(jìn)行了方案升級(jí)和全方位的體系建設(shè)。
基于 Sliver 的 ANR 治理方案介紹
針對(duì) ANR,我們希望獲取到發(fā)生 ANR 時(shí)前一段時(shí)間的堆棧記錄,以快速的找出發(fā)生耗時(shí)的 Method 調(diào)用堆棧。
Sliver 采用采樣的方式來(lái)定時(shí)獲取堆棧,我們?cè)?APP 啟動(dòng)時(shí)打開(kāi) Sliver 的監(jiān)控能力,根據(jù)不同機(jī)型傳入不同的采樣值,通常在低端機(jī)采樣值會(huì)大一些,在高端機(jī)采樣值會(huì)小一些,這樣最大限度降低獲取 trace 本身對(duì)性能的影響,Sliver 定時(shí)抓取堆棧,并對(duì)獲取到的堆棧做 diff 聚合、緩存以區(qū)分不同堆棧的關(guān)系。同時(shí),通過(guò) NPTH 的接口注冊(cè) ANR 的回調(diào),當(dāng)發(fā)生 ANR 時(shí),回調(diào)函數(shù)中將緩存的堆棧 dump 到文件,同時(shí)將文件隨 ANR 其他信息上報(bào)到 Sladar,這樣我們就可以在對(duì) case 的分析中使用精確的 trace 信息問(wèn)題定位,下圖說(shuō)明了針對(duì) ANR 的整體工作流程。
我們將這一套流程運(yùn)行起來(lái),收集了相關(guān) case,在同一個(gè) case 拿到相關(guān)信息對(duì)比。
以上三個(gè)圖是同一個(gè) case 中的不同信息,分別是堆棧、調(diào)度時(shí)序圖、trace,通過(guò) trace 能清晰看出問(wèn)題的原因所在。
目前該方案已在線下、灰度、眾測(cè)渠道常態(tài)開(kāi)啟,作用明顯,如下:
整體上,該方案的上線,使得我們能夠更清晰準(zhǔn)確的定位問(wèn)題原因,加快問(wèn)題的流轉(zhuǎn)解決,促進(jìn)各類(lèi)隱藏較深問(wèn)題的快速解決。
卡頓問(wèn)題的防治方案
不同于 ANR 問(wèn)題,卡頓問(wèn)題的標(biāo)準(zhǔn)是我們自己定義的,卡頓以及多次卡頓的疊加是導(dǎo)致 ANR 以及影響性能的大項(xiàng),現(xiàn)有的卡頓監(jiān)控只能拿到單一的堆棧鏈路,無(wú)法完整還原當(dāng)前卡頓產(chǎn)生現(xiàn)場(chǎng)全貌,基于此我們?cè)O(shè)計(jì)了基于 Sliver trace 的卡頓監(jiān)控體系。
先看整體流程圖:
主要包含兩個(gè)方面:
在監(jiān)控卡頓時(shí),首先需要打開(kāi) Sliver 的 trace 記錄能力,Sliver 采樣記錄 trace 執(zhí)行信息,對(duì)抓取到的堆棧進(jìn)行 diff 聚合和緩存。
同時(shí)基于我們的需要設(shè)置相應(yīng)的卡頓閾值,以 Message 的執(zhí)行耗時(shí)為衡量。對(duì)主線程消息調(diào)度流程進(jìn)行攔截,在消息開(kāi)始分發(fā)執(zhí)行時(shí)埋點(diǎn),在消息執(zhí)行結(jié)束時(shí)計(jì)算消息執(zhí)行耗時(shí),當(dāng)消息執(zhí)行耗時(shí)超過(guò)閾值,則認(rèn)為產(chǎn)生了一次卡頓。
當(dāng)卡頓發(fā)生時(shí),我們需要為此次卡頓準(zhǔn)備數(shù)據(jù),這部分工作是在端上子線程中完成的,主要是 dump trace 到文件以及過(guò)濾聚合要上報(bào)的堆棧。分為以下幾步:
之后,將 trace 文件和堆棧一同上報(bào),這樣的特征堆棧提取策略保證了堆棧聚合的可靠性和準(zhǔn)確性,保證了上報(bào)到平臺(tái)后堆棧的正確合理聚合,同時(shí)提供了進(jìn)一步分析問(wèn)題的 trace 文件。
上線后,我們通過(guò)和原卡頓體系進(jìn)行效果對(duì)比:
以上三圖分別是,針對(duì)高斯模糊問(wèn)題的原卡頓列表、現(xiàn)在卡頓列表、trace 。在原先的卡頓上報(bào)列表中,問(wèn)題分散到了不同的堆棧中,這是由于發(fā)生卡頓時(shí)抓棧隨機(jī),而現(xiàn)在的卡頓列表聚合到了單一的堆棧鏈路中,這是由于我們?nèi)∶恳粚佣褩V泻臅r(shí)最長(zhǎng)的函數(shù)組合成特征堆棧,通過(guò)trace也可以驗(yàn)證特征堆棧的有效性,能夠更準(zhǔn)確的定位問(wèn)題原因。同時(shí),trace 詳細(xì)的展示了函數(shù)調(diào)用鏈路,提供了深入分析問(wèn)題的能力。
經(jīng)過(guò) trace 和堆棧驗(yàn)證,該方式輸出的卡頓信息,堆棧聚合更加契合真正的卡頓點(diǎn),當(dāng)然一個(gè) Message 中可能有多個(gè)大大小小的耗時(shí)函數(shù)存在,trace 文件的存在能夠更全面的還原現(xiàn)場(chǎng)情況,二者的結(jié)合才能更好的解決問(wèn)題。
目前卡頓檢測(cè)體系已經(jīng)在眾測(cè)及線下自動(dòng)化常態(tài)運(yùn)行,產(chǎn)出數(shù)據(jù)來(lái)看均為線上存在問(wèn)題。
前置發(fā)現(xiàn)能力建設(shè)
基于 Sliver 能力的卡頓和 ANR 檢測(cè)方案,能夠極大提高解決問(wèn)題的效率,接下來(lái)我們需要考慮如何將這兩種能力常態(tài)的運(yùn)行起來(lái),服務(wù)于我們的日常存量問(wèn)題、增量問(wèn)題的防和治,尤其是將問(wèn)題的暴露階段提前,減少對(duì)用戶的影響尤為重要。為此,我們進(jìn)行了以下幾個(gè)方向的建設(shè)。
目前,測(cè)試平臺(tái)提供了一些自動(dòng)化測(cè)試 job,這些 job 大多以遍歷方式自動(dòng)的測(cè)試 APP 的功能,對(duì)所有功能優(yōu)先級(jí)一樣的觸達(dá),我們將我們的 ANR 檢測(cè)能力和卡頓檢測(cè)能力進(jìn)行集成打包,觸發(fā)自動(dòng)化 job,產(chǎn)出相關(guān)的卡頓和 ANR case。
分析對(duì)比這些 case 后發(fā)現(xiàn),線下上報(bào)的 TOP 問(wèn)題和線上問(wèn)題差異較大,不符合用戶真實(shí)的使用場(chǎng)景。線下檢測(cè)出的一些量級(jí)較大的 case 在線上場(chǎng)景出現(xiàn)的量級(jí)很小,影響的用戶很少,而線上一些影響用戶較多的 case,線下檢測(cè)卻上報(bào)很少。分析這是由于遍歷式的測(cè)試方案不符合真實(shí)的用戶行為,這會(huì)使我們?cè)谕苿?dòng)解決問(wèn)題中優(yōu)先級(jí)錯(cuò)誤,無(wú)法及時(shí)正確辨別那些真正量級(jí)高、影響用戶多、優(yōu)先級(jí)高的問(wèn)題,影響整體的優(yōu)化節(jié)奏。
為此,我們接入了更智能的基于用戶行為的測(cè)試策略,產(chǎn)出了更符合用戶真實(shí)行為的智能測(cè)試 job,基于此 job 進(jìn)行卡頓和 ANR 數(shù)據(jù)收集,采樣分析相關(guān)數(shù)據(jù)符合線上數(shù)據(jù)分布,在量級(jí)和影響用戶量級(jí)分布上更接近真實(shí)的用戶場(chǎng)景,得到正確的問(wèn)題優(yōu)先級(jí)。
同時(shí)利用測(cè)試平臺(tái)接口,我們構(gòu)建了完全自動(dòng)化的測(cè)試機(jī)制:基于最新 release 分支定時(shí)觸發(fā)打包平臺(tái)打包 -> 配置渠道為性能測(cè)試專(zhuān)用渠道 -> 成功后執(zhí)行自動(dòng)化測(cè)試生成數(shù)據(jù)。
線下的自動(dòng)化測(cè)試畢竟受機(jī)型、場(chǎng)景等條件限制,不易發(fā)現(xiàn)一些用戶個(gè)性化問(wèn)題。為此,在線上進(jìn)行問(wèn)題檢測(cè)顯得尤為重要。beta_version 和灰度渠道都是真實(shí)的用戶渠道,能夠覆蓋各種場(chǎng)景,但二者又有所不同,beta_version 用戶較少但活躍度更高。為此我們?cè)?beta_version 渠道集成了卡頓和 ANR 數(shù)據(jù)的收集方案。同時(shí),灰度渠道由于用戶數(shù)多,可以提供更全面的場(chǎng)景和用戶,我們也在灰度渠道集成了 ANR 方案,不過(guò)由于卡頓發(fā)生的頻率相對(duì)較高,考慮到灰度用戶多的特點(diǎn),我們暫未開(kāi)啟灰度渠道的卡頓采集。
很多時(shí)候需要對(duì)線上用戶遇到的問(wèn)題進(jìn)行動(dòng)態(tài)調(diào)查,相關(guān)調(diào)查能力雖然完備,但出于包大小的考慮很多時(shí)候并不會(huì)帶到線上。針對(duì)此類(lèi)問(wèn)題,需要有一種類(lèi)似于補(bǔ)丁但又相對(duì)輕量的方案,能夠動(dòng)態(tài)的下發(fā)能力到用戶的手機(jī)上。
為了提高西瓜 Android 客戶端的動(dòng)態(tài)調(diào)查能力,將所有的通用能力封裝成一個(gè)模塊,通過(guò)統(tǒng)一的接口進(jìn)行調(diào)度與事件分發(fā),結(jié)合插件化下發(fā)加載能力,實(shí)現(xiàn)精準(zhǔn)下發(fā)調(diào)查能力到任意手機(jī)上。
在實(shí)現(xiàn)上,整體流程如下圖:
可以分為宿主、插件、組件三部分來(lái)看:
基于此框架,我們可以根據(jù)需求以動(dòng)態(tài)下發(fā)插件的方式下發(fā)攜帶不同能力的插件包,同時(shí)利用 Setting 控制宿主執(zhí)行相應(yīng)的操作,完成動(dòng)態(tài)的定向下發(fā)特定能力到特定手機(jī)或某類(lèi)渠道的能力,這有以下優(yōu)點(diǎn):
目前,我們已將多種問(wèn)題調(diào)查能力進(jìn)行了集成,為線上問(wèn)題調(diào)查和修復(fù)提供了支撐。
卡頓數(shù)據(jù)的消費(fèi)鏈路建設(shè)
以上部分從線上、線下、動(dòng)態(tài)能力角度結(jié)合卡頓 & ANR 方案進(jìn)行了全方位的運(yùn)行,產(chǎn)出了易消費(fèi)可消費(fèi)的數(shù)據(jù),接下來(lái)我們需要完善消費(fèi)流程,提高問(wèn)題的解決效率。
針對(duì)產(chǎn)出的數(shù)據(jù),我們通過(guò)輕服務(wù)進(jìn)行數(shù)據(jù)處理,根據(jù) apm_open 開(kāi)放接口,我們可以拿到 job 對(duì)應(yīng)的卡頓& ANR 數(shù)據(jù)列表,遍歷列表,將每一個(gè) case 的相關(guān)信息進(jìn)行拼接,尤其是卡頓的 trace 文件鏈接,避免了文件下載鏈路較長(zhǎng)的弊端,降低優(yōu)化成本,之后將這些信息分發(fā)到對(duì)應(yīng)的跟進(jìn)群中。同時(shí),在 Sladar 上根據(jù)對(duì)應(yīng)的代碼修改人或模塊 owner 指定 owner 跟進(jìn)。效果如下:
同時(shí),針對(duì)需要獲取大量 trace 文件進(jìn)行分析的場(chǎng)景,我們也開(kāi)發(fā)了本地工具,便捷批量拉取 trace 文件。
總的來(lái)說(shuō),西瓜從基礎(chǔ)工具的開(kāi)發(fā)到在此之上卡頓 & ANR 方案的優(yōu)化到線上線下動(dòng)態(tài)前置發(fā)現(xiàn)能力建設(shè)再到最終的消費(fèi)鏈路,完成整個(gè)卡頓 & ANR 監(jiān)控體系的閉環(huán),在存量問(wèn)題解決、增量問(wèn)題防治、單點(diǎn)問(wèn)題跟進(jìn)、整體性能治理上發(fā)揮了重要作用。
典型案例介紹
堆棧聚合錯(cuò)誤案例
對(duì) TOP 1 的 nativePollonce 問(wèn)題撈取多個(gè) trace 樣本進(jìn)行分析,堆棧表現(xiàn)如下:
通過(guò) trace 看出其實(shí)是主線程在執(zhí)行數(shù)據(jù)庫(kù)操作,快速推動(dòng)解決。
通過(guò) trace 看出其實(shí)是 ClassLoader. 執(zhí)行了 20+s,查看源碼,發(fā)現(xiàn)是 PluginClassLoader.->....dex2oat....->Runtime.exec 這樣一個(gè)調(diào)用鏈路。
基于以上的堆棧,我們知道該問(wèn)題是在加載插件時(shí),驗(yàn)證 oat 文件不通過(guò)而觸發(fā)主線程 dex2oat 操作導(dǎo)致。因此我們提前在插件 Plugin 實(shí)例初始化時(shí),判斷 oat 文件是否有效,無(wú)效的話中斷插件狀態(tài)機(jī),置為不可用,同時(shí)異步重新生成 dex2oat 產(chǎn)物。
通過(guò) trace 清晰看出是直播插件內(nèi)部的初始化耗時(shí)嚴(yán)重導(dǎo)致問(wèn)題,而非上報(bào)的堆棧分析發(fā)現(xiàn),觸發(fā)主要發(fā)生在插件加載成功的回調(diào)中,基于現(xiàn)在的插件框架,插件的加載主要有兩條路徑:
為此,我們從兩個(gè)方面進(jìn)行了優(yōu)化:
非常規(guī)案例
有一類(lèi)這樣的問(wèn)題,看堆棧發(fā)生在 JSonObject clone = new JSonObject(origin.toString),在其中的浮點(diǎn)類(lèi)型轉(zhuǎn)換時(shí)。
看到這個(gè)堆棧的第一印象是該方法并不耗時(shí),堆棧偏移,然后拿到對(duì)應(yīng)的 trace 可以看到,確實(shí)是當(dāng)前方法非常耗時(shí)導(dǎo)致。
看 trace 的最下層,都是重復(fù)的位計(jì)算,推測(cè)是一個(gè)超級(jí)長(zhǎng)的 double 類(lèi)型數(shù)字導(dǎo)致的運(yùn)算過(guò)長(zhǎng),在灰度上收集對(duì)應(yīng)的 json 發(fā)現(xiàn)其中無(wú)此類(lèi)數(shù)據(jù),推測(cè)是 toString 的時(shí)候,會(huì)把存放的 double 數(shù)據(jù)轉(zhuǎn)成 string,然后 new 的時(shí)候又把 string 轉(zhuǎn)成 double,這兩次轉(zhuǎn)換可能會(huì)出現(xiàn)精度問(wèn)題,造成 double 的值變成了 1.9999999999999999999999 這種很長(zhǎng)的數(shù),然后計(jì)算耗時(shí)很長(zhǎng),導(dǎo)致 ANR。
為此,將上述 JSonObject clone = new JSonObject(origin.toString) 邏輯修改為遍歷 origin 內(nèi)容復(fù)制拷貝,驗(yàn)證后此問(wèn)題消失。
一類(lèi)問(wèn)題看堆棧報(bào)在了 HashMap.remove 方法中。
同樣,看到該堆棧,第一反應(yīng)是當(dāng)前方法并不耗時(shí),堆棧偏移導(dǎo)致,然而拿到對(duì)應(yīng)的 trace 后,我們發(fā)現(xiàn)確實(shí)是當(dāng)前方法導(dǎo)致的耗時(shí)。
從 trace 可以明顯看出,確實(shí)在 HashMap.remove 中卡了 40+s,結(jié)合 cpu 負(fù)載情況看,也并不是得不到調(diào)度導(dǎo)致。
深入分析,發(fā)現(xiàn)在多線程操作 HashMap 時(shí),若發(fā)生擴(kuò)容,可能會(huì)產(chǎn)生循環(huán)鏈表,進(jìn)而觸發(fā)死循環(huán),最終采用 ConcurrentHashMap 后解決該問(wèn)題。參見(jiàn):https://www.jianshu.com/p/c72af03abba5。
卡頓案例
有一類(lèi)動(dòng)畫(huà)問(wèn)題,動(dòng)畫(huà)本身是個(gè)簡(jiǎn)單的閃光動(dòng)畫(huà),內(nèi)部沒(méi)有復(fù)雜邏輯,關(guān)于其耗時(shí)的可信程度存疑,但是借助 trace 圖可以看到:
確實(shí)動(dòng)畫(huà)存在嚴(yán)重的耗時(shí)情況,清晰的展示耗時(shí)點(diǎn)。此時(shí)再看原先卡頓監(jiān)控和現(xiàn)在卡頓監(jiān)控的堆棧聚合效果:
上邊兩圖分別表示了原卡頓監(jiān)控和現(xiàn)在卡頓監(jiān)控的堆棧聚合效果,可以看到原卡頓監(jiān)控的堆棧聚合到了多個(gè)不同的堆棧鏈路下,這也是因?yàn)槠渥5碾S機(jī)性,這樣會(huì)使我們分散精力,也無(wú)法確定問(wèn)題真實(shí)原因,而在我們現(xiàn)有的卡頓體系下,堆棧高度精確聚合到唯一的堆棧鏈路上,借助 trace 信息,也可以驗(yàn)證堆棧的準(zhǔn)確性。基于此,我們可以精確定位和分析問(wèn)題。
有一種情況,如果一個(gè) Message 中每一層調(diào)用中的函數(shù)都非常耗時(shí),那么就會(huì)有多個(gè)聚合,此時(shí)的每一個(gè)聚合都是一個(gè)真實(shí)耗時(shí)鏈路,對(duì)應(yīng)的 trace 如下:
可以看到,所有函數(shù)的耗時(shí)一目了然,這樣可以清晰明確問(wèn)題之所在,找準(zhǔn)優(yōu)化方向。
以上案例介紹,展示了我們?cè)诳D和 ANR 方面調(diào)查能力的提升,大大提升了我們?cè)趩?wèn)題解決及防治上的能力,解決了長(zhǎng)期以來(lái)制約我們提升性能的瓶頸,為長(zhǎng)期的發(fā)展提升提供了支撐。
卡頓 & ANR 后續(xù)規(guī)劃
過(guò)去一段時(shí)間的監(jiān)控建設(shè)和治理工作取得了不錯(cuò)的成果,但是仍然存在許多問(wèn)題,主要有以下幾類(lèi):
基于這些仍然存在的問(wèn)題,接下來(lái),我們考慮做以下幾方面的工作:
總結(jié)
在上面我們整體介紹了過(guò)去一段時(shí)間西瓜 APP 的體系建設(shè)和治理工作,全方位的展示了我們的思考和各項(xiàng)短板的建設(shè),并使之成功用于優(yōu)化實(shí)踐和常態(tài)問(wèn)題防治,80% 以上的卡頓和 ANR 問(wèn)題能夠準(zhǔn)確還原現(xiàn)場(chǎng)信息,線上嚴(yán)重影響用戶體驗(yàn)的問(wèn)題得到了很大程度的緩解,同時(shí)有效遏制了新增問(wèn)題被帶入線上,為西瓜長(zhǎng)期常態(tài)性能問(wèn)題防治提供了參考。
加入我們
歡迎加入字節(jié)跳動(dòng)西瓜視頻客戶端團(tuán)隊(duì),我們專(zhuān)注于西瓜視頻 App 的開(kāi)發(fā)和基礎(chǔ)技術(shù)建設(shè),在客戶端架構(gòu)、性能、穩(wěn)定性、編譯構(gòu)建、研發(fā)工具等方向都有投入。如果你也想一起攻克技術(shù)難題,迎接更大的技術(shù)挑戰(zhàn),歡迎加入我們!
西瓜視頻客戶端團(tuán)隊(duì)正在熱招 Android、iOS 架構(gòu)師和研發(fā)工程師,最 Nice 的工作氛圍和成長(zhǎng)機(jī)會(huì),各種福利各種機(jī)遇,在北京、杭州、上海、廈門(mén)四地均有職位,歡迎投遞簡(jiǎn)歷!聯(lián)系郵箱:liaojinxing@bytedance.com ;郵件標(biāo)題:姓名-西瓜-工作年限-工作地點(diǎn)。