ProtoLog.

Le système de journalisation Android vise une accessibilité et une facilité d'utilisation universelles, en supposant que toutes les données de journal peuvent être représentées sous la forme d'une séquence de caractères. Cette hypothèse correspond à la plupart des cas d'utilisation, en particulier lorsque la lisibilité des journaux est cruciale sans outils spécialisés. Toutefois, dans les environnements nécessitant des performances de journalisation élevées et des tailles de journaux limitées, la journalisation basée sur du texte peut ne pas être optimale. WindowManager en est un exemple, car il nécessite un système de journalisation robuste capable de gérer les journaux de transition de fenêtre en temps réel avec un impact minimal sur le système.

ProtoLog est une alternative pour répondre aux besoins de journalisation de WindowManager et de services similaires. Les principaux avantages de ProtoLog par rapport à Logcat sont les suivants:

  • La quantité de ressources utilisées pour la journalisation est plus faible.
  • Du point de vue du développeur, c'est la même chose que d'utiliser le framework de journalisation Android par défaut.
  • Permet d'activer ou de désactiver les instructions de journalisation au moment de l'exécution.
  • Vous pouvez toujours vous connecter à Logcat si nécessaire.

Pour optimiser l'utilisation de la mémoire, ProtoLog utilise un mécanisme d'internement de chaîne qui implique de calculer et d'enregistrer un hachage compilé du message. Pour améliorer les performances, ProtoLog effectue l'internement de chaînes lors de la compilation (pour les services système), n'enregistrant que l'identifiant du message et les arguments au moment de l'exécution. De plus, lors de la génération d'une trace ProtoLog ou de l'obtention d'un rapport de bug, ProtoLog intègre automatiquement le dictionnaire de messages créé au moment de la compilation, ce qui permet de décoder les messages à partir de n'importe quel build.

Avec ProtoLog, le message est stocké au format binaire (proto) dans une trace Perfetto. Le décodage des messages se produit dans trace_processor de Perfetto. Le processus consiste à décoder les messages proto binaires, à traduire les identifiants de message en chaînes à l'aide du dictionnaire de messages intégré et à mettre en forme la chaîne à l'aide d'arguments dynamiques.

ProtoLog accepte les mêmes niveaux de journalisation que android.utils.Log, à savoir: d, v, i, w, e et wtf.

ProtoLog côté client

Initialement, ProtoLog était destiné uniquement au côté serveur de WindowManager, fonctionnant dans un seul processus et un seul composant. Par la suite, il a été étendu pour englober le code shell WindowManager dans le processus d'UI système, mais l'utilisation de ProtoLog nécessitait un code de configuration standard complexe. De plus, la journalisation Proto était limitée au serveur système et aux processus d'interface utilisateur système, ce qui rendait son intégration dans d'autres processus fastidieuse et nécessitait une configuration de tampon de mémoire distincte pour chacun. Toutefois, ProtoLog est désormais disponible pour le code côté client, ce qui élimine le besoin de code standard supplémentaire.

Contrairement au code des services système, le code côté client ignore généralement l'internement de chaîne au moment de la compilation. À la place, l'internement de chaîne se produit de manière dynamique dans un thread en arrière-plan. Par conséquent, bien que ProtoLog côté client offre des avantages d'utilisation de la mémoire comparables à ceux de ProtoLog sur les services système, il entraîne une surcharge de performances légèrement plus élevée et ne bénéficie pas de l'avantage de réduction de la mémoire de la mémoire épinglée de son homologue côté serveur.

Groupes ProtoLog

Les messages ProtoLog sont organisés en groupes appelés ProtoLogGroups, comme les messages Logcat sont organisés par TAG. Ces ProtoLogGroups servent de groupes de messages pouvant être activés ou désactivés collectivement au moment de l'exécution. De plus, ils contrôlent si les messages doivent être supprimés lors de la compilation et où ils doivent être consignés (proto, logcat ou les deux). Chaque ProtoLogGroup comprend les propriétés suivantes:

  • enabled: lorsque la valeur est false, les messages de ce groupe sont exclus lors de la compilation et ne sont pas disponibles au moment de l'exécution.
  • logToProto: indique si ce groupe journalise au format binaire.
  • logToLogcat: indique si ce groupe journalise dans Logcat.
  • tag: nom de la source du message journalisé.

