Protolog

El sistema de registro de Android tiene como objetivo lograr la accesibilidad universal y la facilidad de uso, suponiendo que todos los datos de registro se pueden representar como una secuencia de caracteres. Esta suposición se alinea con la mayoría de los casos de uso, en especial cuando la legibilidad de los registros es fundamental sin herramientas especializadas. Sin embargo, en entornos que exigen un alto rendimiento de registro y tamaños de registro restringidos, el registro basado en texto no es óptimo. Uno de estos casos es WindowManager, que requiere un sistema de registro sólido que controle los registros de transición de ventanas en tiempo real con un impacto mínimo en el sistema.

ProtoLog es la alternativa para abordar las necesidades de registro de WindowManager y servicios similares. ProtoLog ofrece los siguientes beneficios en comparación con logcat:

  • Usa menos recursos para el registro.
  • Desde la perspectiva del desarrollador, funciona igual que el framework de registro predeterminado de Android.
  • Te permite habilitar o inhabilitar instrucciones de registro durante el tiempo de ejecución.
  • También se puede registrar en logcat.

Para optimizar el uso de memoria, ProtoLog usa un mecanismo de internación de cadenas. Este mecanismo calcula y guarda un hash compilado del mensaje. Para mejorar el rendimiento, ProtoLog realiza el internamiento de cadenas durante la compilación de los servicios del sistema. Solo registra el identificador y los argumentos del mensaje en el tiempo de ejecución. Cuando generas un registro de seguimiento de ProtoLog o obtienes un informe de errores, ProtoLog incorpora automáticamente el diccionario de mensajes creado en el momento de la compilación. Esto permite decodificar mensajes desde cualquier compilación.

ProtoLog almacena mensajes en formato binario (proto) dentro de un registro de Perfetto. La decodificación de mensajes se produce dentro de trace_processor de Perfetto. El proceso decodifica los mensajes .proto binarios, traduce los identificadores de mensajes a cadenas con el diccionario de mensajes incorporado y formatea la cadena con argumentos dinámicos.

ProtoLog admite los mismos niveles de registro que android.utils.Log, que son d, v, i, w, e y wtf.

ProtoLog del cliente

Inicialmente, ProtoLog solo se diseñó para el servidor de WindowManager, que opera dentro de un solo proceso y componente. Más adelante, se expandió para abarcar el código de shell de WindowManager en el proceso de IU del sistema. Sin embargo, usar ProtoLog requería un código de configuración estándar complejo. Además, el registro de Proto se restringió a los procesos del servidor del sistema y de la IU del sistema. Esto dificultaba la incorporación en otros procesos y requería una configuración de búfer de memoria independiente para cada uno. ProtoLog ahora está disponible para el código del cliente, lo que elimina la necesidad de código adicional repetitivo.

A diferencia del código de los servicios del sistema, el código del cliente suele omitir la internación de cadenas en tiempo de compilación. En cambio, la internación de cadenas se produce de forma dinámica en un subproceso en segundo plano. Como resultado, si bien ProtoLog del cliente proporciona ventajas de uso de memoria comparables a ProtoLog en los servicios del sistema, genera una sobrecarga de rendimiento ligeramente mayor y carece de la ventaja de reducción de memoria de la memoria anclada que ofrece su contraparte del servidor.

Grupos de ProtoLog

Los mensajes de ProtoLog se organizan en grupos llamados ProtoLogGroups, de manera similar a como los mensajes de Logcat se organizan por TAG. Estos ProtoLogGroups sirven como clústeres de mensajes que puedes habilitar o inhabilitar durante el tiempo de ejecución. También controlan si los mensajes se quitan durante la compilación y dónde se registran (proto, logcat o ambos). Cada ProtoLogGroup incluye las siguientes propiedades:

  • enabled: Cuando se configura como false, los mensajes de este grupo se excluyen durante la compilación y no están disponibles en el tiempo de ejecución.
  • logToProto: Define si este grupo registra con formato binario.
  • logToLogcat: Define si este grupo registra en logcat.
  • tag: Es el nombre de la fuente del mensaje registrado.

Cada proceso que usa ProtoLog debe tener configurada una instancia de ProtoLogGroup.

Tipos de argumento compatibles

