Profilage basé sur des déclencheurs

ProfilingManager permet de capturer des profils en fonction de déclencheurs système. Le système gère le processus d'enregistrement et fournit le profil résultant à votre application.

Les déclencheurs sont liés à des événements essentiels pour les performances. Les profils enregistrés par le système fournissent des informations de débogage détaillées pour les critical user journeys (CUJ) associés à ces déclencheurs.

Capturer des données historiques

De nombreux déclencheurs nécessitent l'analyse des données historiques menant à l'événement. Le déclencheur lui-même est souvent la conséquence d'un problème plutôt que la cause première. Si vous ne démarrez un profil qu'après le déclenchement, la cause première peut déjà être perdue.

Par exemple, une opération de longue durée sur le thread UI provoque une erreur Application Not Responding (ANR). Au moment où le système détecte l'ANR et signale l'application, l'opération peut être terminée. Le démarrage d'un profil à ce moment-là ne tient pas compte du travail de blocage réel.

Il est impossible de prédire exactement quand certains déclencheurs se produisent, ce qui rend impossible le démarrage manuel d'un profil à l'avance.

Pourquoi utiliser la capture basée sur des déclencheurs ?

La principale raison d'utiliser des déclencheurs de profilage est de capturer des données pour des événements imprévisibles où il est impossible pour une application de démarrer l'enregistrement manuellement avant que ces événements ne se produisent. Les déclencheurs de profilage peuvent être utilisés pour :

  • Déboguer les problèmes de performances : diagnostiquer les ANR, les fuites de mémoire et autres problèmes de stabilité.
  • Optimiser les critical user journeys : analyser et améliorer les flux, par exemple le démarrage de l'application.
  • Comprendre le comportement des utilisateurs : obtenir des insights sur les événements, par exemple les sorties d'application initiées par l'utilisateur.

Configurer un déclencheur

Le code suivant montre comment s'inscrire au déclencheur TRIGGER_TYPE_APP_FULLY_DRAWN et lui appliquer une limitation du débit.

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

}

Le code effectue les étapes suivantes :

  1. Obtenir le gestionnaire : récupère le service ProfilingManager.
  2. Définir un déclencheur : crée un ProfilingTrigger pour TRIGGER_TYPE_APP_FULLY_DRAWN. Cet événement se produit lorsque l'application indique qu'elle a terminé son démarrage et qu'elle est interactive.
  3. Définir des limites de débit : applique une limite de débit d'une heure à ce déclencheur spécifique (setRateLimitingPeriodHours(1)). Cela empêche l'application d'enregistrer plus d'un profil de démarrage par heure.
  4. Enregistrer l'écouteur : appelle registerForAllProfilingResults pour définir le rappel qui gère le résultat. Ce rappel reçoit le chemin d'accès du profil enregistré via getResultFilePath().
  5. Ajouter des déclencheurs : enregistre la liste des déclencheurs auprès de ProfilingManager à l’aide de addProfilingTriggers.
  6. Déclencher un événement : appelle reportFullyDrawn(), qui émet l'événement TRIGGER_TYPE_APP_FULLY_DRAWN vers le système, ce qui déclenche une collecte de profils en supposant qu'une trace d'arrière-plan du système était en cours d'exécution et qu'un quota de limiteur de débit est disponible. Cette étape facultative illustre un flux de bout en bout, car votre application doit appeler reportFullyDrawn() pour ce déclencheur.

Récupérer la trace

Le système enregistre les profils basés sur des déclencheurs dans le même répertoire que les autres profils. Le nom de fichier des traces déclenchées suit le format suivant :

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

Vous pouvez extraire le fichier à l'aide d'ADB. Par exemple, pour extraire la trace système capturée avec l'exemple de code à l'aide d'ADB, elle peut se présenter comme suit :

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

Pour en savoir plus sur la visualisation de ces traces, consultez Récupérer et analyser des données de profilage.

Fonctionnement du traçage en arrière-plan

Pour capturer des données avant un déclencheur, le système d'exploitation démarre périodiquement une trace en arrière-plan. Si un déclencheur se produit pendant que cette trace en arrière-plan est active et que votre application y est enregistrée, le système enregistre le profil de trace dans le répertoire de votre application. Le profil inclura alors des informations qui ont conduit au déclencheur.

Une fois le profil enregistré, le système envoie une notification à votre application à l'aide du rappel fourni à registerForAllProfilingResults. Ce rappel fournit le chemin d'accès au profil capturé, auquel vous pouvez accéder en appelant ProfilingResult#getResultFilePath().

Schéma illustrant le fonctionnement des instantanés de trace en arrière-plan, avec un tampon circulaire qui capture les données avant un événement déclencheur.
Figure 1 : Fonctionnement des instantanés de trace en arrière-plan.

Pour réduire l'impact sur les performances de l'appareil et l'autonomie de la batterie, le système n'exécute pas de traces en arrière-plan en continu. Il utilise plutôt une méthode d'échantillonnage. Le système démarre de manière aléatoire une trace en arrière-plan dans un délai défini (avec une durée minimale et maximale). L'espacement aléatoire de ces traces améliore la couverture des déclencheurs.

Les profils déclenchés par le système ont une taille maximale définie par le système. Ils utilisent donc une mémoire tampon circulaire. Une fois la mémoire tampon pleine, les nouvelles données de trace écrasent les données les plus anciennes. Comme illustré dans la figure 1, une trace capturée peut ne pas couvrir toute la durée de l'enregistrement en arrière-plan si la mémoire tampon est pleine. Au lieu de cela, elle représente l'activité la plus récente menant au déclencheur.

Implémenter une limitation du débit spécifique aux déclencheurs

Les déclencheurs à haute fréquence peuvent rapidement consommer le quota de limiteur de débit de votre application. Pour mieux comprendre le limiteur de débit, nous vous encourageons à consulter Fonctionnement du limiteur de débit. Pour éviter qu'un seul type de déclencheur n'épuise votre quota, vous pouvez implémenter une limitation du débit spécifique aux déclencheurs.

ProfilingManager est compatible avec la limitation du débit spécifique aux déclencheurs définie par l'application. Cela vous permet d'ajouter une autre couche de limitation basée sur le temps en plus du limiteur de débit existant. Utilisez l'API setRateLimitingPeriodHours pour définir un délai de refroidissement spécifique pour un déclencheur. Une fois le délai de refroidissement expiré, vous pouvez le déclencher à nouveau.

Déboguer les déclencheurs en local

Étant donné que les traces en arrière-plan s'exécutent à des moments aléatoires, le débogage des déclencheurs en local est difficile. Pour forcer une trace en arrière-plan à des fins de test, utilisez la commande ADB suivante :

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

Cette commande force le système à démarrer une trace en arrière-plan continue pour le package spécifié, ce qui permet à chaque déclencheur de collecter un profil si le limiteur de débit le permet.

Vous pouvez également activer d'autres options de débogage, par exemple en désactivant le limiteur de débit lors du débogage en local. Pour en savoir plus, consultez Commandes de débogage pour le profilage local.