FAVORITAR
FecharPlease login

Desvendando o código: Rastreamento de código em Linux e Android

Escolher uma plataforma para um produto em desenvolvimento não é uma tarefa fácil. Há algumas décadas o uso de software bare metal em aplicações embarcadas era praticamente garantida, porém, dado os avanços em eficiência energética de hardwares modernos, sistemas operacionais embarcados se tornam cada vez mais viáveis para os mais diferentes mercados. Nesse contexto, sistemas baseados em Linux vêm tomando um papel de protagonismo em uma fatia significativa dos produtos eletrônicos, desde instrumentos médicos até geladeiras. Existem diversas vantagens em desenvolver uma aplicação no topo do Linux, porém à medida que a complexidade do software aumenta, surge a necessidade de utilizar diferentes técnicas para depurar e solucionar problemas de desempenho e estabilidade.

Diferente dos sistemas bare metal, quando optamos por desenvolver produtos em sistemas Android ou qualquer outra plataforma baseada em Linux, temos que lidar com uma quantidade significativa de overhead, e nem sempre entendemos completamente como as coisas funcionam. Neste artigo, faço uma introdução sobre algumas ferramentas de rastreamento de código em Linux, dando um espaço especial para o ecossistema de rastreamento do Android. Essas ferramentas podem diminuir significativamente o tempo necessário para entender como nosso código está interagindo com a plataforma e assim facilitar a depuração de problemas complexos. 

Nos exemplos, foram utilizados uma Raspberry Pi 4 com a última versão estável do Raspberry Pi OS – 64 bits e um Google Pixel 6 operando uma versão customizada do Android 12, porém os princípios mostrados aqui podem ser replicados em sistemas similares.

Rastreamento no Linux

O rastreamento (tracing, em inglês) é uma das estratégias de depuração mais utilizadas no desenvolvimento de sistemas. Quando instrumentalizamos nosso código, por meio de logs, estamos fundamentalmente rastreando o seu fluxo. Existem, porém, algumas ferramentas que podem auxiliar nesse processo, diminuindo a quantidade de trabalho necessária para entender o ciclo de vida de um programa para além da aplicação em user space.

Dado o tamanho do código fonte do Linux, a adição manual de logs para depuração de problemas seria uma tarefa árdua. As ferramentas que serão mostradas abaixo utilizam probes, i.e. pontos estáticos de logging definidos previamente de acordo com a sua importância no funcionamento do sistema. Esses probes coletam informações durante a execução do sistema operacional, que podem ser usadas posteriormente para compreender o que estava acontecendo durante o período de coleta de dados.    

ftrace: Rastreador de Funções

O ftrace é uma ferramenta de rastreamento interna do Linux que tem como objetivo ajudar o desenvolvedor a entender o que está acontecendo no sistema operacional durante sua execução, o que é muito importante para desenvolvedores do Kernel, porém além disso, pode ajudar um desenvolvedor de aplicação em user space a identificar como a sua aplicação interage com a plataforma.  

Como o ftrace é parte do Linux, para a maioria das distribuições, não precisamos de nenhuma pré-configuração para utilizá-lo. Para verificar se o ftrace está configurado em nosso ambiente, podemos checar a existência do diretório /sys/kernel/tracing no sistema de arquivos. No Raspberry Pi OS:

Conteúdo do diretório: /sys/kernel/tracing no Raspberry Pi OS.

No arquivo de README do diretório mostrado na figura acima , já conseguimos encontrar um guia de como começar a rastrear as funções do kernel, assim como uma descrição dos arquivos mais importantes dessa pasta. É recomendável a leitura completa da documentação. Aqui iremos selecionar algumas linhas para analisar com um pouco mais de profundidade. 

Conteúdo do arquivo README do diretório de tracing do Raspberry Pi OS. 

O arquivo current_tracer é um parâmetro de configuração que podemos utilizar para selecionar o tipo de rastreamento que será utilizado na coleta de dados, as nossas opções estão contidas em available_tracers

Tipos de tracing disponíveis no Raspberry Pi OS.

