Ferramentas e performance

Trace me if you can: Automated instrumentation and field profiling

Alto-falantes

  • Rahul Ravikumar

    Engenheiro de software sênior

Sobre

Sobre a palestra

A otimização de apps Android exige uma visibilidade profunda dos fluxos de trabalho simultâneos e das suspensões de corrotinas.

Embora a criação de perfis local ofereça um resumo útil, escalonar esses insights para a produção sem sobrecarga continua sendo um desafio.

Esta sessão apresenta um modelo arquitetônico para um ciclo de feedback de desempenho contínuo.

androidx.tracing 2.0: troca back-ends dinamicamente e propaga contextos de corrotina (fluxos do Perfetto) entre linhas de execução.

Instrumentação inteligente: use o rastreamento local do ART para encontrar pontos de acesso e automatize a injeção de pontos de rastreamento de baixa sobrecarga.

Macrobenchmark: acompanhe regressões e verifique ganhos de desempenho em dispositivos físicos na CI.

Rastreamento de campo: vá além do rastreamento local e de CI com o ProfilingManager.

Resumo

  • O Tracing 2.0 substitui as APIs de rastreamento mais antigas do Android para remover os limites de comprimento de rótulos e executar sete vezes mais rápido. Ele também permite criar coletores personalizados para back-ends, como o OpenTelemetry.
  • A propagação de contexto mapeia relações causais entre linhas de execução, corrotinas e processos. É possível rastrear a execução assíncrona com traceCoroutine ou transmitir tokens de propagação para o código baseado em callback.
  • O Jetpack Compose 1.13 adiciona o rastreamento de recomposição, usando metadados personalizados para mostrar por que as recomposições ocorrem. Os traces registram os valores de estado modificados e as pilhas de chamadas exatas que os acionaram.
  • As ferramentas de build do Android usam o Tracing 2.0 para identificar gargalos e acelerar os builds. Otimizações guiadas por rastreamento que aceleraram em 25% a redução de código do R8 e reduziram em 15% os tempos de pós-envio do Metalava.

Gemini Notebook

Tópicos

  • rastreamento
  • android-performance
  • perfetto
  • context-propagation
  • jetpack-compose
  • corrotinas
  • criação de perfil

Transcrição

