Profilazione basata sulle app

Questa pagina mostra come registrare una traccia di sistema utilizzando l'API ProfilingManager.

ProfilingManager può anche registrare altri tipi di profili. Questa procedura è simile alla registrazione di una traccia di sistema, ma ogni tipo utilizza un builder diverso. I profili supportati e i relativi builder sono:

  • Tracce di sistema:registrate utilizzando SystemTraceRequestBuilder, che sono utili per l'analisi della latenza e il debug generale delle prestazioni.

  • Dump dell'heap: registrati utilizzando JavaHeapDumpRequestBuilder, utili per il rilevamento e l'ottimizzazione delle perdite di memoria.

  • Profili heap:registrati utilizzando HeapProfileRequestBuilder, utili per l'ottimizzazione della memoria.

  • Profili dello stack di chiamate: registrati utilizzando StackSamplingRequestBuilder, utili per comprendere l'esecuzione del codice e l'analisi della latenza.

Aggiungi dipendenze

Per un'esperienza ottimale con l'API ProfilingManager, aggiungi le seguenti librerie Jetpack al file build.gradle.kts.

Kotlin

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

Alla moda

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

Registra una traccia di sistema

Dopo aver aggiunto le dipendenze richieste, utilizza il seguente codice per registrare una traccia di sistema. Questo esempio mostra come avviare una sessione di profilazione da un componente componibile gestendo in modo sicuro le operazioni pesanti al di fuori del thread principale.

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

Il codice campione configura e gestisce la sessione di profilazione eseguendo i seguenti passaggi:

  1. Configura l'esecutore. Crea un Executor per definire il thread che riceverà i risultati della profilazione. La profilazione avviene in background. L'utilizzo di un executor di thread non UI consente di evitare errori ANR (L'applicazione non risponde) se in un secondo momento aggiungi ulteriore elaborazione al callback.

  2. Gestisci i risultati della profilazione. Crea un oggetto Consumer<ProfilingResult>. Il sistema utilizza questo oggetto per inviare i risultati della profilazione da ProfilingManager alla tua app.

  3. Crea la richiesta di profilazione. Crea un SystemTraceRequestBuilder per impostare la sessione di profilazione. Questo generatore ti consente di personalizzare le impostazioni di traccia ProfilingManager. La personalizzazione del builder è facoltativa; se non lo fai, il sistema utilizza le impostazioni predefinite.

    • Definisci un tag. Utilizza setTag() per aggiungere un tag al nome della traccia. Questo tag ti aiuta a identificare la traccia.
    • (Facoltativo) Imposta la durata. Utilizza setDurationMs() per specificare la durata del profilo in millisecondi. Ad esempio, 60000 imposta una traccia di 60 secondi. La traccia termina automaticamente dopo la durata specificata se CancellationSignal non viene attivato prima.
    • Scegli una policy del buffer. Utilizza setBufferFillPolicy() per definire la modalità di archiviazione dei dati di traccia. BufferFillPolicy.RING_BUFFER significa che quando il buffer è pieno, i nuovi dati sovrascrivono quelli meno recenti, mantenendo una registrazione continua dell'attività recente.
    • Imposta una dimensione del buffer. Utilizza setBufferSizeKb() per specificare una dimensione del buffer per la traccia, che puoi utilizzare per controllare le dimensioni del file di traccia di output.
  4. (Facoltativo) Gestisci il ciclo di vita della sessione. Crea un CancellationSignal. Questo oggetto ti consente di interrompere la sessione di profilazione in qualsiasi momento, offrendoti un controllo preciso sulla sua durata.

  5. Avvia e ricevi i risultati. Quando chiami il numero requestProfiling(), ProfilingManager avvia una sessione di profilazione in background. Una volta completata la profilazione, invia ProfilingResult al tuo metodo di resultCallback#accept. Se la profilazione viene completata correttamente, il ProfilingResult fornisce il percorso in cui la traccia è stata salvata sul dispositivo tramite ProfilingResult#getResultFilePath. Puoi ottenere questo file in modo programmatico o, per la profilazione locale, eseguendo adb pull <trace_path> dal tuo computer.

  6. Aggiungi punti di tracciamento personalizzati. Puoi aggiungere punti di tracciamento personalizzati nel codice della tua app. Nell'esempio di codice precedente, il blocco trace("MyApp:HeavyOperation") { ... } crea una sezione personalizzata nel profilo generato.