Flamegraphs no Node.js são visualizações que mostram onde o processo consome tempo de CPU. Em vez de analisar milhares de amostras de stack manualmente, o gráfico agrega pilhas semelhantes e apresenta blocos cuja largura representa a quantidade de amostras associadas a cada função.
Esse formato ajuda a encontrar loops custosos, serialização excessiva, regex lentas, compressão, criptografia, processamento de imagens, transformação de dados e chamadas repetidas. Ele é especialmente útil quando a CPU está alta e o event loop perde capacidade de responder rapidamente.
Neste guia, você aprenderá a gerar flamegraphs com Linux perf, opções do Node.js, ferramentas prontas, perfis de processos em execução, interpretação correta, limitações e um fluxo seguro de otimização.
O que é um flamegraph?
Um flamegraph agrega amostras de pilha coletadas durante um período. O eixo horizontal não representa tempo cronológico; ele mostra a proporção de amostras. O eixo vertical representa profundidade da pilha.
- Blocos largos consumiram mais amostras de CPU.
- Blocos empilhados mostram quem chamou quem.
- A parte superior representa funções mais profundas.
- A cor normalmente serve apenas para visualização, não para gravidade.
O guia oficial Flame Graphs do Node.js descreve o processo com Linux perf e ferramentas de Brendan Gregg.
Quando usar?
Use flamegraphs quando houver:
- CPU sustentada alta;
- throughput abaixo do esperado;
- event loop bloqueado;
- picos de latência durante processamento;
- funções síncronas suspeitas;
- regressão de performance após mudança;
- necessidade de comparar duas implementações.
Para medir o efeito externo, consulte Autocannon no Node.js. O gerador de carga reproduz o problema; o flamegraph mostra onde a CPU é utilizada.
Prepare um cenário reproduzível
Antes de coletar, defina:
- versão do Node.js;
- commit do código;
- comando de inicialização;
- dados e dependências;
- rota ou job testado;
- taxa de carga;
- duração;
- máquina e limites.
Sem consistência, diferenças no gráfico podem vir do ambiente.
Instalando perf no Linux
Em distribuições baseadas em Debian ou Ubuntu:
sudo apt update
sudo apt install linux-tools-common linux-tools-genericDependendo do kernel, pode ser necessário instalar o pacote específico:
sudo apt install linux-tools-$(uname -r)Confirme:
perf --versionColetando com perf
perf record \
-e cycles:u \
-g \
-- node \
--perf-basic-prof \
--interpreted-frames-native-stack \
server.jsEnquanto o processo roda, aplique carga. Depois finalize de forma controlada. O perf gera um arquivo perf.data.
Gerando saída textual
perf script > perf.outEsse arquivo contém as pilhas amostradas. Ele pode ser convertido para o formato de flamegraph.
Ferramentas FlameGraph
Clone o projeto oficial de Brendan Gregg:
git clone https://github.com/brendangregg/FlameGraph.gitO repositório está disponível em FlameGraph no GitHub.
cat perf.out \
| ./FlameGraph/stackcollapse-perf.pl \
| ./FlameGraph/flamegraph.pl --colors=js \
> profile.svgAbra profile.svg no navegador.
Profiling de processo já em execução
perf record \
-F 99 \
-p $(pgrep -n node) \
-g \
-- sleep 30-F 99 coleta 99 amostras por segundo. O comando sleep 30 mantém a coleta ativa por trinta segundos.
Em produção, execute somente com autorização. Profiling pode exigir privilégios e gerar artefatos com nomes de funções e caminhos internos.
Opções do Node.js
As flags ajudam o perf a resolver símbolos JavaScript:
node \
--perf-basic-prof-only-functions \
--interpreted-frames-native-stack \
server.js--perf-basic-prof-only-functions produz menos dados e tende a ter overhead menor. Teste a combinação na versão de Node.js realmente usada.
Alternativa com CPU profile
O Node.js também gera perfil do V8:
node --cpu-prof server.jsO arquivo pode ser aberto no Chrome DevTools. Essa abordagem é portátil e útil quando Linux perf não está disponível.
Para depuração remota e perfis, veja Inspector no Node.js.
Ferramenta 0x
O 0x automatiza coleta e renderização:
npm install --save-dev 0x
npx 0x server.jsDepois aplique carga e encerre o processo. O 0x gera um relatório navegável. Fixe a versão e valide compatibilidade com seu runtime.
Como ler o gráfico
Comece pelos blocos largos próximos ao topo. Eles representam funções que aparecem frequentemente nas amostras. Clique para ampliar uma região e leia a cadeia de chamadas abaixo.
Perguntas úteis:
- essa função deveria ser chamada tantas vezes?
- há conversão ou serialização repetida?
- o algoritmo cresce mal com a entrada?
- há trabalho síncrono em uma rota HTTP?
- uma dependência domina o custo?
- o trabalho pode ser movido para worker?
Largura não significa bug
Uma função pode ser larga porque representa trabalho essencial. O flamegraph indica onde investigar, não determina automaticamente o que remover. Sempre relacione o bloco ao comportamento do produto e às métricas de latência.
Exemplo: JSON excessivo
Se JSON.stringify aparece muito largo, procure respostas gigantes, serialização repetida ou logs de objetos completos.
const payload = buildLargePayload(data);
const body = JSON.stringify(payload);
res.end(body);Possíveis ações:
- paginar resultados;
- selecionar campos;
- evitar stringify duplicado;
- usar streaming quando apropriado;
- não registrar payload completo.
Exemplo: regex lenta
Uma expressão com backtracking pode dominar CPU. Teste entradas adversariais e simplifique o padrão. Consulte ReDoS no Node.js.
Exemplo: criptografia
Hashing de senhas deve ser caro por segurança. Se aparece no flamegraph, confirme que o custo é esperado e que não está sendo executado desnecessariamente várias vezes. Limite concorrência para evitar saturação.
Worker Threads
Trabalho CPU-bound pode ser transferido:
import { Worker } from 'node:worker_threads';
const worker = new Worker(
new URL('./worker.js', import.meta.url),
{ workerData: input }
);Veja Worker Threads no Node.js. A mudança não reduz necessariamente o custo total, mas evita bloquear a thread que atende requisições.
Comparando antes e depois
Repita o mesmo cenário:
- registre benchmark inicial;
- capture o primeiro flamegraph;
- faça uma alteração pequena;
- capture outro perfil;
- compare throughput e percentis;
- verifique se o custo migrou para outra função.
Uma otimização local pode piorar memória, I/O ou latência de cauda.
Overhead
Profiling adiciona custo. Não use os números absolutos coletados como estimativa exata de produção. Use o perfil para proporções e relações entre funções, e valide o resultado com benchmark sem profiler.
Containers
Perf em containers pode exigir capacidades e acesso ao kernel:
docker run --rm \
--cap-add SYS_ADMIN \
--security-opt seccomp=unconfined \
my-appConceder capacidades aumenta risco. Prefira ambiente de profiling isolado e políticas mínimas. Em clusters, considere uma réplica temporária dedicada.
Kernel e permissões
O valor de perf_event_paranoid controla acesso:
cat /proc/sys/kernel/perf_event_paranoidAlterações exigem avaliação de segurança. Não reduza proteção de hosts compartilhados sem entender o impacto.
Funções internas
O gráfico pode conter V8, libc e internals do Node.js. Filtrar torna a visão mais limpa, mas pode esconder o verdadeiro gargalo. Gere primeiro sem filtros e use limpeza apenas para facilitar leitura.
Código otimizado pelo V8
O V8 compila e otimiza funções durante a execução. Nomes podem aparecer ausentes ou diferentes dependendo das flags, versão e perf. Se o gráfico parece incompleto, compare com --cpu-prof ou Inspector.
Sampling versus instrumentação
Flamegraphs por sampling observam pilhas periodicamente e possuem overhead relativamente controlado. Instrumentação mede cada chamada, podendo ser mais detalhada e mais cara. Para CPU geral, sampling costuma ser um bom começo.
Combine com observabilidade
Durante a coleta, observe:
- CPU por processo;
- event loop lag;
- garbage collection;
- requisições por segundo;
- p95 e p99;
- erros;
- pool do banco;
- memória.
Consulte Performance Hooks no Node.js e Métricas Prometheus no Node.js.
Dados sensíveis
Perfis geralmente registram nomes de funções, bibliotecas e caminhos. Eles podem revelar arquitetura interna. Armazene artefatos com acesso restrito e prazo de retenção.
Erros comuns
- Confundir largura com sequência temporal: o eixo horizontal é proporção.
- Otimizar sem benchmark: melhora visual pode não melhorar usuário.
- Perfil sem reproduzir o problema: coleta caminho irrelevante.
- Ignorar overhead: números absolutos ficam distorcidos.
- Filtrar cedo demais: remove causa real.
- Usar ambiente diferente: comparação fica inválida.
- Dar privilégios excessivos: cria risco operacional.
Fluxo recomendado
- Confirme CPU alta com métricas.
- Reproduza sob carga controlada.
- Colete perfil curto e representativo.
- Localize blocos largos.
- Relacione ao código e ao negócio.
- Faça uma mudança por vez.
- Repita o benchmark sem profiler.
- Documente resultado e ambiente.
Conclusão
Flamegraphs no Node.js transformam amostras de CPU em uma visualização que facilita localizar trabalho síncrono e funções dominantes. Linux perf oferece controle detalhado, enquanto --cpu-prof, Inspector e ferramentas como 0x simplificam a coleta.
O gráfico é um mapa de investigação, não uma resposta automática. Reproduza o problema, valide símbolos, observe métricas e compare antes e depois. Com esse processo, flamegraphs ajudam a otimizar o que realmente limita throughput e latência, sem depender de suposições.



