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

В Android 16 (уровень API 36) и более поздних версиях ProfilingManager позволяет автоматически записывать профили в ответ на системные триггеры, такие как ошибки Приложение не отвечает (ANR), сбои из-за нехватки памяти (OutOfMemoryError) или события запуска приложения. Поскольку вы не можете предсказать, когда произойдут многие из этих событий, начинать запись вручную заранее нецелесообразно. При профилировании на основе триггеров система управляет сбором данных и передает полученный профиль в приложение, чтобы вы могли:

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

Полный список доступных триггеров можно найти в ProfilingTrigger.

Типы триггеров

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

Триггеры, использующие фоновое отслеживание

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

Триггеры, запускающие новую запись

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

Триггеры, в которых поведение захвата зависит от аномалии

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

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

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

Kotlin

@RequiresApi(Build.VERSION_CODES.BAKLAVA)
fun recordWithTrigger() {
    val profilingManager =
        applicationContext.getSystemService(ProfilingManager::class.java) ?: return

    val triggers = ArrayList<ProfilingTrigger>()

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

    triggers.add(triggerBuilder.build())

    val callbackExecutor: 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(callbackExecutor, resultCallback)
    profilingManager.addProfilingTriggers(triggers)

    Choreographer.getInstance().postFrameCallback { frameTimeNanos ->
        // This will cause the TRIGGER_TYPE_APP_FULLY_DRAWN to be emitted.
        reportFullyDrawn()
    }
}

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 callbackExecutor = 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(callbackExecutor, resultCallback);
  profilingManager.addProfilingTriggers(triggers);

  Choreographer.getInstance().postFrameCallback((f) -> {
    // This will cause the TRIGGER_TYPE_APP_FULLY_DRAWN to be emitted.
    reportFullyDrawn();
  });
}

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

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

Жизненный цикл триггера

Зарегистрированные триггеры сохраняются при перезапуске приложения и перезагрузке системы. Вам не нужно повторно регистрировать триггеры при каждом запуске приложения или после перезагрузки системы. После регистрации триггеров с помощью addProfilingTriggers система автоматически поддерживает их для вашего приложения, пока вы не удалите их или приложение.

Когда отменять регистрацию триггеров

Поскольку зарегистрированные триггеры сохраняются при перезапуске приложения и перезагрузке системы, пропуск addProfilingTriggers при последующих запусках не останавливает триггеры, зарегистрированные в предыдущем сеансе. Чтобы прекратить сбор профилей для определенных или всех событий, вызовите removeProfilingTriggersByType или clearProfilingTriggers.

Отменяйте регистрацию триггеров в следующих случаях:

  • Таргетинг на когорты или откат удаленных флагов. Вы можете сегментировать профилирование по когортам, используя удаленную конфигурацию или флаги A/B-тестирования. Если устройство покидает когорту выборки или параметр отключается, отмените регистрацию триггеров.
  • Завершение кампании по отладке. Когда вы соберете достаточно профилей, чтобы диагностировать проблему или выпустить исправление, удалите триггер. Отмена регистрации ненужных триггеров позволяет сохранить хранилище устройства и квоту ограничения частоты запросов вашего приложения для других запросов профилирования.

Жизненный цикл обратного вызова

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

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

  • Во время работы приложения. Если триггер не приводит к сбою или остановке приложения, система вызывает обратный вызов в указанном Executor, когда срабатывает триггер и сохраняется профиль.
  • После завершения работы приложения. Если триггеры часто связаны с завершением работы приложения или предшествуют ему (например, TRIGGER_TYPE_ANR, TRIGGER_TYPE_OOM или TRIGGER_TYPE_KILL_RECENTS) или если приложение останавливается до завершения записи, система сохраняет профиль в фоновом режиме и ставит результат в очередь на перезапуск приложения и перезагрузку системы. Система хранит недоставленные результаты в очереди до семи дней и доставляет их при следующем запуске приложения и вызове registerForAllProfilingResults.

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

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

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

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

Как настроить период задержки для определенного триггера

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

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

Поскольку фоновые трассировки выполняются в рабочей среде со случайными интервалами, для локального тестирования триггеров необходимо принудительно запустить непрерывную фоновую трассировку и обойти ограничение частоты запросов на уровне системы для вашего пакета (любой период задержки для каждого триггера, заданный с помощью setRateLimitingPeriodHours, по-прежнему применяется, а rate_limiter.disabled применяется только к вызовам requestProfiling, управляемым приложением).

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