O sistema de registro do Android tem como objetivo a acessibilidade universal e a facilidade de uso, assumindo que todos os dados de registro podem ser representados como uma sequência de caracteres. Essa suposição está alinhada com a maioria dos casos de uso, principalmente quando a legibilidade do registro é crucial sem ferramentas especializadas. No entanto, em ambientes que exigem um alto desempenho de registro e tamanhos de registro restritos, o registro baseado em texto pode não ser o 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 registro do WindowManager e serviços semelhantes. Os principais benefícios do ProtoLog em relação ao logcat são:
- A quantidade de recursos usados para registro é menor.
- Do ponto de vista do desenvolvedor, é o mesmo que usar o framework de geração de registros padrão do Android.
- Suporta a ativação ou desativação de instruções de registro no momento da execução.
- Ainda é possível fazer o registro no logcat, se necessário.
Para otimizar o uso da memória, o ProtoLog emprega um mecanismo de internação de string que envolve calcular e salvar um hash compilado da mensagem. Para melhorar o desempenho, o ProtoLog realiza a internação de strings durante a compilação (para serviços do sistema), registrando apenas o identificador da mensagem e os argumentos no momento de execução. Além disso, ao gerar um rastro 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
traço 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 oferece suporte aos mesmos níveis de registro que android.utils.Log
, a saber: 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 de shell do WindowManager no processo da interface do sistema, mas o uso do ProtoLog exigia um código de configuração de modelo complexo. Além disso, o registro de Proto era restringido ao servidor do sistema e aos processos da interface do sistema, o que tornava difícil a incorporação a outros processos e exigia 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 dos serviços do sistema, o código do lado do cliente geralmente pula a internação de string no tempo de compilação. Em vez disso, a string interning 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 nos serviços do sistema, ele tem uma sobrecarga de desempenho ligeiramente maior e não tem a vantagem de redução de memória da memória fixada da contraparte do lado do servidor.
Grupos de ProtoLog
As mensagens do ProtoLog são organizadas em grupos chamados ProtoLogGroups
, de forma semelhante
à organização das mensagens do Logcat
por TAG
. Esses ProtoLogGroups
servem como clusters de mensagens que podem ser
ativados ou desativados coletivamente no momento da execução.
Além disso, elas controlam se as mensagens precisam ser removidas durante
a compilação e onde elas precisam ser registradas (proto, logcat ou ambos). Cada
ProtoLogGroup
contém as seguintes propriedades:
enabled
: quando definido comofalse
, as mensagens nesse grupo são excluídas durante a compilação e não estão disponíveis no momento da execução.logToProto
: define se esse grupo gera registros 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
Internamente, as strings formatadas do ProtoLog usam
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 (short, integer ou long)%f
: tipo de ponto flutuante (float ou duplo)%s
: string%%
: um caractere literal de porcentagem
Os modificadores de largura e precisão, como %04d
e %10b
, são aceitos, mas
argument_index
e flags
nã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.Inicializar 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 tempo de compilação
Para ativar o ProtoLog no tempo de 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 a internação 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 de forma estática ou caminho completo,
importações de 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 com base 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 real gerado é inline, e vários novos caracteres de linha são adicionados para preservar o número 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
tempo 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
que ativam 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 rastro.
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 a atribuição de string para a mensagem e 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 momento da compilação, ele se torna 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 a instrução ProtoLog for escrita como:
Protolog.v(MY_GROUP, "The argument value is %s", argument);
O buffer na memória seria:
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 os rastros do ProtoLog organizados em um formato tabular. É possível filtrar rastros 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. Clicar no carimbo de data/hora na primeira coluna leva a linha do tempo para o carimbo de data/hora da mensagem. Além disso, clicar em Go to Current Time rola a tabela ProtoLog de volta para o carimbo de data/hora selecionado na linha do tempo:
Figura 1. Visualizador de ProtoLog