Na documentação oficial do ftrace podemos encontrar a definição de todos os tipos de rastreamentos disponíveis, aqui iremos tratar dois:

  • function – Mostra uma lista sequencial de todas as entradas de funções executadas em kernel space durante o tempo de coleta de dados de rastreamento.
  • function_graph – Similar ao anterior, porém esse tipo de rastreamento também fornece o indicativo de saída de função, o que permite a construção de uma estrutura de árvore.  

Para nosso exemplo vamos configurar current_tracer como function_graph, por ser mais interessante na análise de problemas de performance. Depois de habilitar o usuário root no terminal, podemos modificar current_tracer.  

Habilitando rastreamento por function_graph.

Nesse ponto, a coleta de dados de rastreamento já está acontecendo. Dado o tamanho do kernel, não é ideal utilizar o ftrace sem nenhum filtro, visto que o resultado pode ser muito grande, dificultando a análise do arquivo de saída. Além disso, dependendo das capacidades do hardware, a coleta de dados de rastreamento sem filtro pode causar um impacto significativo no desempenho do sistema, o que pode diminuir a representatividade dos dados capturados. Dito isso, após salvar o conteúdo de trace em um arquivo, podemos ver nosso primeiro exemplo de saída do ftrace. 

Conteúdo do arquivo coletado no processo de rastreamento por function_graph.

O arquivo resultante é bastante inteligível. A primeira coluna mostra o número da CPU utilizada para executar a função, enquanto a segunda coluna mostra a duração de execução da referida linha de código. Agora vamos configurar set_ftrace_filter com algumas funções de interesse, limitando o tamanho do arquivo de saída e focando a análise em pontos específicos. 

Para nossos propósitos, podemos escolher qualquer função disponível em available_filter_functions. Entretanto, na prática, a definição do filtro de rastreamento é uma etapa muito importante da análise e requer um conhecimento prévio do subsistema que se deseja analisar. Digamos que estamos depurando um problema de alocação de memória. Talvez seja interessante verificar o comportamento de algumas funções de malloc() em tempo de execução

Funções relacionadas à malloc disponíveis para rastreamento no Raspberry Pi OS.

Configuração de um filtro de rastreamento por nome de função.

Conteúdo do arquivo coletado no processo de rastreamento por function_graph com filtro por nome de função.

Em ambos os exemplos mostrados acima, podemos ver como o rastreamento por function_graph nos fornece uma saída relativamente intuitiva, contendo a ordem de execução e a duração de funções de interesse. Apenas com os conceitos vistos até aqui, essa ferramenta tem um alto potencial na identificação de problemas de performance em kernel space. Além disso, o ftrace pode ser utilizado para diversas outras aplicações. Neste artigo (em inglês) temos um exemplo de como usamos o ftrace para depurar uma falha em device driver. O que pode ser útil caso estejamos trabalhando com aplicações que dependem de informações de sensores, como: câmeras, geolocalizadores, dentre outros. 

Embora ainda muito usado por quem tem um conhecimento avançado do funcionamento do kernel, a utilização manual do ftrace pode consumir bastante tempo para usuários de nível intermediário. Porém, a sua versatilidade e alto número de aplicações, o faz ser largamente utilizado como base para outras ferramentas de rastreamento. 

trace-cmd: um front-end para o ftrace

Agora que já temos uma ideia de como utilizar o ftrace para rastrear funções em Linux, podemos começar a utilizar as mais diversas ferramentas que fazem interface com ele, de modo a facilitar o seu uso. O trace-cmd é um exemplo disso, por meio de apenas alguns comandos relativamente simples, podemos realizar as mesmas análises mostradas anteriormente.

Todos os comandos disponíveis estão descritos na documentação oficial da ferramenta, e a recomendação é que seja feita a leitura completa. Para os nossos propósitos, os seguintes comandos serão usados:

  • record – Começa a coletar dados de rastreamento em tempo real, os dados são salvos em um arquivo chamado: trace.dat.
  • report – Ler o arquivo trace.dat e o converte em texto ASCII.

A instalação do trace-cmd pode ser feita por meio de um gerenciador de pacotes, no Raspberry Pi OS:

