Protolog

System logowania Androida ma na celu zapewnienie powszechnej dostępności i łatwości użycia, przy założeniu, że wszystkie dane logu można przedstawić jako ciąg znaków. To założenie jest zgodne z większością przypadków użycia, zwłaszcza gdy czytelność dzienników jest kluczowa bez specjalistycznych narzędzi. Jednak w środowiskach wymagających wysokiej wydajności rejestrowania i ograniczonych rozmiarów logów rejestrowanie oparte na tekście nie jest optymalne. Jednym z takich scenariuszy jest WindowManager, który wymaga solidnego systemu rejestrowania, który obsługuje dzienniki przejść okien w czasie rzeczywistym przy minimalnym wpływie na system.

ProtoLog to alternatywne rozwiązanie, które zaspokaja potrzeby w zakresie logowania w przypadku WindowManager i podobnych usług. ProtoLog ma te zalety w porównaniu z logcat:

  • Zużywa mniej zasobów na potrzeby rejestrowania.
  • Z perspektywy dewelopera działa on tak samo jak domyślna platforma logowania Androida.
  • Umożliwia włączanie i wyłączanie instrukcji logowania w czasie działania programu.
  • Może też rejestrować dane w logcat.

Aby zoptymalizować wykorzystanie pamięci, ProtoLog używa mechanizmu internizacji ciągów znaków. Ten mechanizm oblicza i zapisuje skompilowany skrót wiadomości. Aby zwiększyć wydajność, ProtoLog wykonuje podczas kompilacji usług systemowych internowanie ciągów znaków. Rejestruje tylko identyfikator wiadomości i argumenty w czasie działania. Gdy wygenerujesz ślad ProtoLog lub uzyskasz raport o błędzie, ProtoLog automatycznie uwzględni słownik wiadomości utworzony w czasie kompilacji. Umożliwia to dekodowanie wiadomości z dowolnej kompilacji.

ProtoLog przechowuje wiadomości w formacie binarnym (proto) w śladzie Perfetto. Dekodowanie wiadomości odbywa się w trace_processor Perfetto. Proces dekoduje binarne wiadomości proto, tłumaczy identyfikatory wiadomości na ciągi znaków za pomocą wbudowanego słownika wiadomości i formatuje ciąg znaków za pomocą argumentów dynamicznych.

ProtoLog obsługuje te same poziomy rejestrowania co android.utils.Log, czyli d, v, i, w, ewtf.

ProtoLog po stronie klienta

Początkowo ProtoLog był przeznaczony tylko dla serwera WindowManager, działającego w ramach jednego procesu i komponentu. Później rozszerzono go o kod powłoki WindowManager w procesie interfejsu systemu. Jednak korzystanie z ProtoLog wymagało skomplikowanego kodu konfiguracji. Dodatkowo rejestrowanie w protokole Proto zostało ograniczone do procesów serwera systemowego i interfejsu systemu. Utrudniało to włączenie ich do innych procesów i wymagało osobnej konfiguracji bufora pamięci dla każdego z nich. ProtoLog jest teraz dostępny w przypadku kodu po stronie klienta, co eliminuje konieczność stosowania dodatkowego kodu szablonowego.

W przeciwieństwie do kodu usług systemowych kod po stronie klienta zwykle pomija internowanie ciągów znaków w czasie kompilacji. Zamiast tego internizowanie ciągów znaków odbywa się dynamicznie w wątku w tle. W rezultacie ProtoLog po stronie klienta zapewnia podobne korzyści w zakresie wykorzystania pamięci jak ProtoLog w usługach systemowych, ale wiąże się z nieco większym obciążeniem i nie ma zalety w postaci zmniejszenia zużycia pamięci przypiętej, którą oferuje jego odpowiednik po stronie serwera.

Grupy ProtoLog

