Profilazione basata su trigger

ProfilingManager supporta l'acquisizione di profili in base ai trigger di sistema. Il sistema gestisce il processo di registrazione e fornisce il profilo risultante alla tua app.

I trigger sono legati a eventi critici per le prestazioni. I profili registrati dal sistema forniscono informazioni di debug dettagliate per i Critical User Journey (CUJ) associati a questi trigger.

Acquisisci dati storici

Molti trigger richiedono l'analisi dei dati storici che precedono l'evento. Il trigger stesso è spesso una conseguenza di un problema, non la causa principale. Se avvii un profilo solo dopo che si è verificato il trigger, la causa principale potrebbe essere già andata persa.

Ad esempio, un'operazione a lunga esecuzione sul thread dell'interfaccia utente causa un errore L'applicazione non risponde (ANR). Quando il sistema rileva l'ANR e lo segnala all'app, l'operazione potrebbe essere già terminata. Se avvii un profilo in quel momento, non vedrai l'effettivo lavoro di blocco.

Prevedere esattamente quando si verificano alcuni trigger non è fattibile, il che rende impossibile avviare manualmente un profilo in anticipo.

Perché utilizzare l'acquisizione basata su trigger?

Il motivo principale per utilizzare i trigger di profilazione è acquisire dati per eventi imprevedibili in cui è impossibile per un'app avviare la registrazione manualmente prima che si verifichino questi eventi. I trigger di profilazione possono essere utilizzati per:

  • Esegui il debug dei problemi di prestazioni:diagnostica gli errori ANR, le perdite di memoria e altri problemi di stabilità.
  • Ottimizza i Critical User Journey:analizza e migliora i flussi, ad esempio l'avvio dell'app.
  • Comprendi il comportamento degli utenti:ottieni informazioni sugli eventi, ad esempio le uscite dall'app avviate dagli utenti.

Configura un trigger

Il seguente codice mostra come registrarsi per l'attivatore TRIGGER_TYPE_APP_FULLY_DRAWN e applicare la limitazione di frequenza.

Kotlin

fun recordWithTrigger() {
    val profilingManager = applicationContext.getSystemService(ProfilingManager::class.java)

    val triggers = ArrayList<ProfilingTrigger>()

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

    triggers.add(triggerBuilder.build())

    val mainExecutor: 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(mainExecutor, resultCallback)
    profilingManager.addProfilingTriggers(triggers)

}

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

}

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 segnala che ha completato l'avvio ed è interattiva.
  3. Imposta limiti di frequenza: applica un limite di frequenza di 1 ora a questo trigger specifico (setRateLimitingPeriodHours(1)). In questo modo, l'app non può 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: chiama reportFullyDrawn(), che invia 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 una quota del limitatore di frequenza. Questo passaggio facoltativo mostra un flusso end-to-end perché la tua app deve chiamare reportFullyDrawn() per questo trigger.

Recupera la traccia

Il sistema salva i profili basati su trigger nella stessa directory degli altri profili. Il nome file delle tracce attivate ha il seguente formato:

profile_trigger_<profile_type_code>_<datetime>.<profile-type-name>

Puoi estrarre il file utilizzando ADB. Ad esempio, per estrarre il tracciamento del sistema acquisito con il codice di esempio utilizzando ADB, il comando potrebbe essere il seguente:

adb pull /data/user/0/com.example.sampleapp/files/profiling/profile_trigger_1_2025-05-06-14-12-40.perfetto-trace

Per informazioni dettagliate sulla visualizzazione di queste tracce, consulta Recupera e analizza i dati di profilazione.

Come funziona il tracciamento in background

Per acquisire i dati precedenti a un trigger, il sistema operativo avvia periodicamente una traccia in background. Se si verifica un trigger mentre questa traccia in background è attiva e la tua app è registrata per questa traccia, il sistema salva il profilo della traccia nella directory della tua app. Il profilo includerà quindi le informazioni che hanno portato all'attivazione.

Una volta salvato il profilo, il sistema invia una notifica alla tua app utilizzando il callback fornito a registerForAllProfilingResults. Questo callback fornisce il percorso del profilo acquisito, a cui è possibile accedere chiamando ProfilingResult#getResultFilePath().

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.

Per ridurre l'impatto sulle prestazioni del dispositivo e sulla durata della batteria, il sistema non esegue continuamente le tracce in background. Utilizza invece un metodo di campionamento. Il sistema avvia in modo casuale una traccia in background entro un periodo di tempo prestabilito (con una durata minima e massima). La spaziatura casuale di queste tracce migliora la copertura dei trigger.

I profili attivati dal sistema hanno una dimensione massima definita dal sistema, quindi utilizzano un buffer circolare. Quando il buffer è pieno, i nuovi dati di traccia sovrascrivono i dati meno recenti. Come mostrato nella Figura 1, una traccia acquisita potrebbe non coprire l'intera durata della registrazione in background se il buffer si riempie; rappresenta invece l'attività più recente che ha portato all'attivazione.

Implementa la limitazione di frequenza specifica per l'attivatore

Gli attivatori ad alta frequenza possono consumare rapidamente la quota del limitatore di frequenza della tua app. Per comprendere meglio il limitatore di frequenza, ti consigliamo di consultare Come funziona il limitatore di frequenza. Per evitare che un singolo tipo di trigger esaurisca la quota, puoi implementare la limitazione di frequenza specifica per il trigger.

ProfilingManager supporta la limitazione di frequenza specifica per i trigger definita dall'app. In questo modo, puoi aggiungere un ulteriore livello di limitazione basata sul tempo oltre al limitatore di frequenza esistente. Utilizza l'API setRateLimitingPeriodHours per impostare un tempo di attesa specifico per un trigger. Una volta scaduto il periodo di attesa, puoi attivarlo di nuovo.

Esegui il debug dei trigger in locale

Poiché le tracce in background vengono eseguite in momenti casuali, il debug dei trigger in locale è difficile. Per forzare una traccia in background per i test, utilizza il seguente comando ADB:

adb shell device_config put profiling_testing system_triggered_profiling.testing_package_name <com.example.myapp>

Questo comando forza il sistema ad avviare una traccia in background continua per il pacchetto specificato, consentendo a ogni trigger di raccogliere un profilo se il limitatore di frequenza lo consente.

Puoi anche attivare altre opzioni di debug, ad esempio disattivare il limitatore di frequenza durante il debug locale. Per saperne di più, consulta Comandi di debug per la profilazione locale.