ProtoLog

Il sistema di logging di Android mira all'accessibilità universale e alla facilità d'uso, presupponendo che tutti i dati di log possano essere rappresentati come una sequenza di caratteri. Questo presupposto è in linea con la maggior parte dei casi d'uso, in particolare quando la leggibilità dei log è fondamentale senza strumenti specializzati. Tuttavia, negli ambienti che richiedono prestazioni di logging elevate e dimensioni dei log limitate, il logging basato su testo potrebbe non essere ottimale. Uno di questi scenari è WindowManager, che richiede un sistema di logging solido in grado di gestire i log di transizione delle finestre in tempo reale con un impatto minimo sul sistema.

ProtoLog è l'alternativa per soddisfare le esigenze di logging di WindowManager e servizi simili. I principali vantaggi di ProtoLog rispetto a logcat sono:

  • La quantità di risorse utilizzate per la registrazione è inferiore.
  • Dal punto di vista dello sviluppatore, è come utilizzare il framework di logging Android predefinito.
  • Supporta l'attivazione o la disattivazione delle istruzioni di log in fase di runtime.
  • Può comunque accedere a logcat, se necessario.

Per ottimizzare l'utilizzo della memoria, ProtoLog utilizza un meccanismo di internamento delle stringhe che comporta il calcolo e il salvataggio di un hash compilato del messaggio. Per migliorare le prestazioni, ProtoLog esegue l'interning delle stringhe durante la compilazione (per i servizi di sistema), registrando solo l'identificatore e gli argomenti del messaggio in fase di runtime. Inoltre, quando viene generata una traccia ProtoLog o viene ottenuto un report bug, ProtoLog incorpora automaticamente il dizionario dei messaggi creato in fase di compilazione, consentendo la decodifica dei messaggi da qualsiasi build.

Utilizzando ProtoLog, il messaggio viene archiviato in un formato binario (proto) all'interno di una traccia Perfetto. La decodifica del messaggio avviene all'interno di trace_processor di Perfetto. Il processo consiste nella decodifica dei messaggi proto binari, nella traduzione degli identificatori dei messaggi in stringhe utilizzando il dizionario dei messaggi incorporato e nella formattazione della stringa utilizzando argomenti dinamici.

ProtoLog supporta gli stessi livelli di log di android.utils.Log, ovvero: d, v, i, w, e, wtf.

ProtoLog lato client

Inizialmente, ProtoLog era destinato esclusivamente al lato server di WindowManager, operando all'interno di un singolo processo e componente. Successivamente, è stato esteso per includere il codice della shell WindowManager nel processo dell'interfaccia utente di sistema, ma l'utilizzo di ProtoLog richiedeva un codice di configurazione boilerplate complesso. Inoltre, la registrazione Proto era limitata ai processi del server di sistema e dell'interfaccia utente di sistema, il che rendeva laborioso l'incorporamento in altri processi e richiedeva una configurazione separata del buffer di memoria per ciascuno. Tuttavia, ProtoLog è ora disponibile per il codice lato client, eliminando la necessità di codice boilerplate aggiuntivo.

A differenza del codice dei servizi di sistema, il codice lato client in genere salta l'interning delle stringhe in fase di compilazione. Invece, l'interning delle stringhe viene eseguito dinamicamente in un thread in background. Di conseguenza, mentre ProtoLog lato client offre vantaggi di utilizzo della memoria comparabili a ProtoLog nei servizi di sistema, comporta un overhead delle prestazioni leggermente superiore e non presenta il vantaggio di riduzione della memoria della memoria bloccata della sua controparte lato server.

Gruppi ProtoLog

I messaggi ProtoLog sono organizzati in gruppi chiamati ProtoLogGroups, in modo simile a come i messaggi Logcat sono organizzati per TAG. Questi ProtoLogGroups fungono da cluster di messaggi che possono essere attivati o disattivati collettivamente in fase di runtime. Inoltre, controllano se i messaggi devono essere rimossi durante la compilazione e dove devono essere registrati (proto, logcat o entrambi). Ogni ProtoLogGroup comprende le seguenti proprietà:

  • enabled: se impostato su false, i messaggi in questo gruppo vengono esclusi durante la compilazione e non sono disponibili in fase di runtime.
  • logToProto: definisce se questo gruppo registra i log in formato binario.
  • logToLogcat: Definisce se questo gruppo registra i log in logcat.
  • tag: il nome dell'origine del messaggio registrato.

