O Bazel é complexo e faz muitas coisas diferentes ao longo de um build, algumas das quais podem afetar o desempenho do build. Esta página tenta mapear alguns desses conceitos do Bazel para as implicações na performance do build. Embora não seja extensa, incluímos alguns exemplos de como detectar problemas de desempenho de build extraindo métricas e o que você pode fazer para corrigi-los. Esperamos que você aplique esses conceitos ao investigar regressões de desempenho do build.
Builds limpos x incrementais
Um build limpo é aquele que cria tudo do zero, enquanto um build incremental reutiliza algum trabalho já concluído.
Sugerimos analisar builds limpos e incrementais separadamente, especialmente quando você está 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 de usuário diferentes. Em comparação com o início de um build limpo do zero (que leva mais tempo devido a um cache frio), os builds incrementais acontecem com muito mais frequência à medida que os desenvolvedores iteram o código (geralmente mais rápido, já que o cache geralmente já está aquecido).
É possível usar o campo CumulativeMetrics.num_analyses
no BEP para classificar
builds. Se num_analyses <= 1
, é um build limpo. Caso contrário, podemos
categorizá-lo como um build incremental. O usuário pode ter mudado
para flags ou destinos diferentes, causando um build limpo. Qualquer
definição mais rigorosa de incrementalidade provavelmente terá que vir na forma
de uma heurística, por exemplo, analisando o número de pacotes carregados
(PackageMetrics.packages_loaded
).
Métricas de build determinísticas como um proxy para o desempenho do build
Medir o desempenho do build pode ser difícil devido à natureza não determinística de algumas métricas (por exemplo, o tempo de CPU ou de fila do Bazel em um cluster remoto). Por isso, pode ser útil usar métricas determinísticas como um proxy para a quantidade de trabalho realizada pelo Bazel, que afeta a performance.
O tamanho de uma solicitação de build pode ter uma implicação significativa no desempenho do build. Um build maior pode representar mais trabalho na análise e construção dos gráficos de build. O crescimento orgânico dos builds ocorre naturalmente com o desenvolvimento, à medida que mais dependências são adicionadas/criadas, e, portanto, crescem em complexidade e se tornam mais caras de criar.
Podemos dividir esse problema nas várias fases de build e usar as seguintes métricas como métricas de proxy para o trabalho realizado em cada fase:
PackageMetrics.packages_loaded
: o número de pacotes carregados com sucesso. Uma regressão aqui representa mais trabalho que precisa ser feito para ler e analisar cada arquivo BUILD adicional 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 na travessia do gráfico de destino configurado.- Isso geralmente ocorre devido à adição de dependências e à necessidade de construir o gráfico do fechamento transitivo.
- Use cquery para encontrar 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 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 coleciona os 20 principais tipos de ação, mas você pode transmitir o--experimental_record_metrics_for_all_mnemonics
para coletar esses dados para todos os tipos de ação que foram executados. - Isso vai ajudar você a descobrir que tipo de ação foi executada (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 uma implementação de regra tenha sido alterada.
Todas essas métricas são afetadas pelo estado do cache local. Portanto, é importante garantir que os builds de onde você extrai essas métricas sejam builds limpos.
Observamos que uma regressão em qualquer uma dessas métricas pode ser acompanhada por regressões no tempo de parede, tempo de CPU e uso de memória.
Uso de recursos locais
O Bazel consome vários recursos na sua máquina local (tanto para analisar o gráfico de build e direcionar a execução quanto para executar ações locais). Isso pode afetar o desempenho / disponibilidade da máquina na execução do build e também outras tarefas.
Tempo gasto
Talvez as métricas mais suscetíveis a ruídos (e que podem variar muito de build
para build) sejam o tempo, em particular, o tempo de parede, de CPU e do sistema. Você pode
usar o bazel-bench para receber
um comparativo para essas métricas. Com um número suficiente de --runs
, é possível
aumentar a significância estatística da medição.
O tempo real é o tempo real decorrido.
- Se apenas o tempo de parede regredir, sugerimos coletar um perfil de rastreamento JSON e procurar diferenças. Caso contrário, seria mais eficiente investigar outras métricas regressadas, já que elas poderiam ter afetado o tempo de espera.
Tempo de CPU é o tempo que a CPU gasta executando o código do usuário.
- Se o tempo da CPU regredir em duas confirmações de projeto, sugerimos coletar
um perfil de CPU do Starlark. Você provavelmente também vai usar
--nobuild
para restringir o build à fase de análise, já que é onde a maior parte do trabalho pesado da CPU é feito.
- Se o tempo da CPU regredir em duas confirmações de projeto, sugerimos coletar
um perfil de CPU do Starlark. Você provavelmente também vai usar
O tempo do sistema é o tempo gasto pela CPU no kernel.
- Se o tempo do sistema regredir, ele estará principalmente correlacionado com a E/S quando o Bazel ler arquivos do sistema de arquivos.
Criação de perfis de carga em todo o sistema
Usando a flag
--experimental_collect_load_average_in_profiler
introduzida no Bazel 6.0, o
analisador de rastros JSON coleta a
média de carga do sistema durante a invocação.
Figura 1. Perfil que inclui a média de carga do sistema.
Uma carga alta durante uma invocação do Bazel pode indicar que ele está programando
muitas ações locais em paralelo para sua máquina. Talvez seja necessário ajustar
--local_cpu_resources
e --local_ram_resources
,
principalmente em ambientes de contêiner (pelo menos até que
#16512 seja mesclada).
Como monitorar o uso de memória do Bazel
Há duas fontes principais para acessar o uso de memória do Bazel, o info
do Bazel 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 bench também fornece comparativos de mercado 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 eles são menos relevantes.
MemoryMetrics.peak_post_gc_heap_size
do BEP: tamanho do pico da alocação heap da JVM em bytes após o GC (requer a configuração--memory_profile
que tenta forçar um GC completo).
Uma regressão no uso da memória geralmente é resultado de uma regressão nas métricas de tamanho da solicitação de build, que geralmente são 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 Memory Profiler integrado para regras.
Criação de perfil de memória de workers persistentes
Embora os workers persistentes possam ajudar a acelerar os builds
significativamente (especialmente para linguagens interpretadas), o consumo de memória deles pode
ser problemático. O Bazel coleta métricas sobre os workers. Em particular, o
campo WorkerMetrics.WorkerStats.worker_memory_in_kb
informa a quantidade de memória
usada pelos workers (por mnemônico).
O analisador de rastros JSON também
coleciona o uso de memória persistente do worker durante a invocação transmitindo a
indicador --experimental_collect_system_network_usage
(novo no Bazel 6.0).
Figura 2. Perfil que inclui o uso de memória dos workers.
Diminuir o valor de
--worker_max_instances
(padrão 4) pode ajudar a reduzir
a quantidade de memória usada por workers persistentes. Estamos trabalhando ativamente
para tornar o gerenciador de recursos e o programador do Bazel mais inteligentes, para que esse ajuste fino
seja necessário com menos frequência no futuro.
Monitorar o tráfego de rede para builds remotos
Na execução remota, o Bazel faz o download de artefatos que foram criados como resultado da execução de ações. Por isso, a largura de banda da rede pode afetar o desempenho do build.
Se você estiver usando a execução remota para seus builds, considere
monitorar o tráfego de rede durante a invocação usando o
proto NetworkMetrics.SystemNetworkStats
do BEP
(requer a transmissão de --experimental_collect_system_network_usage
).
Além disso, os perfis de rastreamento JSON
permitem que você confira o uso da rede em todo o sistema durante o build
transmitindo a flag --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 bastante plano, ao usar a execução remota pode indicar
que a rede é o gargalo do build. Se você ainda não estiver usando,
ative o build sem os bytes transmitindo
--remote_download_minimal
.
Isso vai acelerar seus builds 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.