Protolog

System rejestrowania danych w Androidzie jest ukierunkowany na uniwersalną dostępność i łatwość użycia, zakładając, że wszystkie dane dziennika mogą być reprezentowane jako sekwencja znaków. To założenie jest zgodne z większością przypadków użycia, zwłaszcza gdy czytelność logów jest kluczowa bez specjalistycznych narzędzi. Jednak w środowiskach wymagających wysokiej wydajności logowania i ograniczonego rozmiaru logów logowanie tekstowe może nie być optymalne. Jednym z takich scenariuszy jest WindowManager, który wymaga solidnego systemu rejestrowania, który może obsługiwać dzienniki przejść między oknami w czasie rzeczywistym z minimalnym wpływem na system.

ProtoLog to alternatywa dla potrzeb rejestrowania WindowManagera i podobnych usług. Główne zalety ProtoLog w porównaniu z logcat:

  • Ilość zasobów używanych do rejestrowania jest mniejsza.
  • Z perspektywy dewelopera jest to to samo, co używanie domyślnego frameworku rejestrowania Androida.
  • Obsługuje instrukcje logowania, które można włączać i wyłączać w czasie wykonywania.
  • W razie potrzeby nadal można zapisywać logi w pliku logcat.

Aby zoptymalizować wykorzystanie pamięci, ProtoLog stosuje mechanizm internowania ciągu znaków, który polega na obliczaniu i zapisywaniu skompilowanego hasza wiadomości. Aby zwiększyć wydajność, ProtoLog wykonuje internowanie ciągu podczas kompilacji (w przypadku usług systemowych), rejestrując tylko identyfikator wiadomości i argumenty w czasie wykonywania. Dodatkowo podczas generowania ścieżki ProtoLog lub uzyskiwania raportu o błędzie ProtoLog automatycznie włącza słownik wiadomości utworzony w czasie kompilacji, co umożliwia dekodowanie wiadomości z dowolnej kompilacji.

Za pomocą ProtoLog wiadomość jest zapisywana w formacie binarnym (proto) w śladzie Perfetto. Dekodowanie wiadomości odbywa się w ramach trace_processor usługi Perfeto. Proces ten polega na dekodowaniu binarnych komunikatów proto, przekształcaniu identyfikatorów komunikatów w ciągi za pomocą wbudowanego słownika komunikatów oraz formatowaniu ciągu za pomocą argumentów dynamicznych.

ProtoLog obsługuje te same poziomy logowania co android.utils.Log, a mianowicie: d, v, i, w, ewtf.

ProtoLog po stronie klienta

Początkowo ProtoLog był przeznaczony tylko do korzystania z serwera WindowManager, działającego w ramach jednego procesu i jednego komponentu. Następnie został on rozszerzony o kod powłoki WindowManager w ramach procesu interfejsu System UI, ale użycie ProtoLog wymagało skomplikowanego szablonowego kodu konfiguracyjnego. Ponadto rejestrowanie protokołów było ograniczone do serwera systemowego i procesów interfejsu systemowego, co utrudniało ich wdrażanie w innych procesach i wymagało skonfigurowania osobnego bufora pamięci dla każdego z nich. ProtoLog jest teraz dostępny dla kodu po stronie klienta, co eliminuje potrzebę stosowania dodatkowego kodu szablonowego.

W odróżnieniu od kodu usług systemowych kod po stronie klienta zazwyczaj pomija internowanie ciągu znaków w czasie kompilacji. Zamiast tego kodowanie ciągu znaków odbywa się dynamicznie w wątku w tle. W rezultacie, chociaż ProtoLog po stronie klienta zapewnia porównywalne zalety w zakresie wykorzystania pamięci jak ProtoLog w usługach systemowych, powoduje nieznacznie wyższe obciążenie wydajności i nie ma zalet w zakresie oszczędzania pamięci dzięki przypiętej pamięci, które ma jego odpowiednik po stronie serwera.

Grupy ProtoLog

Komunikaty ProtoLog są uporządkowane w grupy o nazwie ProtoLogGroups, podobnie jak komunikaty Logcat są uporządkowane według TAG. Te ProtoLogGroups działają jak klastry wiadomości, które można zbiorczo włączać i wyłączać w czasie działania. Dodatkowo określają, czy wiadomości mają być usuwane podczas kompilacji oraz gdzie mają być rejestrowane (w proto, logcat lub w obu tych miejscach). Każdy element ProtoLogGroup zawiera te właściwości:

  • enabled: jeśli ustawisz wartość false, wiadomości w tej grupie zostaną wykluczone podczas kompilacji i nie będą dostępne w czasie wykonywania.
  • logToProto: określa, czy ta grupa ma rejestrować logi w formacie binarnym.
  • logToLogcat: określa, czy ta grupa ma rejestrować logi w logcat.
  • tag: nazwa źródła zarejestrowanej wiadomości.