Introdução e evolução do rastreamento do Android

  • Rahul Ravikumar

    Olá, pessoal.

  • Meu nome é Rahul.

  • Sou engenheiro na equipe de experiência do desenvolvedor Android.

  • Meu foco é o desempenho de apps e bibliotecas, e esta palestra é sobre rastreamento.

  • Antes de nos aprofundarmos no rastreamento, quero estabelecer um vocabulário compartilhado para que você possa acompanhar.

  • Portanto, sempre que eu me referir ao rastreamento nesta palestra, estarei falando sobre o rastreamento explícito, que é a instrumentação manual do código para que você saiba o que o app está fazendo.

  • Não estou falando de criadores de perfil automáticos, que vou abordar mais adiante.

  • O rastreamento manual é a arte de deixar migalhas de pão na base de código para que você possa entender o que aconteceu quando tiver um problema de desempenho ou qualquer outro problema.

  • Geralmente, é muito melhor do que os registros.

  • Não sei se você já viu uma saída do Logcat de um app grande.

  • Geralmente, é um bloco gigante de texto que não é fácil de entender.

  • O rastreamento, por outro lado, é muito melhor.

  • É mais fácil de visualizar.

  • Você tem durações.

  • Você tem uma estrutura.

  • Você tem algumas ferramentas para analisar os dados e ver exatamente o que aconteceu e quando.

  • Por isso, é muito melhor.

  • E você pode, sabe, apontar seus agentes para um trace e fazer perguntas.

  • Você pode usar ferramentas como o Processador de Trace, comparativos de mercado e até mesmo converter seus traces em métricas.

  • Essas são todas as coisas que você pode fazer com os traces que são difíceis de fazer com o Logcat.

  • Por outro lado, a criação de perfis, e estou falando da criação automática de perfis, geralmente é uma resposta a um problema que você conhece.

  • Por exemplo, você pode ter notado que o app inicia lentamente ou que algo está travando e quer descobrir o que está acontecendo.

  • Se você não tiver traces manuais para ajudar, é aí que entra a criação de perfil automática.

  • Você pode usar algo como o rastreamento de método do ART, em que cada entrada e saída de método é instrumentada.

  • Ou talvez você esteja analisando a amostragem de pilha com ferramentas como o Simpleperf.

  • Portanto, em vez de usar cada entrada e saída de método e ter uma sobrecarga alta, você analisa as amostras da pilha de chamadas e vê o que está no caminho crítico do app.

  • Estamos em 2026, a RAM é um problema em todos os lugares, então você pode estar analisando a memória, os vazamentos de memória e os perfis de heap.

  • Esse é mais um criador de perfil que você pode usar.

  • Você pode analisar os caminhos de retenção de heap.

  • Você pode ver qual código está alocando memória, e esses são todos os tipos de análise que você pode fazer com isso.

  • Certo.

  • Agora, temos esse vocabulário.

  • Agora vamos falar sobre por que criamos uma nova biblioteca.

  • As APIs de rastreamento 1.0 foram introduzidas no Android Jelly Bean.

  • Isso foi em 2012.

  • Isso foi há mais de 14 anos.

  • Desde então, as APIs não evoluíram tanto quanto você imagina.

  • Adicionamos algumas extensões.

  • Os eventos e contadores assíncronos foram adicionados no Android 10.

  • Isso foi em 2019.

  • E só.

  • A API não evoluiu desde então.

  • No entanto, o ecossistema evoluiu bastante.

  • O rastreamento da plataforma melhorou muito, mas nada disso se refletiu em adições de API para você.

  • Queremos mudar isso.

  • O outro grande problema é que o rastreamento tem um back-end fixo na versão 1.0. Isso significa que, sempre que você adiciona uma seção de rastreamento ao código, ela sempre acaba no buffer de rastreamento do sistema.

  • O Atrace é o subsistema de rastreamento do Android que possui esses dados.

  • Portanto, se você quiser pegar seus traces e convertê-los em telemetria de campo, isso é muito difícil, porque, primeiro, você precisa entender como o atrace armazena esses dados.

  • É um formato bem documentado, mas que pode ser difícil de consumir.

  • Isso é algo que queremos corrigir.

  • Por isso, queremos oferecer as ferramentas para você levar seus dados de rastreamento para onde quiser.

  • Também havia uma restrição no Tracing 1.0 em que os rótulos eram limitados a 128 caracteres.

  • Essa era uma restrição razoável em 2012, mas em 2026, parece um pouco estranha.

  • Para piorar, também não é possível adicionar metadados extras ao trace, o que é ainda mais problemático.

  • E há também a sobrecarga.

  • Portanto, sempre que você estiver rastreando seu app, é preciso estar extremamente ciente do overhead, porque o ato de observar um sistema não deve mudar o que o sistema está fazendo em segundo plano.

  • Portanto, se houver uma disputa que você queira reproduzir, o rastreamento não deve fazer com que ela desapareça.

  • Portanto, é importante preservar toda a semântica que você está usando.

  • O Tracing 1.0 tinha uma sobrecarga relativamente alta, e queríamos melhorar isso.

  • Ok.

  • Falei muito sobre os problemas.

  • Agora, vamos analisar alguns dos problemas visualmente e ver como eles se agravam na prática.

  • Aqui temos um rastro do Perfetto.

  • É para um app, com.example.app.

  • Temos uma linha de execução principal e uma única fração chamada initializeComponent.

  • Agora, o ideal é que tenhamos mais detalhes.

  • Vamos adicionar um detalhe.

  • Agora sabemos que initializeComponent chama performInitialize.

  • Ainda não sabemos qual componente está sendo inicializado.

  • Agora, a única ferramenta no nosso cinto de utilidades com o Tracing 1 é o nome, então vamos adicioná-lo ao nome da fração.

  • Lembre-se de que os nomes de frações de rastreamento são restritos a 128 caracteres, então eles são truncados no rastreamento.

  • Para piorar, o overhead de truncamento agora também é um overhead que você tem ao fazer o rastreamento.

  • Agora, você se depara com esse problema rapidamente.

  • Então, queríamos muito corrigir isso.

  • Você viu o rastro do Perfetto e como ele é visualmente.

  • Agora, vamos analisar a perspectiva de escrever o código para emitir essas seções de trace.

  • Novamente, temos a API initializeComponent.

  • Ela usa um contexto e um nome e chama performInitialize.

  • Até aqui, tudo bem.

  • Agora, queremos adicionar um trace com a API Tracing 1.0. Para isso, importe androidx.tracing.trace e use um bloco de trace.

  • Dê um nome a ela.

  • Aqui, é performInitialize.

  • Mas, como queríamos fazer antes, queremos saber o nome do componente. Por isso, vamos usar a interpolação de strings do Kotlin e adicionar o nome.

  • E aqui está o primeiro problema.

  • Aqui temos o limite de 128 caracteres.

