Profilazione basata su trigger

Su Android 16 (livello API 36) e versioni successive, ProfilingManager ti consente di acquisire i profili automaticamente in risposta ad attivatori di sistema, come errori di tipo L'applicazione non risponde (ANR), arresti anomali per out of memory (OutOfMemoryError) o eventi di avvio dell'app. Poiché non puoi prevedere quando si verificheranno molti di questi eventi sul campo, non è pratico avviare manualmente una registrazione in anticipo. Con la profilazione basata su trigger, il sistema gestisce l'acquisizione e fornisce il profilo risultante alla tua app in modo che tu possa:

  • Esegui il debug dei problemi di stabilità: diagnostica ANR imprevedibili e perdite di memoria.
  • TRIGGER_TYPE_COLD_STARTTRIGGER_TYPE_APP_FULLY_DRAWN
  • Comprendi il comportamento degli utenti: esamina eventi come le uscite dall'app avviate dagli utenti.

Per l'elenco completo dei trigger disponibili, consulta ProfilingTrigger.

Tipi di trigger

I trigger differiscono nel modo in cui il sistema acquisisce il profilo quando si verifica l'evento.

Trigger che utilizzano la traccia in background

Molti trigger, come un ANR, sono la conseguenza di un problema precedente. L'avvio di una traccia solo dopo che il sistema ha rilevato l'ANR non tiene conto del lavoro di blocco che l'ha causato. Per acquisire l'attività che precede l'evento, i seguenti trigger salvano uno snapshot della traccia in background attiva del sistema. Se non è in esecuzione alcuna traccia in background quando si verifica l'evento, non viene raccolto alcun profilo:

Trigger che avviano una nuova acquisizione

I seguenti trigger non dipendono dalla traccia in background. Invece, il sistema avvia una nuova acquisizione del profilo quando si verifica l'evento:

Trigger in cui il comportamento di acquisizione varia in base all'anomalia

Per i seguenti trigger di anomalie, il tipo di profilo acquisito (salvataggio di uno snapshot della traccia in background attiva o avvio di una nuova acquisizione) e il comportamento di limitazione di frequenza dipendono dall'anomalia specifica rilevata dal sistema:

Configura un trigger

Il codice seguente mostra come registrarsi per l'attivatore TRIGGER_TYPE_APP_FULLY_DRAWN e impostare un periodo di attesa.

Kotlin

@RequiresApi(Build.VERSION_CODES.BAKLAVA)
fun recordWithTrigger() {
    val profilingManager =
        applicationContext.getSystemService(ProfilingManager::class.java) ?: return

    val triggers = ArrayList<ProfilingTrigger>()

    val triggerBuilder = ProfilingTrigger.Builder(ProfilingTrigger.TRIGGER_TYPE_APP_FULLY_DRAWN)
        .setRateLimitingPeriodHours(1)

    triggers.add(triggerBuilder.build())

    val callbackExecutor: Executor = Executors.newSingleThreadExecutor()

    val resultCallback = Consumer<ProfilingResult> { profilingResult ->
        if (profilingResult.errorCode == ProfilingResult.ERROR_NONE) {
            Log.d(
                "ProfileTest",
                "Received profiling result file=" + profilingResult.resultFilePath
            )
            setupProfileUploadWorker(profilingResult.resultFilePath)
        } else {
            Log.e(
                "ProfileTest",
                "Profiling failed errorcode=" + profilingResult.errorCode + " errormsg=" + profilingResult.errorMessage
            )
        }
    }

    profilingManager.registerForAllProfilingResults(callbackExecutor, resultCallback)
    profilingManager.addProfilingTriggers(triggers)

    Choreographer.getInstance().postFrameCallback { frameTimeNanos ->
        // This will cause the TRIGGER_TYPE_APP_FULLY_DRAWN to be emitted.
        reportFullyDrawn()
    }
}

Java

public void recordWithTrigger() {
  ProfilingManager profilingManager = getApplicationContext().getSystemService(
      ProfilingManager.class);
  List<ProfilingTrigger> triggers = new ArrayList<>();
  ProfilingTrigger.Builder triggerBuilder = new ProfilingTrigger.Builder(
      ProfilingTrigger.TRIGGER_TYPE_APP_FULLY_DRAWN);
  triggerBuilder.setRateLimitingPeriodHours(1);
  triggers.add(triggerBuilder.build());

  Executor callbackExecutor = 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());
          }
        }
      };
  profilingManager.registerForAllProfilingResults(callbackExecutor, resultCallback);
  profilingManager.addProfilingTriggers(triggers);

  Choreographer.getInstance().postFrameCallback((f) -> {
    // This will cause the TRIGGER_TYPE_APP_FULLY_DRAWN to be emitted.
    reportFullyDrawn();
  });
}

Il codice esegue questi passaggi:

  1. Recupera il gestore: recupera il servizio ProfilingManager.
  2. Definisci un trigger: crea un ProfilingTrigger per TRIGGER_TYPE_APP_FULLY_DRAWN. Questo evento si verifica quando l'app chiama reportFullyDrawn() durante un avvio a freddo avviato dall'attività per segnalare che ha terminato l'avvio ed è interattiva.
  3. Imposta un periodo di attesa per l'attivatore: applica un periodo di attesa di 1 ora a questo attivatore specifico utilizzando setRateLimitingPeriodHours oltre al limitatore di frequenza del sistema. Questo periodo di attesa impedisce all'app di registrare più di un profilo di avvio all'ora.
  4. Registra il listener: chiama registerForAllProfilingResults per definire il callback che gestisce il risultato. Questo callback riceve il percorso del profilo salvato tramite getResultFilePath().
  5. Aggiungi trigger: registra l'elenco di trigger con ProfilingManager utilizzando addProfilingTriggers.
  6. Attiva evento: pubblica un callback del frame Choreographer che chiama reportFullyDrawn() una volta disegnato il frame successivo, che emette l'evento TRIGGER_TYPE_APP_FULLY_DRAWN al sistema, attivando una raccolta di profili supponendo che sia in esecuzione una traccia in background del sistema e che sia disponibile la quota del limitatore di frequenza. Affinché questo trigger si verifichi, assicurati che la tua app chiami reportFullyDrawn().

