O Bazel é complexo e faz muitas coisas diferentes ao longo de uma versão, sendo que algumas delas podem ter impacto no desempenho da versão. Esta página tenta mapear alguns desses conceitos do Bazel para as implicações no desempenho do build. Embora não seja extenso, incluímos alguns exemplos de como detectar problemas de desempenho de build com a extração de métricas e o que você pode fazer para corrigi-los. Esperamos que você possa aplicar esses conceitos ao investigar as regressões de desempenho do build.
Builds limpos e incrementais
Um build limpo é aquele que cria tudo do zero, enquanto um build incremental reutiliza algum trabalho já concluído.
Sugerimos que você analise builds limpos e incrementais separadamente, especialmente quando você estiver coletando / agregando métricas que dependem do estado dos caches do Bazel (por exemplo, métricas de tamanho de solicitação de build). Eles também representam duas experiências do usuário diferentes. Em comparação com o início de um build limpo do zero (o que leva mais tempo devido a um cache frio), os builds incrementais acontecem com muito mais frequência, já que os desenvolvedores iteram no código (normalmente mais rápido, já que o cache geralmente já está quente).
Você pode usar o campo CumulativeMetrics.num_analyses
no BEP para classificar
builds. Se for num_analyses <= 1
, é um build limpo. Caso contrário, podemos classificá-lo de maneira ampla para ser provavelmente um build incremental, ou seja, o usuário pode ter mudado para sinalizações ou destinos diferentes, causando um build limpo. Qualquer definição mais rigorosa de incrementabilidade provavelmente terá que ser apresentada na forma de uma heurística, por exemplo, analisando o número de pacotes carregados (PackageMetrics.packages_loaded
).
Métricas determinísticas do build como um proxy para o desempenho do build
Medir o desempenho do build pode ser difícil devido à natureza não determinista de determinadas métricas, por exemplo, o tempo de CPU do Bazel ou os tempos de fila em um cluster remoto. Dessa forma, pode ser útil usar métricas determinísticas como proxy para a quantidade de trabalho feito pelo Bazel, o que afeta o desempenho dele.
O tamanho de uma solicitação de versão pode ter uma implicação significativa no desempenho do build. Um build maior pode representar mais trabalho na análise e criação dos gráficos de build. O crescimento orgânico de builds vem naturalmente com o desenvolvimento, à medida que mais dependências são adicionadas/criadas e, portanto, ficam mais complexas e se tornam mais caras para criar.
Podemos dividir esse problema em várias fases de compilação e usar as seguintes métricas como métricas de proxy para o trabalho feito em cada fase:
PackageMetrics.packages_loaded
: o número de pacotes carregados. Uma regressão aqui representa mais trabalho que precisa ser feito para ler e analisar cada arquivo BUILD extra na fase de carregamento.TargetMetrics.targets_configured
: representa o número de destinos e aspectos configurados no build. Uma regressão representa mais trabalho na construção e passando pelo gráfico de destino configurado.- Isso geralmente se deve à adição de dependências e à necessidade de criar o gráfico do fechamento transitivo delas.
- Use cquery para descobrir onde novas dependências podem ter sido adicionadas.
ActionSummary.actions_created
: representa as ações criadas no build, e uma regressão representa mais trabalho na construção do gráfico de ações. Isso também inclui ações não utilizadas que podem não ter sido executadas.- Use aquery para depurar regressões.
Sugerimos começar com
--output=summary
antes de detalhar ainda mais com--skyframe_state
.
- Use aquery para depurar regressões.
Sugerimos começar com
ActionSummary.actions_executed
: o número de ações executadas, uma regressão representa diretamente mais trabalho na execução dessas ações.- O BEP grava as estatísticas de ação
ActionData
que mostram os tipos de ação mais executados. Por padrão, ele coleta os 20 principais tipos de ação, mas você pode transmitir o--experimental_record_metrics_for_all_mnemonics
para coletar esses dados de todos os tipos que foram executados. - Isso ajudará você a descobrir que tipos de ações foram executadas (além disso).
- O BEP grava as estatísticas de ação
BuildGraphSummary.outputArtifactCount
: o número de artefatos criados pelas ações executadas.- Se o número de ações executadas não aumentar, é provável que a implementação de uma regra tenha sido alterada.
Todas essas métricas são afetadas pelo estado do cache local. Portanto, verifique se os builds de onde você extrai essas métricas são builds limpos.
Observamos que uma regressão em qualquer uma dessas métricas pode ser acompanhada de regressões no tempo decorrido, no tempo da CPU e no uso da memória.
Uso de recursos locais
O Bazel consome uma variedade de recursos em sua máquina local, tanto para analisar o gráfico de compilação e impulsionar a execução quanto para executar ações locais. Isso pode afetar o desempenho / disponibilidade de sua máquina para executar a compilação e também outras tarefas.
Tempo gasto
Talvez as métricas mais suscetíveis a ruído (e possam variar bastante de build para build) sejam o tempo. Em particular, o tempo decorrido, o tempo de CPU e o tempo do sistema. Você pode usar bazel-bench para ter um comparativo de mercado para essas métricas e, com um número suficiente de --runs
, poderá aumentar a significância estatística da sua medição.
O tempo decorrido é o tempo real decorrido.
- Se apenas os regressões de tempo decorridos, sugerimos coletar um perfil de trace do JSON e procurar diferenças. Caso contrário, provavelmente seria mais eficiente investigar outras métricas reabertos, porque elas poderiam ter afetado o tempo intermediário.
O tempo de CPU é o tempo que a CPU leva para executar o código do usuário.
- Se o tempo de CPU reaberto em duas confirmações de projeto, sugerimos coletar
um perfil de CPU do Starlark. É provável que você também use
--nobuild
para restringir o build à fase de análise, já que é nele que a maior parte do trabalho pesado da CPU é feito.
- Se o tempo de CPU reaberto em duas confirmações de projeto, sugerimos coletar
um perfil de CPU do Starlark. É provável que você também use
O horário do sistema é o tempo que a CPU gasta no kernel.
- Se o tempo do sistema regredir, ele estará principalmente correlacionado com a E/S quando o Bazel ler arquivos do sistema.
Criação de perfil de carga do sistema
Usando a sinalização
--experimental_collect_load_average_in_profiler
introduzida no Bazel 6.0, o
criador de perfil de rastreamento JSON coleta a
média de carga do sistema durante a invocação.
Figura 1. Perfil que inclui a média de carregamento do sistema.
Uma carga alta durante uma invocação do Bazel pode ser uma indicação de que o Bazel programa muitas ações locais em paralelo para sua máquina. Ajuste
--local_cpu_resources
e --local_ram_resources
,
especialmente em ambientes de contêiner, pelo menos até que
#16512 seja mesclado.
Como monitorar o uso da memória do Bazel
Há duas fontes principais para acessar o uso da memória do Bazel: o Bazel info
e o BEP.
bazel info used-heap-size-after-gc
: a quantidade de memória usada em bytes após uma chamada paraSystem.gc()
.- O Bazel banco também fornece comparativos para essa métrica.
- Além disso, há
peak-heap-size
,max-heap-size
,used-heap-size
ecommitted-heap-size
(consulte a documentação), mas são menos relevantes.
MemoryMetrics.peak_post_gc_heap_size
do BEP: tamanho do tamanho máximo do heap da JVM em bytes após a GC (requer configuração--memory_profile
que tenta forçar uma GC completa).
Uma regressão no uso da memória geralmente é resultado de uma regressão nas métricas do tamanho da solicitação de build, que geralmente ocorrem devido à adição de dependências ou a uma mudança na implementação da regra.
Para analisar a pegada de memória do Bazel em um nível mais granular, recomendamos usar o criador de perfil de memória integrado para regras.
Criação de perfil de memória de workers permanentes
Embora os workers permanentes possam ajudar a acelerar os builds de maneira significativa (especialmente para linguagens interpretadas), o consumo de memória pode
ser problemático. O Bazel coleta métricas sobre os workers, especificamente, o campo WorkerMetrics.WorkerStats.worker_memory_in_kb
informa a quantidade de memória que os workers usam (por mnemônica).
O criador de perfil de rastreamento JSON também
coleta o uso persistente da memória do worker durante a invocação transmitindo a sinalização
--experimental_collect_system_network_usage
(nova no Bazel 6.0).
Figura 2. Perfil que inclui o uso de memória dos workers.
Reduzir o valor de
--worker_max_instances
(padrão 4) pode ajudar a reduzir
a quantidade de memória usada pelos workers persistentes. Estamos trabalhando ativamente para deixar o gerenciador de recursos e o programador do Bazel mais inteligentes para que esse ajuste refinado seja necessário com menos frequência no futuro.
Monitoramento do tráfego de rede para compilações remotas
Na execução remota, o Bazel faz o download de artefatos que foram criados como resultado de ações de execução. Dessa forma, sua largura de banda de rede pode afetar o desempenho do build.
Se você estiver usando a execução remota para seus builds, monitore
o tráfego de rede durante a invocação usando o proto
NetworkMetrics.SystemNetworkStats
do BEP,
que requer a transmissão de --experimental_collect_system_network_usage
.
Além disso, os perfis de trace JSON
permitem que você visualize o uso da rede em todo o sistema ao longo do build,
transmitindo a sinalização --experimental_collect_system_network_usage
(nova no Bazel
6.0).
Figura 3. Perfil que inclui o uso de rede em todo o sistema.
Um uso de rede alto, mas plano, ao usar a execução remota, pode indicar que a rede é o gargalo na sua versão. Se você ainda não estiver usando-a, considere ativar a versão sem os bytes transmitindo --remote_download_minimal
.
Isso acelera as versões, evitando o download de artefatos intermediários desnecessários.
Outra opção é configurar um cache de disco local para economizar na largura de banda de download.