O Node.js inclui um profiler de CPU baseado no V8 que pode ser ativado com a opção --prof. Ele coleta amostras enquanto a aplicação executa e grava um arquivo de log contendo informações sobre JavaScript, código nativo, garbage collection e bibliotecas do sistema. Esse recurso é útil quando a aplicação apresenta CPU alta, throughput baixo, event loop bloqueado ou uma regressão que não aparece claramente nos logs.
O profiler integrado reduz a necessidade de instalar uma ferramenta adicional para uma primeira investigação. Ainda assim, ele deve ser usado com método: é preciso reproduzir o problema, gerar carga representativa, processar o log e validar qualquer otimização com uma nova medição.
Quando usar o –prof
O profiling por amostragem é indicado quando a pergunta principal é “onde o processo gasta tempo de CPU?”. Exemplos:
- uma rota fica lenta sob concorrência;
- o processo alcança 100% de um núcleo;
- p95 e p99 aumentam sem crescimento proporcional no banco;
- serialização de respostas grandes parece cara;
- regex, criptografia ou compressão bloqueiam a thread;
- uma nova versão reduziu requisições por segundo;
- garbage collection consome uma parcela relevante da CPU.
Para investigar memória retida, use heap snapshots. Para cadeias assíncronas lentas, combine o perfil com traces, Async Hooks ou Clinic Bubbleprof.
Executando o primeiro perfil
Inicie a aplicação com:
node --prof dist/server.jsO V8 criará um arquivo semelhante a:
isolate-0x123456789-12345-v8.logEnquanto o processo executa, gere carga em outro terminal:
npx autocannon -c 40 -d 45 http://127.0.0.1:3000/api/itemsEspere o cenário lento acontecer e encerre o servidor de forma controlada. Uma captura muito curta possui poucas amostras; uma captura longa pode misturar inicialização, períodos ociosos e diferentes tipos de tráfego.
Processando o log
O arquivo bruto não é agradável de ler. Processe-o com:
node --prof-process isolate-0x123456789-12345-v8.log > profile.txtO relatório inclui seções de ticks, código JavaScript, C++, bibliotecas compartilhadas e perfil pesado. O nome exato das seções pode variar conforme a plataforma e a versão do runtime.
Entendendo ticks
Um tick representa uma amostra periódica da pilha de execução. Quanto mais ticks uma função acumula, maior a parcela estimada de CPU associada a ela. O resultado é estatístico, não uma medição exata de cada chamada.
Procure funções com porcentagem alta e confirme se aparecem em pilhas relevantes. Uma função chamadora pode acumular tempo inclusivo porque executa descendentes caros; a causa real costuma estar em uma função folha.
Perfil pesado
A seção “Bottom up (heavy) profile” organiza amostras começando pelas funções que consumiram CPU. Ela ajuda a responder quais caminhos de chamada dominam o processamento.
Observe:
- funções JavaScript do projeto;
JSON.stringifye parsing;- ordenação de grandes coleções;
- funções de regex;
- hash, assinatura e criptografia;
- compressão;
- garbage collector;
- addons nativos;
- funções desconhecidas ou sem símbolos.
Exemplo de CPU síncrona
import { createServer } from 'node:http';
import { pbkdf2Sync } from 'node:crypto';
const server = createServer((req, res) => {
if (req.url === '/hash') {
const hash = pbkdf2Sync('senha', 'salt', 300000, 32, 'sha256');
res.end(hash.toString('hex'));
return;
}
res.end('ok');
});
server.listen(3000);Com várias requisições simultâneas, pbkdf2Sync bloqueia a thread principal. O perfil deve concentrar amostras na pilha de criptografia. A correção pode usar a versão assíncrona, uma fila ou Worker Threads. Não reduza parâmetros de segurança apenas para melhorar um benchmark.
Exemplo de serialização cara
function gerarResposta(items) {
return JSON.stringify({
generatedAt: new Date().toISOString(),
items: items.map(normalizarItem),
});
}Se a lista possui milhares de objetos, normalização e serialização podem dominar CPU. Alternativas incluem paginação, redução de campos, cache de conteúdo estável, streaming ou pré-processamento.
Aquecimento do V8
O V8 otimiza funções quentes durante a execução. Um perfil iniciado imediatamente após o bootstrap pode mostrar compilação, carregamento de módulos e caches frios, em vez do comportamento estável.
- inicie a aplicação;
- espere conexões e pools ficarem prontos;
- execute uma carga de aquecimento;
- comece o cenário medido;
- mantenha carga constante;
- encerre depois de coletar amostras suficientes.
Comparando versões
Para medir uma otimização, mantenha constantes:
- versão do Node.js;
- hardware e limites de CPU;
- conjunto de dados;
- concorrência e duração;
- estado de cache;
- variáveis de ambiente;
- gerador de carga;
- rota e payload.
Compare o perfil junto com throughput, p95, p99, erros, CPU total e event loop delay. Uma função pode ficar menor enquanto o custo migra para outra etapa.
Profiling em produção
O profiler adiciona overhead e gera um arquivo que pode revelar nomes de funções, caminhos e arquitetura interna. Em produção:
- capture somente uma réplica;
- use uma janela curta;
- monitore latência e CPU durante a captura;
- grave em diretório com espaço suficiente;
- proteja e remova o arquivo após a análise;
- não exponha uma rota pública para iniciar profiling;
- registre a operação em auditoria.
Quando possível, reproduza em staging com uma carga semelhante. Se o incidente só acontece em produção, isole o processo e prepare um plano de rollback.
Containers e Kubernetes
Em containers, persista o log em volume ou copie o arquivo antes que o pod seja removido. Registre requests, limits e throttling, porque uma aplicação limitada por cgroup pode parecer mais lenta mesmo sem mudança de código.
FROM node:24-alpine
WORKDIR /app
COPY . .
CMD ["node", "--prof", "dist/server.js"]Não mantenha o profiler ativado permanentemente em todas as réplicas. Use uma imagem ou comando temporário de diagnóstico.
Funções sem nome e símbolos
Frames desconhecidos podem surgir por código otimizado, bibliotecas nativas, símbolos ausentes ou limitações da plataforma. Compare o resultado com um flamegraph, 0x, Linux perf ou Clinic Flame. Ferramentas diferentes podem revelar perspectivas complementares.
Garbage collection
Se o relatório mostra muita CPU em GC, investigue a taxa de alocação. Objetos temporários, cópias de arrays, buffers e serialização podem pressionar o coletor mesmo sem vazamento.
Combine com métricas de process.memoryUsage(), traces de GC e heap profiler. Aumentar o limite do heap pode apenas adiar pausas e tornar o processo maior.
Automatizando a captura
Um script pode nomear arquivos por commit e cenário:
mkdir -p diagnostics
NODE_OPTIONS="--prof" node dist/server.js
mv isolate-*-v8.log "diagnostics/${GIT_SHA:-local}-v8.log"Garanta que somente um processo grave no diretório ou use subpastas por PID. Em cluster e Worker Threads, diferentes isolates podem gerar logs separados.
Erros comuns
- capturar enquanto a aplicação está ociosa;
- não aquecer o runtime;
- usar uma carga diferente no teste posterior;
- olhar somente a função com maior porcentagem;
- confundir tempo inclusivo e exclusivo;
- otimizar sem medir percentis;
- ignorar CPU do gerador de carga;
- compartilhar logs de profiling publicamente;
- concluir que toda CPU alta é um problema de JavaScript.
Fluxo recomendado
- confirme o sintoma com métricas;
- formule uma hipótese;
- reproduza com carga estável;
- capture com
--prof; - processe com
--prof-process; - localize pilhas dominantes;
- faça uma alteração pequena;
- repita o benchmark e o perfil;
- documente os números antes e depois.
Combine o profiler integrado com Flamegraphs no Node.js, diagnóstico visual em Clinic.js, carga com Autocannon e cenários com k6.
Consulte a referência oficial da opção –prof e o guia oficial de flamegraphs do Node.js.