Visão geral do rastreamento 2.0 e do desempenho

  • Rahul Ravikumar

    Podemos corrigir isso com o Tracing 2.

  • Em vez de usar androidx.tracing.trace, use Tracer.

  • A API é muito parecida.

  • Em vez de trace, você tem Tracer.global.trace.

  • Você tem uma categoria.

  • Vamos falar sobre as categorias em mais detalhes depois, mas, basicamente, você tem um nome.

  • E, em seguida, você ainda tem o bloco de código que está tentando rastrear.

  • E agora, você não tem mais a limitação de 128 caracteres.

  • Falei sobre a sobrecarga de rastreamento.

  • Quero entrar em mais detalhes e ver como melhoramos isso.

  • Em um Pixel 6a com o Android 16, o ato de inserir uma única seção de início e fim levou cerca de 1.200 nanossegundos.

  • Isso pode não parecer muito, mas se você tiver um código muito quente que está adicionando muitas seções de trace, esse é um tempo que o app poderia estar gastando na renderização de frames.

  • Por isso, não queremos tomar seu tempo.

  • Na verdade, melhoramos bastante as coisas com o SDK do AndroidX Perfetto.

  • Esse era um SDK experimental que introduzimos para o Composition Tracing, e ele melhorou muito a situação.

  • Não queríamos que você fizesse isso porque esse é o SDK do Perfetto C com vinculações JNI, e o SDK do C tem cerca de 4 MB no APK.

  • Não queríamos que você enviasse isso.

  • Agora, com o Tracing 2, temos uma biblioteca criada para essa finalidade.

  • Melhoramos muito mais a sobrecarga, e agora você vai gostar de saber que o Tracing 2 é sete vezes mais rápido, o que significa que você tem mais tempo para fazer o que quer, e a infraestrutura leva menos tempo.

  • O que isso possibilita que não era possível antes?

  • Um exemplo é o recurso de rastreamento de ambiente de execução do Metro.

  • O Metro, para quem não sabe, é uma biblioteca de injeção de dependência.

  • Ele é compatível com o Kotlin Multiplatform.

  • Ele usa um plug-in do compilador Kotlin.

  • Ele gera todo o código para injeção de dependência e se integra ao Tracing 2.0 para facilitar a visualização do custo de cada @Inject no seu código.

  • Veja um exemplo.

  • Digamos que temos um gráfico de app.

  • No Metro, tudo tem o escopo de um gráfico, então temos um único campo, AnalyticsTracker.

  • Também temos uma vinculação de fornecimento.

  • Temos um cliente de rede com o OkHttpClient como impl, e a implementação de AnalyticsTracker vem do AnalyticsTrackerImpl. Precisamos de uma instância do cliente de rede, que o gráfico sabe como fornecer.

  • Por trás dos panos, o plug-in do compilador gera um código parecido com este.

  • Portanto, se você tiver o AnalyticsTracker, o getter vai criar uma instância do cliente de rede e transmiti-la ao construtor de AnalyticsTrackerImpl, o que é bom.

  • Mas agora, com o rastreamento ativado, é possível ver o custo de criação não apenas da impl do rastreador, mas de cada dependência necessária.

  • Agora, é possível ver não apenas o custo de instanciar o tipo, mas também todas as dependências dele em um rastro, o que é muito útil.

  • Usando essas informações, o Wise, que é um app de finanças, melhorou a inicialização em 45%.

  • Eles conseguiram adiar coisas que não eram necessárias para inicializar durante a inicialização do app e também identificaram alguns itens com escopo incorreto, em que coisas que deveriam ter sido um singleton não eram realmente singletons por acidente, e essas são coisas que você pode ver e corrigir facilmente no seu código.

  • Se você analisar as métricas de inicialização, elas são realmente impressionantes.

  • Além de melhorar o tempo até o frame inicial, eles também melhoraram o tempo até a exibição completa.

  • Outro parceiro, o BandLab, usa as mesmas informações sobre CI.

  • Assim, você não só sabe o custo de cada vinculação @Inject, mas também qual linha de execução gerou esse custo.

  • Depois de saber disso, você pode encontrar todas as vinculações @Inject caras na linha de execução principal e, na CI, executar uma consulta para receber uma lista de todas as vinculações caras e atribuí-las à mudança que as introduziu. Assim, você pode identificar a mudança real e tentar corrigi-la.