Une instance ProtoLogGroup doit être configurée pour chaque processus utilisant ProtoLog.

Types d'arguments pris en charge

En interne, les chaînes de format ProtoLog utilisent android.text.TextUtils#formatSimple(String, Object...). La syntaxe est donc la même.

ProtoLog accepte les types d'arguments suivants:

  • %b : booléen
  • %d, %x : type entier (court, entier ou long)
  • %f : type à virgule flottante (float ou double)
  • %s : chaîne
  • %% : caractère littéral en pourcentage

Les modificateurs de largeur et de précision tels que %04d et %10b sont acceptés, mais argument_index et flags ne le sont pas.

Utiliser ProtoLog dans un nouveau service

Pour utiliser ProtoLog dans un nouveau processus:

  1. Créez une définition ProtoLogGroup pour ce service.

  2. Initialisez la définition avant sa première utilisation (par exemple, lors de la création du processus):

    Protolog.init(ProtologGroup.values());

  3. Utilisez Protolog de la même manière que android.util.Log:

    ProtoLog.v(WM_SHELL_STARTING_WINDOW, "create taskSnapshot surface for task: %d", taskId);

Activer l'optimisation au moment de la compilation

Pour activer ProtoLog au moment de la compilation dans un processus, vous devez modifier ses règles de compilation et appeler le binaire protologtool.

ProtoLogTool est un binaire de transformation de code qui effectue l'internement de chaînes et met à jour l'appel de ProtoLog. Ce binaire transforme chaque appel de journalisation ProtoLog, comme illustré dans cet exemple:

ProtoLog.x(ProtoLogGroup.GROUP_NAME, "Format string %d %s", value1, value2);

dans:

if (ProtoLogImpl.isEnabled(GROUP_NAME)) {
    int protoLogParam0 = value1;
    String protoLogParam1 = String.valueOf(value2);
    ProtoLogImpl.x(ProtoLogGroup.GROUP_NAME, 1234560b0100, protoLogParam0, protoLogParam1);
}

Dans cet exemple, ProtoLog, ProtoLogImpl et ProtoLogGroup sont les classes fournies en tant qu'arguments (elles peuvent être importées, importées de manière statique ou en tant que chemin d'accès complet. Les importations avec caractères génériques ne sont pas autorisées) et x est la méthode de journalisation.

La transformation est effectuée au niveau de la source. Un hachage est généré à partir de la chaîne de format, du niveau de journalisation et du nom du groupe de journaux, puis inséré après l'argument ProtoLogGroup. Le code généré réel est intégré et un certain nombre de nouveaux caractères de ligne sont ajoutés afin de préserver la numérotation des lignes dans le fichier.

Exemple :

genrule {
    name: "wm_shell_protolog_src",
    srcs: [
        ":protolog-impl", // protolog lib
        ":wm_shell_protolog-groups", // protolog groups declaration
        ":wm_shell-sources", // source code
    ],
    tools: ["protologtool"],
    cmd: "$(location protologtool) transform-protolog-calls " +
        "--protolog-class com.android.internal.protolog.ProtoLog " +
        "--loggroups-class com.android.wm.shell.protolog.ShellProtoLogGroup " +
        "--loggroups-jar $(location :wm_shell_protolog-groups) " +
        "--viewer-config-file-path /system_ext/etc/wmshell.protolog.pb " +
        "--legacy-viewer-config-file-path /system_ext/etc/wmshell.protolog.json.gz " +
        "--legacy-output-file-path /data/misc/wmtrace/shell_log.winscope " +
        "--output-srcjar $(out) " +
        "$(locations :wm_shell-sources)",
    out: ["wm_shell_protolog.srcjar"],
}