Ogni processo che utilizza ProtoLog deve avere un'istanza ProtoLogGroup configurata.

Tipi di argomenti supportati

Internamente, le stringhe di formato ProtoLog utilizzano android.text.TextUtils#formatSimple(String, Object...), quindi la sintassi è la stessa.

ProtoLog supporta i seguenti tipi di argomenti:

  • %b - booleano
  • %d, %x - tipo integrale (short, integer o long)
  • %f: tipo di virgola mobile (float o double)
  • %s - stringa
  • %%: un carattere percentuale letterale

Sono supportati i modificatori di larghezza e precisione come %04d e %10b, ma argument_index e flags non sono supportati.

Utilizzare ProtoLog in un nuovo servizio

Per utilizzare ProtoLog in un nuovo processo:

  1. Crea una definizione ProtoLogGroup per questo servizio.

  2. Inizializza la definizione prima del primo utilizzo (ad esempio, durante la creazione del processo):

    Protolog.init(ProtologGroup.values());

  3. Utilizza Protolog nello stesso modo di android.util.Log:

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

Attivare l'ottimizzazione in fase di compilazione

Per abilitare ProtoLog in fase di compilazione in un processo, devi modificare le regole di build e richiamare il binario protologtool.

ProtoLogTool è un file binario di trasformazione del codice che esegue l'interning delle stringhe e aggiorna la chiamata ProtoLog. Questo file binario trasforma ogni chiamata di logging ProtoLog come mostrato in questo esempio:

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

in:

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

In questo esempio, ProtoLog, ProtoLogImpl e ProtoLogGroup sono le classi fornite come argomenti (possono essere importate, importate staticamente o con percorso completo, le importazioni con caratteri jolly non sono consentite) e x è il metodo di logging.

La trasformazione viene eseguita a livello di origine. Un hash viene generato dalla stringa di formato, dal livello di log e dal nome del gruppo di log e viene inserito dopo l'argomento ProtoLogGroup. Il codice reale generato è incorporato e viene aggiunto un numero di nuovi caratteri di fine riga per preservare la numerazione delle righe nel file.

Esempio:

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"],
}

Opzioni della riga di comando

Uno dei principali vantaggi di ProtoLog è che puoi attivarlo o disattivarlo in fase di runtime. Ad esempio, puoi avere un logging più dettagliato in una build, disattivato per impostazione predefinita e attivato durante lo sviluppo locale, per eseguire il debug di un problema specifico. Questo pattern viene utilizzato, ad esempio, in WindowManager con i gruppi WM_DEBUG_WINDOW_TRANSITIONS e WM_DEBUG_WINDOW_TRANSITIONS_MIN che consentono diversi tipi di logging delle transizioni, con il primo abilitato per impostazione predefinita.

Puoi configurare ProtoLog utilizzando Perfetto quando avvii una traccia. Puoi anche configurare ProtoLog localmente utilizzando la riga di comando adb.

Il comando adb shell cmd protolog_configuration supporta i seguenti argomenti:

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

Suggerimenti per un utilizzo efficace

ProtoLog utilizza l'interning delle stringhe sia per il messaggio che per gli argomenti stringa trasmessi. Ciò significa che, per ottenere maggiori vantaggi da ProtoLog, i messaggi devono isolare i valori ripetuti in variabili.

Ad esempio, considera la seguente affermazione:

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

Quando viene ottimizzato in fase di compilazione, si traduce in questo:

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

Se ProtoLog viene utilizzato nel codice con gli argomenti 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");

Vengono visualizzati i seguenti messaggi in memoria:

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)

Se, invece, l'istruzione ProtoLog è stata scritta come segue:

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

Il buffer in memoria risulterebbe:

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)

Questa sequenza comporta un utilizzo di memoria inferiore del 35%.

Visualizzatore Winscope

La scheda del visualizzatore ProtoLog di Winscope mostra le tracce ProtoLog organizzate in formato tabellare. Puoi filtrare le tracce in base al livello di log, al tag, al file di origine (in cui è presente l'istruzione ProtoLog) e ai contenuti del messaggio. Tutte le colonne sono filtrabili. Se fai clic sul timestamp nella prima colonna, la sequenza temporale viene spostata sul timestamp del messaggio. Inoltre, se fai clic su Vai all'ora corrente, la tabella ProtoLog torna al timestamp selezionato nella sequenza temporale:

Visualizzatore ProtoLog

Figura 1. Visualizzatore ProtoLog