Propagação de contexto, corrotinas e Jetpack Compose

  • Rahul Ravikumar

    Ok.

  • Até agora, falei sobre todas as melhorias que fizemos em relação ao Tracing 1, mas agora vou falar sobre os novos recursos do Tracing 2 que nunca foram possíveis antes.

  • Um dos recursos principais é chamado de propagação de contexto. Como os apps modernos estão cada vez mais complexos, as cargas de trabalho são executadas em várias linhas de execução, corrotinas e processos.

  • Você quer visualizar como uma solicitação se propaga entre cada linha de execução e processo.

  • E é isso que a propagação de contexto possibilita.

  • Se eu apenas fornecesse uma lista de fatias sem explicar o que causou o quê, elas seriam úteis, mas não seriam fáceis de entender.

  • Mas se eu disser que essa foi a cadeia causal de eventos, vai ser muito mais fácil descobrir o que estava acontecendo e associar o custo de cada fração.

  • É isso que a propagação de contexto disponibiliza.

  • A propagação de contexto é totalmente compatível. Veja um exemplo real.

  • Voltando ao rastro do Perfetto de com.example.app, digamos que performInitialize estava demorando muito e você queria adiar o custo ou movê-lo para uma linha de execução diferente.

  • Você consegue fazer isso.

  • Digamos que eu tenha movido performInitialize para outra conversa.

  • Agora, você perdeu a natureza hierárquica da fração.

  • Agora, não é mais possível saber que initializeComponent foi o que causou performInitialize, a menos que você olhe o código.

  • Em um mundo ideal, você gostaria de uma seta para indicar que A causou B e, em seguida, poder raciocinar sobre a cadeia causal de eventos.

  • Talvez você já tenha visto um rastreamento do sistema em que fazemos isso para transações de binder na plataforma.

  • Aqui, você pode ver que o comparativo de mercado do Compose faz uma transação de binder para system_server para preparar um frame.

  • Isso sempre foi possível do ponto de vista do rastreamento da plataforma, mas nunca disponibilizamos as ferramentas para desenvolvedores de apps.

  • Agora, a propagação de contexto de corrotina do Kotlin é totalmente compatível com o Tracing 2.0.

  • Não é necessário fazer nada.

  • Você já recebe tudo pronto para uso.

  • Aqui, temos um único método de suspensão chamado fetchProfile.

  • Ele chama um client.get, que é outro método de suspensão, e depois muda para Dispatchers.Default para desserializar a resposta.

  • É um código de corrotinas bem comum, algo com que você provavelmente lida todos os dias.

  • Agora, digamos que queremos rastrear isso.

  • Adicionamos uma seção de trace.

  • Aqui, em vez de trace, vamos usar a API traceCoroutine, já que é um bloco de código de suspensão. Depois, para a chamada interna antes de alternar o contexto para Dispatchers.Default, vamos adicionar outra seção de trace interna usando a mesma API traceCoroutine.

  • Agora, em um trace, você verá algo assim.

  • Você vai notar que o trabalho começou no worker-1 e, em seguida, mudou de contexto e foi para o worker-3. Você vai ver setas úteis no rastro que indicam isso.

  • Além disso, se você ampliar o quadrado no meio, vai notar que a desserialização começou no worker-1, e a chamada para withContext foi o que causou a troca de contexto. Depois, ela continuou no worker-3, e fetchProfile foi retomado no worker-3 porque o mecanismo de corrotinas quer minimizar o número de trocas de contexto que acontecem.

  • É possível ver exatamente quando uma corrotina foi suspensa, quando foi retomada e a cadeia causal completa de eventos.

  • Outra possibilidade do Tracing 2 é adicionar metadados personalizados a um trace.

  • No passado, a única ferramenta disponível era o nome da seção de trace, mas agora você pode adicionar um bloco de metadados que permite incluir quantas entradas de metadados quiser.

  • E você pode adicionar isso ao trace.

  • Ao analisar o trace real, é possível ver que todos os metadados adicionados estão no trace para que todas as ferramentas os consumam e usem.

  • Agora, você pode incorporar tudo o que sabe, todas as informações adicionais que sempre quis.

  • Então, eu mostrei vários recursos abstratos que o rastreamento possibilita, mas o que realmente se destaca é um novo recurso no Jetpack Compose chamado rastreamento de recomposição.

  • Isso é enviado como parte do Compose 1.13, então já está pronto para uso.

  • Veja um exemplo.

  • Este é o rastreamento de composição, que vocês provavelmente já conhecem.

  • Isso existe desde os primórdios do Compose.

  • Ele informa o custo relativo de um elemento combinável e de todas as, você sabe, subfatias dele, além do custo de cada uma na linha de execução em que estavam ocorrendo.

  • É bem simples.

  • O que ele não diz é o contexto.

  • Ele não informa o motivo.

  • Ele não informa o motivo da recomposição.

  • Se eu diminuir o zoom, essa é a região de interesse que acabamos de ver no slide anterior.

  • À esquerda, é possível ver que ocorreu um evento de entrada de envio.

  • Talvez isso tenha mudado algum estado e causado a recomposição.

  • Mas não está claro.

  • Você não tem certeza.

  • No entanto, com o rastreamento de recomposição, agora adicionamos metadados personalizados ao rastreamento que informam exatamente por que as recomposições aconteceram.

  • Além disso, agora é muito fácil perceber que esses eventos estão relacionados.

  • Se você clicar neles, vai saber exatamente qual estado mudou.

  • Ele vai informar que era um estado, um valor de estado mutável que foi alterado para falso, que antes era verdadeiro.

  • Além disso, vamos informar a pilha de chamadas exata que causou o problema.

  • Aqui, você pode ver que AbstractClickableNode.performClick aconteceu, o que fez com que o estado mutável mudasse, o que causou a recomposição.

  • Isso é muito legal, algo que não podíamos fazer antes.

  • Essas ferramentas também estão disponíveis, ou melhor, todas essas funcionalidades também estão disponíveis na nova ferramenta Android Performance Analyzer, se você quiser testar algumas das ferramentas de agente.

  • E à direita.

  • Eu disse que a propagação de contexto é totalmente compatível com as corrotinas do Kotlin.

  • Mas e quanto ao código que usa uma mistura de corrotinas e código sem corrotinas, ou talvez você esteja executando linhas de execução por conta própria, ou talvez tenha algum código brownfield muito antigo que usa callbacks por algum motivo?

  • Ok.

  • A boa notícia é que você ainda pode aproveitar todos os benefícios da propagação de contexto.

  • Digamos que temos uma API brownfield que usa um callback e um executor.

  • A implementação usa executor.execute, chama runCatching, que é o detalhe da implementação, recebe a resposta e, em seguida, informa o callback sobre a resposta.

  • Ok.

  • Agora, se eu quiser rastrear isso, vou usar Tracer.global.trace, dar um rótulo e cercar o bloco.

  • Mas, como também quero preservar a cadeia causal de eventos, vou aceitar um novo tipo opcional, chamado de token de propagação.

  • Vou transmitir o token para a API de trace interna.

  • Assim, quem chamar esse método pode, opcionalmente, me dar o token de propagação para que eu possa preservar a cadeia causal de eventos que acontecem.

  • Ok.

  • Agora, o autor da chamada dessa API pode ser uma API de suspensão.

  • Aqui, tenho uma função de suspensão profileRequest, que vai chamar a API que usa um callback e um executor.

  • Aqui, como estou conectando dois mundos, o de suspensão e o de callback, vou usar o builder suspendCoroutine.

  • Vou receber uma continuação, invocar a API e retomar a continuação no callback assim que receber a resposta.

  • E aqui está o executor que estou definindo, chamado profileRequestContext.

  • É um executor de linha de execução única.

  • Como o traceCoroutine incorpora um token de propagação no contexto da corrotina, posso obter o token de propagação de dentro do contexto da corrotina e transmiti-lo como entrada para a API brownfield, preservando a cadeia causal de eventos.

  • E no trace, isso fica muito óbvio.

  • Aqui, temos algo que começou no worker-3 e mudou para o executor de linha de execução única, depois que fizemos a troca de contexto, e depois voltou para o worker.

  • É bem legal.

