了解 Systrace

systrace 是分析 Android 裝置效能的主要工具。然而,它實際上是其他工具的包裝。它是atrace的主機端包裝器、控制用戶空間追蹤並設定ftrace的裝置端可執行檔以及 Linux 核心中的主要追蹤機制。 systrace 使用 atrace 啟用跟踪,然後讀取 ftrace 緩衝區並將其全部包裝在獨立的 HTML 檢視器中。 (雖然較新的核心支援 Linux 增強型伯克利資料包過濾器 (eBPF),但以下文件適用於 3.18 核心(無 eFPF),因為 Pixel/Pixel XL 上使用的是該核心。)

systrace 由 Google Android 和 Google Chrome 團隊所有,並且作為Catapult 專案的一部分開源。除了 systrace 之外,Catapult 還包括其他有用的實用程式。例如,ftrace 具有比 systrace 或 atrace 直接啟用的功能更多的功能,並且包含一些對於偵錯效能問題至關重要的進階功能。 (這些功能需要 root 存取權限,並且通常需要新核心。)

運行系統追蹤

在 Pixel/Pixel XL 上偵錯抖動時,請從下列指令開始:

./systrace.py sched freq idle am wm gfx view sync binder_driver irq workq input -b 96000

當與 GPU 和顯示管道活動所需的附加追蹤點結合使用時,您可以追蹤從使用者輸入到螢幕上顯示的幀。將緩衝區大小設為較大的值以避免遺失事件(因為如果沒有較大的緩衝區,某些 CPU 在追蹤中的某個點之後將不包含任何事件)。

在查看 systrace 時,請記住每個事件都是由 CPU 上的某些事件觸發的

由於 systrace 建構在 ftrace 上,且 ftrace 在 CPU 上運行,因此 CPU 上的某些內容必須寫入記錄硬體變更的 ftrace 緩衝區。這意味著,如果您對顯示圍欄更改狀態的原因感到好奇,您可以查看在其轉換的確切點上 CPU 上運行的內容(CPU 上運行的內容觸發了日誌中的更改)。這個概念是使用 systrace 分析效能的基礎。

範例:工作框架

此範例描述了普通 UI 管道的 systrace。若要按照該範例進行操作,請下載追蹤的 zip 檔案(其中還包括本節中提到的其他追蹤),解壓縮該文件,然後在瀏覽器中開啟systrace_tutorial.html檔案。請注意,此 systrace 是一個大檔案;除非您在日常工作中使用 systrace,否則這可能是一個更大的跟踪,包含比您以前在單一跟踪中看到的更多資訊。

對於一致的週期性工作負載(例如 TouchLatency),UI 管道包含以下內容:

  1. SurfaceFlinger 中的 EventThread 喚醒應用程式 UI 線程,表明是時候渲染新幀了。
  2. 應用程式使用 CPU 和 GPU 資源在 UI 執行緒、RenderThread 和 hwuiTasks 中渲染幀。這是用於 UI 的大部分容量。
  3. 應用程式使用綁定器將渲染的幀發送到 SurfaceFlinger,然後 SurfaceFlinger 進入睡眠狀態。
  4. SurfaceFlinger 中的第二個 EventThread 喚醒 SurfaceFlinger 以觸發合成和顯示輸出。如果 SurfaceFlinger 確定沒有工作要做,它就會回到睡眠狀態。
  5. SurfaceFlinger 使用 Hardware Composer (HWC)/Hardware Composer 2 (HWC2) 或 GL 處理合成。 HWC/HWC2 組合速度更快、功耗更低,但有局限性,取決於系統單晶片 (SoC)。這通常需要約 4-6 毫秒,但可能與步驟 2 重疊,因為 Android 應用程式始終是三重緩衝的。 (雖然應用程式始終是三重緩衝的,但 SurfaceFlinger 中可能只有一個待處理幀在等待,這使其看起來與雙緩衝相同。)
  6. SurfaceFlinger 調度最終輸出以使用供應商驅動程式進行顯示,然後返回睡眠狀態,等待 EventThread 喚醒。

讓我們瀏覽一下從 15409 毫秒開始的幀:

執行 EventThread 的正常 UI 管道
圖 1.正常 UI 管道,EventThread 正在執行

