了解 Systrace

systrace 是分析 Android 設備性能的主要工具。但是,它實際上是其他工具的包裝。它是圍繞atrace的主機端包裝器、控制用戶空間跟踪和設置ftrace的設備端可執行文件,以及 Linux 內核中的主要跟踪機制。 systrace 使用 atrace 啟用跟踪,然後讀取 ftrace 緩衝區並將其全部包裝在一個獨立的 HTML 查看器中。 (雖然較新的內核支持 Linux Enhanced Berkeley Packet Filter (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,否則這可能是一個更大的跟踪,其中包含比您以前在單個跟踪中看到的更多的信息。

對於一致的周期性工作負載,例如 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 線程行在不同時間包含不同的顏色。條形表示線程的不同狀態:

  • 灰色。睡眠。
  • 藍色的。 Runnable(它可以運行,但調度程序還沒有選擇它來運行)。
  • 綠色的。主動運行(調度程序認為它正在運行)。
  • 紅色的。不間斷睡眠(通常在內核中的鎖上睡眠)。可以指示 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 ) 在 vsync 處的掛起緩衝區計數。

FrameMissed 與 SurfaceFlinger 的相關性
圖 11. FrameMissed 與 SurfaceFlinger 的相關性

圖 11 顯示了 15598.29&nbps;ms 的丟失幀。 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_retire 柵欄
圖 13. mdss_fb0_retire 柵欄

圖 13 顯示了顯示 33 毫秒的幀,而不是預期的 16.7 毫秒。在該切片的中途,該幀應該被新的幀替換,但沒有。查看上一幀並查找任何內容。

破壞幀的前一幀
圖 14.破壞幀的前一幀

圖 14 顯示了 14.482 毫秒一幀。損壞的兩幀片段為 33.6 毫秒,這大致是我們對兩幀的預期(我們以 60 赫茲的速度渲染,每幀 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 毫秒。