Rastreamento global e inicialização personalizada

  • Rahul Ravikumar

    Ok.

  • Outra coisa que você pode fazer é personalizar o Tracer.global.

  • Até agora, a API Tracing 2.0 oferece padrões muito bons para o que Tracer.global precisa ser, mas, se você quiser personalizar, é possível.

  • Além disso, o Tracing 2.0 é compatível com a JVM e o Android, porque queremos que o rastreamento seja totalmente compatível com as bibliotecas Java e Android.

  • As três etapas para inicialização são: criar um coletor de trace.

  • Um coletor de trace é, na verdade, como os pacotes de trace são armazenados.

  • Ele define o formato de transmissão e o local dos traces de saída em que eles são armazenados.

  • Oferecemos uma implementação pronta para uso de um coletor de rastreamento que oferece suporte ao formato Perfetto, mas você pode criar seu próprio coletor de rastreamento.

  • Isso volta à tentativa de resolver o problema de que o atrace era o único back-end disponível no Tracing 1.

  • Queríamos corrigir isso.

  • Portanto, agora, se você quiser escrever um coletor do OpenTelemetry ou trazer seu próprio coletor, isso é totalmente possível.

  • Você pode optar por substituir isso.

  • Depois de ter um coletor, você pode criar um driver de trace e atribuir o coletor a ele.

  • E também é possível escolher quais categorias você quer ativar.

  • É aqui que as categorias realmente se destacam.

  • Assim, você pode decidir que, na versão, algumas categorias são mais importantes do que outras, em comparação com a depuração, e pode dizer que essas são as categorias que você quer ativar.

  • Ou você também pode dizer: "Quero ativar todas as categorias, independentemente de onde elas vieram".

  • E não tem problema nenhum nisso.

  • Também é possível adicionar atributos personalizados que são globais ao seu trace.

  • Assim, é possível incorporar itens como a versão do build, o tipo ou qualquer outro metadado que você queira anexar a um trace usando a API addAttribute.

  • Por fim, registre um rastreador chamando setGlobalTracer.

  • É isso que vincula Tracer.global a essa instância do rastreador e, consequentemente, é isso que está disponível para o processo.

  • No Android, como os processos têm um ciclo de vida muito mais bem definido, basta implementar AbstractTraceDriver.Factory, substituir o método create e fazer exatamente o que você fez antes.

  • Então, você cria o coletor e, em seguida, o driver.

  • Não é necessário se registrar aqui.

  • Vamos fazer o registro para você, porque sabemos que isso é o que aconteceu primeiro.

  • Ok.

  • Quero reiterar que o rastreamento está pronto para várias plataformas.

  • Essa é uma escolha intencional, porque sabemos que o Compose é executado em várias plataformas e queremos que seja possível fazer o rastreamento no maior número possível de lugares.

  • Hoje, oferecemos suporte ao Android e à JVM, mas nossa implementação deixa totalmente aberta a possibilidade de oferecer suporte a plataformas futuras, se quisermos.

