Protolog

O sistema de registro do Android busca acessibilidade universal e facilidade de uso, presumindo que todos os dados de registro podem ser representados como uma sequência de caracteres. Essa proposição está alinhada à maioria dos casos de uso, principalmente quando a legibilidade do registro é crucial sem ferramentas especializadas. No entanto, em ambientes que exigem alta performance de geração de registros e tamanhos restritos, o registro baseado em texto pode não ser ideal. Um desses cenários é o WindowManager, que exige um sistema de geração de registros robusto capaz de processar registros de transição de janela em tempo real com impacto mínimo no sistema.

O ProtoLog é a alternativa para atender às necessidades de geração de registros do WindowManager e de serviços semelhantes. Os principais benefícios do ProtoLog em relação ao logcat são:

  • A quantidade de recursos usados para geração de registros é menor.
  • Do ponto de vista do desenvolvedor, é o mesmo que usar a estrutura de registro em log padrão do Android.
  • Permite que as instruções de registro sejam ativadas ou desativadas no ambiente de execução.
  • Ainda é possível fazer login no logcat, se necessário.

Para otimizar o uso da memória, o ProtoLog emprega um mecanismo de internamento de strings que envolve o cálculo e o salvamento de um hash compilado da mensagem. Para melhorar o desempenho, o ProtoLog realiza o internamento de strings durante a compilação (para serviços do sistema), registrando apenas o identificador da mensagem e os argumentos em tempo de execução. Além disso, ao gerar um rastreamento do ProtoLog ou receber um relatório de bug, o ProtoLog incorpora automaticamente o dicionário de mensagens criado no momento da compilação, permitindo a decodificação de mensagens de qualquer build.

Com o ProtoLog, a mensagem é armazenada em um formato binário (proto) em um rastreamento do Perfetto. A decodificação da mensagem acontece dentro do trace_processor do Perfetto. O processo consiste em decodificar as mensagens proto binárias, traduzir os identificadores de mensagens em strings usando o dicionário de mensagens incorporado e formatar a string usando argumentos dinâmicos.

O ProtoLog é compatível com os mesmos níveis de registro do android.utils.Log, ou seja: d, v, i, w, e e wtf.

ProtoLog do lado do cliente

Inicialmente, o ProtoLog era destinado apenas ao lado do servidor do WindowManager, operando em um único processo e componente. Posteriormente, ele foi expandido para abranger o código shell do WindowManager no processo da interface do sistema, mas o uso do ProtoLog exigia um código de configuração boilerplate complexo. Além disso, o registro em Proto foi restringido ao servidor do sistema e aos processos da interface do sistema, o que torna trabalhoso incorporar em outros processos e exige uma configuração separada de buffer de memória para cada um. No entanto, o ProtoLog agora está disponível para código do lado do cliente, eliminando a necessidade de código boilerplate adicional.

Ao contrário do código de serviços do sistema, o código do lado do cliente geralmente ignora o internamento de strings no tempo de compilação. Em vez disso, o internamento de strings ocorre dinamicamente em uma linha de execução em segundo plano. Como resultado, embora o ProtoLog do lado do cliente ofereça vantagens comparáveis de uso de memória ao ProtoLog em serviços do sistema, ele incorre em uma sobrecarga de desempenho marginalmente maior e não tem a vantagem de redução de memória da memória fixada da contraparte do lado do servidor.

Grupos ProtoLog

As mensagens do ProtoLog são organizadas em grupos chamados ProtoLogGroups, semelhante a como as mensagens do Logcat são organizadas pelo TAG. Esses ProtoLogGroups servem como clusters de mensagens que podem ser ativadas ou desativadas coletivamente durante a execução. Além disso, eles controlam se as mensagens precisam ser removidas durante a compilação e onde elas precisam ser registradas (proto, logcat ou ambos). Cada ProtoLogGroup tem as seguintes propriedades:

  • enabled: quando definido como false, as mensagens nesse grupo são excluídas durante a compilação e não ficam disponíveis no tempo de execução.
  • logToProto: define se este grupo gera registros com formato binário.
  • logToLogcat: define se este grupo faz login no Logcat.
  • tag: nome da origem da mensagem registrada.