Options de ligne de commande

L'un des principaux avantages de ProtoLog est que vous pouvez l'activer ou le désactiver au moment de l'exécution. Par exemple, vous pouvez activer une journalisation plus détaillée dans un build, désactivée par défaut, et l'activer pendant le développement local pour déboguer un problème spécifique. Ce modèle est utilisé, par exemple, dans WindowManager avec les groupes WM_DEBUG_WINDOW_TRANSITIONS et WM_DEBUG_WINDOW_TRANSITIONS_MIN qui permettent différents types de journalisation des transitions, le premier étant activé par défaut.

Vous pouvez configurer ProtoLog à l'aide de Perfetto lorsque vous démarrez une trace. Vous pouvez également configurer ProtoLog localement à l'aide de la ligne de commande adb.

La commande adb shell cmd protolog_configuration accepte les arguments suivants:

help
  Print this help text.

groups (list | status)
  list - lists all ProtoLog groups registered with ProtoLog service"
  status <group> - print the status of a ProtoLog group"

logcat (enable | disable) <group>"
  enable or disable ProtoLog to logcat

Conseils pour une utilisation efficace

ProtoLog utilise l'internement de chaîne à la fois pour le message et pour tous les arguments de chaîne transmis. Cela signifie que, pour tirer le meilleur parti de ProtoLog, les messages doivent isoler les valeurs répétées dans des variables.

Prenons l'exemple de l'instruction suivante:

Protolog.v(MY_GROUP, "%s", "The argument value is " + argument);

Lorsqu'il est optimisé au moment de la compilation, il se traduit comme suit:

ProtologImpl.v(MY_GROUP, 0x123, "The argument value is " + argument);

Si ProtoLog est utilisé dans le code avec les arguments A,B,C:

Protolog.v(MY_GROUP, "%s", "The argument value is A");
Protolog.v(MY_GROUP, "%s", "The argument value is B");
Protolog.v(MY_GROUP, "%s", "The argument value is C");
Protolog.v(MY_GROUP, "%s", "The argument value is A");

Les messages suivants s'affichent en mémoire:

Dict:
  0x123: "%s"
  0x111: "The argument value is A"
  0x222: "The argument value is B"
  0x333: "The argument value is C"

Message1 (Hash: 0x123, Arg1: 0x111)
Message2 (Hash: 0x123, Arg2: 0x222)
Message3 (Hash: 0x123, Arg3: 0x333)
Message4 (Hash: 0x123, Arg1: 0x111)

Si l'instruction ProtoLog était écrite comme suit:

Protolog.v(MY_GROUP, "The argument value is %s", argument);

Le tampon en mémoire se présente comme suit:

Dict:
  0x123: "The argument value is %s" (24 b)
  0x111: "A" (1 b)
  0x222: "B" (1 b)
  0x333: "C" (1 b)

Message1 (Hash: 0x123, Arg1: 0x111)
Message2 (Hash: 0x123, Arg2: 0x222)
Message3 (Hash: 0x123, Arg3: 0x333)
Message4 (Hash: 0x123, Arg1: 0x111)

Cette séquence réduit l'espace mémoire utilisé de 35 %.

Lecteur Winscope

L'onglet du lecteur ProtoLog de Winscope affiche les traces ProtoLog organisées sous forme de tableau. Vous pouvez filtrer les traces par niveau de journal, balise, fichier source (où l'instruction ProtoLog est présente) et contenu du message. Toutes les colonnes sont filtrables. Cliquez sur l'horodatage de la première colonne pour faire avancer la chronologie jusqu'à l'horodatage du message. En outre, si vous cliquez sur Accéder à l'heure actuelle, la table ProtoLog revient au code temporel sélectionné dans la timeline:

Visionneuse ProtoLog

Figure 1 : Visionneuse ProtoLog