Criação de perfil orientada por apps

Nesta página, mostramos como gravar um rastreamento do sistema usando a API ProfilingManager.

O ProfilingManager também pode gravar outros tipos de perfil. Esse processo é semelhante a gravar um rastreamento do sistema, mas cada tipo usa um builder diferente. Os perfis compatíveis e os builders deles são:

  • Traces do sistema:gravados usando SystemTraceRequestBuilder, que são úteis para análise de latência e depuração geral de desempenho.

  • Heap dumps:gravados usando JavaHeapDumpRequestBuilder, que são úteis para detecção e otimização de vazamentos de memória.

  • Perfis de heap:gravados usando HeapProfileRequestBuilder, que são úteis para otimização de memória.

  • Perfis de pilha de chamadas:gravados usando StackSamplingRequestBuilder, que são úteis para entender a execução de código e a análise de latência.

para ver todas as solicitações.

Adicionar dependências

Para ter a melhor experiência com a API ProfilingManager, adicione as seguintes bibliotecas do Jetpack ao arquivo build.gradle.kts.

Kotlin

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

Groovy

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

Registrar um rastro do sistema

Depois de adicionar as dependências necessárias, use o código a seguir para registrar um rastro do sistema. Este exemplo mostra como iniciar uma sessão de criação de perfil de um combinável enquanto gerencia com segurança operações pesadas fora da linha de execução principal.

Kotlin

@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();
}

O exemplo de código configura e gerencia a sessão de criação de perfil seguindo estas etapas:

  1. Configure o executor. Crie um Executor para definir a linha de execução que vai receber os resultados do perfil. A criação de perfil acontece em segundo plano. Usar um executor de linha de execução que não seja de UI ajuda a evitar erros de "O app não está respondendo" (ANR) se você adicionar mais processamento ao callback depois.

  2. Processar resultados de criação de perfil. Crie um objeto Consumer<ProfilingResult>. O sistema usa esse objeto para enviar resultados de criação de perfil de ProfilingManager de volta ao seu app.

  3. Crie a solicitação de criação de perfil. Crie um SystemTraceRequestBuilder para configurar sua sessão de criação de perfil. Com esse builder, é possível personalizar as configurações de rastreamento ProfilingManager. A personalização do builder é opcional. Se você não fizer isso, o sistema usará as configurações padrão.

    • Defina uma tag. Use setTag() para adicionar uma tag ao nome do rastreamento. Essa tag ajuda a identificar o rastreamento.
    • Opcional: defina a duração. Use setDurationMs() para especificar por quanto tempo fazer o perfil em milissegundos. Por exemplo, 60000 define um rastreamento de 60 segundos. O rastreamento é encerrado automaticamente após a duração especificada se CancellationSignal não for acionado antes disso.
    • Escolha uma política de buffer. Use setBufferFillPolicy() para definir como os dados de rastreamento são armazenados. BufferFillPolicy.RING_BUFFER significa que, quando o buffer está cheio, os dados mais recentes substituem os mais antigos, mantendo um registro contínuo da atividade recente.
    • Defina um tamanho de buffer. Use setBufferSizeKb() para especificar um tamanho de buffer para rastreamento, que pode ser usado para controlar o tamanho do arquivo de rastreamento de saída.
  4. Opcional: gerencie o ciclo de vida da sessão. Crie um CancellationSignal. Com esse objeto, você pode interromper a sessão de criação de perfil quando quiser, tendo controle preciso sobre a duração dela.

  5. Comece e receba resultados. Quando você chama requestProfiling(), ProfilingManager inicia uma sessão de criação de perfil em segundo plano. Depois que o criação de perfil é concluída, ela envia o ProfilingResult para seu método resultCallback#accept. Se o perfil for concluído com êxito, o ProfilingResult vai fornecer o caminho em que o rastreamento foi salvo no dispositivo pelo ProfilingResult#getResultFilePath. Você pode receber esse arquivo de forma programática ou, para criação de perfil local, executando adb pull <trace_path> no seu computador.

  6. Adicione pontos de trace personalizados. É possível adicionar pontos de trace personalizados no código do app. No exemplo de código anterior, o bloco trace("MyApp:HeavyOperation") { ... } cria uma fração personalizada no perfil gerado.