Outils et performances

Trace me if you can: Automated instrumentation and field profiling

Haut-parleurs

  • Rahul Ravikumar

    Staff Software Engineer

À propos

À propos de l'intervention

Pour optimiser les applications Android, vous devez disposer d'une visibilité approfondie sur les workflows simultanés et les suspensions de coroutines.

Bien que le profilage local offre un aperçu utile, l'application de ces insights à la production sans surcharge reste un défi.

Cette session présente un plan d'architecture pour une boucle de rétroaction continue sur les performances.

androidx.tracing 2.0 : échangez les backends de manière dynamique et propagez les contextes de coroutine (flux Perfetto) entre les threads.

Instrumentation intelligente : utilisez le traçage ART local pour identifier les points d'accès, puis automatisez l'injection de points de trace à faible surcharge.

Macrobenchmarking : suivez les régressions et vérifiez les gains de performances sur les appareils physiques dans l'intégration continue.

Traçage sur le terrain : allez au-delà du traçage local et de l'intégration continue avec ProfilingManager.

Résumé

  • Tracing 2.0 remplace les anciennes API de traçage Android pour supprimer les limites de longueur des libellés et s'exécuter sept fois plus vite. Il vous permet également de créer des récepteurs personnalisés pour des backends tels qu'OpenTelemetry.
  • La propagation du contexte cartographie les relations de cause à effet entre les threads, les coroutines et les processus. Vous pouvez suivre l'exécution asynchrone avec traceCoroutine ou transmettre des jetons de propagation dans du code basé sur des rappels.
  • Jetpack Compose 1.13 ajoute le traçage de recomposition, qui utilise des métadonnées personnalisées pour indiquer pourquoi les recompositions se produisent. Les traces enregistrent les valeurs d'état modifiées et les piles d'appel exactes qui les ont déclenchées.
  • Les outils de compilation Android utilisent Tracing 2.0 pour identifier les goulots d'étranglement et accélérer les compilations. Optimisations guidées par le traçage qui ont permis d'accélérer de 25 % la minification de code R8 et de réduire de 15 % les temps de post-envoi de Metalava.

Gemini Notebook

Thèmes

  • traçage
  • android-performance
  • perfetto
  • context-propagation
  • jetpack-compose
  • coroutines
  • profilage

Transcription

