Protolog

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

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

  • Usa menos recursos para registro.
  • Do ponto de vista do desenvolvedor, ele funciona da mesma forma que o framework de registro padrão do Android.
  • Permite ativar ou desativar instruções de registro no ambiente de execução.
  • Também pode registrar no logcat.

Para otimizar o uso da memória, o ProtoLog usa um mecanismo de interning de string. Esse mecanismo calcula e salva um hash compilado da mensagem. Para melhorar o desempenho, o ProtoLog realiza o interning de string durante a compilação de serviços do sistema. Ele registra apenas o identificador da mensagem e os argumentos no ambiente de execução. Quando você gera um rastreamento do ProtoLog ou recebe um relatório de bugs, o ProtoLog incorpora automaticamente o dicionário de mensagens criado no tempo de compilação. Isso permite a decodificação de mensagens de qualquer build.

O ProtoLog armazena mensagens em um formato binário (proto) em um rastreamento do Perfetto. A decodificação de mensagens ocorre no trace_processor do Perfetto. O processo decodifica as mensagens proto binárias, traduz os identificadores de mensagens em strings usando o dicionário de mensagens incorporado e formata a string usando argumentos dinâmicos.

O ProtoLog oferece suporte aos mesmos níveis de registro que android.utils.Log, que são 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. Mais tarde, ele foi expandido para abranger o código do shell do WindowManager no processo da interface do sistema. No entanto, o uso do ProtoLog exigia um código de configuração repetitivo complexo. Além disso, o registro do Proto era restrito ao servidor do sistema e aos processos da interface do sistema. Isso dificultou a incorporação em outros processos e exigiu uma configuração de buffer de memória separada para cada um. 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 interning de string no momento da compilação. Em vez disso, o interning de string ocorre dinamicamente em uma linha de execução em segundo plano. Como resultado, embora o ProtoLog do lado do cliente ofereça vantagens de uso de memória comparáveis ao ProtoLog em serviços do sistema, ele incorre em uma sobrecarga de desempenho ligeiramente maior e não tem a vantagem de redução de memória da memória fixada que a contraparte do lado do servidor oferece.

Grupos do ProtoLog

As mensagens do ProtoLog são organizadas em grupos chamados ProtoLogGroups, de maneira semelhante a como Logcat mensagens são organizadas por TAG. Esses ProtoLogGroups servem como clusters de mensagens que podem ser ativados ou desativados no ambiente de execução. Eles também controlam se as mensagens são removidas durante a compilação e onde são registradas (proto, logcat ou ambos). Cada ProtoLogGroup inclui as seguintes propriedades:

  • enabled: quando definido como false, as mensagens nesse grupo são excluídas durante a compilação e não estão disponíveis no ambiente de execução.
  • logToProto: define se esse grupo registra com formato binário.
  • logToLogcat: define se esse grupo registra 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

O ProtoLog formata strings internamente usando android.text.TextUtils#formatSimple(String, Object...), então a sintaxe é a mesma.

O ProtoLog oferece suporte aos 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 percentual literal

Modificadores de largura e precisão, como %04d e %10b, são compatíveis. No entanto, argument_index e flags não são compatíveis.

Usar o ProtoLog em um novo serviço

Para usar o ProtoLog em um novo serviço, siga estas etapas:

  1. Crie uma definição ProtoLogGroup para esse serviço.
  2. Inicialize a definição antes do primeiro uso. Por exemplo, inicialize-a durante a 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 momento da compilação em um processo, é necessário mudar as regras de build e invocar o binário protologtool.

ProtoLogTool é um binário de transformação de código que realiza o interning de string e atualiza a invocação do ProtoLog. Esse binário transforma todas as chamadas de registro 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 registro.

A transformação é feita no nível da origem. Um hash é gerado na string de formato, no nível de registro e no nome do grupo de registro, e inserido após o argumento ProtoLogGroup. O código gerado real é in-line e um número de novos caracteres de linha é adicionado 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 no ambiente de execução. Por exemplo, é possível ter um registro mais detalhado em um build, desativado por padrão, e ativá-lo 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 ativando diferentes tipos de registro de transição, com 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 adb linha de comando.

O comando adb shell cmd protolog_configuration oferece suporte aos 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 interning de string para a mensagem e para todos os argumentos de string transmitidos. Isso significa que, para aproveitar melhor 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 tempo de compilação, ele é traduzido para o seguinte:

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

Se o ProtoLog for usado no código com 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 do ProtoLog fosse escrita como:

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

O buffer na memória terminaria como:

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 um consumo de memória 35% menor.

Visualizador do Winscope

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

Visualizador de ProtoLog

Figura 1. Visualizador do ProtoLog