Протолог

Система логирования 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 в процессе системного пользовательского интерфейса. Однако использование ProtoLog требовало сложного шаблонного кода настройки. Кроме того, логирование Proto было ограничено серверными процессами и процессами системного пользовательского интерфейса. Это затрудняло интеграцию в другие процессы и требовало отдельной настройки буфера памяти для каждого из них. Теперь 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 - тип данных с плавающей запятой (loat или 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