Профилирование на основе приложений

На этой странице показано, как записать трассировку системы с помощью API ProfilingManager .

ProfilingManager также может записывать другие типы профилей. Этот процесс аналогичен записи трассировки системы, но для каждого типа используется свой построитель. Поддерживаемые профили и их построители:

  • Трассировка системы: Записана с помощью SystemTraceRequestBuilder , что полезно для анализа задержек и общей отладки производительности.

  • Дампы памяти: Запись производилась с помощью JavaHeapDumpRequestBuilder , что полезно для обнаружения утечек памяти и оптимизации процессов.

  • Профили кучи: Записываются с помощью HeapProfileRequestBuilder , что полезно для оптимизации использования памяти.

  • Профили стека вызовов: Записаны с помощью StackSamplingRequestBuilder , которые полезны для понимания выполнения кода и анализа задержек.

Добавить зависимости

Для оптимальной работы с API ProfilingManager добавьте следующие библиотеки Jetpack в файл build.gradle.kts .

Котлин

   dependencies {
       implementation("androidx.tracing:tracing-ktx:2.0.0")
       implementation("androidx.core:core:1.19.0")
   }
   

Классный

   dependencies {
       implementation 'androidx.tracing:tracing:2.0.0'
       implementation 'androidx.core:core:1.19.0'
   }
   

Запись трассировки системы

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

Котлин

@RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
@Composable
fun ProfiledScreen(modifier: Modifier = Modifier) {
    // Use the application context: requestProfiling resolves the ProfilingManager
    // system service from it, so there's no reason to hand it a short-lived Activity.
    val appContext = LocalContext.current.applicationContext
    val scope = rememberCoroutineScope()

    Button(
        onClick = {
            // Run the orchestration off the main thread. Profiling a heavy operation
            // on the UI thread would freeze the UI (ANR) and distort the very metrics
            // you're trying to capture.
            //
            // Note: this scope is tied to composition. If the user leaves this screen
            // mid-session, the coroutine is cancelled and stopSignal.cancel() might not
            // run, but setDurationMs() acts as a safety net and ends the trace.
            scope.launch(Dispatchers.Default) {
                val callbackExecutor = Dispatchers.IO.asExecutor()
                val resultCallback = Consumer<ProfilingResult> { profilingResult ->
                    if (profilingResult.errorCode == ProfilingResult.ERROR_NONE) {
                        Log.d("ProfileTest", "Result file: ${profilingResult.resultFilePath}")
                    } else {
                        // errorMessage explains the failure (e.g., rate limiting); keep it.
                        Log.e(
                            "ProfileTest",
                            "Profiling failed errorCode=${profilingResult.errorCode} " +
                                "errorMessage=${profilingResult.errorMessage}"
                        )
                    }
                }

                val stopSignal = CancellationSignal()
                val requestBuilder = SystemTraceRequestBuilder().apply {
                    setCancellationSignal(stopSignal)
                    setTag("FOO") // Caller-supplied tag for identification.
                    setDurationMs(60000) // Hard cap: ends the session if cancel() never fires.
                    setBufferFillPolicy(BufferFillPolicy.RING_BUFFER)
                    setBufferSizeKb(32768)
                }

                // 1. Start the session. This is asynchronous system IPC. The tracing
                //    engine takes a moment to start and allocate buffers.
                requestProfiling(appContext, requestBuilder.build(), callbackExecutor, resultCallback)

                // 2. The API exposes no "profiling started" signal, so pad with a short,
                //    best-effort delay before running the code you care about. This is
                //    approximate. Increase it on slower or heavily loaded devices.
                delay(STARTUP_PADDING_MS)

                // 3. The session is already recording every thread in your app. This slice
                //    doesn't scope what's captured. It just labels this region of the
                //    timeline so heavyOperation() is easier to find. trace { } closes the
                //    section even if the block throws.

                trace("MyApp:HeavyOperation") {
                    heavyOperation()
                }

                // 4. Stop recording. Until this fires or the setDurationMs() cap is
                //    reached (whichever comes first), the session keeps capturing app-wide
                //    activity.

                stopSignal.cancel()
            }
        }
    ) {
        Text("Run & Profile Heavy Operation")
    }
}