Otimizações de ferramentas de build (R8 e metalava)

  • Rahul Ravikumar

    Você vai gostar de saber que o R8, a ferramenta usada para reduzir e otimizar o código-fonte no Android, usa o Tracing 2.0.

  • E, aproveitando os traces, otimizamos o R8 para reduzir e otimizar seu código 25% mais rápido.

  • Isso significa tempos de compilação mais curtos e, você sabe, tempos de espera mais curtos para as passagens de otimização.

  • Não vou falar sobre todas as otimizações que fizemos no R8, mas quero falar sobre uma delas para dar uma ideia do que o rastreamento possibilita.

  • O R8 é, essencialmente, um transformador de código-fonte para código-fonte.

  • Assim, você recebe um monte de JARs, reduz e otimiza eles e produz outro conjunto de JARs que podem ser dexados.

  • Aqui, o AGP começa lendo os JARs, que não são apenas o app, mas também todas as dependências dele.

  • Por isso, ele usa workers do Gradle para começar a ler todos os JARs de entrada.

  • Você tem muitos arquivos JAR que quer ler.

  • Depois de ler os JARs, o R8 começa a analisar o código e a criar um modelo do que ele representa, além de todos os pontos de entrada, para que possa começar a otimizar e reduzir o código.

  • Essa é a fração, await, que representa quando o R8 está realmente pronto para iniciar o processo de otimização, certo?

  • Talvez você perceba um espaço morto no meio e se pergunte por que temos esse tempo de CPU não utilizado.

  • Isso acontece porque, na API antiga, esperávamos que todos os workers lessem todos os arquivos antes de analisar qualquer coisa.

  • É claro que podemos corrigir isso, e foi exatamente o que fizemos.

  • No AGP 9, assim que terminamos de ler os JARs de entrada, começamos a processá-los imediatamente.

  • E não precisamos esperar que todos os workers terminassem de ler as entradas.

  • Isso reduziu bastante o tempo de espera para ler todas as entradas.

  • Essa é uma otimização óbvia que você pode ver em um rastro, mas não é tão óbvia quando você está apenas olhando para o código-fonte para ver o que vale a pena otimizar.

  • Outra ferramenta que usamos todos os dias como engenheiros do AndroidX é o Metalava.

  • O Metalava é uma ferramenta que usamos para garantir que as APIs que enviamos tenham as garantias de compatibilidade que você espera e sejam totalmente utilizáveis por pessoas externas sem os problemas que queremos evitar.

  • Essa ferramenta é executada em todas as APIs públicas e restritas. Portanto, qualquer tempo economizado aqui é, na verdade, tempo que recuperamos como engenheiros do AndroidX.

  • O Metalava também usou o rastreamento para melhorar os tempos de pós-envio em impressionantes 15%.

  • E, ao analisar o trace para explicar o que aconteceu, o Metalava executa dois tipos de verificação: a primeira verifica as APIs restritas, e a segunda verifica as APIs públicas.

  • Mas talvez você esteja percebendo um padrão.

  • Estamos fazendo uma série de configurações para executar essas verificações duas vezes.

  • Então, o que acontece é que todas as entradas do programa são lidas, criamos a PSI, que é a interface de estrutura do programa, ou seja, exatamente o formato do seu código que pode ser analisado.

  • Em seguida, criamos a AST antes de começar a aplicar todas as verificações de lint que queremos.

  • E estamos fazendo esse trabalho caro duas vezes.

  • Como corrigimos isso?

  • Fizemos isso apenas uma vez.

  • Então, em vez de fazer todo esse trabalho de configuração duas vezes, fizemos apenas uma vez e reutilizamos essas entradas para as verificações restritas e públicas, o que resultou em todas essas economias.

  • Sim.

  • Ok.