Présentation et évolution du traçage Android

  • Rahul Ravikumar

    Bonjour à toutes et à tous.

  • Je m'appelle Rahul.

  • Je suis ingénieur dans l'équipe Android Developer Experience.

  • Je me concentre sur les performances des applications et des bibliothèques, et cette présentation porte sur le traçage.

  • Avant d'aborder le traçage de façon plus approfondie, j'aimerais établir un vocabulaire commun pour que vous puissiez suivre.

  • Ainsi, chaque fois que je ferai référence au traçage dans cette présentation, je parlerai du traçage explicite, qui consiste à instrumenter manuellement le code pour savoir ce que fait l'application.

  • Je ne parle pas des profileurs automatiques que je vais aborder.

  • Le traçage manuel consiste donc à laisser des traces dans votre code base pour pouvoir l'analyser plus tard en cas de problème de performances ou de tout autre problème.

  • C'est généralement bien mieux que les journaux.

  • Je ne sais pas si vous avez déjà vu la sortie Logcat d'une application volumineuse.

  • Il s'agit généralement d'un énorme bloc de texte qu'il est difficile d'analyser facilement.

  • En revanche, le traçage est beaucoup plus agréable.

  • Il est plus facile à visualiser.

  • Vous avez des durées.

  • Vous avez une structure.

  • Vous disposez d'outils qui vous permettent même de segmenter les données et de voir exactement ce qui s'est passé et quand.

  • C'est donc beaucoup plus agréable.

  • Vous pouvez, par exemple, pointer vos agents vers une trace et leur poser des questions.

  • Vous pouvez utiliser des outils tels que Trace Processor, des benchmarks et même convertir vos traces en métriques.

  • Ce sont toutes des choses que vous pouvez faire avec les traces, mais qui sont difficiles à faire avec Logcat.

  • En revanche, le profilage, et je parle ici du profilage automatique, est généralement une réponse à un problème que vous connaissez.

  • Par exemple, vous avez peut-être remarqué que votre application démarre lentement ou qu'elle est instable, et vous souhaitez identifier l'origine du problème.

  • Si vous n'avez pas encore de traces manuelles pour vous aider, c'est là que le profilage automatique entre en jeu.

  • Vous pouvez utiliser une méthode de traçage ART, par exemple, pour instrumenter chaque entrée et sortie de méthode.

  • Vous pouvez également examiner l'échantillonnage de la pile avec des outils tels que Simpleperf.

  • Ainsi, plutôt que d'utiliser chaque entrée et sortie de méthode et d'avoir une surcharge élevée, vous examinez les échantillons de pile d'appel et vous voyez ce qui se trouve sur le chemin critique de votre application.

  • Nous sommes en 2026, la RAM est un problème partout. Vous vous intéressez donc peut-être à la mémoire, aux fuites de mémoire et aux profils de tas.

  • Il s'agit donc d'un autre profileur que vous pouvez utiliser.

  • Vous pouvez examiner les chemins de rétention du tas de mémoire.

  • Vous pouvez voir quel code alloue de la mémoire, et ce sont là tous les types d'analyse que vous pouvez effectuer avec cet outil.

  • C'est vrai.

  • Maintenant, nous avons ce vocabulaire.

  • Voyons maintenant pourquoi nous avons créé une nouvelle bibliothèque.

  • Les API de traçage 1.0 ont été introduites dans Android Jelly Bean.

  • C'était en 2012.

  • C'était il y a plus de 14 ans.

  • Depuis, les API n'ont pas évolué autant que vous le pensez.

  • Nous avons ajouté quelques extensions.

  • Les événements et compteurs asynchrones ont été ajoutés dans Android 10.

  • C'était en 2019.

  • Et voilà.

  • L'API n'a pas évolué depuis.

  • Cependant, l'écosystème a beaucoup évolué.

  • Le traçage de la plate-forme s'est donc considérablement amélioré, mais cela n'a pas eu d'impact sur les ajouts d'API.

  • C'est quelque chose que nous voulons changer.

  • L'autre problème majeur est que le traçage dispose d'un backend de traçage fixe dans la version 1.0. Cela signifie que chaque fois que vous ajoutez une section de trace dans votre code, elle se retrouve toujours dans le tampon de trace du système.

  • Atrace est le sous-système de traçage Android qui possède ces données.

  • Si vous souhaitez convertir vos traces en données de télémétrie de champ, c'est très difficile, car vous devez d'abord comprendre comment atrace stocke ces données.

  • Il s'agit d'un format bien documenté, mais potentiellement difficile à exploiter.

  • Nous voulons donc corriger cela.

  • Nous voulons donc vous donner les outils nécessaires pour transférer vos données de traçage où vous le souhaitez.

  • De plus, dans Tracing 1.0, les libellés étaient limités à 128 caractères.

  • Cette restriction était probablement raisonnable en 2012, mais en 2026, elle semble plutôt étrange.

  • Pire encore, vous ne pouvez pas ajouter de métadonnées supplémentaires à votre trace, ce qui est particulièrement problématique.

  • Ensuite, il y a les frais généraux.

  • Par conséquent, chaque fois que vous tracez votre application, vous devez être extrêmement conscient de la surcharge, car l'observation d'un système ne doit pas modifier ce que le système fait en arrière-plan.

  • Ainsi, si vous souhaitez reproduire une condition de concurrence, le traçage ne doit pas la faire disparaître.

  • Vous devez donc vous assurer de préserver toute la sémantique que vous utilisez par ailleurs.

  • Tracing 1.0 avait un coût relativement élevé, et nous voulions l'améliorer.

  • OK.

  • J'ai beaucoup parlé des problèmes.

  • Examinons maintenant certains de ces problèmes de manière visuelle et voyons comment ils se combinent en pratique.

  • Voici une trace Perfetto.

  • C'est pour une application, com.example.app.

  • Nous avons un thread principal et une seule tranche appelée initializeComponent.

  • Idéalement, nous aimerions obtenir plus de détails.

  • Ajoutons donc un détail.

  • Nous savons maintenant que initializeComponent appelle performInitialize.

  • Nous ne savons toujours pas quel composant est en cours d'initialisation.

  • Maintenant, le seul outil de notre boîte à outils avec Tracing 1 est le nom. Nous l'ajoutons donc au nom de la tranche.

  • N'oubliez pas que les noms des segments de trace sont limités à 128 caractères. Ils sont donc tronqués dans votre trace.

  • Pour couronner le tout, la surcharge liée à la troncature s'ajoute désormais à celle que vous subissez lors du traçage.

  • Vous rencontrez donc ce problème assez rapidement.

  • Nous voulions donc vraiment résoudre ce problème.

  • Vous avez vu la trace Perfetto et son aspect visuel.

  • Voyons maintenant comment écrire le code pour émettre ces sections de trace.

  • Là encore, nous avons l'API initializeComponent.

  • Elle accepte un contexte et un nom, puis appelle performInitialize.

  • Jusque-là, tout va bien.

  • Nous voulons maintenant ajouter une trace avec l'API Tracing 1.0. Pour ce faire, importez androidx.tracing.trace et utilisez un bloc de trace.

  • Vous lui donnez un nom.

  • Ici, il s'agit de performInitialize.

  • Mais comme nous voulions le faire auparavant, nous voulions connaître le nom du composant. Nous allons donc utiliser l'interpolation de chaîne de Kotlin et ajouter le nom.

  • Voici le premier problème.

  • Ici, nous avons la limite de 128 caractères.