Instalação do trace-cmd no Raspberry Pi OS.

Os comandos a seguir nos permitem coletar dados de rastreamento pelas mesmas técnicas usadas nos exemplos anteriores.

Rastreamento sem filtros

Coleta de dados de rastreamento por function_graph no trace-cmd.

Rastreamento com filtro por nome de função

Coleta de dados de rastreamento por function_graph com filtro por nome de função no trace-cmd.

Ao fim da etapa de coleta de dados, podemos utilizar o comando report para obter um resultado similar ao que vimos anteriormente.  

Uma das principais vantagens de utilizar o trace-cmd é a facilidade com a qual podemos adicionar novos filtros, o que pode ajudar a diminuir a quantidade de pontos de rastreamento coletados, facilitando a análise. Eventos são entidades utilizadas por sistemas operacionais para sincronização, dentre outras coisas. Podemos filtrar nossos resultados com base nesses eventos, caso estejamos rastreando um problema relacionado. Para verificar a lista de eventos disponíveis, podemos utilizar: 

Também podemos limitar o arquivo de rastreamento filtrando apenas às entradas relacionadas a um processo por meio de seu PID (Identificador de Processos), ou gravar os dados de rastreamento apenas durante a execução de uma aplicação em user space. Respectivamente:

Isso pode diminuir significantemente o tempo necessário para investigar a saída do trace-cmd, mas mesmo utilizando um conjunto ideal de filtros, os arquivos trace.dat podem ser relativamente grandes, o que faz com que sua análise manual seja trabalhosa. Uma forma de facilitar a análise de arquivos de rastreamento são as ferramentas de análise visual. Um exemplo disso é o Kernel Shark. Por meio desta ferramenta é possível processar os dados contidos no arquivo trace.dat, de forma a permitir uma melhor visualização do comportamento da plataforma durante aquele período. As ferramentas de zoom são particularmente interessantes, uma vez que é possível focar o estudo em determinados pontos do período gravado, facilitando a investigação.

Exemplo: Depurando uma degradação de performance

Dada a seguinte situação: Após uma atualização do Kernel em um produto, observou-se uma queda significativa de desempenho na nova versão. Essa queda não se limitou apenas à aplicação padrão em user space, mas afeta a plataforma como um todo. 

Durante a análise do desempenho de vários subsistemas críticos à performance, utilizamos o comando abaixo para verificar o comportamento das funções relacionadas à alocação de memória:

Uma análise preliminar dos dados de rastreamento revela que a função kvmalloc_node() claramente consome significativamente mais tempo para ser executada que suas contrapartes similares. Ao ajustarmos o filtro de coleta de dados de rastreamento, podemos confirmar esse comportamento.

Resultado da coleta de dados de rastreamento, evidenciando a função com defeito.

Depois disso, verificamos o comportamento dessa função na versão anterior do produto e confirmamos que a função está consumindo muito mais tempo de processamento na versão atual. Ao verificar as mudanças nessa parte do código-fonte, identificamos a causa do problema. 

Causa raiz da degradação de performance observada.

Embora esse exemplo seja propositalmente simples e não leve em consideração possíveis mudanças em parâmetros de gestão energética, dentre outros subsistemas críticos à performance de um produto, na minha experiência, esse tipo de análise já foi muito útil na diminuição do escopo de problemas, facilitando a análise posterior do código.  

Rastreamento no Android

As ferramentas e estratégias mostradas até aqui, em teoria, também podem ser utilizadas para depurar a versão modificada do Linux que opera por baixo do Android. Entretanto, a Google fornece diversas outras ferramentas que facilitam a análise de problemas de performance e estabilidade. Aqui, vamos fazer uma introdução ao Perfetto, uma vez que sua integração com o ftrace, somado à facilidade relativa de utilização, resultam em uma ferramenta poderosa para análise de performance e rastreamento de processos.

Perfetto

