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

RFC-0084:在 zx_info_task_runtime_t 中新增更多指標
狀態已接受
區域
  • Kernel
說明

在 zx_info_task_runtime_t 中新增其他指標,方便偵錯媒體效能問題。

問題
Gerrit 變更
作者
審查人員
提交日期 (年-月-日)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 會確保每個週期的執行緒,在每個週期開始的期限內,最多可分配到容量的 CPU。舉例來說:

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

每隔 10 毫秒,執行緒會在接下來的 5 毫秒內分配到 2 毫秒的 CPU。有兩種情況會導致錯過期限:

  1. 工作排程已延遲。舉例來說,如果下一個週期從時間 T 開始,但工作排定在時間 T+4 毫秒,工作就不可能趕上截止時間 (最晚應排定在 T+3 毫秒)。核心可以偵測到這類錯過期限的情況,但實際上,除非排程器有錯誤或過度訂閱,否則絕不會發生這種情況。

  2. 這項工作完成時間超過 2 毫秒。核心無法瞭解工作界線,因此無法得知何時發生這種情況。舉例來說,如果工作排定在 T+1 毫秒執行,總共執行 1 毫秒,然後封鎖 9 毫秒,核心就無法判斷工作是否錯過期限 (因為工作遭到封鎖),或是工作要求 2 毫秒,但只需要 1 毫秒即可完成 (然後休眠 9 毫秒,等待下一個週期)。

我們的目標是協助您診斷第二種錯過期限的情況。目前,如果工作執行時間過長,可以查詢 ZX_INFO_TASK_RUNTIME,瞭解在使用者空間的 CPU 上執行工作所耗費的時間。如果該 cpu_time 大於工作預期執行時間,工作就會知道執行時間過長。不過,如果 cpu_time 占總工作時間的一小部分,則工作的大部分時間都花在核心中,可能遭到封鎖而無法執行。這項 RFC 的目標是協助瞭解時間的用途。

設計空間

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

  1. 我們的答案應該多完整?具體來說,我們應該列出工作遭到封鎖的所有原因,還是只列出幾個看似重要的原因?

  2. 我們應該以多細的粒度回答這個問題?舉例來說,我們是否應只回報使用者層級可理解的簡單事件,例如「blocked on zx_channel_read」,還是應納入 Zircon 目前實作方式特有的低層級事件?

  3. 如果我們回報 N 個統計資料,是否應要求這 N 個統計資料不得重疊,還是應允許重疊 (也許是以任意方式)?

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

可能還有其他潛在問題,這兩項統計資料不會顯示。因此,全方位解決方案相當具有吸引力。其中一個想法是列舉執行緒進入核心的所有方式。這包括 N 個硬體中斷 (計時器和裝置中斷) 和 K 個軟體中斷 (系統呼叫和錯誤)。接著,我們會產生 N+K 統計資料,每種中斷各一項。當執行緒進入核心時,計時器會開始計時,並在控制項傳回使用者空間執行緒時停止計時。不過,使用者層級已可計算「在系統呼叫 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 互斥基準測試,確認沒有任何回歸。我們會對原始硬體 (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 系統呼叫說明文件必須更新,加入新的 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 可強制核心發生鎖定爭用或觸發頁面錯誤 (除了程序終止區隔錯誤)。