Cada processo que usa o ProtoLog precisa ter uma instância ProtoLogGroup configurada.

Tipos de argumentos compatíveis

Internamente, as strings de formato do ProtoLog usam android.text.TextUtils#formatSimple(String, Object...), portanto, a sintaxe é a mesma.

O ProtoLog é compatível com os seguintes tipos de argumento:

  • %b: booleano
  • %d, %x: tipo integral (curto, inteiro ou longo)
  • %f: tipo de ponto flutuante (float ou double)
  • %s: string
  • %%: um caractere de porcentagem literal

Os modificadores de largura e precisão, como %04d e %10b, são compatíveis, mas argument_index e flags não são.

Usar o ProtoLog em um novo serviço

Para usar o ProtoLog em um novo processo:

  1. Crie uma definição de ProtoLogGroup para esse serviço.

  2. Inicialize a definição antes do primeiro uso (por exemplo, na criação do processo):

    Protolog.init(ProtologGroup.values());

  3. Use Protolog da mesma forma que android.util.Log:

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

Ativar a otimização no momento da compilação

Para ativar o ProtoLog no tempo de compilação em um processo, mude as regras de build e invoque o binário protologtool.

ProtoLogTool é um binário de transformação de código que realiza o internamento de strings e atualiza a invocação do ProtoLog. Esse binário transforma cada chamada de registro de ProtoLog, conforme mostrado neste exemplo:

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

em:

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

Neste exemplo, ProtoLog, ProtoLogImpl e ProtoLogGroup são as classes fornecidas como argumentos (podem ser importadas, importadas estaticamente ou caminho completo. Importações curinga não são permitidas) e x é o método de geração de registros.

A transformação é feita no nível da origem. Um hash é gerado com base na string de formato, no nível e no nome do grupo de registros e inserido após o argumento ProtoLogGroup. O código gerado real é inserido em linha, e vários novos caracteres de nova linha são adicionados para preservar a numeração de linhas no arquivo.

Exemplo:

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

Opções de linha de comando

Uma das principais vantagens do ProtoLog é que ele pode ser ativado ou desativado em tempo de execução. Por exemplo, é possível ter um registro mais detalhado em um build, desativado por padrão e ativado durante o desenvolvimento local, para depurar um problema específico. Esse padrão é usado, por exemplo, no WindowManager com os grupos WM_DEBUG_WINDOW_TRANSITIONS e WM_DEBUG_WINDOW_TRANSITIONS_MIN que permitem diferentes tipos de registro de transição, sendo o primeiro ativado por padrão.

É possível configurar o ProtoLog usando o Perfetto ao iniciar um rastreamento. Também é possível configurar o ProtoLog localmente usando a linha de comando adb.

O comando adb shell cmd protolog_configuration é compatível com os seguintes 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

Dicas para uso eficaz

O ProtoLog usa o internamento de strings para a mensagem e todos os argumentos de string transmitidos. Isso significa que, para aproveitar mais o ProtoLog, as mensagens precisam isolar valores repetidos em variáveis.

Por exemplo, considere a seguinte instrução:

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

Quando otimizado no momento da compilação, ele é traduzido para isto:

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

Se o ProtoLog for usado no código com os argumentos 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");

Isso resulta nas seguintes mensagens na memória:

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, em vez disso, a instrução ProtoLog fosse escrita como:

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

O buffer na memória ficaria assim:

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)

Essa sequência resulta em uma pegada de memória 35% menor.

Leitor do Winscope

A guia do visualizador ProtoLog do Winscope mostra rastreamentos do ProtoLog organizados em um formato tabular. É possível filtrar rastreamentos por nível de registro, tag, arquivo de origem (onde a instrução ProtoLog está presente) e conteúdo da mensagem. Todas as colunas podem ser filtradas. Ao clicar no carimbo de data/hora na primeira coluna, a linha do tempo é transportada para o carimbo de data/hora da mensagem. Além disso, clicar em Ir para o horário atual rola a tabela ProtoLog de volta para o carimbo de data/hora selecionado na linha do tempo:

Visualizador de ProtoLog

Figura 1. Visualizador de ProtoLog