圖 1 是一個被普通框架包圍的普通框架,因此它是了解 UI 管道如何運作的一個很好的起點。 TouchLatency 的 UI 執行緒行在不同時間包含不同的顏色。條形表示線程的不同狀態:

  • 灰色的。睡眠。
  • 藍色的。可運行(它可以運行,但調度程序尚未選擇它運行)。
  • 綠色的。主動運行(調度程序認為它正在運行)。
  • 紅色的。不間斷睡眠(通常在內核中的鎖上睡眠)。可指示 I/O 負載。對於調試效能問題非常有用。
  • 橘子.由於 I/O 負荷而不間斷的睡眠。

若要查看不間斷睡眠的原因(可從sched_blocked_reason追蹤點取得),請選擇紅色不間斷睡眠片段。

當 EventThread 執行時,TouchLatency 的 UI 執行緒變得可運作。要查看是什麼喚醒了它,請點擊藍色部分。

TouchLatency 的 UI 線程
圖 2. TouchLatency 的 UI 線程

圖 2 顯示 TouchLatency UI 執行緒被 tid 6843 喚醒,該執行緒對應於 EventThread。 UI 執行緒喚醒,渲染幀,並將其排入佇列以供 SurfaceFlinger 使用。

UI 執行緒喚醒,渲染幀,並將其排入佇列以供 SurfaceFlinger 使用
圖 3. UI 執行緒喚醒、渲染幀並將其排入佇列以供 SurfaceFlinger 使用

如果在追蹤中啟用了binder_driver標記,您可以選擇一個binder事務來查看該事務中涉及的所有程序的資訊。

圖 4. Binder 事務

圖 4 顯示,在 15,423.65 毫秒時,SurfaceFlinger 中的 Binder:6832_1 由於 tid 9579(TouchLatency 的 RenderThread)而變得可運作。還可以看到binder事務兩側的queueBuffer。

在SurfaceFlinger端的queueBuffer期間,TouchLatency的待處理幀數從1變成2。

待處理幀從 1 變為 2
圖 5.待處理影格從 1 變成 2

圖 5 顯示了三重緩衝,其中有兩個已完成的幀,應用程式即將開始渲染第三個幀。這是因為我們已經丟掉了一些幀,因此應用程式會保留兩個掛起的幀而不是一個,以避免進一步丟幀。

不久之後,SurfaceFlinger 的主執行緒被第二個 EventThread 喚醒,以便它可以將較舊的待處理幀輸出到顯示器:

SurfaceFlinger的主執行緒被第二個EventThread喚醒
圖 6. SurfaceFlinger 的主執行緒被第二個 EventThread 喚醒

SurfaceFlinger 首先鎖定較舊的待處理緩衝區,這會導致待處理緩衝區計數從 2 減少到 1。

SurfaceFlinger 首先鎖存到舊的掛起緩衝區
圖 7. SurfaceFlinger 先鎖存到舊的掛起緩衝區

鎖定緩衝區後,SurfaceFlinger 設定合成並將最終幀提交到顯示器。 (其中一些部分是作為mdss追蹤點的一部分啟用,因此它們可能不包含在您的 SoC 中。)

SurfaceFlinger 設定合成並提交最終幀
圖 8. SurfaceFlinger 設定合成並提交最終幀

接下來, mdss_fb0在 CPU 0 上喚醒mdss_fb0是顯示管道的核心線程,用於將渲染幀輸出到顯示器。我們可以在追蹤中看到mdss_fb0作為其自己的行(向下滾動查看)。

mdss_fb0 在 CPU 0 上喚醒
圖 9. mdss_fb0 在 CPU 0 上喚醒

mdss_fb0喚醒,短暫運行,進入不間斷睡眠,然後再次喚醒。

範例:非工作框架

此範例描述了用於偵錯 Pixel/Pixel XL 抖動的 systrace。若要按照該範例進行操作,請下載追蹤的 zip 檔案(其中包括本節中提到的其他追蹤),解壓縮該文件,然後在瀏覽器中開啟systrace_tutorial.html檔案。

當你打開 systrace 時,你會看到類似這樣的內容:

TouchLatency 在 Pixel XL 上運行且啟用了大多數選項
圖 10. Pixel XL 上運行的 TouchLatency(啟用了大多數選項,包括 mdss 和 kgsl 追蹤點)

在尋找卡頓時,請檢查 SurfaceFlinger 下的 FrameMissed 行。 FrameMissed 是 HWC2 提供的生活品質改進。當查看其他裝置的 systrace 時,如果裝置不使用 HWC2,則可能不會出現 FrameMissed 行。無論哪種情況,FrameMissed 都與 SurfaceFlinger 缺少一個極其常規的運行時間以及垂直同步時應用程式 ( com.prefabulated.touchlatency ) 未更改的待處理緩衝區計數相關。

