Протолог

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

ProtoLog viewer

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