ProtoLog

Android 記錄系統的目標是提供普遍適用的易用功能,並假設所有記錄資料都能以字元序列表示。這項假設符合大多數用途,特別是在沒有專用工具的情況下,記錄可讀性至關重要時。不過,在需要高記錄效能和限制記錄大小的環境中,以文字為基礎的記錄可能不是最佳選擇。其中一個例子是 WindowManager,這需要強大的記錄系統,能夠處理即時視窗轉換記錄,且對系統的影響極小。

ProtoLog 是替代方案,可滿足 WindowManager 和類似服務的記錄需求。與 logcat 相比,ProtoLog 的主要優點如下:

  • 記錄使用的資源量較少。
  • 從開發人員的角度來看,這與使用預設的 Android 記錄架構相同。
  • 支援在執行階段啟用或停用記錄陳述式。
  • 如有需要,仍可記錄到 logcat。

為最佳化記憶體用量,ProtoLog 採用字串實習機制,包括計算及儲存訊息的已編譯雜湊。為提升效能,ProtoLog 會在編譯期間執行字串實習 (適用於系統服務),並在執行階段只記錄訊息 ID 和引數。此外,產生 ProtoLog 追蹤記錄或取得錯誤報告時,ProtoLog 會自動納入編譯時建立的訊息字典,因此可解碼任何建構版本的訊息。

使用 ProtoLog 時,訊息會以二進位格式 (proto) 儲存在 Perfetto 追蹤記錄中。訊息解碼作業會在 Perfetto 的 trace_processor 內部進行。這個程序包括解碼二進位 proto 訊息、使用內嵌訊息字典將訊息 ID 轉換為字串,以及使用動態引數格式化字串。

ProtoLog 支援與 android.utils.Log 相同的記錄層級,包括:dviwewtf

用戶端 ProtoLog

最初,ProtoLog 僅適用於 WindowManager 的伺服器端,在單一程序和元件中運作。隨後,這項功能擴大涵蓋系統 UI 程序中的 WindowManager Shell 程式碼,但 ProtoLog 使用需要複雜的樣板設定程式碼。此外,Proto 記錄僅限於系統伺服器和系統 UI 程序,因此難以納入其他程序,且每個程序都需要設定個別的記憶體緩衝區。不過,ProtoLog 現在已適用於用戶端程式碼,因此不必使用額外的樣板程式碼。

與系統服務程式碼不同,用戶端程式碼通常會略過編譯時間字串實習。而是會在背景執行緒中動態發生字串實習。因此,雖然用戶端 ProtoLog 提供的記憶體用量優勢與系統服務上的 ProtoLog 相當,但效能負擔略高,且不像伺服器端對應項目那樣具有釘選記憶體的記憶體減少優勢。

ProtoLog 群組

ProtoLog 訊息會歸類到名為 ProtoLogGroups 的群組,與 Logcat 訊息的分類方式 (依據 TAG) 類似。這些 ProtoLogGroups 可做為訊息叢集,可在執行階段集體啟用或停用。此外,他們還能控制是否要在編譯期間移除訊息,以及訊息的記錄位置 (proto、logcat 或兩者皆是)。每個 ProtoLogGroup 都包含下列屬性:

  • enabled:設為 false 時,系統會在編譯期間排除這個群組中的訊息,且這些訊息在執行階段無法使用。
  • logToProto:定義這個群組是否以二進位格式記錄。
  • logToLogcat:定義這個群組是否會記錄到 Logcat。
  • tag:記錄訊息的來源名稱。

使用 ProtoLog 的每個程序都必須設定 ProtoLogGroup 執行個體。

支援的引數類型

在內部,ProtoLog 格式字串會使用 android.text.TextUtils#formatSimple(String, Object...),因此語法相同。

ProtoLog 支援下列引數類型:

  • %b - 布林值
  • %d%x - 整數型別 (short、integer 或 long)
  • %f - 浮點類型 (float 或 double)
  • %s - 字串
  • %% - 百分比字元

系統支援 %04d%10b 等寬度和精確度修飾符,但不支援 argument_indexflags

在新服務中使用 ProtoLog

如要在新程序中使用 ProtoLog,請按照下列步驟操作:

  1. 為這項服務建立 ProtoLogGroup 定義。

  2. 在首次使用定義前先進行初始化 (例如在程序建立時):

    Protolog.init(ProtologGroup.values());

  3. 以與 android.util.Log 相同的方式使用 Protolog

    ProtoLog.v(WM_SHELL_STARTING_WINDOW, "create taskSnapshot surface for task: %d", taskId);

啟用編譯時間最佳化

如要在程序中啟用編譯時間 ProtoLog,您必須變更其建構規則,並叫用 protologtool 二進位檔。

ProtoLogTool 是程式碼轉換二進位檔,可執行字串實習並更新 ProtoLog 呼叫。這個二進位檔會轉換每個 ProtoLog 記錄呼叫,如下列範例所示:

ProtoLog.x(ProtoLogGroup.GROUP_NAME, "Format string %d %s", value1, value2);

轉換為:

