Protolog

System logowania Androida ma być powszechnie dostępny i łatwy w użyciu, przy założeniu, że wszystkie dane logów można przedstawić jako sekwencję znaków. To założenie jest zgodne z większością przypadków użycia, zwłaszcza gdy czytelność logów jest kluczowa i nie można używać specjalistycznych narzędzi. Jednak w środowiskach wymagających wysokiej wydajności logowania i ograniczonych rozmiarów logów logowanie tekstowe nie jest optymalne. Jednym z takich scenariuszy jest WindowManager, który wymaga niezawodnego systemu logowania obsługującego logi przejść okien w czasie rzeczywistym przy minimalnym wpływie na system.

ProtoLog to alternatywa, która ma zaspokoić potrzeby logowania WindowManager i podobnych usług. W porównaniu z logcat ProtoLog oferuje te korzyści:

  • Zużywa mniej zasobów na logowanie.
  • Z perspektywy dewelopera działa tak samo jak domyślny framework logowania Androida.
  • Umożliwia włączanie i wyłączanie instrukcji logowania w czasie działania.
  • Może też logować do logcat.

Aby zoptymalizować wykorzystanie pamięci, ProtoLog używa mechanizmu internowania ciągów znaków. Ten mechanizm oblicza i zapisuje skompilowany skrót wiadomości. Aby zwiększyć wydajność, ProtoLog wykonuje internowanie ciągów znaków podczas kompilacji usług systemowych. W czasie działania rejestruje tylko identyfikator wiadomości i argumenty. Gdy wygenerujesz ślad ProtoLog lub uzyskasz raport o błędach, 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 logowania co android.utils.Log: d, v, i, w, e, i wtf.

ProtoLog po stronie klienta

Początkowo ProtoLog był przeznaczony tylko do strony serwera WindowManager, działającej w ramach jednego procesu i komponentu. Później został rozszerzony o kod powłoki WindowManager w procesie interfejsu systemowego. Jednak używanie ProtoLog wymagało skomplikowanego kodu konfiguracji. Ponadto logowanie Proto było ograniczone do procesów serwera systemowego i interfejsu systemowego. Utrudniało to włączenie go do innych procesów i wymagało oddzielnej konfiguracji bufora pamięci dla każdego z nich. ProtoLog jest teraz dostępny dla kodu po stronie klienta, co eliminuje potrzebę dodatkowego powtarzalnego kodu.

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 internowanie ciągów znaków odbywa się dynamicznie w wątku w tle. Dzięki temu 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 wydajnościowym 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 o nazwie ProtoLogGroups, podobnie jak jak Logcat wiadomości są uporządkowane według TAG. Te ProtoLogGroups służą jako klastry wiadomości, które można włączać i wyłączać w czasie działania. Określają też, czy wiadomości są usuwane podczas kompilacji i gdzie są logowane (proto, logcat lub oba). Każda ProtoLogGroup zawiera następujące właściwości:

  • enabled: gdy ta opcja jest ustawiona na false, wiadomości w tej grupie są wykluczane podczas kompilacji i nie są dostępne w czasie działania.
  • logToProto: określa, czy ta grupa loguje się w formacie binarnym.
  • logToLogcat: określa, czy ta grupa loguje się do logcat.
  • tag: nazwa źródła logowanej wiadomości.

Każdy proces, który używa ProtoLog, musi mieć skonfigurowaną instancję ProtoLogGroup.

Obsługiwane typy argumentów

ProtoLog wewnętrznie 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 i %10b. Jednak argument_index i flags nie są obsługiwane.

Używanie ProtoLog w nowej usłudze

Aby używać ProtoLog w nowej usłudze:

  1. Utwórz definicję ProtoLogGroup dla tej usługi.
  2. Zainicjuj definicję przed jej pierwszym użyciem. Na przykład zainicjuj ją 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ć 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ągów znaków i aktualizuje wywołanie ProtoLog. Ten plik binarny przekształca każde wywołanie logowania ProtoLog w sposób pokazany 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, ProtoLogImpl i ProtoLogGroup to klasy przekazywane jako argumenty (można je importować, importować statycznie lub używać pełnej ścieżki, importy z symbolem wieloznacznym są niedozwolone), a x to metoda logowania.

Transformacja odbywa się 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, a liczba znaków nowego 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 to, że można go włączać i wyłączać w czasie działania. Możesz na przykład mieć bardziej szczegółowe logowanie w kompilacji, które jest domyślnie wyłączone, ale możesz je włączyć podczas lokalnego programowania, aby debugować konkretny problem. Ten wzorzec jest używany np. w WindowManager w przypadku grup WM_DEBUG_WINDOW_TRANSITIONS i WM_DEBUG_WINDOW_TRANSITIONS_MIN, które umożliwiają różne typy logowania przejść. Pierwsza z nich jest domyślnie włączona.

ProtoLog możesz skonfigurować za pomocą Perfetto podczas uruchamiania śledzenia. ProtoLog możesz też skonfigurować 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 efektywnego używania

ProtoLog używa internowania ciągów znaków zarówno w przypadku wiadomości, jak i wszystkich przekazywanych argumentów ciągu znaków. Oznacza to, że aby w pełni wykorzystać zalety ProtoLog, wiadomości powinny izolować powtarzające się wartości w zmiennych.

Rozważmy na przykład to stwierdzenie:

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

Po zoptymalizowaniu w czasie kompilacji zostanie ono przetłumaczone na:

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

Jeśli w kodzie używany jest 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 została napisana 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 zmniejszenie wykorzystania pamięci o 35%.

Przeglądarka Winscope

Karta przeglądarki ProtoLog w Winscope pokazuje ś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 przenosi oś 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