Práticas recomendadas de rastreamento e comparativo de mercado

  • Rahul Ravikumar

    Agora que você já tem uma ideia do que o Tracing 2.0 possibilita, é importante saber que, embora o rastreamento seja importante, saber o que rastrear é igualmente importante.

  • Não é bom espalhar traces por toda parte.

  • É preciso ter muito cuidado com o que você escolhe, e vou tentar dar algumas diretrizes sobre o que vale a pena rastrear.

  • A primeira coisa que você deve fazer é encontrar lacunas inexplicáveis no trace.

  • Portanto, sempre que as linhas de execução estiverem em estado de execução ou executável e você notar lacunas óbvias no rastreamento do sistema, sem conseguir entender o motivo ou o que o app está fazendo, talvez seja interessante investigar.

  • Nesse ponto, posso fazer um rastreamento de método sobreposto ao rastreamento do sistema e ver o que vale a pena rastrear. Depois, posso adicionar essas seções de rastreamento relevantes.

  • Também é importante priorizar as jornadas do usuário.

  • Portanto, você quer analisar a inicialização do app.

  • Você quer analisar as principais jornadas do usuário no app e descobrir se é possível explicá-las analisando um trace.

  • Portanto, sempre que você não conseguir entender algo, vale a pena rastrear.

  • Você também deve analisar pilhas de chamadas profundas ou o uso inexplicável da CPU em um trace e tentar descobrir exatamente o que está acontecendo. Em seguida, converta essas partes da base de código em traces.

  • Por isso, é possível rastreá-los.

  • E, sempre que as linhas de execução estiverem em suspensão, você precisa entender o motivo.

  • Elas devem estar aguardando uma E/S, uma transação de binder ou alguns eventos de entrada externos que você espera que aconteçam.

  • Mas se você tem, sabe, sono inexplicável, então vale muito a pena investigar, porque obviamente pode haver algo oculto ali.

  • Depois de adicionar as seções de trace no código, converta-as em comparativos de mercado para acompanhar as mudanças.

  • Assim, conforme sua base de código evolui, você pode ver se as métricas estão mudando, se os painéis realmente refletem grandes mudanças, porque assim você sabe que algo importante mudou.

  • Ou você pode pular todas as etapas anteriores e deixar que o agente faça isso.

  • Pegue essas diretrizes, transforme-as em uma habilidade, aponte seu agente favorito para ela e deixe que ele faça o trabalho.

  • Sim.

