Profilage basé sur des déclencheurs

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

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

Capturer les données historiques

De nombreux déclencheurs nécessitent d'analyser les données historiques précédant 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 commencez à profiler 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 le signale à l'application, l'opération peut être terminée. Si vous commencez à profiler à ce moment-là, vous manquerez le véritable travail de blocage.

Il est impossible de prédire exactement quand certains déclencheurs se produiront, ce qui rend impossible de démarrer manuellement un profil à l'avance.

Pourquoi utiliser la capture basée sur un déclencheur ?

La principale raison d'utiliser des déclencheurs de profilage est de capturer des données pour des événements imprévisibles pour lesquels il est impossible pour une application de commencer à enregistrer manuellement avant qu'ils ne se produisent. Les déclencheurs de profilage peuvent être utilisés pour :

  • Déboguer les problèmes de performances : diagnostiquez les erreurs ANR, les fuites de mémoire et d'autres problèmes de stabilité.
  • Optimisez les parcours utilisateur critiques : analysez et améliorez les flux, par exemple le démarrage de l'application.
  • Comprendre le comportement des utilisateurs : obtenez des insights sur les événements, par exemple les sorties d'application initiées par les utilisateurs.

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. Get the manager : 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é le 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. Add triggers (Ajouter des déclencheurs) : enregistre la liste des déclencheurs avec ProfilingManager à l'aide de addProfilingTriggers.
  6. Déclencher un événement : appelle reportFullyDrawn(), qui émet l'événement TRIGGER_TYPE_APP_FULLY_DRAWN au système, ce qui déclenche une collecte de profil 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 fréquence est disponible. Cette étape facultative montre 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, cela peut ressembler à ceci :

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

Pour savoir comment visualiser ces traces, consultez Récupérer et analyser les données de profilage.

Fonctionnement du traçage en arrière-plan

Pour capturer des données antérieures à un déclencheur, l'OS démarre périodiquement une trace en arrière-plan. Si un déclencheur se produit alors 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 ensuite les 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 et l'autonomie de la batterie de l'appareil, le système n'exécute pas les traces en arrière-plan en continu. Elle 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 un tampon circulaire. Une fois le tampon plein, les nouvelles données de trace écrasent les données les plus anciennes. Comme le montre la figure 1, une trace capturée peut ne pas couvrir toute la durée de l'enregistrement en arrière-plan si le tampon est plein. Elle représente plutôt l'activité la plus récente précédant le 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 du limiteur de fréquence de votre application. Pour mieux comprendre le limiteur de débit, nous vous invitons à 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 temps de latence spécifique pour un déclencheur. Une fois le délai de récupération expiré, vous pouvez déclencher à nouveau l'action.

Déboguer les déclencheurs en local

Étant donné que les traces en arrière-plan s'exécutent à des moments aléatoires, il est difficile de déclencher le débogage localement. 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 fréquence le permet.

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