Android 性能監控框架 Matrix(4)卡頓監控

使用

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

出現 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
    }
}
複製代碼

其中,設備分級以下:數組

  1. BEST,內存大於等於 4 G
  2. HIGH,內存大於等於 3G,或內存大於等於 2G 且 CPU 核心數大於等於 4 個
  3. MIDDLE,內存大於等於 2G 且 CPU 核心數大於等於 2 個,或內存大於等於 1G 且 CPU 核心數大於等於 4 個
  4. LOW,內存大於等於 1G
  5. BAD,內存小於 1G

啓動

正常啓動狀況下: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

  1. 100,Activity 拉起的
  2. 114,Service 拉起的
  3. 113,Receiver 拉起的
  4. -100,未知,好比 ContentProvider

若是是啓動過慢的狀況:函數

{
    "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 方法,並記錄時間戳,這樣就能知道該方法的執行耗時。

插樁過程有幾個關鍵點:

  1. 選擇在編譯任務執行時插樁,是由於 proguard 操做是在該任務以前就完成的,意味着插樁時的 class 文件已經被混淆過的。而選擇 proguard 以後去插樁,是由於若是提早插樁會形成部分方法不符合內聯規則,無法在 proguard 時進行優化,最終致使程序方法數沒法減小,從而引起方法數過大問題

  2. 爲了減小插樁量及性能損耗,經過遍歷 class 方法指令集,判斷掃描的函數是否只含有 PUT/READ FIELD 等簡單的指令,來過濾一些默認或匿名構造函數,以及 get/set 等簡單不耗時函數。

  3. 針對界面啓動耗時,由於要統計從 Activity#onCreate 到 Activity#onWindowFocusChange 間的耗時,因此在插樁過程當中須要收集應用內全部 Activity 的實現類,並覆蓋 onWindowFocusChange 函數進行打點。

  4. 爲了方便及高效記錄函數執行過程,Matrix 爲每一個插樁的函數分配一個獨立 ID,在插樁過程當中,記錄插樁的函數簽名及分配的 ID,在插樁完成後輸出一份 mapping,做爲數據上報後的解析支持。爲了優化內存使用,method id 及時間戳是經過一個 long 數組記錄的,格式以下:

  1. 堆棧聚類問題: 若是將收集的原始數據進行上報,數據量很大並且後臺很難聚類有問題的堆棧,因此在上報以前須要對採集的數據進行簡單的整合及裁剪,並分析出一個能表明卡頓堆棧的 key,方便後臺聚合。具體的方法是經過遍歷採集的 buffer ,相鄰 i 與 o 爲一次完整函數執行,計算出一個調用樹及每一個函數執行耗時,並對每一級中的一些相同執行函數作聚合,最後經過一個簡單策略,分析出主要耗時的那一級函數,做爲表明卡頓堆棧的key。

幀率監控的方法是向 Choreographer 註冊監聽,在每一幀 doframe 回調時判斷距離上一幀的時間差是否超出閾值(卡頓),若是超出閾值,則獲取數組 index 前的全部數據(即兩幀之間的全部函數執行信息)進行分析上報。

ANR 監控則更簡單,在每一幀 doFrame 到來時,重置一個定時器,並往 buffer 數組裏插入一個結點,若是 5s 內沒有 cancel,則認爲發生了 ANR,從以前插入的結點開始,到最後一個結點,收集中間執行過的方法數據,能夠認爲致使 ANR 的關鍵方法就在這裏面,計算時間戳便可獲得關鍵方法。

另外,考慮到每一個方法執行先後都獲取系統時間(System.nanoTime)會對性能影響比較大,而實際上,單個函數執行耗時小於 5ms 的狀況,對卡頓來講不是主要緣由,能夠忽略不計,若是是屢次調用的狀況,則在它的父級方法中能夠反映出來,因此爲了減小對性能的影響,Matrix 建立了一條專門用於更新時間的線程,每 5ms 去更新一個時間變量,而每一個方法執行先後只讀取該變量來減小性能損耗。

相關文章
相關標籤/搜索