RFC-0084:在 zx_info_task_runtime_t 新增更多指標

RFC-0084:將更多指標新增至 zx_info_task_runtime_t
狀態已接受
區域
  • 核心
說明

為 zx_info_task_runtime_t 新增額外指標,以便排除媒體效能問題。

問題
變更
作者
審查人員
提交日期 (年/月)2021-03-09
審查日期 (年/月)2021-04-06

摘要

ZX_INFO_TASK_RUNTIME 主題可讓您擷取工作在 CPU 上執行,或已排入佇列執行的總時間。為了診斷排程瓶頸,建議您加入額外的精細執行階段資訊,特別是等待頁面錯誤所花費的時間,以及等待核心互斥鎖所花費的時間。

提振精神

即時任務設有期限。有時候,會錯過期限,因為任務會在核心中花費非預期的長時間遭到封鎖。如要對這類情況進行偵錯,建議您先瞭解工作遭到封鎖的原因。舉例來說,如果工作的期限為 10 毫秒,但花費 11 毫秒等待頁面錯誤,我們可以斷定是因為網頁錯誤速度太慢而錯過了期限。

具體而言,我們想改善 audio_core 等媒體子系統產生的診斷問題。使用 audio_core 時,每個混合工作都必須在 10 毫秒內完成。如果工作時間超過 10 毫秒,使用者會聽到有瑕疵的音訊。最近,由於頁面錯誤 (也就是需要重新分頁 audio_core 執行檔頁面) 和核心堆積互斥運算的爭用情況,導致缺少期限。快照無法用來診斷這些問題,而是我們必須記錄追蹤記錄,而這個程序有時會因為在本機重現問題,而受到干擾 (因為錯誤只會透過特定環境中的特定應用程式觸發)。這些是高優先順序的問題,可能會對媒體成效造成嚴重負面影響。

我們的目標是從核心匯出足夠的診斷資訊,方便我們從快照診斷這些問題,而不必深入瞭解執行追蹤記錄。本文件建議在 zx_info_task_runtime_t 中加入新的統計資料,以便更完整回答「這項工作為何無法執行?」的問題。

Zircon 執行緒期限的背景

Zircon 期限設定檔包含三個元件:期限、容量和期限。將執行緒指派到期限設定檔後,Zircon 會保證每個「週期」在每個「週期」開始的「期限」內,執行緒都會分配給「容量」。舉例來說:

  • 經期 = 10 毫秒
  • 容量 = 2 毫秒
  • 期限 = 5 毫秒

該執行緒每 10 毫秒會在接下來 5 毫秒內分配 2 毫秒的 CPU。錯過期限的方式有兩種:

  1. 工作預定延誤。舉例來說,如果下一個週期從 T 時間開始,但工作並未安排至 T+4 毫秒,則工作無法排定期限 (時間不應晚於 T+3 毫秒)。核心可以偵測這類錯過的期限,但在實務上,除非排程器發生錯誤或超額訂閱,否則這種情況應該不會發生。

  2. 這項工作需要 2 毫秒以上的時間才能完成。核心無法得知這種情況何時發生,因為它無法解讀任務邊界。例如,如果排定於 T+1 毫秒執行所有工作,總共執行 1 毫秒,接著封鎖 9 毫秒,則封鎖 9 毫秒。

我們的目標是協助診斷第二類錯過的期限。就目前的工作而言,如果工作的執行時間過長,它可以查詢 ZX_INFO_TASK_RUNTIME,瞭解在使用者空間的 CPU 上執行多少時間。如果 cpu_time 大於任務預期的執行階段,任務就會知道執行的時間過長。不過,如果 cpu_time 佔總工作時間的一小部分,代表任務在核心中花費的大部分時間都有可能遭到封鎖且無法執行。這個 RFC 的目標是協助您瞭解時間所花費的時間。

設計空間

我們的目標是回答「這項工作為何無法執行?」的問題。我們必須做出一些決定:

  1. 回答是否完整?具體來說,我們是否應該列舉工作遭到封鎖的所有原因,或只列舉幾個重要原因?

  2. 我們該回答這個問題的精細程度為何?舉例來說,是否應只回報使用者層級能夠理解的簡單事件,例如「在 zx_channel_read 上封鎖」),還是應該納入 Zircon 目前實作項目的較低層級事件?

  3. 如果記錄 N 統計資料,我們是否應該要求這些 N 統計資料不能重疊,還是應該允許以任意方式重疊?