if (ProtoLogImpl.isEnabled(GROUP_NAME)) {
    int protoLogParam0 = value1;
    String protoLogParam1 = String.valueOf(value2);
    ProtoLogImpl.x(ProtoLogGroup.GROUP_NAME, 1234560b0100, protoLogParam0, protoLogParam1);
}

在本範例中,ProtoLogProtoLogImplProtoLogGroup 是以引數形式提供的類別 (可匯入、靜態匯入或完整路徑,不允許使用萬用字元匯入),而 x 是記錄方法。

轉換作業是在來源層級完成,系統會從格式字串、記錄層級和記錄群組名稱產生雜湊,並插入 ProtoLogGroup 引數後方。系統會內嵌實際產生的程式碼,並新增多個換行字元,以保留檔案中的行號。

例子:

genrule {
    name: "wm_shell_protolog_src",
    srcs: [
        ":protolog-impl", // protolog lib
        ":wm_shell_protolog-groups", // protolog groups declaration
        ":wm_shell-sources", // source code
    ],
    tools: ["protologtool"],
    cmd: "$(location protologtool) transform-protolog-calls " +
        "--protolog-class com.android.internal.protolog.ProtoLog " +
        "--loggroups-class com.android.wm.shell.protolog.ShellProtoLogGroup " +
        "--loggroups-jar $(location :wm_shell_protolog-groups) " +
        "--viewer-config-file-path /system_ext/etc/wmshell.protolog.pb " +
        "--legacy-viewer-config-file-path /system_ext/etc/wmshell.protolog.json.gz " +
        "--legacy-output-file-path /data/misc/wmtrace/shell_log.winscope " +
        "--output-srcjar $(out) " +
        "$(locations :wm_shell-sources)",
    out: ["wm_shell_protolog.srcjar"],
}

指令列選項

ProtoLog 的主要優點之一,就是您可以在執行階段啟用或停用這項功能。舉例來說,您可以在建構版本中啟用預設為停用的詳細記錄,並在進行本機開發時啟用,以便偵錯特定問題。舉例來說,WindowManager 中的群組 WM_DEBUG_WINDOW_TRANSITIONSWM_DEBUG_WINDOW_TRANSITIONS_MIN 會使用這個模式,啟用不同類型的轉換記錄,前者預設為啟用。

啟動追蹤記錄時,您可以使用 Perfetto 設定 ProtoLog。您也可以使用 adb 指令列在本機設定 ProtoLog。

adb shell cmd protolog_configuration 指令支援下列引數:

help
  Print this help text.

groups (list | status)
  list - lists all ProtoLog groups registered with ProtoLog service"
  status <group> - print the status of a ProtoLog group"

logcat (enable | disable) <group>"
  enable or disable ProtoLog to logcat

有效使用訣竅

ProtoLog 會對訊息和傳遞的任何字串引數使用字串實習。也就是說,如要從 ProtoLog 獲得更多好處,訊息應將重複值隔離到變數中。

舉例來說,請看以下陳述:

Protolog.v(MY_GROUP, "%s", "The argument value is " + argument);

在編譯時最佳化後,會轉譯為:

ProtologImpl.v(MY_GROUP, 0x123, "The argument value is " + argument);

如果在程式碼中使用 ProtoLog,並搭配 A,B,C 引數:

Protolog.v(MY_GROUP, "%s", "The argument value is A");
Protolog.v(MY_GROUP, "%s", "The argument value is B");
Protolog.v(MY_GROUP, "%s", "The argument value is C");
Protolog.v(MY_GROUP, "%s", "The argument value is A");

這會導致記憶體中出現下列訊息:

Dict:
  0x123: "%s"
  0x111: "The argument value is A"
  0x222: "The argument value is B"
  0x333: "The argument value is C"

Message1 (Hash: 0x123, Arg1: 0x111)
Message2 (Hash: 0x123, Arg2: 0x222)
Message3 (Hash: 0x123, Arg3: 0x333)
Message4 (Hash: 0x123, Arg1: 0x111)

如果 ProtoLog 陳述式改為以下形式:

Protolog.v(MY_GROUP, "The argument value is %s", argument);

記憶體緩衝區最終會變成:

Dict:
  0x123: "The argument value is %s" (24 b)
  0x111: "A" (1 b)
  0x222: "B" (1 b)
  0x333: "C" (1 b)

Message1 (Hash: 0x123, Arg1: 0x111)
Message2 (Hash: 0x123, Arg2: 0x222)
Message3 (Hash: 0x123, Arg3: 0x333)
Message4 (Hash: 0x123, Arg1: 0x111)

這個序列可減少 35% 的記憶體用量。

Winscope 檢視器

Winscope 的 ProtoLog 檢視器分頁會以表格格式顯示 ProtoLog 追蹤記錄。您可以依記錄層級、標記、來源檔案 (ProtoLog 陳述式所在位置) 和訊息內容篩選追蹤記錄。所有資料欄都可以篩選。 按一下第一欄中的時間戳記,時間軸就會跳到該訊息的時間戳記。此外,按一下「前往目前時間」,ProtoLog 表格就會捲動回時間軸中選取的時間戳記:

ProtoLog 檢視器

圖 1. ProtoLog 檢視器