// Best-effort wait for the system trace engine to initialize before profiling.
// There is no deterministic start callback; tune this for your target devices.
private const val STARTUP_PADDING_MS = 100L

fun heavyOperation() {
    // Background computations to profile.
}

Java

void heavyOperation() {
  // Computations you want to profile
}

void sampleRecordSystemTrace() {
  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());
          }
        }
      };
  CancellationSignal stopSignal = new CancellationSignal();

  SystemTraceRequestBuilder requestBuilder = new SystemTraceRequestBuilder();
  requestBuilder.setCancellationSignal(stopSignal);
  requestBuilder.setTag("FOO");
  requestBuilder.setDurationMs(60000);
  requestBuilder.setBufferFillPolicy(BufferFillPolicy.RING_BUFFER);
  requestBuilder.setBufferSizeKb(32768);
  Profiling.requestProfiling(getApplicationContext(), requestBuilder.build(), mainExecutor,
      resultCallback);

  // Wait some time for profiling to start.

  Trace.beginSection("MyApp:HeavyOperation");
  heavyOperation();
  Trace.endSection();

  // Once the interesting code section is profiled, stop profile
  stopSignal.cancel();
}

В приведенном примере кода настройка и управление сессией профилирования осуществляется в несколько этапов:

  1. Настройте исполнитель. Создайте Executor , определяющий поток, который будет получать результаты профилирования. Профилирование происходит в фоновом режиме. Использование исполнителя, не относящегося к пользовательскому интерфейсу, помогает предотвратить ошибки «Приложение не отвечает» (ANR), если вы добавите дополнительную обработку в функцию обратного вызова позже.

  2. Обработайте результаты профилирования. Создайте объект Consumer<ProfilingResult> . Система использует этот объект для отправки результатов профилирования из ProfilingManager обратно в ваше приложение.

  3. Создайте запрос на профилирование. Создайте объект SystemTraceRequestBuilder для настройки сеанса профилирования. Этот конструктор позволяет настраивать параметры трассировки ProfilingManager . Настройка конструктора необязательна; если вы этого не сделаете, система будет использовать настройки по умолчанию.

    • Определите тег. Используйте setTag() , чтобы добавить тег к имени трассировки. Этот тег поможет вам идентифицировать трассировку.
    • Необязательно: задайте продолжительность. Используйте setDurationMs() , чтобы указать, сколько миллисекунд нужно отследить в процессе профилирования. Например, значение 60000 задает 60-секундную трассировку. Трассировка автоматически завершается по истечении указанного времени, если CancellationSignal не срабатывает раньше.
    • Выберите политику буферизации. Используйте setBufferFillPolicy() для определения способа хранения данных трассировки. BufferFillPolicy.RING_BUFFER означает, что когда буфер заполнен, новые данные перезаписывают самые старые, обеспечивая непрерывную запись последней активности.
    • Задайте размер буфера. Используйте setBufferSizeKb() , чтобы указать размер буфера для трассировки, который можно использовать для управления размером выходного файла трассировки.
  4. Необязательно: Управление жизненным циклом сессии. Создайте объект CancellationSignal . Этот объект позволяет остановить сессию профилирования в любой момент, обеспечивая точный контроль над ее продолжительностью.

  5. Запуск и получение результатов. При вызове requestProfiling() ProfilingManager запускает сеанс профилирования в фоновом режиме. После завершения профилирования он отправляет ProfilingResult в ваш метод resultCallback#accept . Если профилирование завершилось успешно, ProfilingResult предоставляет путь к файлу трассировки, сохраненному на вашем устройстве, через ProfilingResult#getResultFilePath . Вы можете получить этот файл программно или, для локального профилирования, выполнив команду adb pull <trace_path> на вашем компьютере.

  6. Добавление пользовательских точек трассировки. Вы можете добавить пользовательские точки трассировки в код вашего приложения. В предыдущем примере кода блок trace("MyApp:HeavyOperation") { ... } создает пользовательский срез в сгенерированном профиле.