Présentation du traçage 2.0 et des performances

  • Rahul Ravikumar

    Nous pouvons résoudre ce problème avec Tracing 2.

  • Au lieu d'utiliser androidx.tracing.trace, vous utilisez Tracer.

  • L'API est remarquablement similaire.

  • Au lieu de trace, vous avez Tracer.global.trace.

  • Vous avez une catégorie.

  • Nous parlerons des catégories plus en détail plus tard, mais fondamentalement, vous avez un nom.

  • Ensuite, vous avez toujours le bloc de code que vous essayez de tracer.

  • Désormais, vous n'êtes plus limité à 128 caractères.

  • J'ai parlé de la surcharge de traçage.

  • Je souhaite entrer un peu plus dans les détails et voir comment nous avons amélioré ce point.

  • Sur un Pixel 6a exécutant Android 16, l'insertion d'une seule section de début et de fin a pris environ 1 200 nanosecondes.

  • Cela peut sembler peu, mais si vous avez un code très actif qui ajoute de nombreuses sections de trace, cela représente du temps que votre application pourrait consacrer au rendu des images.

  • Nous ne voulons donc pas vous faire perdre ce temps.

  • Nous avons considérablement amélioré les choses avec le SDK AndroidX Perfetto.

  • Il s'agissait d'un SDK expérimental que nous avons introduit pour le traçage de la composition, et qui a considérablement amélioré la situation.

  • Nous ne voulions pas que vous l'expédiiez, car il s'agit du SDK Perfetto C avec des liaisons JNI, et le SDK C fait environ 4 Mo dans votre APK.

  • Nous n'avons jamais voulu que vous l'expédiiez.

  • Avec Tracing 2, nous disposons désormais d'une bibliothèque spécialement conçue à cet effet.

  • Nous avons considérablement réduit les frais généraux. Vous serez ravi d'apprendre que Tracing 2 est sept fois plus rapide. Vous pouvez donc consacrer plus de temps à ce que vous voulez faire, et l'infrastructure prend moins de temps.

  • Qu'est-ce qu'un tel outil rend possible, alors que ce n'était pas le cas auparavant ?

  • La fonctionnalité de traçage de l'environnement d'exécution de Metro en est un exemple.

  • Pour ceux qui ne le savent pas, Metro est une bibliothèque d'injection de dépendances.

  • Elle est compatible avec Kotlin Multiplatform.

  • Il utilise un plug-in de compilation Kotlin.

  • Il génère tout le code pour l'injection de dépendances et s'intègre à Tracing 2.0 pour faciliter la visualisation du coût de chaque @Inject dans votre code.

  • Voici un exemple.

  • Imaginons que nous ayons un graphique d'application.

  • Dans Metro, tout est limité à un graphique. Nous n'avons donc qu'un seul champ, AnalyticsTracker.

  • Nous avons également une liaison de fournisseur.

  • Nous avons un client réseau avec OkHttpClient comme implémentation, puis l'implémentation de AnalyticsTracker provient de AnalyticsTrackerImpl, et nous avons besoin d'une instance du client réseau, que le graphique sait fournir.

  • En arrière-plan, le plug-in du compilateur génère un code qui ressemble à ceci.

  • Ainsi, si vous disposez de AnalyticsTracker, le getter crée une instance du client réseau et la transmet au constructeur de AnalyticsTrackerImpl, ce qui est une bonne chose.

  • Mais maintenant que le traçage est activé, vous pouvez voir le coût de création non seulement de l'implémentation du traceur, mais aussi de chacune des dépendances dont il a besoin.

  • Désormais, vous pouvez voir non seulement le coût d'instanciation du type, mais aussi toutes ses dépendances dans une trace, ce qui est vraiment très utile.

  • En utilisant ces informations, Wise, une application financière, a amélioré le démarrage de son application de 45 %.

  • Ils ont pu différer les éléments qui n'étaient pas nécessaires à l'initialisation au démarrage de l'application. Ils ont également identifié des éléments dont le champ d'application était incorrect, où des éléments qui auraient dû être des singletons ne l'étaient pas par accident. Ce sont des éléments que vous pouvez voir et corriger facilement dans votre code.

  • Si vous regardez leurs métriques de démarrage, elles sont vraiment impressionnantes.

  • Non seulement ils ont amélioré le délai d'affichage de la première image, mais ils ont également amélioré le délai d'affichage complet.

  • Un autre partenaire, BandLab, utilise les mêmes informations sur l'IC.

  • Ainsi, non seulement vous connaissez le coût de chaque liaison @Inject, mais vous savez également quel thread a généré ce coût.

  • Une fois que vous savez cela, vous pouvez trouver toutes les liaisons @Inject coûteuses sur le thread principal. Dans votre CI, vous pouvez exécuter une requête pour obtenir la liste de toutes les liaisons coûteuses, puis l'attribuer à la modification qui les a introduites. Vous pouvez alors identifier la modification en cause et essayer de la corriger.

