1. 程式人生 > >手把手教你使用Systrace(一)

手把手教你使用Systrace(一)

Systrace是分析Android效能問題的神器,Google IO 2017上更是對其各種強推;由於TraceView過於嚴重的執行時開銷,我懷疑這個方向是不是壓根兒就是錯誤的。個人預計Google會放棄TraceView轉向全力支援Systrace;不過這個工具並不像TraceView那樣簡單直觀,使用起來也不太方便,而且沒有一個詳盡的文件介紹如何使用和分析;本文和後續旨在彌補這一塊的缺失,儘可能地完整介紹Systrace的方方面面。

在介紹使用之前,先簡單說明一下Systrace的原理:它的思想很樸素,在系統的一些關鍵鏈路(比如System Service,虛擬機器,Binder驅動)插入一些資訊(我這裡稱之為Label),通過Label的開始和結束來確定某個核心過程的執行時間,然後把這些Label資訊收集起來得到系統關鍵路徑的執行時間資訊,進而得到整個系統的執行效能資訊。Android Framework裡面一些重要的模組都插入了Label資訊(Java層的通過android.os.Trace類完成,native層通過ATrace巨集完成),使用者App中可以新增自定義的Label,這樣就組成了一個完成的效能分析系統。

TraceView試圖收集某個階段所有函式的執行資訊(sampling的也是基於此思路),它希望在你並不知道哪個函式有問題的時候直接定位到關鍵函式;但可惜的是,**收集所有資訊** 這個是不現實的,它的執行時開銷嚴重干擾了執行環境;一個人犯罪了,你要把全國人民都抓起來審問一遍嗎?Systrace的思路是反過來的,在不清楚問題的情況下,你壓根兒無法下手,只有掌握了一些基本的資訊,通過假設-分析-驗證 的過程一步一步找出問題的原因;TraceView那種一招吃遍天下鮮的方式,講道理是不符合科學依據的(當然在特定的場合TraceView有他的用途)。

## 簡單使用

Systrace對系統版本有一個要求,就是需要Android 4.1以上,最好是Android 4.3以上(我相信這個已經不是什麼問題了);但是系統版本越高,Android Framework中新增的系統可用Label就越多,能夠支援和分析的系統模組也就越多;因此,在可能的情況下,儘可能使用高版本的Android系統來進行分析;然後對待分析的App也有一個限制——需要是debuggable的。

與TraceView不同的是,systrace需要PC端配合手機端使用;然後systrace也沒有辦法在程式碼裡面自由滴控制trace的開始和結束,因此剛開始使用有點彆扭。

回到正題。首先,在手機端準備好你需要分析的過程的環境;比如假設你要分析App的冷啟動過程,那就先把App程序殺掉,切換到Launcher中有你的App 圖示的那個頁面,隨時準備點選圖示啟動程序;假設你要分析某個Activity的卡頓情況,那就先在手機上進入到上一個Activity,隨時準備點按鈕切換到待分析的Activity中。

有同學會問,至於這麼如臨大敵緊張兮兮的麼?其實準備這個過程非常重要,因為Systrace沒辦法自由滴控制開始和結束(下面有一個辦法可以緩解),而trace得到的資料有可能非常多,因此我們需要手工縮小需要分析的資料集合;不然你可能被一堆眼花繚亂的資料和影象弄得暈頭轉向,然後什麼有用的結論也分析不出來。記住哦,**手動縮小範圍,會幫助你加速收斂問題的分析過程,進而快速地定位和解決問題。

**

手機上App上的環境準備好以後,開啟PC端的命令列;進入systrace的目錄,也即(假設$ANDROID_HOME是你Android SDK的根目錄):

cd $ANDROID_HOME/platform-tools/systrace

然後執行如下命令:

./systrace.py -t 10 sched gfx view wm am app webview -a <package-name>

這樣,`systrace.py` 這個指令碼就通過adb給手機發送了收集trace的通知;與此同時,切換到手機上進行你需要分析的操作,比如點選Launcher中App的Icon啟動App,或者進入某個Activity開始滑動ListView/RecyclerView。經過你指定的時間之後(以上是10s),就會有trace資料生成在當前目錄,預設是 `trace.html`;用Chrome瀏覽器開啟即可。

`systrace.py`命令的一般用法是:

systrace.py [options] [category1 [category2 ...]]

其中,`[options]` 是一些命令引數,`[category]` 等是你感興趣的系統模組,比如view代表view系統(包含繪製流程),am代表ActivityManager(包含Activity建立過程等);分析不同問題的時候,可以選擇不同你感興趣的模組。需要重複的是,儘可能縮小需要Trace的模組,其一是資料量小易與分析;其二,雖然systrace本身開銷很小,但是縮小需要Trace的模組也能減少執行時開銷。比如你分析卡頓的時候,`power`, `webview` 就幾乎是無用的。

`[option]` 中比較重要的幾個引數如下:

  • -a <package_name>:這個選項可以開啟指定包名App中自定義Trace Label的Trace功能。也就是說,如果你在程式碼中使用了`Trace.beginSection("tag")`, `Trace.endSection`;預設情況下,你的這些程式碼是不會生效的,因此,這個選項一定要開啟!
  • -t N:用來指定Trace執行的時間,取決於你需要分析過程的時間;還是那句話,在需要的時候儘可能縮小時間;當然,絕對不要把時間設的太短導致你操作沒完Trace就跑完了,這樣會出現`Did not finish` 的標籤,分析資料就基本無效了。
  • -l:這個用來列出你分析的那個手機系統支援的Trace模組;也就是上面命令中 `[category1]`能使用的部分;不同版本的系統能支援的模組是不同的,一般來說,高版本的支援的模組更多。
  • -o FILE:指定trace資料檔案的輸出路徑,如果不指定就是當前目錄的`trace.html`。

