Протолог

Система журналирования Android призвана обеспечить универсальную доступность и простоту использования, предполагая, что все данные журнала могут быть представлены в виде последовательности символов. Это предположение соответствует большинству случаев использования, особенно когда читаемость журналов имеет решающее значение без специальных инструментов. Однако в средах, требующих высокой производительности ведения журнала и ограниченного размера журнала, ведение журнала на основе текста может оказаться неоптимальным. Одним из таких сценариев является WindowManager, который требует надежной системы журналирования, способной обрабатывать журналы перехода окон в реальном времени с минимальным воздействием на систему.

ProtoLog — это альтернатива для удовлетворения потребностей ведения журналов WindowManager и аналогичных служб. Основные преимущества ProtoLog перед logcat:

  • Количество ресурсов, используемых для журналирования, меньше.
  • С точки зрения разработчика, это то же самое, что использование платформы ведения журналов Android по умолчанию.
  • Поддерживает операторы журнала, которые можно включить или отключить во время выполнения.
  • При необходимости все еще можно войти в logcat.

Чтобы оптимизировать использование памяти, ProtoLog использует механизм интернирования строк, который включает в себя вычисление и сохранение скомпилированного хеша сообщения. Для повышения производительности ProtoLog выполняет интернирование строк во время компиляции (для системных служб), записывая только идентификатор сообщения и аргументы во время выполнения. Кроме того, при создании трассировки ProtoLog или получении отчета об ошибке ProtoLog автоматически включает словарь сообщений, созданный во время компиляции, что позволяет декодировать сообщения из любой сборки.

Используя ProtoLog, сообщение сохраняется в двоичном формате (прото) в трассировке Perfetto. Декодирование сообщения происходит внутри trace_processor Perfetto. Процесс состоит из декодирования двоичных прото-сообщений, перевода идентификаторов сообщений в строки с использованием встроенного словаря сообщений и форматирования строки с использованием динамических аргументов.

ProtoLog поддерживает те же уровни журнала, что и android.utils.Log , а именно: d , v , i , w , e , wtf .

Клиентский протокол ProtoLog

Изначально ProtoLog предназначался исключительно для серверной части WindowManager, работающей в рамках одного процесса и компонента. Впоследствии он был расширен, чтобы включить код оболочки WindowManager в процесс системного пользовательского интерфейса, но использование ProtoLog потребовало сложного шаблонного кода настройки. Кроме того, ведение журнала Proto было ограничено процессами системного сервера и системного пользовательского интерфейса, что затрудняло его включение в другие процессы и требовало отдельной настройки буфера памяти для каждого. Однако теперь ProtoLog доступен для кода на стороне клиента, что устраняет необходимость в дополнительном шаблонном коде.

В отличие от кода системных служб, код на стороне клиента обычно пропускает интернирование строк во время компиляции. Вместо этого интернирование строк происходит динамически в фоновом потоке. В результате, хотя ProtoLog на стороне клиента обеспечивает преимущества использования памяти, сравнимые с ProtoLog в системных службах, он требует немного более высоких накладных расходов на производительность и лишен преимущества по сокращению памяти, свойственного закрепленной памяти его аналога на стороне сервера.

Группы Протолога

Сообщения ProtoLog организованы в группы, называемые ProtoLogGroups , аналогично тому, как сообщения Logcat организуются с помощью TAG . Эти ProtoLogGroups служат кластерами сообщений, которые можно коллективно включать или отключать во время выполнения. Кроме того, они контролируют, должны ли сообщения удаляться во время компиляции и где они должны регистрироваться (прото, 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 в новом процессе:

  1. Создайте определение ProtoLogGroup для этой службы.

  2. Инициализируйте определение перед его первым использованием (например, при создании процесса):

    Protolog.init(ProtologGroup.values());

  3. Используйте 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 обратно к временной метке, выбранной на временной шкале:

ProtoLog viewer

Рисунок 1. Средство просмотра ProtoLog