O sistema de geração de registros do Android busca alcançar 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 registro e tamanhos de registros restritos, o registro baseado em texto não é ideal. Um desses cenários é o WindowManager, que exige um sistema de geração de registros 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 geração de registros do WindowManager e de serviços semelhantes. O ProtoLog oferece os seguintes benefícios em relação ao logcat:
- Usa menos recursos para geração de registros.
- Do ponto de vista de um desenvolvedor, ele funciona da mesma forma que o framework de registro em log padrão do Android.
- Permite ativar ou desativar instruções de registro em tempo de execução.
- Também é possível fazer o registro no logcat.
Para otimizar o uso da memória, o ProtoLog usa um mecanismo de interning de strings. Esse mecanismo calcula e salva 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. Ele grava apenas o identificador e os argumentos da mensagem no tempo de execução. Quando você gera um rastreamento do ProtoLog ou recebe um relatório de bug, o ProtoLog incorpora automaticamente o dicionário de mensagens criado no momento da 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 é compatível com os mesmos níveis de registro do 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 incluir o código do shell do WindowManager no processo da interface do sistema. No entanto, usar o ProtoLog exigia um código de configuração boilerplate complexo. Além disso, o registro de Proto foi restrito ao servidor do sistema e aos processos da interface do sistema. Isso dificultava a incorporação em outros processos e exigia uma configuração separada de buffer de memória 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 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 de uso de memória comparáveis ao ProtoLog em serviços do sistema, ele incorre em uma sobrecarga de desempenho um pouco 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 ProtoLog
As mensagens do ProtoLog são organizadas em grupos chamados ProtoLogGroups
, semelhante a como as mensagens do Logcat são organizadas por TAG
. Esses ProtoLogGroups
servem como
clusters de mensagens que podem ser ativados ou desativados durante a 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 comofalse
, 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
O ProtoLog formata strings internamente usando android.text.TextUtils#formatSimple(String, Object...)
, então 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
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:
- Crie uma definição
ProtoLogGroup
para esse serviço. Inicialize a definição antes do primeiro uso. Por exemplo, inicialize-o durante a criação do processo:
ProtoLog.init(ProtoLogGroup.values());
Use
ProtoLog
da mesma forma queandroid.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, 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 com 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, você pode 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
, permitindo
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:
Figura 1. Visualizador de ProtoLog