Propagation du contexte, coroutines et Jetpack Compose

  • Rahul Ravikumar

    OK.

  • Jusqu'à présent, j'ai parlé de toutes les améliorations que nous avons apportées par rapport à Tracing 1, mais je vais maintenant parler des nouvelles fonctionnalités de Tracing 2 qui n'étaient pas possibles auparavant.

  • L'une des fonctionnalités essentielles est la propagation du contexte. Comme les applications modernes sont de plus en plus complexes, les charges de travail s'exécutent sur plusieurs threads, coroutines et processus.

  • Vous voulez pouvoir visualiser la propagation d'une requête entre chaque thread et chaque processus.

  • C'est ce que permet la propagation du contexte.

  • Si je vous donnais simplement une liste de tranches sans vous expliquer ce qui a causé quoi, elles seraient utiles, mais pas très faciles à analyser.

  • Mais si je vous disais qu'il s'agit de la chaîne d'événements causale qui s'est produite, il vous serait beaucoup plus facile de comprendre ce qui s'est passé, puis d'associer le coût de chaque tranche.

  • C'est ce que permet la propagation du contexte.

  • Vous serez ravi d'apprendre que la propagation du contexte est entièrement prise en charge. Voici un exemple concret.

  • En examinant la trace Perfetto com.example.app, supposons que performInitialize prenne trop de temps et que vous souhaitiez différer le coût ou le déplacer vers un autre thread.

  • C'est tout à fait possible.

  • Imaginons que je déplace performInitialize vers un autre fil de discussion.

  • Vous avez maintenant perdu la nature hiérarchique de la tranche.

  • Vous ne pouvez donc plus savoir que initializeComponent est à l'origine de performInitialize, à moins de regarder le code.

  • Dans un monde idéal, vous aimeriez qu'une flèche vous indique que A a causé B, puis que vous puissiez raisonner sur la chaîne causale des événements.

  • Vous avez peut-être même vu une trace système où nous faisons cela pour les transactions de binder sur la plate-forme.

  • Ici, vous pouvez voir que le benchmark Compose effectue une transaction de liaison vers system_server pour préparer une frame.

  • Il s'agit donc d'une fonctionnalité qui a toujours été possible du point de vue du traçage de la plate-forme, mais nous n'avons jamais mis les outils à la disposition des développeurs d'applications.

  • La propagation du contexte des coroutines Kotlin est désormais entièrement compatible avec Tracing 2.0.

  • Aucune action de votre part n'est nécessaire.

  • Il vous suffit de le sortir de la boîte.

  • Ici, nous avons une seule méthode de suspension appelée fetchProfile.

  • Il appelle un client.get, qui est une autre méthode de suspension, puis passe à Dispatchers.Default pour désérialiser la réponse.

  • Il s'agit d'un code de coroutines très classique, que vous avez probablement l'habitude d'utiliser tous les jours.

  • Supposons maintenant que nous voulions tracer cette requête.

  • Nous ajoutons une section de trace.

  • Ici, au lieu de "trace", nous allons utiliser l'API traceCoroutine, car il s'agit d'un bloc de code de suspension. Ensuite, pour l'appel interne avant de passer à Dispatchers.Default, nous allons ajouter une autre section de trace interne à l'aide de la même API traceCoroutine.

  • Dans une trace, vous verrez quelque chose comme ceci.

  • Vous verrez que notre travail a commencé dans worker-1, puis qu'il a changé de contexte et est passé à worker-3. Des flèches utiles dans la trace l'indiquent.

  • De plus, si vous zoomez sur le carré au milieu, vous pouvez voir que la désérialisation a en fait commencé dans worker-1 et que l'appel à withContext est ce qui a provoqué le changement de contexte, puis elle s'est poursuivie sur worker-3, puis fetchProfile a repris sur worker-3, car le mécanisme des coroutines veut minimiser le nombre de changements de contexte qui se produisent.

  • Vous pouvez voir exactement quand une coroutine a été suspendue, quand elle a repris et vous pouvez voir la chaîne causale complète des événements.

  • Tracing 2 permet également d'ajouter des métadonnées personnalisées à une trace.

  • Auparavant, le seul outil dont vous disposiez était le nom de la section de trace. Désormais, vous pouvez également ajouter un bloc de métadonnées qui vous permet d'ajouter autant d'entrées de métadonnées que vous le souhaitez.

  • Vous pouvez l'ajouter à la trace.

  • Si vous examinez la trace réelle, vous pouvez voir que toutes les métadonnées que vous avez ajoutées sont bien présentes dans la trace, et que tous les outils peuvent les utiliser.

  • Vous pouvez désormais intégrer toutes vos connaissances et toutes les informations supplémentaires que vous avez toujours voulu ajouter.

  • Je vous ai présenté un certain nombre de fonctionnalités abstraites que le traçage rend possibles, mais la véritable valeur ajoutée réside dans une nouvelle fonctionnalité de Jetpack Compose appelée "traçage de recomposition".

  • Cette fonctionnalité est disponible dans Compose 1.13. Vous pouvez donc l'utiliser dès aujourd'hui.

  • Voici un exemple.

  • Il s'agit du traçage de composition, que vous connaissez probablement tous.

  • Cette fonctionnalité existe depuis les débuts de Compose.

  • Il vous indique le coût relatif d'un composable et de tous ses sous-segments, ainsi que le coût de chacun d'eux sur le thread dans lequel ils se sont produits.

  • C'est donc assez simple.

  • En revanche, il ne vous donne pas le contexte.

  • Elle n'en donne pas la raison.

  • Il ne vous indique pas la raison pour laquelle les éléments ont été recomposés.

  • Si je fais un zoom arrière, voici la région d'intérêt que nous venons de voir dans la diapositive précédente.

  • Sur la gauche, vous pouvez voir qu'un événement d'entrée de distribution s'est produit.

  • C'est peut-être ce qui a modifié un état et provoqué la recomposition.

  • Mais ce n'est pas clair.

  • Vous n'en êtes pas sûr.

  • Cependant, avec le traçage de recomposition, nous ajoutons désormais des métadonnées personnalisées à la trace qui vous indiquent exactement pourquoi les recompositions ont eu lieu.

  • De plus, il est désormais très facile de voir que ces événements sont liés.

  • Si vous cliquez dessus, vous verrez exactement quel état a changé.

  • Il vous indiquera qu'il s'agissait d'un état, d'une valeur d'état modifiable qui est passée à "false" (alors qu'elle était auparavant définie sur "true").

  • De plus, nous vous indiquerons la pile d'appel exacte qui en est à l'origine.

  • Ici, vous pouvez voir que AbstractClickableNode.performClick s'est produit, ce qui a entraîné la modification de l'état modifiable, ce qui a déclenché la recomposition.

  • C'est vraiment génial, car nous n'avions jamais pu le faire auparavant.

  • Toutes ces fonctionnalités sont également disponibles dans le nouvel outil Android Performance Analyzer, si vous souhaitez tester certains des outils d'agent.

  • Et à droite.

  • Je vous ai dit que la propagation du contexte était entièrement compatible avec les coroutines Kotlin.

  • Mais qu'en est-il du code qui utilise un mélange de coroutines et de code non-coroutine, ou peut-être que vous exécutez des threads vous-même, ou peut-être que vous avez du code brownfield trop ancien qui utilise des rappels pour une raison quelconque ?

  • OK.

  • La bonne nouvelle, c'est que vous pouvez toujours profiter de tous les avantages de la propagation du contexte.

  • Imaginons que nous ayons une API existante qui utilise un rappel et un exécuteur.

  • L'implémentation utilise executor.execute, appelle runCatching (qui est le détail de l'implémentation), obtient la réponse, puis informe le rappel de la réponse.

  • OK.

  • Maintenant, si je voulais tracer cela, je ferais Tracer.global.trace, je lui donnerais un libellé, puis j'entourerais le bloc.

  • Mais comme je souhaite également préserver la chaîne causale des événements, j'accepterai un nouveau type facultatif, appelé jeton de propagation.

  • Je vais simplement transmettre le jeton à l'API Trace interne.

  • Ainsi, la personne qui appelle cette méthode peut éventuellement me fournir le jeton de propagation, ce qui me permet de préserver la chaîne causale des événements qui se produisent.

  • OK.

  • Désormais, l'appelant de cette API peut être une API de suspension.

  • Ici, j'ai une fonction de suspension profileRequest, qui va maintenant appeler l'API qui utilise un rappel et un exécuteur.

  • Ici, comme je fais le lien entre deux mondes, celui des fonctions de suspension et celui des rappels, je vais utiliser le compilateur suspendCoroutine.

  • Je vais obtenir une continuation, puis j'appellerai l'API et je reprendrai la continuation dans le rappel une fois que j'aurai reçu la réponse.

  • Ensuite, l'exécuteur que je définis s'appelle profileRequestContext.

  • Il s'agit d'un exécuteur à thread unique.

  • Comme traceCoroutine intègre un jeton de propagation dans le contexte de la coroutine, je peux obtenir le jeton de propagation à partir du contexte de la coroutine, puis le transmettre en entrée à l'API brownfield, ce qui me permet de préserver la chaîne causale des événements.

  • Et dans la trace, cela devient très évident.

  • Ici, vous avez donc quelque chose qui a commencé dans worker-3, qui est passé à l'exécuteur monothread une fois le changement de contexte effectué, puis qui a repris sur worker.

  • C'est donc très pratique.

