Matrix 中負責卡頓監控的組件是 TraceCanary,它是基於 ASM 插樁實現的,用於監控界面流暢性、啓動耗時、頁面切換耗時、慢函數及卡頓等問題。和 ResourceCanary 相似,使用前須要配置以下,主要包括幀率、耗時方法、ANR、啓動等選項:java
TraceConfig traceConfig = new TraceConfig.Builder()
.dynamicConfig(dynamicConfig)
.enableFPS(fpsEnable) // 幀率
.enableEvilMethodTrace(traceEnable) // 耗時方法
.enableAnrTrace(traceEnable) // ANR
.enableStartup(traceEnable) // 啓動
.splashActivities("sample.tencent.matrix.SplashActivity;") // 可指定多個啓動頁,使用分號 ";" 分割
.isDebug(true)
.isDevEnv(false)
.build();
TracePlugin tracePlugin = new TracePlugin(traceConfig);
複製代碼
接着在 Application / Activity 中啓動便可:android
tracePlugin.start();
複製代碼
除了以上配置以外,Trace Canary 還有以下自定義的配置選項:git
enum ExptEnum {
// trace
clicfg_matrix_trace_care_scene_set, // 閃屏頁
clicfg_matrix_trace_fps_time_slice, // 若是同一 Activity 掉幀數 * 16.66ms > time_slice,就上報,默認爲 10s
clicfg_matrix_trace_evil_method_threshold, // 慢方法的耗時閾值,默認爲 700ms
clicfg_matrix_fps_dropped_normal, // 正常掉幀數,默認 [3, 9)
clicfg_matrix_fps_dropped_middle, // 中等掉幀數,默認 [9, 24)
clicfg_matrix_fps_dropped_high, // 高掉幀數,默認 [24, 42)
clicfg_matrix_fps_dropped_frozen, // 卡頓掉幀數,默認 [42, ~)
clicfg_matrix_trace_app_start_up_threshold, // 冷啓動時間閾值,默認 10s
clicfg_matrix_trace_warm_app_start_up_threshold, // 暖啓動時間閾值,默認 4s
}
複製代碼
相比內存泄漏,卡頓監控報告的信息複雜了不少。github
出現 ANR 時,Matrix 上報信息以下:json
{
"tag": "Trace_EvilMethod",
"type": 0,
"process": "sample.tencent.matrix",
"time": 1590397340910,
"machine": "HIGH", // 設備等級
"cpu_app": 0.001405802921652454, // 應用佔用的 CPU 時間比例,appTime/cpuTime * 100
"mem": 3030949888, // 設備總運行內存
"mem_free": 1695964, // 設備可用的運行內存,不絕對,可能有部分已經被系統內核使用
"detail": "ANR",
"cost": 5006, // 方法執行總耗時
"stackKey": "30|", // 關鍵方法
"scene": "sample.tencent.matrix.trace.TestTraceMainActivity",
"stack": "0,1048574,1,5006\n1,15,1,5004\n2,30,1,5004\n", // 方法執行關鍵路徑
"threadStack": " \nat android.os.SystemClock:sleep(120)\nat sample.tencent.matrix.trace.TestTraceMainActivity:testInnerSleep(234)\nat sample.tencent.matrix.trace.TestTraceMainActivity:testANR(135)\nat java.lang.reflect.Method:invoke(-2)\nat android.view.View$DeclaredOnClickListener:onClick(4461)\nat android.view.View:performClick(5212)\nat android.view.View$PerformClick:run(21214)\nat android.os.Handler:handleCallback(739)\nat android.os.Handler:dispatchMessage(95)\nat android.os.Looper:loop(148)\nat android.app.ActivityThread:main(5619)\n", // 線程堆棧
"processPriority": 20, // 進程優先級
"processNice": 0, // 進程的 nice 值
"isProcessForeground": true, // 應用是否可見
"memory": {
"dalvik_heap": 17898, // 虛擬機中已分配的 Java 堆內存,kb
"native_heap": 6796, // 已分配的本地內存,kb
"vm_size": 858132, // 虛擬內存大小,指進程總共可訪問的地址空間,kb
}
}
複製代碼
其中,設備分級以下:數組
正常啓動狀況下:markdown
{
"tag": "Trace_StartUp",
"type": 0,
"process": "sample.tencent.matrix",
"time": 1590405971796,
"machine": "HIGH",
"cpu_app": 2.979125443261738E-4,
"mem": 3030949888,
"mem_free": 1666132,
"application_create": 35, // 應用啓動耗時
"application_create_scene": 100, // 啓動場景
"first_activity_create": 318, // 第一個 activity 啓動耗時
"startup_duration": 2381, // 啓動總耗時
"is_warm_start_up": false, // 是不是暖啓動
}
複製代碼
其中,application_create、first_activity_create、startup_duration 分別對應 applicationCost、firstScreenCost、coldCost:app
firstMethod.i LAUNCH_ACTIVITY onWindowFocusChange LAUNCH_ACTIVITY onWindowFocusChange
^ ^ ^ ^ ^
| | | | |
|---------app---------|---|---firstActivity---|---------...---------|---careActivity---|
|<--applicationCost-->|
|<--------------firstScreenCost-------------->|
|<---------------------------------------coldCost------------------------------------->|
. |<-----warmCost---->|
複製代碼
啓動場景分爲 4 種:ide
若是是啓動過慢的狀況:函數
{
"tag": "Trace_EvilMethod",
"type": 0,
"process": "sample.tencent.matrix",
"time": 1590407016547,
"machine": "HIGH",
"cpu_app": 3.616498950411638E-4,
"mem": 3030949888,
"mem_free": 1604416,
"detail": "STARTUP",
"cost": 2388,
"stack": "0,2,1,43\n1,121,1,0\n1,1,8,0\n2,99,1,0\n0,1048574,1,0\n0,1048574,1,176\n1,15,1,144\n0,1048574,1,41\n",
"stackKey": "2|",
"subType": 1 // 1 表明冷啓動,2 表明暖啓動
}
複製代碼
{
"tag": "Trace_EvilMethod",
"type": 0,
"process": "sample.tencent.matrix",
"time": 1590407411286,
"machine": "HIGH",
"cpu_app": 8.439117339531338E-4,
"mem": 3030949888,
"mem_free": 1656536,
"detail": "NORMAL",
"cost": 804, // 方法執行總耗時
"usage": "0.37%", // 在方法執行總時長中,當前線程佔用的 CPU 時間比例
"scene": "sample.tencent.matrix.trace.TestTraceMainActivity",
"stack": "0,1048574,1,804\n1,14,1,803\n2,29,1,798\n",
"stackKey": "29|"
}
複製代碼
在出現掉幀的狀況時,Matrix 上報信息以下:
{
"tag": "Trace_FPS",
"type": 0,
"process": "sample.tencent.matrix",
"time": 1590408900258,
"machine": "HIGH",
"cpu_app": 0.0030701181354057853,
"mem": 3030949888,
"mem_free": 1642296,
"scene": "sample.tencent.matrix.trace.TestFpsActivity",
"dropLevel": { // 不一樣級別的掉幀問題出現的次數
"DROPPED_FROZEN": 0,
"DROPPED_HIGH": 0,
"DROPPED_MIDDLE": 3,
"DROPPED_NORMAL": 14,
"DROPPED_BEST": 451
},
"dropSum": { // 不一樣級別的掉幀問題對應的總掉幀數
"DROPPED_FROZEN": 0,
"DROPPED_HIGH": 0,
"DROPPED_MIDDLE": 41,
"DROPPED_NORMAL": 57,
"DROPPED_BEST": 57
},
"fps": 46.38715362548828, // 幀率
"dropTaskFrameSum": 0 // 意義不明,正常狀況下值老是爲 0
}
複製代碼
(注:這部份內容主要整理自 Matrix 的官方文檔)
開頭說到,Matrix 的卡頓監控是基於 ASM 插樁實現的,其原理是經過代理編譯期間的任務 transformClassesWithDexTask,將全局 class 文件做爲輸入,利用 ASM 工具對全部 class 文件進行掃描及插樁,插樁的意思是在每個方法的開頭處插入 AppMethodBeat.i 方法,在方法的結尾處插入 AppMethodBeat.o 方法,並記錄時間戳,這樣就能知道該方法的執行耗時。
插樁過程有幾個關鍵點:
選擇在編譯任務執行時插樁,是由於 proguard 操做是在該任務以前就完成的,意味着插樁時的 class 文件已經被混淆過的。而選擇 proguard 以後去插樁,是由於若是提早插樁會形成部分方法不符合內聯規則,無法在 proguard 時進行優化,最終致使程序方法數沒法減小,從而引起方法數過大問題
爲了減小插樁量及性能損耗,經過遍歷 class 方法指令集,判斷掃描的函數是否只含有 PUT/READ FIELD 等簡單的指令,來過濾一些默認或匿名構造函數,以及 get/set 等簡單不耗時函數。
針對界面啓動耗時,由於要統計從 Activity#onCreate 到 Activity#onWindowFocusChange 間的耗時,因此在插樁過程當中須要收集應用內全部 Activity 的實現類,並覆蓋 onWindowFocusChange 函數進行打點。
爲了方便及高效記錄函數執行過程,Matrix 爲每一個插樁的函數分配一個獨立 ID,在插樁過程當中,記錄插樁的函數簽名及分配的 ID,在插樁完成後輸出一份 mapping,做爲數據上報後的解析支持。爲了優化內存使用,method id 及時間戳是經過一個 long 數組記錄的,格式以下:
幀率監控的方法是向 Choreographer 註冊監聽,在每一幀 doframe 回調時判斷距離上一幀的時間差是否超出閾值(卡頓),若是超出閾值,則獲取數組 index 前的全部數據(即兩幀之間的全部函數執行信息)進行分析上報。
ANR 監控則更簡單,在每一幀 doFrame 到來時,重置一個定時器,並往 buffer 數組裏插入一個結點,若是 5s 內沒有 cancel,則認爲發生了 ANR,從以前插入的結點開始,到最後一個結點,收集中間執行過的方法數據,能夠認爲致使 ANR 的關鍵方法就在這裏面,計算時間戳便可獲得關鍵方法。
另外,考慮到每一個方法執行先後都獲取系統時間(System.nanoTime)會對性能影響比較大,而實際上,單個函數執行耗時小於 5ms 的狀況,對卡頓來講不是主要緣由,能夠忽略不計,若是是屢次調用的狀況,則在它的父級方法中能夠反映出來,因此爲了減小對性能的影響,Matrix 建立了一條專門用於更新時間的線程,每 5ms 去更新一個時間變量,而每一個方法執行先後只讀取該變量來減小性能損耗。