Le système de journalisation Android vise l'accessibilité universelle et la 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 peut ne pas être optimale. WindowManager est un exemple de ce type de scénario. Il nécessite un système de journalisation robuste capable de gérer 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. 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 d'un développeur, cela revient à 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înes qui consiste à calculer et à enregistrer un hachage compilé du message. Pour améliorer les performances, ProtoLog effectue l'interning de chaînes lors de la compilation (pour les services système), en n'enregistrant que l'identifiant et les arguments du message lors 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 quelle version.
Avec ProtoLog, le message est stocké au format binaire (proto) dans une trace Perfetto. Le décodage des messages s'effectue 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 du WindowManager, fonctionnant dans un seul processus et composant. Il a ensuite été étendu pour englober le code shell WindowManager dans le processus d'interface utilisateur du système, mais l'utilisation de ProtoLog nécessitait un code de configuration de boilerplate complexe. De plus, la journalisation Proto était limitée aux processus system_server et System UI, ce qui rendait son intégration dans d'autres processus laborieuse et nécessitait une configuration de mémoire tampon distincte pour chacun d'eux. Toutefois, 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'internement 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 comparables en termes d'utilisation de la mémoire à 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 de son homologue côté serveur.
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 qui peuvent être activés ou désactivés collectivement au moment de l'exécution.
Ils contrôlent également 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 définie surfalse
, 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 utilisant ProtoLog doit avoir une instance ProtoLogGroup
configurée.
Types d'arguments pris en charge
En interne, les chaînes de format ProtoLog utilisent android.text.TextUtils#formatSimple(String, Object...)
. Leur 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, mais argument_index
et flags
ne le sont pas.
Utiliser ProtoLog dans un nouveau service
Pour utiliser ProtoLog dans un nouveau processus :
Créez une définition
ProtoLogGroup
pour ce service.Initialisez la définition avant sa première utilisation (par exemple, lors de la création du processus) :
Protolog.init(ProtologGroup.values());
Utilisez
Protolog
de la même manière queandroid.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, mais les importations avec caractère générique 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 journal 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 build, 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
, ce qui permet 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 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 :
Figure 1 : Visionneuse ProtoLog