Protolog

El sistema de registro de Android tiene como objetivo la accesibilidad universal y la facilidad de uso, y supone 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 podría no ser óptimo. Uno de estos casos es WindowManager, que requiere un sistema de registro sólido capaz de controlar 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. Los principales beneficios de ProtoLog en comparación con logcat son los siguientes:

  • La cantidad de recursos que se usan para el registro es menor.
  • Desde el punto de vista del desarrollador, es lo mismo que usar el framework de registro de Android predeterminado.
  • Admite que las instrucciones de registro se habiliten o inhabiliten durante el tiempo de ejecución.
  • Aún se puede registrar en logcat si es necesario.

Para optimizar el uso de memoria, ProtoLog emplea un mecanismo de internación de cadenas que implica calcular y guardar un hash compilado del mensaje. Para mejorar el rendimiento, ProtoLog realiza la internación de cadenas durante la compilación (para los servicios del sistema), y solo registra el identificador y los argumentos del mensaje en el tiempo de ejecución. Además, cuando se genera un registro de ProtoLog o se obtiene un informe de errores, ProtoLog incorpora automáticamente el diccionario de mensajes creado en el momento de la compilación, lo que permite decodificar mensajes desde cualquier compilación.

Con ProtoLog, el mensaje se almacena en formato binario (proto) dentro de un registro de Perfetto. La decodificación de mensajes se realiza dentro de trace_processor de Perfetto. El proceso consiste en decodificar los mensajes .proto binarios, traducir los identificadores de mensajes a cadenas con el diccionario de mensajes incorporado y dar formato a la cadena con argumentos dinámicos.

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

ProtoLog del cliente

En un principio, ProtoLog se diseñó solo para el servidor de WindowManager, que opera dentro de un solo proceso y componente. Posteriormente, se expandió para abarcar el código de shell de WindowManager en el proceso de IU del sistema, pero el uso de ProtoLog requería un código de configuración de boilerplate complejo. Además, el registro de Proto se restringió a los procesos del servidor del sistema y de la IU del sistema, lo que dificultó su incorporación a otros procesos y requirió una configuración de búfer de memoria independiente para cada uno. Sin embargo, 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 el almacenamiento interno 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 comparables en el uso de la memoria a ProtoLog en los servicios del sistema, genera una sobrecarga de rendimiento marginalmente mayor y carece de la ventaja de reducción de memoria de la memoria anclada de 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 se pueden habilitar o inhabilitar de forma colectiva durante el tiempo de ejecución. Además, controlan si los mensajes se deben quitar durante la compilación y dónde se deben registrar (proto, logcat o ambos). Cada ProtoLogGroup comprende 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 use ProtoLog debe tener configurada una instancia de ProtoLogGroup.

Tipos de argumento compatibles

Internamente, las cadenas de formato de ProtoLog usan 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 los modificadores de ancho y precisión, como %04d y %10b, pero no argument_index ni flags.

Cómo usar ProtoLog en un servicio nuevo

Para usar ProtoLog en un proceso nuevo, haz lo siguiente:

  1. Crea una definición de ProtoLogGroup para este servicio.

  2. Inicializa la definición antes de su primer uso (por ejemplo, en la creación del proceso):

    Protolog.init(ProtologGroup.values());

  3. Usa Protolog de la misma manera que 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 registros, y se inserta después del argumento ProtoLogGroup. El código generado real se inserta en línea y se agrega una cantidad de caracteres de salto de 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, que está 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. 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