最簡單的直接方法是列舉幾個關注的事件,並產生這些事件的統計資料。由於媒體子系統發現頁面錯誤和核心鎖定爭用問題,因此我們可能會產生「網頁錯誤時間」和「在核心鎖定上花費的時間」等統計資料。

此外,這兩個統計資料可能無法捕捉到其他問題。因此,完整的解決方案具有吸引力。其中一個想法是列舉出執行緒進入核心的所有方式。包括 N 個硬體中斷 (計時器和裝置中斷) 和 K 軟體中斷 (系統呼叫和錯誤)。然後針對每種中斷類型分別產生 N+K 統計資料。計時器會在執行緒進入核心時啟動,並在控制項返回使用者空間執行緒時停止。不過,使用者層級可以計算「Syscall X 花費的時間」,因此核心計算這項資訊是多餘的。

另一個做法是產生「在核心模式下在 CPU 上執行的時間」和「在 X 上運作的時間」,其中 X 是核心基本功能,例如「核心鎖定」或「通道」。這樣的想法可能會因核心原始元件的變動而發生暴動和流失風險。

回到剛剛的步驟,我們真正希望能從野外裝置擷取追蹤記錄。理想情況下,我們會持續將追蹤記錄記錄到環形緩衝區中,並在觸及 TRACE_ALERT 後上傳該緩衝區。

設計

您想要完整的解決方案,但設計及建構可能需要較長的時間。我們需要立即在野外診斷效能迴歸。因此,我們建議您新增兩個目標指標來解決立即的問題:等待頁面錯誤所花費的時間,以及等待核心鎖定所花費的時間。

關於以上設計空間問題:

  1. 但是,我們不會力求完整性。

  2. 精細程度可以任意決定 (我們會記錄任何必要項目)

  3. 統計資料可能會以任意方式重疊

核心異動

// This struct contains a partial breakdown of time spent by this task since
// creation. The breakdown is not complete and individual fields may overlap:
// there is no expectation that these fields should sum to an equivalent
// "wall time".
typedef struct zx_info_task_runtime {
  // Existing fields
  zx_duration_t cpu_time;
  zx_duration_t queue_time;

  // New fields below here

  // The total amount of time this task and its children spent handling page faults.
  zx_duration_t page_fault_time;

  // The total amount of time this task and its children spent waiting on contended
  // kernel locks.
  zx_duration_t lock_contention_time;

} zx_info_task_runtime_t;

系統會針對每個執行緒計算這兩個欄位,然後加總所有程序和工作 (與目前針對 cpu_timequeue_time 的工作) 進行加總。請注意,媒體子系統不需要執行各項程序和每項工作的匯總作業,但會將這些資料納入這裡,以便與 zx_info_task_runtime_t 中的現有欄位保持一致。

頁面錯誤分為多種類型和多個核心鎖定,page_fault_time 代表處理各類型網頁錯誤所花費的總時間。透過涵蓋所有頁面錯誤,我們就不必說明所涵蓋的頁面錯誤子集,因為核心可能會新增或移除特定類型的頁面錯誤,因為實作會隨著時間變更,並支援新架構。

lock_contention_time 涵蓋所有邊界鎖定。不過,「競爭」這個字詞並非刻意設定,因此核心可能會隨時間改進實作方式,進而平衡評估爭用成本與報表競爭時間的好處。如需更多討論,請參閱下文的「導入作業」。

使用者空間如何診斷錯過的期限

就這些新欄位而言,使用者空間可以使用如下的程式碼來診斷未超過的期限:

for (;;) {
  zx_object_get_info(current_thread, ZX_TASK_RUNTIME_INFO, &start_info, ...)
  deadline_task()
  if (current_time() > deadline) {
    zx_object_get_info(current_thread, ZX_TASK_RUNTIME_INFO, &end_info, ...)
    // ...
    // report stats from (end_info - start_info)
    // ...
  }
}

實作