Traçage global et initialisation personnalisée

  • Rahul Ravikumar

    OK.

  • Vous pouvez également personnaliser Tracer.global.

  • Jusqu'à présent, l'API Tracing 2.0 vous fournit de très bonnes valeurs par défaut pour Tracer.global, mais vous pouvez les personnaliser si vous le souhaitez.

  • De plus, Tracing 2.0 est compatible avec la JVM et Android, car nous voulons que le traçage soit entièrement pris en charge pour les bibliothèques Java et Android.

  • Les trois étapes d'initialisation sont les suivantes : vous créez un récepteur de trace.

  • Un récepteur de trace correspond à la manière dont les paquets de trace sont stockés.

  • Il définit le format de transmission et l'emplacement de stockage des traces de sortie.

  • Nous vous fournissons une implémentation prête à l'emploi pour un récepteur de trace compatible avec le format Perfetto, mais vous pouvez bien sûr écrire votre propre récepteur de trace.

  • Cela nous ramène au problème que nous essayions de résoudre : atrace était le seul backend disponible dans Tracing 1.

  • Nous voulions résoudre ce problème.

  • Désormais, si vous souhaitez écrire un récepteur OpenTelemetry ou utiliser votre propre récepteur, c'est tout à fait possible.

  • Vous pouvez choisir d'ignorer ce blocage.

  • Une fois que vous disposez d'un récepteur, vous pouvez créer un pilote de trace et lui attribuer le récepteur.

  • Vous pouvez également choisir les catégories que vous souhaitez activer.

  • C'est là que les catégories sont vraiment utiles.

  • Vous pouvez donc décider que, dans la version de production, certaines catégories sont plus importantes que d'autres par rapport à la version de débogage, et vous pouvez indiquer que ce sont ces catégories que vous souhaitez activer.

  • Vous pouvez aussi simplement dire : "Je veux activer toutes les catégories, quelle que soit leur provenance."

  • Et ça ne pose aucun problème.

  • Vous pouvez également ajouter des attributs personnalisés qui sont globaux à votre trace.

  • Vous pouvez ainsi intégrer des éléments tels que la version de compilation, la variante ou toute autre métadonnée que vous souhaitez associer à une trace à l'aide de l'API addAttribute.

  • Enfin, vous pouvez enregistrer un traceur en appelant setGlobalTracer.

  • C'est ce qui lie Tracer.global à cette instance de traceur, et par conséquent, ce qui est disponible pour le processus.

  • Sur Android, comme les processus ont un cycle de vie beaucoup mieux défini, il vous suffit d'implémenter AbstractTraceDriver.Factory, de remplacer sa méthode create et de faire exactement ce que vous avez fait auparavant.

  • Vous créez donc le récepteur, puis le pilote.

  • Vous n'avez pas besoin de vous inscrire ici.

  • Nous allons simplement effectuer l'enregistrement pour vous, car nous savons que c'est ce qui s'est passé en premier.

  • OK.

  • Je tiens à rappeler que le traçage est compatible avec plusieurs plates-formes.

  • Il s'agit d'un choix délibéré, car nous savons que Compose s'exécute sur plusieurs plates-formes et nous voulons vous permettre d'effectuer des traçages dans autant d'endroits que possible.

  • Aujourd'hui, nous prenons en charge Android et la JVM, mais notre implémentation nous permet de prendre en charge d'autres plates-formes à l'avenir si nous le souhaitons.

