Протолог

Система регистрации 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 в новом процессе:

  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) и содержимому сообщения. Все столбцы можно фильтровать. Щелчок по временной метке в первом столбце переносит временную шкалу к временной метке сообщения. Кроме того, щелчок по Go to Current Time прокручивает таблицу ProtoLog обратно к временной метке, выбранной на временной шкале:

ProtoLog viewer

Рисунок 1. Просмотрщик ProtoLog