page_fault_time 會計算所有頁面錯誤處理常式所耗費的總時間。在目前的實作項目中,這包括 vmm_page_fault_handlervmm_accessed_fault_handler

lock_contention_time 會計算 Mutex::AcquireContendedMutexBrwLock::Block 的總時間。這個方法已可存取目前的 Threadcurrent_ticks()。實作未涵蓋旋轉鎖定。雖然旋轉鎖定可以終止,但目前我們會忽略旋轉轉動鎖定,因為評估旋轉鎖定的爭用情形可能所費不貲。

為了盡可能降低負擔,我們會將這些時間長度記錄為刻點計數,並在 zx_object_get_info 系統呼叫期間轉譯為 zx_duration_t。其他實作詳細資料將依 cpu_timequeue_time 使用的現有模式生效。原型實作可在 fxrev.dev/469818 取得。

效能

我們會執行 Zircon mutex 基準測試,確認沒有任何迴歸問題。我們會在原始硬體 (x86 和 ARM) 上執行這些基準測試。此外,為驗證虛擬化環境中沒有任何迴歸問題,我們會在 QEMU (x86 和 ARM) 上執行這些基準測試。

回溯相容性

系統會產生 zx_info_task_runtime_t 結構體的版本,類似於其他 zx_info_* 結構體所完成的版本 (例如,請參閱 fxrev.dev/406754)。

安全性考量

ZX_INFO_TASK_RUNTIME 是側邊管道,可洩露已檢查工作的相關資訊。舉例來說,page_fault_time 可能會用來測量任務的記憶體存取模式。為緩解這類外洩情形,ZX_INFO_TASK_RUNTIME 主題已經需要 ZX_RIGHT_INSPECT。凡是擁有該權利的使用者,皆可假設擁有工作私人資料的存取權。

ZX_INFO_TASK_RUNTIME 也可能會外洩其他工作的間接資訊。舉例來說,如果某項工作知道自己的 page_fault_time,或許就能推論「其他」工作的記憶體存取模式。同樣地,如果工作知道在爭用核心鎖定上等待了多少時間,可以推斷其他工作使用共用核心資源的方式。未來我們可能會使用低解析度計時器建構 zx_info_task_runtime_t。這不一定能防止時間攻擊,但可能會限制其有效性。

另一個防禦措施是限制 zx_info_task_runtime_t 對特殊開發人員版本的存取權。不過,這種做法會大幅限制這項功能的實用性,因為在開發環境中通常無法順利重現效能錯誤。我們需要可在正式版本中啟用的解決方案。

如要完全避免這個側邊管道,我們需要將指標報表和指標檢查分為不同的功能。舉例來說,如果我們持續將追蹤記錄記錄到環形緩衝區中,並在完成 TRACE_ALERT 後將該緩衝區上傳至特定的管道或通訊埠,則觸發 TRACE_ALERT 的工作就不需要獲得追蹤記錄的存取權,而這樣會排除側邊管道。如先前所述,這類解決方案的設計及建構需要很長的時間,但我們可以立即處理。

隱私權注意事項

無。

說明文件

Zircon syscall 說明文件必須更新,加入新的 zx_info_task_runtime_t 欄位。

先前的圖片和參考資料

在 Linux 中,最密切相關的先驗技術是 getrusage,會回報使用者和系統 CPU 時間和頁面錯誤、I/O 作業和結構定義切換次數。Windows 的 GetThreadTimes 會回報使用者和系統 CPU 作業時間。硬體效能計數器 (例如 x86 上的 RDPMC) 會提供類似的資訊,並有類似的安全疑慮

測試

我們將透過 audio_core 記錄新執行階段資訊,手動進行測試 (而我們在原型實作上已完成這個步驟,請參閱 fxrev.dev/469819)。

我們會更新 cpu_timequeue_time 的現有測試,以測試舊版 zx_info_task_runtime_t (名為 zx_info_task_runtime_v1_t)。此外,Zircon 的 abi_type_validator.h 將會更新,以驗證新舊 ABI。確保 ABI 回溯相容性。

要為這項功能新增整合測試並不容易,因為沒有 API 可強制核心對核心造成鎖定爭用或觸發頁面錯誤 (除了程序終止的區隔錯誤之外)。