Optimisations des outils de compilation (R8 et metalava)

  • Rahul Ravikumar

    Vous serez ravi d'apprendre que R8, l'outil utilisé pour réduire et optimiser le code source sur Android, utilise Tracing 2.0.

  • En exploitant les traces, nous avons optimisé R8 pour réduire et optimiser votre code 25 % plus rapidement.

  • Cela signifie des temps de compilation plus courts et, vous savez, des temps d'attente plus courts pour les passes d'optimisation.

  • Je ne vais pas passer en revue toutes les optimisations que nous avons apportées à R8, mais je souhaite en aborder une, juste pour vous donner un aperçu de ce que le traçage permet de faire.

  • R8 est donc un transformateur de code source à code source.

  • Vous obtenez donc un ensemble de fichiers JAR, que vous réduisez et optimisez, et vous produisez un autre ensemble de fichiers JAR que vous pouvez convertir en fichiers DEX.

  • Ici, AGP commence par lire les fichiers JAR, qui ne sont pas seulement votre application, mais toutes ses dépendances.

  • Il utilise donc des nœuds de calcul Gradle pour commencer à lire tous les fichiers JAR d'entrée.

  • Vous avez de très nombreux fichiers JAR à lire.

  • Une fois la lecture des fichiers JAR terminée, R8 commence à analyser le code et à créer un modèle de ce que votre code représente, ainsi que tous ses points d'entrée, afin de pouvoir commencer à optimiser et à minifier votre code.

  • Cette tranche, "await", représente le moment où R8 est vraiment prêt à lancer le processus d'optimisation.

  • Vous remarquerez peut-être un espace vide au milieu et vous vous demanderez peut-être pourquoi nous avons ce temps CPU inutilisé.

  • En effet, l'ancienne API attendait que tous les nœuds de calcul aient lu tous leurs fichiers avant d'analyser quoi que ce soit.

  • Évidemment, nous pouvons résoudre ce problème, et c'est exactement ce que nous avons fait.

  • Dans AGP 9, vous pouvez voir que dès que nous avons fini de lire les fichiers JAR d'entrée, nous avons commencé à les traiter immédiatement.

  • Et nous n'avons pas eu à attendre que chaque nœud de calcul ait fini de lire ses entrées.

  • Cela a permis de réduire considérablement le temps d'attente pour lire toutes les entrées.

  • Il s'agit donc d'une optimisation évidente que vous pouvez voir lorsque vous examinez une trace, mais qui n'est pas aussi évidente lorsque vous examinez simplement le code source pour voir ce qui mérite d'être optimisé.

  • Metalava est un autre outil que nous utilisons tous les jours en tant qu'ingénieurs AndroidX.

  • Metalava est un outil que nous utilisons pour nous assurer que toutes les API que nous fournissons offrent les garanties de compatibilité que vous attendez et qu'elles sont entièrement utilisables par des personnes externes sans les pièges que nous voulons éviter.

  • Cet outil s'exécute sur chaque API publique et API restreinte. Ainsi, tout le temps que nous gagnons ici est du temps que nous récupérons en tant qu'ingénieurs AndroidX.

  • Metalava a également utilisé le traçage pour améliorer les temps de post-envoi de 15 %.

  • En examinant la trace pour expliquer ce qui s'est passé, Metalava effectue deux types de vérifications : la première vérifie les API restreintes, et la seconde vérifie les API publiques.

  • Mais vous remarquez peut-être une tendance.

  • Nous effectuons de nombreuses configurations pour exécuter ces vérifications deux fois.

  • Toutes les entrées du programme sont lues, puis nous créons l'interface PSI (Program Structure Interface), qui correspond à la structure exacte de votre code.

  • Ensuite, nous créons l'AST avant de commencer à appliquer toutes les vérifications lint souhaitées.

  • Et nous effectuons ce travail coûteux deux fois.

  • Comment avons-nous résolu ce problème ?

  • Nous ne l'avons fait qu'une seule fois.

  • Ainsi, au lieu d'effectuer deux fois tout ce travail de configuration, nous ne l'avons fait qu'une seule fois et nous avons réutilisé ces entrées pour les vérifications restreintes et publiques, ce qui nous a permis de réaliser toutes ces économies.

  • Oui.

  • OK.

