Профилирование на основе триггеров

ProfilingManager поддерживает запись профилей на основе системных триггеров. Система управляет процессом записи и предоставляет полученный профиль вашему приложению.

Триггеры связаны с событиями, критически важными для эффективности. Профили, записанные системой, содержат подробные сведения для отладки алгоритмов достижения цели, связанных с этими триггерами.

Сбор статистики

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

Например, продолжительная операция в потоке UI вызывает ошибку Приложение не отвечает (ANR). К тому времени, когда система обнаружит ошибку ANR и отправит сигнал приложению, операция может быть завершена. Если начать профилирование в этот момент, то мы не увидим, что именно блокирует поток.

Точно предсказать, когда сработают некоторые триггеры, невозможно, поэтому запустить профиль вручную заранее не получится.

Зачем использовать съемку на основе триггеров?

Основная причина использования триггеров профилирования – сбор данных о непредсказуемых событиях, когда приложение не может начать запись вручную до их возникновения. Триггеры профилирования можно использовать для следующих целей:

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

Как настроить триггер

В следующем коде показано, как зарегистрировать триггер TRIGGER_TYPE_APP_FULLY_DRAWN и применить к нему ограничение частоты запросов.

Kotlin

fun recordWithTrigger() {
    val profilingManager = applicationContext.getSystemService(ProfilingManager::class.java)

    val triggers = ArrayList<ProfilingTrigger>()

    val triggerBuilder = ProfilingTrigger.Builder(ProfilingTrigger.TRIGGER_TYPE_APP_FULLY_DRAWN)
        .setRateLimitingPeriodHours(1)

    triggers.add(triggerBuilder.build())

    val mainExecutor: Executor = Executors.newSingleThreadExecutor()

    val resultCallback = Consumer<ProfilingResult> { profilingResult ->
        if (profilingResult.errorCode == ProfilingResult.ERROR_NONE) {
            Log.d(
                "ProfileTest",
                "Received profiling result file=" + profilingResult.resultFilePath
            )
            setupProfileUploadWorker(profilingResult.resultFilePath)
        } else {
            Log.e(
                "ProfileTest",
                "Profiling failed errorcode=" + profilingResult.errorCode + " errormsg=" + profilingResult.errorMessage
            )
        }
    }

    profilingManager.registerForAllProfilingResults(mainExecutor, resultCallback)
    profilingManager.addProfilingTriggers(triggers)

}

Java

public void recordWithTrigger() {
  ProfilingManager profilingManager = getApplicationContext().getSystemService(
      ProfilingManager.class);
  List<ProfilingTrigger> triggers = new ArrayList<>();
  ProfilingTrigger.Builder triggerBuilder = new ProfilingTrigger.Builder(
      ProfilingTrigger.TRIGGER_TYPE_APP_FULLY_DRAWN);
  triggerBuilder.setRateLimitingPeriodHours(1);
  triggers.add(triggerBuilder.build());

  Executor mainExecutor = Executors.newSingleThreadExecutor();
  Consumer<ProfilingResult> resultCallback =
      new Consumer<ProfilingResult>() {
        @Override
        public void accept(ProfilingResult profilingResult) {
          if (profilingResult.getErrorCode() == ProfilingResult.ERROR_NONE) {
            Log.d(
                "ProfileTest",
                "Received profiling result file=" + profilingResult.getResultFilePath());
            setupProfileUploadWorker(profilingResult.getResultFilePath());
          } else {
            Log.e(
                "ProfileTest",
                "Profiling failed errorcode="
                    + profilingResult.getErrorCode()
                    + " errormsg="
                    + profilingResult.getErrorMessage());
          }
        }
      };
  profilingManager.registerForAllProfilingResults(mainExecutor, resultCallback);
  profilingManager.addProfilingTriggers(triggers);

}

Код выполняет следующие действия:

  1. Получение менеджера. Получает сервис ProfilingManager.
  2. Определить триггер. Создает ProfilingTrigger для TRIGGER_TYPE_APP_FULLY_DRAWN. Это событие происходит, когда приложение сообщает, что оно завершило запуск и готово к взаимодействию.
  3. Установить ограничения частоты запросов: К этому триггеру (setRateLimitingPeriodHours(1)) применяется ограничение частоты запросов в один час. Это не позволяет приложению записывать более одного профиля запуска в час.
  4. Регистрация прослушивателя. Вызывается метод registerForAllProfilingResults, чтобы определить обратный вызов, который обрабатывает результат. Этот обратный вызов получает путь к сохраненному профилю через getResultFilePath().
  5. Добавление триггеров. Регистрирует список триггеров в ProfilingManager с помощью addProfilingTriggers.
  6. Активация события. Вызывает reportFullyDrawn(), который передает событие TRIGGER_TYPE_APP_FULLY_DRAWN в систему, запускающую сбор профилей, если выполняется фоновое отслеживание системы и доступна квота ограничения частоты запросов. Этот необязательный шаг демонстрирует сквозной поток, поскольку для этого триггера ваше приложение должно вызывать reportFullyDrawn().

Как получить трассировку

Система сохраняет профили на основе триггеров в том же каталоге, что и другие профили. Название файла для трассировок, запущенных триггером, имеет следующий формат:

profile_trigger_<profile_type_code>_<datetime>.<profile-type-name>

Вы можете извлечь файл с помощью ADB. Например, чтобы извлечь трассировку системы, полученную с помощью примера кода, используя ADB, можно использовать следующий код:

adb pull /data/user/0/com.example.sampleapp/files/profiling/profile_trigger_1_2025-05-06-14-12-40.perfetto-trace

Подробнее о том, как визуализировать эти трассировки, рассказывается в разделе Получение и анализ данных профилирования.

Как работает фоновое отслеживание

Чтобы собирать данные до триггера, ОС периодически запускает фоновую трассировку. Если триггер срабатывает, когда фоновая трассировка активна и ваше приложение зарегистрировано для нее, система сохраняет профиль трассировки в каталоге приложения. В профиль будет включена информация, которая привела к срабатыванию триггера.

После сохранения профиля система уведомляет приложение с помощью обратного вызова, предоставленного registerForAllProfilingResults. Этот обратный вызов предоставляет путь к записанному профилю, к которому можно получить доступ, вызвав ProfilingResult#getResultFilePath().

Диаграмма, показывающая, как работают снимки фоновой трассировки с кольцевым буфером, который собирает данные до события-триггера.
Рисунок 1. Как работают снимки фоновой трассировки.

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

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

Ограничение частоты запросов для триггеров

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

ProfilingManager поддерживает ограничение частоты запросов для триггеров, заданных в приложении. Это позволяет добавить ещё один уровень ограничения по времени в дополнение к существующему ограничению частоты запросов. Чтобы задать определенное время задержки для триггера, используйте API setRateLimitingPeriodHours. После окончания периода восстановления вы можете активировать его снова.

Отладка триггеров на локальном устройстве

Поскольку фоновые трассировки выполняются в случайное время, отладка триггеров локально затруднена. Чтобы принудительно запустить фоновую трассировку для тестирования, используйте следующую команду ADB:

adb shell device_config put profiling_testing system_triggered_profiling.testing_package_name <com.example.myapp>

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

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