RFC-0079:偵測偵錯記錄檔資料遺失

RFC-0079:偵測 debuglog 資料遺失
狀態已接受
區域
  • Kernel
  • 診斷
說明

修訂偵測 debuglog 資料遺失的機制。

問題
Gerrit 變更
作者
審查人員
提交日期 (年-月-日)2021-02-17
審查日期 (年-月-日)2021-03-25

摘要

這份文件建議更新機制,偵測核心 debuglog 物件中捨棄的訊息。

背景和動機

核心 debuglog 子系統是簡單的記錄功能,可讓使用者模式程式讀取及寫入記錄訊息。從邏輯上來看,這個系統提供單一 FIFO 記錄緩衝區,可供多個寫入器或讀取器寫入或讀取。

Debuglog 可能會遺失。假設讀者可以跟上寫入速率,所有讀者都會看到所有寫入者寫入的所有訊息,且訊息順序與寫入順序相同。不過,如果讀取器速度緩慢且無法跟上,就會遺漏訊息。

使用者模式程式可透過 zx_debuglog_write 將訊息寫入 debuglog,核心則可透過 printf 寫入訊息。訊息可透過 zx_debuglog_read 讀取。此外,核心還有一個專用執行緒 (稱為 debuglog_dumper),可從 debuglog 讀取訊息,並將訊息寫入偵錯序列埠。

debuglog 緩衝區的容量固定。達到容量上限時,系統可能會捨棄最近最少寫入的訊息,以便容納新訊息。如果讀者尚未「趕上進度」,就永遠不會看到遭捨棄的訊息。

瞭解記錄是否完整,有助於推斷是否缺少特定事件,因此偵測遺失的記錄訊息是記錄系統的重要功能。Debuglog 讀者必須能夠偵測記錄訊息何時遭到捨棄。

目前,debuglog 提供一種機制,讓讀者偵測記錄資料何時遭到捨棄,以及捨棄的位元組數,也就是 zx_log_record_tuint32_t rolled_out 欄位。

使用 zx_debuglog_read 讀取 zx_log_record_t 後,rolled_out 欄位會包含自該讀取器上次成功讀取以來,從 debuglog 捨棄的記錄訊息位元組數。這個值包含捨棄的記錄檔標頭位元組和捨棄的記錄檔主體位元組。

rolled_out 機制的實作方式是讓每個讀取器維護指向 debuglog 緩衝區的指標,該指標指向尚未讀取的下一個訊息。debuglog 會維護寫入指標,指向下一個訊息的寫入位置。如果讀取器發現寫入指標已通過讀取指標,就會知道自己遺漏了一或多則訊息。讀取器可以相減指標值,判斷遺失多少位元組的記錄資料 (包括標頭)。

目前未使用 rolled_out 機制。

提案

這項 RFC 提案...

  1. 以記錄導向的偵測方式,取代以位元組為導向的資料遺失偵測方式。

    為了更貼近 debuglog 讀者的期望 (尤其是 debuglog_dumper),現有的位元導向 rolled_out 機制將替換為每個記錄的序號,可用於偵測資料遺失 (序號中的間隙)。

    debuglog_dumper 必須將讀取的每則訊息寫入偵錯序列埠。由於序列埠可能不夠快,debuglog_dumper 通常無法跟上 debuglog,導致訊息遭到捨棄。發生這種情況時,我們想在序列埠列印訊息,指出資料遺失,以及遺失的訊息數量,類似於 Linux 的 printk 輸出內容。

  2. 使用 64 位元值,消除未偵測到資料遺失的可能性。

    由於 rolled_out 欄位的大小為 32 位元,且會計算位元組而非記錄,因此如果在兩次呼叫 zx_debuglog_read 之間寫入 4 GB 的記錄資料,可能會導致值溢位,進而造成資料遺失但未偵測到。這種情況在實務上不太可能發生,但最好還是完全消除這種可能性。如果我們將 32 位元位元組序列欄位替換為每個記錄 32 位元的序列欄位,建立溢位所需的資料量就會增加至約 128 GB。使用 64 位元序列欄位時,即使記錄速率非常高,我們也能完全忽略溢位的可能性。

  3. 啟用日後實作最佳化功能。

    日後可能會有某些最佳化功能,需要允許多個 debuglog 讀取器「共用」單一 zx_log_record_t

    如果沒有讀取速度緩慢的讀取器,所有 debuglog 讀取器應該會看到完全相同的記錄資料,且順序相同。除了 rolled_out 之外,zx_log_record_t 的所有欄位在記錄寫入偵錯記錄時都會固定。rolled_out 的不同之處在於,它是根據每個讀取器計算,時間點是從 debuglog 讀取記錄時。如果我們有其他方法可偵測資料遺失,而不使用可能因讀取器而異的欄位,就能啟用一些潛在的未來最佳化功能,讓所有讀取器共用單一記錄。

設計

debuglog 會為每個記錄指派 64 位元的序號 (從 0 開始),並寫入 debuglog。