Wiadomości ProtoLog są uporządkowane w grupy nazywane ProtoLogGroups, podobnie jak wiadomości Logcat są uporządkowane według TAG. Te ProtoLogGroups działają jako klastry wiadomości, które możesz włączać i wyłączać w czasie działania programu. Mają też kontrolę nad tym, czy wiadomości są usuwane podczas kompilacji i gdzie są rejestrowane (w protokole, logcat lub w obu tych miejscach). Każdy element ProtoLogGroup zawiera te właściwości:

  • enabled: jeśli ta opcja jest ustawiona na false, wiadomości w tej grupie są wykluczane podczas kompilacji i nie są dostępne w czasie działania programu.
  • logToProto: określa, czy ta grupa rejestruje dane w formacie binarnym.
  • logToLogcat: określa, czy ta grupa rejestruje dane w logcat.
  • tag: Nazwa źródła zalogowanej wiadomości.

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

Obsługiwane typy argumentów

Wewnętrznie ProtoLog formatuje ciągi znaków za pomocą 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

Obsługiwane są modyfikatory szerokości i precyzji, takie jak %04d%10b. Atrybuty argument_index i flags nie są jednak obsługiwane.

Używanie ProtoLog w nowej usłudze

Aby używać ProtoLog w nowej usłudze, wykonaj te czynności:

  1. Utwórz ProtoLogGroup definicję tej usługi.
  2. Zainicjuj definicję przed pierwszym użyciem. Na przykład zainicjuj go podczas tworzenia procesu:

    ProtoLog.init(ProtoLogGroup.values());
    
  3. Używaj ProtoLog w taki sam sposób jak android.util.Log:

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

Włączanie optymalizacji w czasie kompilacji

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

ProtoLogTool to binarny plik przekształcający kod, który wykonuje internizację ciągów znaków i aktualizuje wywołanie ProtoLog. Ten plik binarny przekształca każde wywołanie ProtoLogrejestrowania, jak pokazano w tym przykładzie:

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

do:

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, ProtoLogImpl i ProtoLogGroup to klasy podane jako argumenty (można je importować, importować statycznie lub podać pełną ścieżkę; importy z symbolem wieloznacznym są niedozwolone), a x to metoda rejestrowania.

Przekształcenie jest wykonywane na poziomie źródła. Skrót jest generowany na podstawie ciągu formatu, poziomu logowania i nazwy grupy logów, a następnie wstawiany po argumencie ProtoLogGroup. Rzeczywisty wygenerowany kod jest wstawiany w tekście, a liczba nowych znaków końca wiersza jest dodawana w celu zachowania numeracji 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 ProtoLog jest możliwość włączania i wyłączania tej funkcji w czasie działania programu. Możesz na przykład w kompilacji włączyć bardziej szczegółowe logowanie, które jest domyślnie wyłączone, i używać go podczas lokalnego programowania, aby debugować konkretny problem. Ten wzorzec jest używany np. w klasie WindowManager z grupami WM_DEBUG_WINDOW_TRANSITIONSWM_DEBUG_WINDOW_TRANSITIONS_MIN, które umożliwiają rejestrowanie różnych typów przejść. Pierwsza z nich jest domyślnie włączona.

Podczas rozpoczynania śledzenia możesz skonfigurować ProtoLog za pomocą 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

ProtoLog używa internizacji ciągów znaków zarówno w przypadku wiadomości, jak i przekazywanych argumentów w postaci ciągów znaków. Oznacza to, że aby uzyskać większe korzyści z ProtoLog, wiadomości powinny wyodrębniać powtarzające się wartości do zmiennych.

Rozważmy na przykład to stwierdzenie:

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

Po zoptymalizowaniu w czasie kompilacji kod wygląda tak:

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

Jeśli w kodzie użyto 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 pojawią się te wiadomości:

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 wyglądałaby tak:

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 o 35% mniejsze.

Przeglądarka Winscope

Karta przeglądarki ProtoLog w Winscope wyświetla ślady ProtoLog w formacie tabelarycznym. Ślady możesz filtrować według poziomu logowania, 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 powoduje przejście osi czasu do sygnatury czasowej wiadomości. Dodatkowo kliknięcie Przejdź do bieżącego czasu powoduje przewinięcie tabeli ProtoLog z powrotem do sygnatury czasowej wybranej na osi czasu:

Przeglądarka ProtoLog

Rysunek 1. Przeglądarka ProtoLog