| RFC-0084:在 zx_info_task_runtime_t 中新增更多指標 | |
|---|---|
| 狀態 | 已接受 |
| 區域 |
|
| 說明 | 在 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。有兩種情況會導致錯過期限:
工作排程已延遲。舉例來說,如果下一個週期從時間 T 開始,但工作排定在時間 T+4 毫秒,工作就不可能趕上截止時間 (最晚應排定在 T+3 毫秒)。核心可以偵測到這類錯過期限的情況,但實際上,除非排程器有錯誤或過度訂閱,否則絕不會發生這種情況。
這項工作完成時間超過 2 毫秒。核心無法瞭解工作界線,因此無法得知何時發生這種情況。舉例來說,如果工作排定在 T+1 毫秒執行,總共執行 1 毫秒,然後封鎖 9 毫秒,核心就無法判斷工作是否錯過期限 (因為工作遭到封鎖),或是工作要求 2 毫秒,但只需要 1 毫秒即可完成 (然後休眠 9 毫秒,等待下一個週期)。
我們的目標是協助您診斷第二種錯過期限的情況。目前,如果工作執行時間過長,可以查詢 ZX_INFO_TASK_RUNTIME,瞭解在使用者空間的 CPU 上執行工作所耗費的時間。如果該 cpu_time 大於工作預期執行時間,工作就會知道執行時間過長。不過,如果 cpu_time 占總工作時間的一小部分,則工作的大部分時間都花在核心中,可能遭到封鎖而無法執行。這項 RFC 的目標是協助瞭解時間的用途。
設計空間
我們的目標是回答「為什麼這項工作無法執行?」這個問題。我們必須做出幾項決定:
我們的答案應該多完整?具體來說,我們應該列出工作遭到封鎖的所有原因,還是只列出幾個看似重要的原因?
我們應該以多細的粒度回答這個問題?舉例來說,我們是否應只回報使用者層級可理解的簡單事件,例如「blocked on zx_channel_read」,還是應納入 Zircon 目前實作方式特有的低層級事件?
如果我們回報 N 個統計資料,是否應要求這 N 個統計資料不得重疊,還是應允許重疊 (也許是以任意方式)?
最簡單直接的方法是列舉幾個我們關心的事件,並產生這些事件的統計資料。由於媒體子系統發生過頁面錯誤和核心鎖定爭用問題,我們可能會產生「頁面錯誤所花費的時間」和「核心鎖定遭封鎖所花費的時間」統計資料。
可能還有其他潛在問題,這兩項統計資料不會顯示。因此,全方位解決方案相當具有吸引力。其中一個想法是列舉執行緒進入核心的所有方式。這包括 N 個硬體中斷 (計時器和裝置中斷) 和 K 個軟體中斷 (系統呼叫和錯誤)。接著,我們會產生 N+K 統計資料,每種中斷各一項。當執行緒進入核心時,計時器會開始計時,並在控制項傳回使用者空間執行緒時停止計時。不過,使用者層級已可計算「在系統呼叫 X 中花費的時間」,因此讓核心計算這項資訊是多餘的。
另一個想法是產生「在核心模式下於 CPU 執行的時間」和「在 X 上遭到封鎖的時間」統計資料,其中 X 是一組核心基本類型,例如「核心鎖定」或「通道」。隨著核心基本體集隨時間變化,這個想法有膨脹和流失的風險。
回過頭來看,我們真正想做的是從實際環境中的裝置擷取追蹤記錄。
理想情況下,我們會持續將追蹤記錄錄製到循環緩衝區,並在達到 TRACE_ALERT 後上傳該緩衝區。
設計
雖然我們希望提供完整的解決方案,但設計和建構可能需要很長的時間。我們需要立即診斷外部環境中的效能回歸問題。因此,我建議新增兩項目標指標,以解決當前問題:等待頁面錯誤所花費的時間,以及等待核心鎖定所花費的時間。
關於上述設計空間問題:
我們不會追求完整性。
粒度是任意的 (我們會記錄我們認為需要的任何內容)
統計資料可能會以任意方式重疊
核心異動
// 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_time 和 queue_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_handler 和 vmm_accessed_fault_handler。
lock_contention_time會計算 Mutex::AcquireContendedMutex 和 BrwLock::Block 執行的總時間。這個方法已可存取目前的 Thread 和 current_ticks()。實作不會涵蓋自旋鎖。雖然自旋鎖可能會發生爭用情形,但我們目前會忽略自旋鎖,因為評估自旋鎖的爭用情形可能非常耗費資源。
為盡量減少額外負擔,我們會將這些時間長度記錄為刻度計數,並在 zx_object_get_info 系統呼叫期間轉換為 zx_duration_t。實作的其他詳細資料會遵循 cpu_time 和 queue_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_time 和 queue_time 的現有測試,以測試舊版 zx_info_task_runtime_t,並將其命名為 zx_info_task_runtime_v1_t。此外,Zircon 的
abi_type_validator.h
也會更新,以驗證新舊 ABI。確保 ABI 回溯相容性。
新增這項功能的整合測試並不容易,因為沒有 API 可強制核心發生鎖定爭用或觸發頁面錯誤 (除了程序終止區隔錯誤)。