每個記錄的序號都會比前一個記錄大 1。移除 zx_log_record_trolled_out 欄位,並替換為記錄的序號 uint64_t sequence

zx_debuglog_read 的呼叫端隨後可以偵測序列中的間隙,並計算遺失的訊息數量。

實作

樹狀結構外的 zx_debuglog_readzx_log_record_t 不會使用。雖然不需要完整的 Fuchsia 大規模變更 (LSC) 程序,但系統會提交 FYI LSC 錯誤,並分階段完成實作。

rolled_out 欄位未經使用,但包含的結構體 zx_log_record_t 在 fuchsia.git 中有幾個地方會用到。請務必小心,以免破壞現有程式碼。zx_log_record_t 不會用於樹狀結構外。

zx_debuglog_read 的系統呼叫定義和說明文件實際上並未指定會傳回 zx_log_record_t。而是指定 void*size_t,呼叫端必須知道要將 zx_log_record_t 轉換或「疊加」在結果頂端。轉換為 zx_log_record_t* 容易出錯,因此 void* syscall 參數會變更為 zx_log_record_t*,並更新呼叫端。

目前沒有 zx_log_record_t 的 Rust 對等項目,Rust 呼叫端會使用硬式編碼的偏移量存取欄位,因此變更欄位的大小或偏移量可能會導致這些呼叫端無聲無息地中斷。在實作過程中,我們會建立 zx_log_record_t 的 Rust 等效項目,並更新 Rust 呼叫端以使用該項目。

步驟如下:

  1. 在 debuglog 訊息 (dlog_header_t) 的私有內部表示法中,加入 64 位元序號。

  2. 將 debuglog 消費者變更為使用 zx_log_record_t (或對應語言),而非硬式編碼的欄位偏移。具體來說,請建立 Rust 對等型別,並更新 Rust 程式碼以使用該型別。

  3. zx_debuglog_readvoid* 參數變更為 zx_log_record_t*

  4. zx_log_record_tzx_log_record_t 欄位變更為填滿零的 unused 欄位。rolled_out

  5. zx_log_record_tunused 欄位替換為 uint64_t sequence,並確保不會建立隱含的結構體填補。針對所有zx_log_record_t等值類型執行相同操作,不論語言為何。

  6. 更新 zx_debuglog_read 說明文件,說明呼叫端如何使用新的序列欄位偵測資料遺失。

步驟 1、2 和 3 各自會有一個 CL。步驟 4、5 和 6 會在單一 CL 中進行。

效能

管理序號計數器的執行階段費用

執行 Debuglog 作業時會持有鎖定,因此我們可以使用一般 uint64_t 值產生序列。預計不會對成效造成可評估的影響。

每個記錄序列值的大小影響

zx_log_record_tdlog_header_t (核心的私有記錄實作項目) 的大小都會增加。zx_log_record_t 的 32 位元 rolled_out 欄位會替換為 64 位元記錄序列欄位,淨增 4 個位元組。

dlog_header_t 大小變更更有趣,因為這是 FIFO 中儲存記錄的原始形式。dlog_header_t 的大小為 32 個位元組,且沒有 rolled_out 欄位,因此淨增益為完整的 8 個位元組。debuglog 物件的 FIFO 空間有限,因此每增加 8 個位元組的記錄,FIFO 中可儲存的記錄數量上限就會減少,訊息大小上限也會減少 (從 224 個位元組減少至 216 個位元組)。

FIFO 可儲存 128 KB 的標頭和訊息。訊息樣本顯示平均大小約為 100 個位元組。假設平均大小為 1 KB,且標頭為 32 位元組,則 FIFO 大約可儲存 971 則訊息。如果使用每筆記錄序號,則數量會減少至約 917 個。

安全性考量

提案不會改變系統的安全性。Debuglog 讀取器是具備特殊權限的元件。如果沒有資料遺失,debuglog 讀取器就能以完美準確度合成記錄檔記錄序列。

隱私權注意事項

不會影響隱私權。

測試

核心內單元測試會驗證基礎 debuglog 實作,debuglog 核心測試則會驗證 syscall 層可觀察到的行為。

說明文件

zx_log_record_t 的說明文件將會更新。

缺點、替代方案和未知事項

不執行任何動作

由於目前尚未採用 rolled_out,因此現在導入提案所需的工程工作量相對較少。下游程式碼一旦使用 rolled_out,實作這項或類似提案的成本就會更高。

記錄 UINT32_MAX 的語意為「UINT32_MAX 以上」,或許可以稍微減輕 32 位元溢位問題。或者,我們可以將 rolled_out 的型別變更為 uint64_t

記錄序列和位元組序列

如果空間足夠,我們可以在每個 zx_log_record_t 中放入記錄序列值和位元組序列值。然後,Debuglog 讀取器可以測量記錄數或位元組數的資料遺失量。不過,這會進一步增加 dlog_header_t 的大小,而且我們不確定是否有位元組捨棄的用途。

既有技術和參考資料

Linux 的 printk 會回報捨棄/抑制的訊息數量,而非捨棄/抑制的記錄資料位元組數。