Система журналирования Android стремится к универсальной доступности и простоте использования, предполагая, что все данные журнала могут быть представлены в виде последовательности символов. Это предположение согласуется с большинством вариантов использования, особенно когда читаемость журнала критически важна без специализированных инструментов. Однако в средах, требующих высокой производительности журналирования и ограниченного размера журнала, текстовое журналирование может быть неоптимальным. Одним из таких сценариев является WindowManager, для которого требуется надежная система журналирования, способная обрабатывать журналы переходов окон в режиме реального времени с минимальным влиянием на систему.
ProtoLog — это альтернатива для решения задач журналирования WindowManager и подобных сервисов. Основные преимущества ProtoLog перед logcat:
- Объем ресурсов, используемых для ведения журнала, меньше.
- С точки зрения разработчика, это то же самое, что использовать стандартную структуру журналирования Android.
- Поддерживает включение и отключение операторов журнала во время выполнения.
- При необходимости можно по-прежнему записывать данные в logcat.
Для оптимизации использования памяти ProtoLog использует механизм интернирования строк, включающий вычисление и сохранение скомпилированного хэша сообщения. Для повышения производительности ProtoLog выполняет интернирование строк во время компиляции (для системных служб), записывая только идентификатор сообщения и аргументы во время выполнения. Кроме того, при создании трассировки ProtoLog или получении отчёта об ошибке ProtoLog автоматически включает словарь сообщений, созданный во время компиляции, что позволяет декодировать сообщения из любой сборки.
При использовании ProtoLog сообщение сохраняется в двоичном формате (proto) в трассировке Perfetto. Декодирование сообщения происходит в trace_processor
Perfetto. Процесс включает декодирование двоичных proto-сообщений, перевод идентификаторов сообщений в строки с использованием встроенного словаря сообщений и форматирование строки с использованием динамических аргументов.
ProtoLog поддерживает те же уровни журналирования, что и android.utils.Log
, а именно: d
, v
, i
, w
, e
, wtf
.
ProtoLog на стороне клиента
Изначально ProtoLog предназначался исключительно для серверной части WindowManager, работая в рамках одного процесса и компонента. Впоследствии он был расширен, чтобы охватить код оболочки WindowManager в процессе System UI, но использование ProtoLog требовало сложного шаблонного кода настройки. Более того, ведение журнала Proto было ограничено процессами системного сервера и System UI, что затрудняло его внедрение в другие процессы и требовало отдельной настройки буфера памяти для каждого из них. Однако теперь ProtoLog доступен и для клиентского кода, устраняя необходимость в дополнительном шаблонном коде.
В отличие от кода системных служб, клиентский код обычно пропускает интернирование строк на этапе компиляции. Вместо этого интернирование строк происходит динамически в фоновом потоке. В результате, хотя ProtoLog на клиентской стороне обеспечивает сопоставимые преимущества в использовании памяти с ProtoLog на системных службах, он влечет за собой несколько более высокие накладные расходы и не обладает преимуществом в сокращении потребления памяти, которое обеспечивается закреплённой памятью, как его серверный аналог.
Группы ProtoLog
Сообщения ProtoLog организованы в группы, называемые ProtoLogGroups
, аналогично тому, как сообщения Logcat организованы с помощью TAG
. Эти ProtoLogGroups
служат кластерами сообщений, которые можно включать и отключать во время выполнения. Кроме того, они определяют, следует ли удалять сообщения во время компиляции и где они должны быть зарегистрированы (proto, logcat или оба). Каждая ProtoLogGroup
включает в себя следующие свойства:
-
enabled
: если установлено значениеfalse
, сообщения в этой группе исключаются во время компиляции и недоступны во время выполнения. -
logToProto
: определяет, ведет ли эта группа журнал в двоичном формате. -
logToLogcat
: определяет, записывает ли эта группа данные в logcat. -
tag
: Имя источника зарегистрированного сообщения.
Для каждого процесса, использующего ProtoLog, должен быть настроен экземпляр ProtoLogGroup
.
Поддерживаемые типы аргументов
Внутренне ProtoLog форматирует строки с помощью android.text.TextUtils#formatSimple(String, Object...)
, поэтому его синтаксис тот же.
ProtoLog поддерживает следующие типы аргументов:
-
%b
- логическое значение -
%d
,%x
- целочисленный тип (короткий, целый или длинный) -
%f
- тип с плавающей точкой (float или double) -
%s
- строка -
%%
- буквальный символ процента
Поддерживаются модификаторы ширины и точности, такие как %04d
и %10b
, но argument_index
и flags
не поддерживаются.
Используйте ProtoLog в новом сервисе
Чтобы использовать ProtoLog в новом процессе:
Создайте определение
ProtoLogGroup
для этой службы.Инициализируйте определение перед его первым использованием (например, при создании процесса):
Protolog.init(ProtologGroup.values());
Используйте
Protolog
таким же образом, как иandroid.util.Log
:ProtoLog.v(WM_SHELL_STARTING_WINDOW, "create taskSnapshot surface for task: %d", taskId);
Включить оптимизацию во время компиляции
Чтобы включить ProtoLog во время компиляции в процессе, необходимо изменить его правила сборки и вызвать двоичный файл protologtool
.
ProtoLogTool
— это двоичный файл для преобразования кода, который выполняет интернирование строк и обновляет вызовы ProtoLog. Этот двоичный файл преобразует каждый вызов журнала ProtoLog
, как показано в этом примере:
ProtoLog.x(ProtoLogGroup.GROUP_NAME, "Format string %d %s", value1, value2);
в:
if (ProtoLogImpl.isEnabled(GROUP_NAME)) {
int protoLogParam0 = value1;
String protoLogParam1 = String.valueOf(value2);
ProtoLogImpl.x(ProtoLogGroup.GROUP_NAME, 1234560b0100, protoLogParam0, protoLogParam1);
}
В этом примере ProtoLog
, ProtoLogImpl
и ProtoLogGroup
— это классы, предоставленные в качестве аргументов (могут быть импортированы, статически импортированы или импортированы по полному пути, импорт с подстановочными знаками не допускается), а x
— это метод ведения журнала.
Преобразование выполняется на уровне исходного кода. Хеш генерируется из строки формата, уровня журнала и имени группы журнала и вставляется после аргумента ProtoLogGroup. Сгенерированный код встраивается, и для сохранения нумерации строк в файле добавляется несколько символов новой строки.
Пример:
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"],
}
Параметры командной строки
Одно из главных преимуществ ProtoLog заключается в возможности включения и отключения его во время выполнения. Например, можно настроить более подробное журналирование в сборке, отключённое по умолчанию, и включить его во время локальной разработки для отладки конкретной проблемы. Этот шаблон используется, например, в WindowManager с группами WM_DEBUG_WINDOW_TRANSITIONS
и WM_DEBUG_WINDOW_TRANSITIONS_MIN
, включающими различные типы журналирования переходов, причём первый тип включен по умолчанию.
Вы можете настроить ProtoLog с помощью Perfetto при запуске трассировки. Также можно настроить ProtoLog локально с помощью командной строки adb
.
Команда adb shell cmd protolog_configuration
поддерживает следующие аргументы:
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
Советы по эффективному использованию
ProtoLog использует интернирование строк как для самого сообщения, так и для всех переданных строковых аргументов. Это означает, что для максимальной эффективности ProtoLog сообщения должны изолировать повторяющиеся значения в переменных.
Например, рассмотрим следующее утверждение:
Protolog.v(MY_GROUP, "%s", "The argument value is " + argument);
При оптимизации во время компиляции это выглядит так:
ProtologImpl.v(MY_GROUP, 0x123, "The argument value is " + argument);
Если ProtoLog используется в коде с аргументами 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");
В результате в памяти появляются следующие сообщения:
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)
Если бы вместо этого оператор ProtoLog был записан так:
Protolog.v(MY_GROUP, "The argument value is %s", argument);
Буфер памяти в итоге будет выглядеть так:
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)
Такая последовательность позволяет сократить объем используемой памяти на 35%.
Просмотрщик Winscope
Вкладка просмотра ProtoLog в Winscope отображает трассировки ProtoLog в табличном формате. Вы можете фильтровать трассировки по уровню журнала, тегу, исходному файлу (где присутствует оператор ProtoLog) и содержанию сообщения. Все столбцы фильтруются. Щелчок по временной метке в первом столбце переносит временную шкалу к временной метке сообщения. Кроме того, нажатие кнопки «Перейти к текущему времени» прокручивает таблицу ProtoLog обратно к временной метке, выбранной на временной шкале:
Рисунок 1. Средство просмотра ProtoLog