Criação de perfil orientada por apps

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

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

  • Rastreamentos 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 alocação 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.

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

Groovy

   dependencies {
       implementation 'androidx.tracing:tracing:2.0.5'
       implementation 'androidx.core:core:1.19.1'
   }
   

Registrar um rastro do sistema

Depois de adicionar as dependências necessárias, use o código abaixo para registrar um rastreamento do sistema. Este exemplo mostra como iniciar uma sessão de criação de perfil em um elemento 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 da criação de perfil. A criação de perfil acontece em segundo plano. O uso de um executor de linha de execução que não seja de interface ajuda a evitar erros do tipo "O app não está respondendo" (ANR) se você adicionar mais processamento ao callback posteriormente.

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

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

    • Defina uma tag. Use setTag() para adicionar uma tag ao nome do trace. Essa tag ajuda a identificar o trace.
    • Opcional: defina a duração. Use setDurationMs() para especificar por quanto tempo criar o perfil em milissegundos. Por exemplo, 60000 define um trace de 60 segundos. O trace é 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 trace são armazenados. BufferFillPolicy.RING_BUFFER significa que, quando o buffer está cheio, os dados novos 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 o 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. Esse objeto permite que você interrompa a sessão de criação de perfil quando quiser, oferecendo controle preciso sobre a duração dela.

  5. Inicie e receba resultados. Quando você chama requestProfiling(), o ProfilingManager inicia uma sessão de criação de perfil em segundo plano. Depois que a criação de perfil é concluída, ele 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 por meio de ProfilingResult#getResultFilePath. É possível conseguir esse arquivo de forma programática ou, para criação de perfil local, executando adb pull <trace_path> no 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.