ProtoLog

Android ロギング システムは、すべてのログデータを文字列として表すことができることを前提として、誰もがアクセスできて使いやすいことを目標としています。この前提は、特に専用のツールがなくてもログの読み取り可能性が重要である場合、ほとんどのユースケースに適しています。ただし、ロギング パフォーマンスと制限付きログサイズが求められる環境では、テキストベースのロギングが最適ではない場合があります。そのようなシナリオの 1 つが WindowManager です。このシナリオでは、システムへの影響を最小限に抑えながら、リアルタイムのウィンドウ遷移ログを処理できる堅牢なロギング システムが必要です。

ProtoLog は、WindowManager などのサービスのロギングニーズに対応する代替手段です。logcat と比較した ProtoLog の主なメリットは次のとおりです。

  • ロギングに使用されるリソースの量が少なくなります。
  • デベロッパーの視点からは、デフォルトの Android ロギング フレームワークを使用する場合と同じです。
  • ログステートメントをランタイムで有効または無効にできます。
  • 必要に応じて logcat にログを記録できます。

メモリ使用量を最適化するために、ProtoLog は文字列の内部化メカニズムを使用します。このメカニズムでは、メッセージのコンパイル済みハッシュの計算と保存が行われます。パフォーマンスを向上させるため、ProtoLog はコンパイル時に文字列の内部化を行い(システム サービスの場合)、実行時にメッセージ ID と引数のみを記録します。また、ProtoLog トレースを生成するときやバグレポートを取得するときに、ProtoLog はコンパイル時に作成されたメッセージ辞書を自動的に組み込み、任意のビルドからのメッセージのデコードを可能にします。

ProtoLog を使用すると、メッセージは Perfetto トレース内のバイナリ形式(proto)で保存されます。メッセージのデコードは Perfetto の trace_processor 内で行われます。このプロセスは、バイナリ proto メッセージをデコードし、埋め込まれたメッセージ ディクショナリを使用してメッセージ ID を文字列に変換し、動的引数を使用して文字列をフォーマットします。

ProtoLog は、android.utils.Log と同じログレベル(dviwewtf)をサポートしています。

クライアントサイド ProtoLog

当初、ProtoLog は WindowManager のサーバーサイド専用で、単一のプロセスとコンポーネント内で動作していました。その後、システム UI プロセスの WindowManager シェルコードを対象に拡張されましたが、ProtoLog を使用するには複雑なボイラープレート設定コードが必要でした。さらに、Proto ロギングはシステム サーバー プロセスとシステム UI プロセスに限定されていたため、他のプロセスに組み込むのが手間がかかり、それぞれに個別のメモリバッファを設定する必要がありました。ただし、ProtoLog がクライアントサイド コードで利用可能になったため、追加のボイラープレート コードは不要になりました。

システム サービス コードとは異なり、クライアントサイド コードでは通常、コンパイル時の文字列インターニングがスキップされます。代わりに、文字列の内部化はバックグラウンド スレッドで動的に実行されます。その結果、クライアントサイドの ProtoLog は、システム サービス上の ProtoLog と同等のメモリ使用量のメリットを提供しますが、パフォーマンス オーバーヘッドがわずかに高く、サーバーサイドの ProtoLog のピン留めメモリによるメモリ削減のメリットがありません。

ProtoLog グループ

ProtoLog メッセージは、Logcat メッセージが TAG で編成されるように、ProtoLogGroups というグループに編成されます。これらの 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. Protologandroid.util.Log と同じ方法で使用します。

    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 の主な利点の 1 つは、実行時に有効または無効にできることです。たとえば、ビルドでは詳細ログをデフォルトで無効にして、特定の問題をデバッグするためにローカル開発中に有効にすることができます。このパターンは、たとえば 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 ビューア