Każdy proces korzystający z ProtoLog musi mieć skonfigurowaną instancję ProtoLogGroup.

Obsługiwane typy argumentów

Wewnętrzne formatowanie ciągów znaków ProtoLog używa android.text.TextUtils#formatSimple(String, Object...), więc jego składnia jest taka sama.

ProtoLog obsługuje te typy argumentów:

  • %b – wartość logiczna
  • %d, %x – typ całkowity (short, integer lub long)
  • %f – typ zmiennoprzecinkowy (float lub double)
  • %s – ciąg znaków
  • %% – dosłowny znak procenta

Modyfikatory szerokości i dokładności, takie jak %04d%10b, są obsługiwane, ale argument_indexflags nie są obsługiwane.

Korzystanie z ProtoLog w nowej usłudze

Aby użyć ProtoLog w nowym procesie:

  1. Utwórz definicję ProtoLogGroup dla tej usługi.

  2. Inicjalizacja definicji przed jej pierwszym użyciem (np. podczas tworzenia procesu):

    Protolog.init(ProtologGroup.values());

  3. Używaj właściwości Protolog w taki sam sposób jak właściwości android.util.Log:

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

Włącz optymalizację w czasie kompilacji

Aby włączyć ProtoLog w czasie kompilacji w procesie, musisz zmienić jego reguły kompilacji i wywołać plik binarny protologtool.

ProtoLogTool to plik binarny do transformacji kodu, który wykonuje internowanie ciągu znaków i aktualizuje wywołanie ProtoLog. Ten plik binarny przekształca każdy wywołanie funkcji ProtoLog do rejestrowania, jak pokazano w tym przykładzie:

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

w:

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

W tym przykładzie ProtoLog, ProtoLogImplProtoLogGroup to klasy podane jako argumenty (mogą być zaimportowane, statycznie zaimportowane lub podane jako pełna ścieżka; nie są dozwolone importy z użyciem znaku zapytania), a x to metoda rejestrowania.

Przekształcenie jest wykonywane na poziomie źródła. Na podstawie ciągu formatu, poziomu logowania i nazwy grupy logów generowany jest ciąg skrótu, który jest wstawiany po argumencie ProtoLogGroup. Prawdziwy wygenerowany kod jest wstawiany w kod źródłowy i dodaje się do niego kilka nowych znaków wiersza, aby zachować numerację wierszy w pliku.

Przykład:

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"],
}

Opcje wiersza poleceń

Jedną z głównych zalet protokołu ProtoLog jest to, że można go włączyć lub wyłączyć w czasie wykonywania. Możesz na przykład włączyć bardziej szczegółowe rejestrowanie w ramach kompilacji, która jest domyślnie wyłączona, i włączyć je podczas lokalnego tworzenia aplikacji, aby debugować konkretny problem. Ten wzór jest używany np. w WindowManager z grupami WM_DEBUG_WINDOW_TRANSITIONSWM_DEBUG_WINDOW_TRANSITIONS_MIN, które umożliwiają różne typy rejestrowania przejść, przy czym pierwszy z nich jest domyślnie włączony.

Podczas uruchamiania śledzenia możesz skonfigurować ProtoLog za pomocą narzędzia Perfetto. Możesz też skonfigurować ProtoLog lokalnie za pomocą wiersza poleceń adb.

Polecenie adb shell cmd protolog_configuration obsługuje te argumenty:

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

Wskazówki dotyczące skutecznego korzystania z usługi

ProtoLog używa internowania ciągu znaków zarówno w przypadku wiadomości, jak i przekazanych argumentów ciągu znaków. Oznacza to, że aby uzyskać więcej korzyści z ProtoLog, należy w wiadomościach odizolować powtarzające się wartości w zmiennych.

Weź pod uwagę na przykład to stwierdzenie:

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

Po optymalizacji w czasie kompilacji oznacza to:

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

Jeśli w kodzie jest używana funkcja ProtoLog z argumentami 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");

W pamięci pojawiają się następujące komunikaty:

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)

Jeśli zamiast tego instrukcja ProtoLog zostałaby zapisana w ten sposób:

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

Bufor w pamięci będzie wyglądał tak:

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)

Ta sekwencja powoduje, że zużycie pamięci jest mniejsze o 35%.

Wyświetlający Winscope

Na karcie Podgląd protokołu w Winscope widać ścieżki protokołu w formacie tabelarycznym. Możesz filtrować ścieżki według poziomu dziennika, tagu, pliku źródłowego (w którym znajduje się instrukcja ProtoLog) i treści wiadomości. Wszystkie kolumny można filtrować. Kliknięcie sygnatury czasowej w pierwszej kolumnie przeniesie osi czasu do sygnatury czasowej wiadomości. Poza tym kliknięcie Przejdź do bieżącego czasu spowoduje przewinięcie tabeli ProtoLog do wybranego na osi czasu sygnatury czasowej:

Przeglądarka ProtoLog

Rysunek 1. Przeglądarka ProtoLog