Criação de perfil orientada por apps

Esta página mostra como registrar um rastro do sistema usando a API ProfilingManager.

ProfilingManager também pode registrar outros tipos de perfil. Esse processo é semelhante ao registro de um rastro do sistema, mas cada tipo usa um builder diferente. Os perfis com suporte e os builders são:

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

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

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

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

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.1")
       implementation("androidx.core:core:1.19.0")
   }
   

Groovy

   dependencies {
       implementation 'androidx.tracing:tracing:2.0.1'
       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 elemento combinável, gerenciando 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 código de exemplo configura e gerencia a sessão de criação de perfil seguindo estas etapas:

  1. Configurar o executor. Crie um Executor para definir a linha de execução que vai receber os resultados da criação de perfil. A criação de perfil acontece em segundo plano. O uso de um executor de linha de execução não relacionada à interface ajuda a evitar erros de "O app não está respondendo" (ANR) se você adicionar mais processamento ao callback mais tarde.

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

  3. Criar a solicitação de criação de perfil. Crie um SystemTraceRequestBuilder para configurar a sessão de criação de perfil. Esse builder permite personalizar as configurações de rastreamento de ProfilingManager. A personalização do builder é opcional. Se você não fizer isso, o sistema vai usar as configurações padrão.

    • Definir uma tag. Use setTag() para adicionar uma tag ao nome do rastro. Essa tag ajuda a identificar o rastro.
    • Opcional: definir a duração. Use setDurationMs() para especificar por quanto tempo criar o perfil em milissegundos. Por exemplo, 60000 define um rastro de 60 segundos. O rastro termina automaticamente após a duração especificada se CancellationSignal não for acionado antes disso.
    • Escolher 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 novos dados substituem os mais antigos, mantendo um registro contínuo da atividade recente.
    • Definir 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 rastro de saída.
  4. Opcional: gerenciar o ciclo de vida da sessão. Crie um CancellationSignal. Esse objeto permite interromper a sessão de criação de perfil quando quiser, oferecendo controle preciso sobre a duração.

  5. Iniciar e receber resultados. Ao chamar requestProfiling(), ProfilingManager inicia uma sessão de criação de perfil em segundo plano. Quando a criação de perfil é concluída, ela envia o ProfilingResult para o método resultCallback#accept. Se a criação de perfil for concluída, o ProfilingResult vai fornecer o caminho em que o rastro foi salvo no dispositivo usando ProfilingResult#getResultFilePath. É possível acessar esse arquivo de maneira programática ou, para criação de perfil local, executando adb pull <trace_path> no computador.

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