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 nafalse, 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:
- Utwórz definicję
ProtoLogGroupdla tej usługi. Zainicjuj definicję przed jej pierwszym użyciem. Na przykład zainicjuj ją podczas tworzenia procesu:
ProtoLog.init(ProtoLogGroup.values());Używaj
ProtoLogw taki sam sposób jakandroid.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:
Rysunek 1. Przeglądarka ProtoLog