FrameMissed 與 SurfaceFlinger 的關聯
圖 11. FrameMissed 與 SurfaceFlinger 的關聯

圖 11 顯示了 15598.29ms 處的遺失畫面。 SurfaceFlinger 在 vsync 間隔短暫喚醒,然後返回睡眠狀態而不執行任何工作,這意味著 SurfaceFlinger 確定不值得再次嘗試將幀發送到顯示器。為什麼?

若要了解此框架的管道如何分解,請先查看上面的工作框架範例,看看正常的 UI 管道在 systrace 中如何顯示。準備好後,返回到錯過的幀並向後工作。請注意,SurfaceFlinger 喚醒並立即進入睡眠狀態。從 TouchLatency 查看待處理影格的數量時,有兩個影格(這是幫助弄清楚發生了什麼情況的好線索)。

SurfaceFlinger 喚醒並立即進入睡眠狀態
圖 12. SurfaceFlinger 喚醒並立即進入睡眠狀態

因為 SurfaceFlinger 中有框架,所以這不是應用程式問題。此外,SurfaceFlinger 在正確的時間喚醒,因此這不是 SurfaceFlinger 問題。如果 SurfaceFlinger 和應用程式看起來都正常,則可能是驅動程式問題。

由於啟用了mdsssync追蹤點,因此我們可以獲得有關控制何時將幀提交到顯示器的柵欄(在顯示驅動程式和 SurfaceFlinger 之間共享)的資訊。這些柵欄列在mdss_fb0_retire下,表示幀何時顯示在顯示幕上。這些柵欄作為sync追蹤類別的一部分提供。哪些柵欄對應於 SurfaceFlinger 中的特定事件取決於您的 SOC 和驅動程式堆疊,因此請與您的 SOC 供應商合作,以了解追蹤中柵欄類別的含義。

mdss_fb0_退休柵欄
圖 13. mdss_fb0_retire 柵欄

圖 13 顯示的畫面顯示時間為 33 毫秒,而非預期的 16.7 毫秒。在該片段的中間,該幀應該被新的幀替換,但沒有。查看上一幀並查找任何內容。

損壞幀之前的幀
圖 14.損壞幀之前的幀

圖 14 顯示每格 14.482 毫秒。損壞的兩幀片段為 33.6 毫秒,這大致是我們對兩幀的預期(我們以 60 Hz 渲染,每幀 16.7 毫秒,很接近)。但 14.482 毫秒根本不接近 16.7 毫秒,這表示顯示管道出現了嚴重問題。

準確調查柵欄的末端,以確定控制它的因素。

調查柵欄末端
圖 15.研究柵欄末端

工作佇列包含__vsync_retire_work_handler ,它在柵欄變更時執行。查看核心原始碼,您可以看到它是顯示驅動程式的一部分。它似乎位於顯示管道的關鍵路徑上,因此必須盡快運行。它可以運行 70 us 左右(調度延遲不是很長),但它是一個工作隊列,可能無法準確調度。

檢查前一幀以確定其是否有貢獻;有時,抖動會隨著時間的推移而累積,最終導致錯過最後期限。

上一幀
圖 16.前一幀

kworker 上的可操作行是不可見的,因為檢視器在選擇它時會將其變成白色,但統計數據說明了問題:顯示管道關鍵路徑的一部分的調度程序延遲為 2.3 毫秒,很糟糕。在繼續之前,請將顯示管道關鍵路徑的這一部分從工作佇列(以SCHED_OTHER CFS 執行緒執行)移至專用SCHED_FIFO kthread 來修復延遲。此功能需要工作佇列無法(也不打算)提供的時序保證。

這是卡頓的原因嗎?很難下定論。除了易於診斷的情況(例如核心鎖定爭用導致顯示關鍵執行緒休眠)之外,追蹤通常不會指定問題。這種抖動是否是丟幀的原因?絕對地。柵欄時間應為 16.7 毫秒,但在導致丟幀的幀中,它們與該值根本不接近。考慮到顯示管道的耦合程度,柵欄時序周圍的抖動可能會導致丟幀。

在此範例中,解決方案涉及將__vsync_retire_work_handler從工作佇列轉換為專用 kthread。這導致抖動顯著改善,並減少了彈跳球測試中的卡頓現象。隨後的追蹤顯示柵欄時間非常接近 16.7 毫秒。