O Perfetto é uma ferramenta desenvolvida pela Google que une rastreamento de código nativo, com caracterização de performance (utilização/frequência de CPU, demanda de consumo de memória dinâmica, dentre outros) de sistemas baseados em Linux, porém, sua verdadeira força se mostra no Android, dado a integração com o framework e a HAL desse sistema. Com essa ferramenta é possível adicionar rastreamento em aplicações em user space e aplicações do Android de forma relativamente simples, o que junto com o as demais informações aferidas, permite um entendimento profundo do comportamento da plataforma durante sua execução. 

Na página oficial do Perfetto UI encontramos um arquivo de rastreamento já coletado em um dispositivo Android. A figura abaixo, mostra que mesmo com uma análise superficial, podemos ter uma noção de fatores como: alocação de memória, frequência das CPUs e comportamento de planejamento preemptivo, tanto da aplicação de câmera da Google, quanto no serviço provedor de dados de câmera. 

Interface gráfica do Perfetto, evidenciando a caracterização de performance no período de coleta de dados de rastreamento.

Outro fator interessante do Perfetto em comparação com outras ferramentas de rastreamento é a facilidade de utilização. Na documentação, encontramos instruções de como capturar dados de rastreamento utilizando linha de comando no Android e em outros sistemas Linux, semelhante há como fizemos anteriormente. O diferencial é a ferramenta web de coleta de dados de rastreamento para dispositivos Android, que permite ajustes no perfil de rastreamento, além de outras configurações. Abaixo, podemos ver esse processo em andamento no Pixel 6. 

Processo de coleta de dados de rastreamentos por meio da interface gráfica do Perfetto.

É importante notar que por questão de performance, a maioria dos probes utilizado pelo Perfetto para aquisição de dados, não é habilitada por padrão, sendo necessário o estudo da documentação para habilitar esses pontos de interesse. Além disso, alguns probes, são customizados pelo fabricante do hardware, então pode ser necessário procurar por documentação específica deste. 

Exemplo: Adicionando probes em aplicações Android

Adicionar seções de rastreamento (probes) em aplicações em user space, pode ajudar a identificar exatamente quando uma operação com alta demanda computacional está sendo executada, e assim, junto com as demais informações de rastreamento, analisar possíveis pontos de otimização. O Perfetto fornece uma API para adicionar seções de processamento em locais de interesse em código nativo. Para aplicações Android, apenas com o trecho de código abaixo em qualquer aplicação escrita em Java, conseguimos visualizar a duração e localização de uma região de código no Perfetto UI, como podemos ver em seguida:

Interface gráfica do Perfetto, evidenciando a seção Change Button.

Se, por exemplo, identificarmos que slow_task() é executada no mesmo momento que uma outra operação de alta demanda computacional, podemos tentar evitar esse comportamento ou propor mudanças de gestão enérgica para chegar em um tempo alvo de execução.

Também podemos usar essa estratégia para identificar mais facilmente pontos de interesse no framework e HAL, caso nossa aplicação faça uma chamada direta dentro da seção em estudo, diminuindo o escopo de análise de problemas. 

Conclusão

Conforme os sistemas vão se tornando mais complexos, a depuração também fica mais difícil. Se usadas corretamente, as ferramentas de análise de rastreamento podem ser aliadas importantes nesse processo. Embora ainda exista uma alta barreira de entrada para que desenvolvedores de Linux e Android possam utilizar as ferramentas nativas de rastreamento, instrumentos como o Perfetto são um passo na direção correta por fornecerem um ambiente intuitivo para análise de plataforma.

Referências

https://www.kernel.org/doc/html/v5.0/trace/ftrace.html

https://man7.org/linux/man-pages/man1/trace-cmd.1.html

https://opensource.com/article/21/7/linux-kernel-trace-cmd

https://www.itdev.co.uk/blog/how-debug-linux-driver-using-ftrace

https://kernelshark.org/

https://perfetto.dev/docs/

Licença Creative Commons Esta obra está licenciada com uma Licença Creative Commons Atribuição-CompartilhaIgual 4.0 Internacional.
Home » Software » Desvendando o código: Rastreamento de código em Linux e Android

JUNTE-SE HOJE À COMUNIDADE EMBARCADOS