Bonnes pratiques de traçage et analyse comparative

  • Rahul Ravikumar

    Maintenant que je vous ai donné un aperçu de ce que Tracing 2.0 permet de faire, il est important de savoir que, même si le traçage est important, savoir quoi tracer l'est tout autant.

  • Vous ne voulez pas laisser de traces partout.

  • Vous devez faire très attention à ce que vous choisissez, et je vais essayer de vous donner quelques conseils sur ce qui mérite d'être tracé.

  • La première chose à faire est donc de rechercher les écarts inexpliqués dans votre trace.

  • Ainsi, chaque fois que vos threads sont en cours d'exécution ou exécutables, et que vous constatez des écarts évidents dans votre trace système, que vous ne pouvez pas les expliquer et que vous ne savez pas ce que fait votre application, il peut être utile d'examiner la situation.

  • À ce stade, je peux effectuer un traçage de méthode superposé au traçage système, voir ce qui mérite d'être tracé et ajouter les sections de traçage pertinentes.

  • Vous devez également prioriser les parcours utilisateur.

  • Vous devez donc examiner le démarrage de l'application.

  • Vous souhaitez examiner les parcours utilisateur principaux de votre application et déterminer si vous pouvez les expliquer en examinant une trace.

  • Ainsi, chaque fois que vous ne pouvez pas raisonner sur quelque chose, cela mérite d'être tracé.

  • Vous devez également examiner les piles d'appels profondes ou l'utilisation inexpliquée du processeur à partir d'une trace, essayer de comprendre exactement ce qui se passe, puis convertir ces parties de votre codebase en traces.

  • Vous pouvez donc les tracer.

  • Ensuite, chaque fois que vos threads sont en veille, vous devez vous assurer que vous pouvez comprendre pourquoi ils sont en veille.

  • Ils doivent être en attente d'E/S, d'une transaction de binder ou d'événements d'entrée externes que vous attendez.

  • Mais si vous avez, vous savez, un sommeil inexpliqué, alors ceux-ci méritent d'être étudiés car il pourrait évidemment y avoir quelque chose qui se cache là-bas.

  • Une fois que vous avez ajouté toutes les sections de trace dans ce code, vous devez les convertir en benchmarks pour pouvoir suivre leurs modifications.

  • Ainsi, à mesure que votre code base évolue, vous pouvez voir si les métriques changent, si vos tableaux de bord reflètent réellement des changements importants, car vous savez alors que quelque chose d'important a changé.

  • Vous pouvez aussi ignorer toutes les étapes précédentes et laisser votre agent s'en charger.

  • Prenez ces consignes, transformez-les en compétence, puis associez-les à votre agent préféré et laissez-le faire.

  • Oui.

