ProtoLog.

Le système de journalisation Android vise à atteindre une accessibilité universelle et une facilité d'utilisation, 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 essentielle sans outils spécialisés. Toutefois, dans les environnements exigeant des performances de journalisation élevées et des tailles de journaux limitées, la journalisation basée sur du texte n'est pas optimale. WindowManager, par exemple, nécessite un système de journalisation robuste qui gère les journaux de transition de fenêtres en temps réel avec un impact minimal sur le système.

ProtoLog est l'alternative pour répondre aux besoins de journalisation de WindowManager et des services similaires. ProtoLog offre les avantages suivants par rapport à logcat :

  • Utilise moins de ressources pour la journalisation.
  • Du point de vue d'un développeur, il fonctionne de la même manière que le framework de journalisation Android par défaut.
  • Vous permet d'activer ou de désactiver les instructions de journalisation au moment de l'exécution.
  • Peut également consigner dans logcat.

Pour optimiser l'utilisation de la mémoire, ProtoLog utilise un mécanisme d'internement de chaînes. Ce mécanisme calcule et enregistre 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. Il n'enregistre que l'identifiant et les arguments du message lors de l'exécution. Lorsque vous générez une trace ProtoLog ou obtenez un rapport de bug, ProtoLog intègre automatiquement le dictionnaire de messages créé au moment de la compilation. Cela permet le décodage des messages à partir de n'importe quelle version.

ProtoLog stocke les messages au format binaire (proto) dans une trace Perfetto. Le décodage des messages a lieu dans trace_processor de Perfetto. Le processus décode les messages proto binaires, traduit les identifiants de message en chaînes à l'aide du dictionnaire de messages intégré et met en forme la chaîne à l'aide d'arguments dynamiques.

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

ProtoLog côté client

Initialement, ProtoLog n'était destiné qu'au côté serveur de WindowManager, fonctionnant dans un seul processus et composant. Il a ensuite été étendu pour englober le code shell WindowManager dans le processus d'UI système. Toutefois, l'utilisation de ProtoLog nécessitait un code de configuration récurrent complexe. De plus, la journalisation Proto était limitée aux processus du serveur système et de l'interface utilisateur système. Il était donc difficile de l'intégrer à d'autres processus et il fallait configurer un tampon de mémoire distinct pour chacun d'eux. ProtoLog est désormais disponible pour le code côté client, ce qui élimine le besoin de code passe-partout supplémentaire.

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

Groupes ProtoLog

Les messages ProtoLog sont organisés en groupes appelés ProtoLogGroups, de la même manière que les messages Logcat sont organisés par TAG. Ces ProtoLogGroups servent de clusters de messages que vous pouvez activer ou désactiver au moment de l'exécution. Ils contrôlent également si les messages sont supprimés lors de la compilation et où ils sont consignés (proto, logcat ou les deux). Chaque ProtoLogGroup inclut les propriétés suivantes :

  • enabled : lorsque la valeur est définie sur false, les messages de ce groupe sont exclus lors de la compilation et ne sont pas disponibles lors de l'exécution.
  • logToProto : définit si ce groupe enregistre les journaux au format binaire.
  • logToLogcat : définit si ce groupe enregistre les journaux dans Logcat.
  • tag : nom de la source du message consigné.

Chaque processus qui utilise ProtoLog doit avoir une instance ProtoLogGroup configurée.

Types d'arguments pris en charge

ProtoLog met en forme les chaînes en interne à l'aide de android.text.TextUtils#formatSimple(String, Object...). Sa syntaxe est donc identique.

ProtoLog est compatible avec les types d'arguments suivants :

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

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

Utiliser ProtoLog dans un nouveau service

Pour utiliser ProtoLog dans un nouveau service, procédez comme suit :

  1. Créez une définition ProtoLogGroup pour ce service.
  2. Initialisez la définition avant sa première utilisation. Par exemple, initialisez-le 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 ProtoLog. Ce binaire transforme chaque appel de journalisation ProtoLog, comme le montre cet exemple :

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

into:

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 avec un chemin d'accès complet, les importations 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 réel généré est intégré et un certain nombre de caractères de nouvelle ligne sont ajoutés pour 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 avoir une journalisation plus détaillée dans une compilation, désactivée par défaut, et l'activer lors du 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 journaux de transition, 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 en local à 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'interning de chaînes pour le message et tous les arguments de chaîne transmis. Cela signifie que, pour tirer davantage 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");

Cela entraîne les messages suivants 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, au lieu de cela, l'instruction ProtoLog était écrite comme suit :

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

La mémoire tampon en mémoire se présenterait 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 permet de réduire l'empreinte mémoire de 35 %.

Lecteur Winscope

L'onglet "ProtoLog viewer" de Winscope affiche les traces ProtoLog organisées sous forme de tableau. Vous pouvez filtrer les traces par niveau de journal, tag, fichier source (où se trouve l'instruction ProtoLog) et contenu du message. Toutes les colonnes sont filtrables. Si vous cliquez sur le code temporel de la première colonne, la chronologie est redirigée vers le code temporel du message. De plus, 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