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 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
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:
Crie uma definição de
ProtoLogGroup
para esse serviço.Inicialize a definição antes do primeiro uso (por exemplo, na 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 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:
Figura 1. Visualizador de ProtoLog