Configuration, résumé et conclusion

  • Rahul Ravikumar

    Si je vous ai convaincu que le traçage est une tâche utile et que vous devriez l'effectuer, voici la configuration du projet.

  • C'est assez simple.

  • Le traçage est une simple bibliothèque Java traditionnelle. Vous définissez donc une dépendance sur androidx.tracing:tracing-wire, qui est le pilote de trace compatible avec Perfetto et le combo de traçage. La version stable actuelle est la 2.0.5.

  • Si vous utilisez une bibliothèque Java, c'est exactement la même chose. Encore une fois, il s'agit d'une API entièrement multiplate-forme.

  • Nous souhaitons l'étendre à d'autres plates-formes lorsque cela est pertinent.

  • Je voulais juste faire un petit récapitulatif.

  • Je vous ai parlé de toutes ces nouvelles fonctionnalités intéressantes que nous avons ajoutées.

  • La propagation du contexte est donc très, très puissante.

  • Nous pouvons intégrer des informations personnalisées, des métadonnées, des informations sur la pile d'appel et bien d'autres choses encore.

  • Je n'ai pas parlé de beaucoup, beaucoup de choses.

  • Vous pouvez évidemment modifier le format de traçage, changer votre récepteur de trace, ou encore transférer vos données de traçage vers OpenTelemetry.

  • Ce sont toutes des choses que vous pouvez faire.

  • Le traçage est multiplate-forme.

  • Là encore, il s'agit d'une décision mûrement réfléchie, car nous voulons que le plus grand nombre de personnes possible puisse en profiter.

  • Tous les outils que nous fournissons, qu'il s'agisse de Perfetto, des benchmarks AndroidX, du nouvel analyseur de performances ou de l'ancien profileur Android, sont compatibles avec Tracing 2.0.

  • Merci de votre attention.

  • Je vous souhaite un excellent sommet des développeurs. Merci.