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
, e
i wtf
.
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
i %10b
, są obsługiwane, ale argument_index
i flags
nie są obsługiwane.
Korzystanie z ProtoLog w nowej usłudze
Aby użyć ProtoLog w nowym procesie:
Utwórz definicję
ProtoLogGroup
dla tej usługi.Inicjalizacja definicji przed jej pierwszym użyciem (np. podczas tworzenia procesu):
Protolog.init(ProtologGroup.values());
Używaj właściwości
Protolog
w taki sam sposób jak właściwościandroid.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
, ProtoLogImpl
i ProtoLogGroup
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_TRANSITIONS
i WM_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:
Rysunek 1. Przeglądarka ProtoLog