Flamegraphs são visualizações de amostras de CPU agrupadas por pilha de chamadas. Em aplicações Node.js, eles mostram onde o processo gasta tempo executando JavaScript, funções nativas, garbage collection e bibliotecas do sistema. São especialmente úteis para localizar operações síncronas, serialização cara, loops, regex, criptografia e processamento que bloqueia o event loop.
Cada retângulo representa uma função. A largura indica a quantidade de amostras em que aquela função apareceu, incluindo chamadas abaixo dela. A posição vertical representa a pilha: chamadores ficam embaixo e funções chamadas ficam acima. A ordem horizontal não é uma linha do tempo.
Quando usar
Use flamegraphs quando:
- CPU está alta;
- throughput caiu;
- event loop lag aumentou;
- p99 piorou sob carga;
- uma rota consome muito processamento;
- uma mudança de código causou regressão;
- não está claro qual função é o gargalo.
Para vazamentos de memória, heap snapshots e heap profiler são mais adequados. Flamegraphs respondem principalmente “onde a CPU está gastando tempo?”.
Como ler
Procure barras largas. Uma barra larga no topo indica função folha consumindo CPU. Uma barra larga embaixo pode apenas representar um chamador comum.
A cor normalmente não indica gravidade; em muitos geradores é apenas estética ou categoria. Não conclua que vermelho é erro.
Inclusive e exclusive time
Tempo inclusivo inclui funções chamadas. Tempo exclusivo representa amostras na própria função. Em um flamegraph tradicional, a largura da função inclui descendentes; a parte sem filhos acima aproxima trabalho exclusivo.
Reproduzindo a carga
Antes de perfilar, crie um cenário estável:
- inicie a mesma versão usada no problema;
- aqueça aplicação, JIT, pools e caches;
- gere carga representativa;
- confirme CPU e latência;
- capture durante o período lento;
- repita para verificar consistência.
Um perfil sem carga relevante pode mostrar apenas inicialização ou espera.
Usando 0x
0x é uma ferramenta pré-empacotada para gerar flamegraph:
npm install -D 0x
npx 0x dist/server.jsEm outro terminal, gere carga:
npx autocannon -c 50 -d 30 http://127.0.0.1:3000/api/itemsInterrompa o processo de forma controlada e abra o relatório gerado.
Produção com 0x
Profiling adiciona overhead. Em produção, use por período curto, em uma réplica isolada e com autorização. Remova o pod do balanceador se o problema puder ser reproduzido sem tráfego externo, ou mantenha pequena parcela de tráfego quando precisa capturar o comportamento real.
Linux perf
O fluxo oficial usa perf. Instale ferramentas do kernel e execute:
perf record \
-e cycles:u \
-g \
-- node \
--perf-basic-prof \
--interpreted-frames-native-stack \
app.jsDepois gere texto:
perf script > perfs.outConverta com FlameGraph de Brendan Gregg:
cat perfs.out \
| ./FlameGraph/stackcollapse-perf.pl \
| ./FlameGraph/flamegraph.pl --colors=js \
> profile.svgAbra o SVG em um navegador e clique em áreas para zoom.
Perfilar processo em execução
perf record -F 99 -p PID -g -- sleep 30-F 99 coleta aproximadamente 99 amostras por segundo. sleep 30 define duração da captura. Aumentar frequência melhora resolução, mas aumenta overhead e tamanho.
Identificando o PID
pgrep -n node
ps -ef | grep nodeEm containers, o processo pode ser PID 1 dentro do namespace e outro PID no host. Execute perf no nível que possui permissões e símbolos adequados.
Flags do Node.js
--perf-basic-prof fornece informações para nomear funções JavaScript. --perf-basic-prof-only-functions gera menos dados e menor overhead. --interpreted-frames-native-stack melhora nomes de frames interpretados.
Sem flags, muitas barras podem aparecer apenas como chamadas genéricas do V8.
Permissões do perf
Linux pode bloquear profiling:
cat /proc/sys/kernel/perf_event_paranoid
Alterar essa configuração afeta segurança do host. Em produção, siga política da infraestrutura e restaure o valor. Containers podem precisar de capabilities e acesso a perf events.
Filtrando internos
É possível remover frames do V8 e libc para reduzir ruído, mas filtre somente depois de gerar uma versão completa. O gargalo pode estar em código nativo ou no próprio runtime.
sed -i -r \
-e "/(v8::internal::|Builtin:|Stub:|\\[unknown\\])/d" \
perfs.outFrames desconhecidos
[unknown], endereços ou nomes quebrados podem indicar símbolos ausentes, versão do perf sem demangle, otimização do V8 ou stacks não capturadas. Compare com 0x, flags adicionais ou outro profiler.
Exemplo de CPU síncrona
function gerarRelatorio(items) {
return items
.map((item) => JSON.stringify(normalizar(item)))
.sort()
.join('\n');
}Um flamegraph pode mostrar JSON.stringify, sort e normalizar como barras largas. A solução pode ser streaming, paginação, cache, worker thread ou pré-processamento.
Regex e ReDoS
Expressões regulares vulneráveis aparecem como tempo concentrado em execução de regex, especialmente com payload específico. Reproduza com entrada problemática e substitua a expressão ou limite o tamanho.
Serialização JSON
Respostas grandes podem gastar CPU em serialização. Considere:
- reduzir campos;
- paginar;
- pré-serializar conteúdo estável;
- usar schema serializers;
- streaming;
- compressão fora do processo quando aplicável.
Criptografia
Hash de senha, assinatura e criptografia são intencionalmente caros. Se aparecem largos:
- verifique parâmetros;
- limite concorrência;
- use APIs assíncronas;
- dimensione thread pool;
- não reduza segurança sem análise.
Garbage collection
Tempo em GC pode indicar alta taxa de alocação, heap pressionado ou objetos grandes. Um flamegraph mostra CPU do GC, mas heap profiler e traces de GC ajudam a explicar por quê.
Event loop
Uma função larga e síncrona bloqueia todas as conexões daquele processo. Meça event loop utilization e delay junto ao perfil. CPU baixa não elimina bloqueio: uma chamada nativa ou I/O síncrono pode parar a thread.
Worker threads
Se uma tarefa é CPU-bound e não pode ser evitada, mova para worker. Perfilar cada worker separadamente pode ser necessário, pois cada thread tem stacks próprias.
Native addons
Bibliotecas nativas podem aparecer com símbolos C/C++. Instale símbolos de debug quando disponível. Um addon bloqueando a thread principal pode explicar latência mesmo sem função JavaScript larga.
Comparando antes e depois
Use mesma carga, hardware, duração, versão do Node e dados. Compare:
- largura da função alvo;
- throughput;
- p95 e p99;
- CPU total;
- event loop delay;
- GC;
- erros.
Uma barra menor pode apenas ter movido custo para outro lugar.
Differential flamegraphs
Ferramentas de diff destacam aumento e redução de amostras entre perfis. São úteis para regressões, desde que as capturas tenham condições comparáveis.
Perfil curto ou longo
Capturas curtas isolam pico, mas têm poucas amostras. Capturas longas misturam fases. Escolha janela alinhada ao sintoma e repita.
Continuous profiling
Ferramentas como Pyroscope coletam perfis ao longo do tempo e permitem filtrar por versão, serviço e ambiente. Isso reduz necessidade de reproduzir incidentes raros. Controle overhead e cardinalidade de labels.
Kubernetes
Em clusters:
- identifique pod e container corretos;
- garanta permissões de perf;
- capture uma réplica;
- correlacione com throttling de CPU;
- verifique requests e limits;
- copie artefatos com segurança;
- remova ferramentas de debug da imagem final quando não usadas.
CPU throttling
Uma aplicação pode parecer CPU-bound porque o container é limitado. Flamegraph mostra onde usa o tempo disponível, enquanto métricas de cgroup mostram throttling. Corrigir código e ajustar limites podem ser necessários.
Benchmark junto com profiling
Use Autocannon ou k6 para manter carga constante durante a captura. O gerador deve rodar em máquina separada em medições sérias.
Overhead
Sampling é menos invasivo que instrumentação completa, mas não é gratuito. Frequência alta, símbolos e duração aumentam overhead. Faça uma captura controle para medir impacto.
Segurança dos perfis
Perfis podem revelar nomes de funções, caminhos, bibliotecas e arquitetura. Trate arquivos como dados internos, limite acesso e defina retenção.
Erros comuns
- interpretar eixo horizontal como tempo;
- otimizar barra estreita;
- capturar sem reproduzir lentidão;
- ignorar gerador saturado;
- filtrar internos cedo demais;
- comparar ambientes diferentes;
- não validar impacto após otimização;
- confundir CPU com memória.
Processo de investigação
- confirme sintoma com métricas;
- reproduza carga;
- capture perfil curto;
- localize barras largas;
- relacione a código e métricas;
- crie hipótese;
- otimize uma causa;
- repita benchmark e perfil;
- documente resultado.
Fluxo recomendado
Use profiling por amostragem durante carga estável, leia largura como CPU, investigue funções largas e valide toda otimização com percentis. Combine com Autocannon no Node.js, carga em k6, memória em Heap Snapshots e profiling contínuo em Pyroscope.
Consulte o guia oficial de flamegraphs do Node.js e o repositório oficial FlameGraph de Brendan Gregg.