Configuração, resumo e conclusão

  • Rahul Ravikumar

    Se eu convenci você de que o rastreamento é uma atividade que vale a pena e que você deve fazer, aqui está a configuração do projeto.

  • Isto é bem objetivo.

  • O rastreamento é apenas uma biblioteca Java tradicional, então você define uma dependência em androidx.tracing:tracing-wire, que é o driver de rastreamento compatível com o Perfetto e o combo de rastreamento, e a versão estável atual é a 2.0.5.

  • Se você usa uma biblioteca Java, é exatamente a mesma coisa. Novamente, essa é uma API totalmente multiplataforma.

  • Queremos estendê-lo a outras plataformas quando fizer sentido.

  • E sim, eu só queria fazer uma recapitulação rápida.

  • Então, eu falei sobre todos esses novos recursos legais que adicionamos.

  • Portanto, a propagação de contexto é muito, muito poderosa.

  • Podemos incorporar informações personalizadas, metadados, informações da pilha de chamadas e todos os tipos de outras coisas.

  • Não falei sobre muitas coisas.

  • É claro que você pode mudar o formato do trace, o coletor de trace e, por exemplo, levar os dados de trace para o OpenTelemetry.

  • Você pode fazer tudo isso.

  • O rastreamento é multiplataforma.

  • Essa é uma decisão muito consciente, porque queremos que o maior número possível de pessoas possa aproveitar esse recurso.

  • E todas as ferramentas que enviamos, seja o Perfetto, os comparativos de mercado do AndroidX, o novo analisador de desempenho ou o antigo Android Profiler, todas elas são compatíveis com o Tracing 2.0.

  • Agradecemos sua atenção.

  • Espero que você aproveite o Dev Summit. Até mais!