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); }
Код выполняет следующие действия:
- Get the manager (Получить менеджер): извлекает сервис
ProfilingManager. - Определите триггер. Создает
ProfilingTriggerдляTRIGGER_TYPE_APP_FULLY_DRAWN. Это событие происходит, когда приложение сообщает, что оно запустилось и готово к взаимодействию. - Задать ограничения частоты. К этому триггеру применяется ограничение частоты в один час (
setRateLimitingPeriodHours(1)). Это не позволит приложению записывать более одного профиля запуска в час. - Зарегистрировать прослушиватель. Вызывает
registerForAllProfilingResults, чтобы определить обратный вызов, который обрабатывает результат. Этот обратный вызов получает путь к сохраненному профилю черезgetResultFilePath(). - Добавить триггеры. Регистрирует список триггеров в
ProfilingManagerс помощьюaddProfilingTriggers. - Активировать событие. Вызывает
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, записанная трассировка может не охватывать всю продолжительность фоновой записи, если буфер заполнен. Вместо этого она представляет собой последние действия, предшествующие триггеру.
Как реализовать ограничение частоты запросов для определенных триггеров
Триггеры с высокой частотой могут быстро исчерпать квоту ограничителя частоты приложения. Чтобы лучше понять, как работает ограничитель частоты, ознакомьтесь с разделом Как работает ограничитель частоты. Чтобы один тип триггера не исчерпал квоту, можно реализовать ограничение частоты запросов для триггеров.
ProfilingManager поддерживает ограничение частоты запросов для определенных триггеров, заданное в приложении. Это позволяет добавить ещё один уровень ограничения на основе времени в дополнение к существующему ограничению скорости. Используйте API setRateLimitingPeriodHours, чтобы задать определенное время ожидания для триггера. После этого вы сможете снова активировать его.
Как отлаживать триггеры на устройстве
Поскольку фоновые трассировки выполняются в случайное время, отладка триггеров на устройстве затруднена. Чтобы принудительно запустить фоновую трассировку для тестирования, используйте следующую команду ADB:
adb shell device_config put profiling_testing system_triggered_profiling.testing_package_name <com.example.myapp>
Эта команда заставляет систему начать непрерывную фоновую трассировку для указанного пакета, позволяя каждому триггеру собирать профиль, если это разрешено ограничителем скорости.
Вы также можете включить другие параметры отладки, например отключить ограничитель скорости при локальной отладке. Подробнее о командах отладки для локального профилирования…