System logowania Androida został zaprojektowany z myślą o powszechnej dostępności i łatwości obsługi. Zakłada się, ż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ść logów jest kluczowa bez użycia specjalistycznych narzędzi. W środowiskach wymagających wysokiej wydajności rejestrowania i ograniczonej wielkości logów rejestrowanie oparte na tekście 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ść 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. Główne zalety ProtoLog w porównaniu z logcat:
- Ilość zasobów używanych do rejestrowania jest mniejsza.
- Z punktu widzenia dewelopera jest to takie samo jak korzystanie z domyślnego frameworka logowania Androida.
- Umożliwia włączanie i wyłączanie instrukcji logowania w czasie działania programu.
- W razie potrzeby nadal możesz logować się w logcat.
Aby zoptymalizować wykorzystanie pamięci, ProtoLog używa mechanizmu internizacji ciągów znaków, który polega na obliczaniu i zapisywaniu skompilowanego skrótu wiadomości. Aby zwiększyć wydajność, ProtoLog wykonuje podczas kompilacji (w przypadku usług systemowych) internizację ciągów znaków, rejestrując w czasie działania tylko identyfikator wiadomości i argumenty. Dodatkowo podczas generowania śladu ProtoLog lub uzyskiwania raportu o błędzie ProtoLog automatycznie uwzględnia słownik wiadomości utworzony w czasie kompilacji, co umożliwia dekodowanie wiadomości z dowolnej kompilacji.
W przypadku ProtoLog wiadomość jest przechowywana w formacie binarnym (proto) w śladzie Perfetto. Dekodowanie wiadomości odbywa się w trace_processor
Perfetto. Proces ten polega na dekodowaniu binarnych wiadomości proto, tłumaczeniu identyfikatorów wiadomości na ciągi znaków za pomocą wbudowanego słownika wiadomości i formatowaniu ciągu znaków za pomocą argumentów dynamicznych.
ProtoLog obsługuje te same poziomy rejestrowania co android.utils.Log
, czyli: d
, v
, i
, w
, e
i wtf
.
ProtoLog po stronie klienta
Początkowo ProtoLog był przeznaczony wyłącznie 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, ale korzystanie z ProtoLog wymagało skomplikowanego kodu konfiguracji. Ponadto rejestrowanie w protokole Proto było ograniczone do procesów serwera systemowego i interfejsu systemowego, co utrudniało włączenie go do innych procesów i wymagało skonfigurowania oddzielnego bufora pamięci dla każdego z nich. Jednak ProtoLog jest teraz dostępny w przypadku kodu po stronie klienta, co eliminuje potrzebę stosowania dodatkowego kodu szablonowego.
W przeciwieństwie do kodu usług systemowych kod po stronie klienta zwykle pomija internizację 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, chociaż ProtoLog po stronie klienta zapewnia podobne korzyści w zakresie wykorzystania pamięci jak ProtoLog w usługach systemowych, wiąże się z nieco większym obciążeniem wydajnościowym i nie ma zalety w postaci zmniejszenia zużycia pamięci przypiętej, jak 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
służą jako klastry wiadomości, które można zbiorczo włączać i wyłączać w czasie działania programu.
Dodatkowo kontrolują, czy wiadomości mają być usuwane podczas kompilacji i gdzie mają być 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 nafalse
, 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 ciągi formatu ProtoLog używają android.text.TextUtils#formatSimple(String, Object...)
, więc ich 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 precyzji, takie jak %04d
i %10b
, są obsługiwane, ale argument_index
i flags
nie są.
Używanie ProtoLog w nowej usłudze
Aby użyć ProtoLog w nowym procesie:
Utwórz
ProtoLogGroup
definicję tej usługi.Zainicjuj definicję przed jej pierwszym użyciem (np. podczas tworzenia procesu):
Protolog.init(ProtologGroup.values());
Używaj
Protolog
w 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ć 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 ProtoLog
rejestrowania, 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 używać pełnej ścieżki, importowanie z użyciem symbolu wieloznacznego jest 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 logu 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, a następnie włączyć je podczas lokalnego tworzenia, aby debugować konkretny problem. Ten wzorzec jest używany np. w klasie WindowManager z grupami WM_DEBUG_WINDOW_TRANSITIONS
i WM_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. Możesz filtrować ślady 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:
Rysunek 1. Przeglądarka ProtoLog