Ciclo di vita dei trigger

I trigger registrati rimangono invariati dopo i riavvii dell'app e del sistema. Non è necessario registrare nuovamente i trigger ogni volta che l'app viene avviata o dopo un riavvio del sistema: una volta registrati i trigger utilizzando addProfilingTriggers, il sistema li gestisce automaticamente per la tua app finché non li rimuovi o l'app non viene disinstallata.

Quando annullare la registrazione dei trigger

Poiché i trigger registrati persistono tra i riavvii dell'app e del sistema, se salti addProfilingTriggers nei lanci successivi, i trigger registrati in una sessione precedente non vengono interrotti. Per interrompere l'acquisizione dei profili per eventi specifici o per tutti gli eventi, chiama esplicitamente removeProfilingTriggersByType o clearProfilingTriggers.

Annulla la registrazione degli attivatori nelle seguenti situazioni:

  • Targeting di coorti o rollback di flag remoti: potresti segmentare la profilazione tra coorti utilizzando la configurazione remota o i flag di test A/B. Ogni volta che un dispositivo lascia la coorte campionata o un flag viene disattivato, annulla la registrazione degli attivatori.
  • Completamento di una campagna di debug: una volta raccolti abbastanza profili per diagnosticare un problema o applicare una correzione, rimuovi l'attivatore. Annullando la registrazione dei trigger non necessari, preservi lo spazio sul dispositivo e la quota del limitatore di frequenza della tua app per altre richieste di profilazione.

Ciclo di vita del callback

A differenza delle richieste di profilazione diretta, i profili basati su trigger forniscono risultati solo a un listener globale registrato con registerForAllProfilingResults. Nel callback, chiama ProfilingResult.getTriggerType per identificare quale trigger ha avviato l'acquisizione e chiama ProfilingResult.getResultFilePath per individuare il file del profilo salvato. Per maggiori dettagli sui nomi file dei profili di attivazione, sul recupero dei file con adb e sul caricamento dei risultati in background, consulta Recupera e carica i profili.

Sebbene i trigger vengano mantenuti durante i riavvii dell'app e del sistema, il callback dei risultati non viene mantenuto. Registra il callback su un Executor in background dopo il completamento dell'avvio dell'app (in modo da non rallentare l'avvio a freddo) per ricevere i profili in entrambi i seguenti scenari:

  • Mentre l'app è in esecuzione: per i trigger che non arrestano o non causano l'arresto anomalo dell'app, il sistema richiama il callback sul Executor fornito quando si verifica il trigger e il profilo viene salvato.
  • Dopo la chiusura dell'app: per i trigger che spesso coinvolgono o precedono la chiusura dell'app (come TRIGGER_TYPE_ANR, TRIGGER_TYPE_OOM o TRIGGER_TYPE_KILL_RECENTS) o se l'app si arresta prima del completamento dell'acquisizione, il sistema salva il profilo in background e mette in coda il risultato tra i riavvii dell'app e del sistema. Il sistema conserva i risultati in coda non recapitati per un massimo di 7 giorni e li recapita alla successiva esecuzione dell'app e alla chiamata di registerForAllProfilingResults.

Come funziona il tracciamento in background

Per bilanciare l'overhead di profilazione con l'acquisizione di artefatti utili, il sistema non esegue continuamente tracce in background. Invece, campiona periodicamente avviando tracce in background a intervalli casuali.

Le tracce in background attivate dal sistema utilizzano un buffer circolare con una dimensione massima definita dal sistema. Quando il buffer si riempie, i nuovi dati di traccia sovrascrivono i dati più vecchi. Come mostrato nella Figura 1, quando un trigger registrato si attiva mentre è attiva una traccia in background e la quota del limitatore di frequenza è disponibile, il sistema salva lo snapshot del buffer circolare contenente l'attività più recente che ha portato al trigger.

Diagramma che mostra il funzionamento degli snapshot di traccia in background, con un buffer circolare che acquisisce i dati prima di un evento di attivazione.
Figura 1. Come funzionano gli snapshot di traccia in background.

Imposta un periodo di attesa specifico per l'attivatore

Gli attivatori ad alta frequenza possono consumare rapidamente la quota del limitatore di frequenza della tua app. Per evitare che un singolo tipo di trigger esaurisca la quota, utilizza setRateLimitingPeriodHours per aggiungere un periodo di attesa minimo per trigger oltre al limitatore di frequenza del sistema.

Esegui il debug dei trigger in locale

Poiché le tracce in background vengono eseguite a intervalli casuali in produzione, l'attivazione dei trigger localmente richiede l'esecuzione forzata di una traccia in background continua e l'elusione del limitatore di frequenza a livello di sistema per il tuo pacchetto (viene comunque applicato qualsiasi cooldown per trigger impostato con setRateLimitingPeriodHours e rate_limiter.disabled si applica solo alle chiamate requestProfiling basate sull'app).

Per i comandi adb per forzare le tracce in background, simulare i trigger dalla riga di comando e ispezionare lo stato del servizio, consulta Forza l'attivazione delle tracce in background per i test dei trigger in Comandi di debug per la profilazione locale.