`systrace.py -l` 可以輸出手機能支援的Trace模組,而且輸出還給出了此模組的用途;常用的模組如下:

  • `sched`: CPU排程的資訊,非常重要;你能看到CPU在每個時間段在執行什麼執行緒;執行緒排程情況,比如鎖資訊。
  • `gfx`:Graphic系統的相關資訊,包括SerfaceFlinger,VSYNC訊息,Texture,RenderThread等;分析卡頓非常依賴這個。
  • `view`: View繪製系統的相關資訊,比如onMeasure,onLayout等;對分析卡頓比較有幫助。
  • `am`:ActivityManager呼叫的相關資訊;用來分析Activity的啟動過程比較有效。
  • `dalvik`: 虛擬機器相關資訊,比如GC停頓等。
  • `binder_driver`: Binder驅動的相關資訊,如果你懷疑是Binder IPC的問題,不妨開啟這個。
  • `core_services`: SystemServer中系統核心Service的相關資訊,分析特定問題用。

這樣,我們就學會了Systrace的使用;命令本身並不複雜,不過與TraceView相比,易用性差遠了——但這是值得的,使用上的不便換來了極低執行時開銷,而這對分析效能問題尤為重要。

先別急,如何分析資料是後話,我們先介紹一些使用上的小技巧。

## 無法程式碼控制開始和結束怎麼辦?

如上文所述,systrace沒有辦法在程式碼中控制Trace執行的開始和結束;那麼,如果我們要分析App的啟動效能,我點了桌面圖示,把Trace時間設定為10s,我怎麼知道這10s中,哪段時間是我App的啟動過程?如果不知道我們需要分析的時間段,那後續不是扯淡麼?

我們可以用自定義Trace Label解決;Android SDK中提供了`android.os.Trace#beginSection`和`android.os.Trace#endSection` 這兩個介面;我們可以在程式碼中插入這些程式碼來分析某個特定的過程;比如我們覺得Fragment的onCreateView過程有問題,那就在`onCreateView` 中加上程式碼:

public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
            Bundle savedInstanceState) {
    Trace.beginSection("Fragement_onCreateView");
    // .. 其他程式碼
    // ...
    // .. 結束處
    Trace.endSection();
}

這樣,在Trace的分析結果中就會帶上`Fragement_onCreateView` 這個過程的執行時間段資訊(當然你得開啟 -a 選項!),如下:

我們可以在任意自己感興趣的地方新增自定義的Label;一般來說,分析過程就是,你懷疑哪裡有問題,就在那那個函式加上Label,執行一遍抓一個Trace,看看自己的猜測對不對;如果猜測正確,進一步加Label縮小範圍,定位到具體的自定義函式,函式最終呼叫到系統內部,那就開啟系統相關模組的Trace,繼續定位;如果猜測錯誤,那就轉移目標,一步步縮小範圍,直至問題收斂。

回到正題,我們如何控制Systace的開始和結束?事實上這是沒法辦到的,不過控制開始和結束的目的是什麼?其實就是得到開始和結束這段時間內的Trace資訊。要達到這個目的,我們只需要在期望開始和結束的地方加上自定義的Label就可以了。比如你要分析App的冷啟動過程,那就在Application類的attachBaseContext呼叫`Trace.beginSection("Boot Procedure")`,然後在App首頁的`onWindowFocusChanged` 或者你認為別的合適的啟動結束點呼叫`Trace.endSection`就可以到啟動過程的資訊;比如下圖是我的Label:

從bindApplication到activityStart,到Phase2,Phase3;這幾個過程組合就是我感興趣的啟動過程。從圖中可以直觀地看出來,從Application到activityStart佔用了啟動一半的時間,activityStart下面那有一大段空白是在幹什麼?這是個問題。

## 如何分析非Debug的App

systrace官方文件說待trace的App必須是debuggable的,但是官方又說,debuggable的App與非debuggable的效能有較大差別;因為系統為了支援debug開啟了一些列功能並且關閉掉了某些重要的優化,見 文件 :

Run a release (or at least non-debuggable) version of your app. The ART runtime disables several important optimizations in order to support debugging features, so make sure you're looking at something similar to what a user will see.

如果我們想要待分析的App儘可能接近真實情況,那麼必須要在非Debug的App中能啟用systrace功能;因為相同情況下Debug的App效能比非Debuggable的差,你無法確保在debuggable版本上分析出來的結論能準確推廣到非debuggable的版本上。

分析systrace原始碼之後 ,發現這個條件只是個障眼法而已;我們可以手動開啟App的自定義Label的Trace功能,方法也很簡單,呼叫一個函式即可;但是這個函式是SDK @hide的,我們需要反射呼叫:

Class<?> trace = Class.forName("android.os.Trace");
Method setAppTracingAllowed = trace.getDeclaredMethod("setAppTracingAllowed", boolean.class);
setAppTracingAllowed.invoke(null, true);

把這段程式碼放在Application的`attachBaseContext` 中,這樣就可以手動開啟App自定義Label的Trace功能,在非debuggable的版本中也適用!

OK,systrace命令的使用說到這裡也沒什麼要注意的了;其實命令的使用不難,分析思路也很簡單:合理假設-> 加自定義Label驗證 -> (結論成立-> 縮小範圍繼續)/(結論推翻-> 重新假設) 這樣一步一步直至問題收斂;如果你分析了一段時間,發現問題是發散的,一會兒覺得是這裡有問題,一會兒又覺得是那裡有問題,那麼或許你的假設根本就是錯誤的;需要重新調整方向分析。

閱讀更多