Internamente, ProtoLog formatea cadenas con android.text.TextUtils#formatSimple(String, Object...), por lo que su sintaxis es la misma.

ProtoLog admite los siguientes tipos de argumentos:

  • %b: booleano
  • %d, %x: Tipo integral (short, integer o long)
  • %f: Tipo de punto flotante (float o double)
  • %s: Cadena
  • %%: Es un carácter de porcentaje literal.

Se admiten modificadores de ancho y precisión, como %04d y %10b. Sin embargo, argument_index y flags no son compatibles.

Cómo usar ProtoLog en un servicio nuevo

Para usar ProtoLog en un servicio nuevo, sigue estos pasos:

  1. Crea una definición de ProtoLogGroup para este servicio.
  2. Inicializa la definición antes de su primer uso. Por ejemplo, inicialízalo durante la creación del proceso:

    ProtoLog.init(ProtoLogGroup.values());
    
  3. Usa ProtoLog de la misma manera que usas android.util.Log:

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

Habilita la optimización en tiempo de compilación

Para habilitar ProtoLog en tiempo de compilación en un proceso, debes cambiar sus reglas de compilación y llamar al archivo binario protologtool.

ProtoLogTool es un objeto binario de transformación de código que realiza el almacenamiento interno de cadenas y actualiza la invocación de ProtoLog. Este objeto binario transforma cada llamada de registro de ProtoLog, como se muestra en este ejemplo:

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

en:

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

En este ejemplo, ProtoLog, ProtoLogImpl y ProtoLogGroup son las clases proporcionadas como argumentos (se pueden importar, importar de forma estática o usar la ruta completa; no se permiten las importaciones con comodines) y x es el método de registro.

La transformación se realiza a nivel de la fuente. Se genera un hash a partir de la cadena de formato, el nivel de registro y el nombre del grupo de registro, y se inserta después del argumento ProtoLogGroup. El código generado real se inserta y se agrega una cantidad de caracteres de nueva línea para conservar la numeración de líneas en el archivo.

Ejemplo:

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

Opciones de línea de comandos

Una de las principales ventajas de ProtoLog es que puedes habilitarlo o inhabilitarlo en el tiempo de ejecución. Por ejemplo, puedes tener un registro más detallado en una compilación, inhabilitado de forma predeterminada, y habilitarlo durante el desarrollo local para depurar un problema específico. Este patrón se usa, por ejemplo, en WindowManager con los grupos WM_DEBUG_WINDOW_TRANSITIONS y WM_DEBUG_WINDOW_TRANSITIONS_MIN que habilitan diferentes tipos de registro de transiciones, y el primero está habilitado de forma predeterminada.

Puedes configurar ProtoLog con Perfetto cuando inicies un registro. También puedes configurar ProtoLog de forma local con la línea de comandos adb.

El comando adb shell cmd protolog_configuration admite los siguientes argumentos:

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

Sugerencias para un uso eficaz

ProtoLog usa la internación de cadenas tanto para el mensaje como para los argumentos de cadena que se pasan. Esto significa que, para obtener más beneficios de ProtoLog, los mensajes deben aislar los valores repetidos en variables.

Por ejemplo, considera la siguiente declaración:

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

Cuando se optimiza en el tiempo de compilación, se traduce a lo siguiente:

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

Si se usa ProtoLog en el código con argumentos A,B,C, sucede lo siguiente:

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");

Esto genera los siguientes mensajes en la 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)

Si, en cambio, la instrucción de ProtoLog se hubiera escrito de la siguiente manera:

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

El búfer en la memoria terminaría de la siguiente manera:

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)

Esta secuencia genera un uso de memoria un 35% menor.

Visualizador de Winscope

La pestaña del visor de ProtoLog de Winscope muestra los registros de ProtoLog organizados en formato de tabla. Puedes filtrar los registros por nivel de registro, etiqueta, archivo fuente (en el que se encuentra la instrucción ProtoLog) y contenido del mensaje. Todas las columnas se pueden filtrar. Si haces clic en la marca de tiempo de la primera columna, el cronograma se desplazará hasta la marca de tiempo del mensaje. Además, si haces clic en Ir a la hora actual, la tabla de ProtoLog se desplazará hasta la marca de tiempo seleccionada en la línea de tiempo:

Visor de ProtoLog

Figura 1: Visor de ProtoLog