Home > Blog > Desenvolvimento Web
Desenvolvimento Web
JavaScript
Programação

perf_hooks no Node.js

Atualizado em: 27 de setembro de 2026

Rack de servidores processando fluxos de dados no Node.js

O módulo node:perf_hooks reúne APIs para medir desempenho dentro de aplicações Node.js. Ele permite criar marcas e medidas, observar entradas de performance, acompanhar o event loop, registrar histogramas e coletar informações de tempo com resolução adequada para diagnóstico.

Essas APIs são mais confiáveis que espalhar chamadas de Date.now() pelo código. Elas seguem conceitos semelhantes à Performance Timeline dos navegadores e podem ser integradas a logs, métricas e testes de regressão.

Importando o módulo

import {
  performance,
  PerformanceObserver,
  monitorEventLoopDelay,
  createHistogram,
} from 'node:perf_hooks';

O módulo é interno do Node.js, portanto não precisa ser instalado. Use o prefixo node: para deixar claro que a dependência pertence ao runtime.

performance.now

performance.now() retorna um valor monotônico em milissegundos. Ele é adequado para medir duração porque não sofre ajustes do relógio do sistema.

const start = performance.now();

await executarOperacao();

const durationMs = performance.now() - start;
console.log({ durationMs });

Para métricas financeiras, auditoria ou timestamps de negócio, use data e hora absolutas. Para duração, prefira relógio monotônico.

Marcas e medidas

Marcas identificam pontos; medidas calculam intervalos:

performance.mark('users:start');

const users = await repository.list();
performance.mark('users:database');

const result = users.map(normalizeUser);
performance.mark('users:end');

performance.measure(
  'users:database-duration',
  'users:start',
  'users:database',
);
performance.measure(
  'users:transform-duration',
  'users:database',
  'users:end',
);

Essa abordagem facilita medir etapas sem manter várias variáveis de início.

PerformanceObserver

Um observador recebe entradas criadas:

const observer = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    console.log({
      name: entry.name,
      entryType: entry.entryType,
      startTime: entry.startTime,
      duration: entry.duration,
    });
  }
});

observer.observe({ entryTypes: ['measure'] });

Crie o observador antes das medidas que precisa capturar. Desconecte quando não for mais necessário:

observer.disconnect();

Limpando entradas

A timeline ocupa memória. Em processos longos, limpe marcas e medidas:

performance.clearMarks('users:start');
performance.clearMarks('users:database');
performance.clearMarks('users:end');
performance.clearMeasures('users:database-duration');
performance.clearMeasures('users:transform-duration');

Outra estratégia é consumir as entradas e limpar periodicamente. Não crie nomes únicos por requisição sem retenção controlada.

Timerify

performance.timerify() envolve uma função e gera entradas do tipo function:

function normalizeUser(user) {
  return {
    id: user.id,
    name: user.name.trim(),
  };
}

const timedNormalizeUser = performance.timerify(normalizeUser);

const observer = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    console.log(entry.name, entry.duration);
  }
});
observer.observe({ entryTypes: ['function'] });

const normalized = timedNormalizeUser({ id: 1, name: ' Ana ' });

O wrapper adiciona overhead. Use em diagnóstico, funções importantes ou amostragem, não automaticamente em todo o código.

Funções assíncronas

Timerify também pode medir uma função que devolve Promise:

const timedFetchUser = performance.timerify(async function fetchUser(id) {
  return repository.findById(id);
});

await timedFetchUser(42);

A duração inclui espera assíncrona. Para separar banco, rede e processamento local, crie spans ou medidas por etapa.

Histogramas

createHistogram() registra valores inteiros e calcula percentis:

const histogram = createHistogram();

for (let i = 0; i < 1000; i += 1) {
  const start = process.hrtime.bigint();
  await executarOperacao();
  const durationNs = process.hrtime.bigint() - start;
  histogram.record(durationNs);
}

console.log({
  minNs: histogram.min,
  maxNs: histogram.max,
  meanNs: histogram.mean,
  p95Ns: histogram.percentile(95),
  p99Ns: histogram.percentile(99),
});

Registre unidades consistentes. Histogramas desse módulo trabalham com inteiros; nanossegundos são úteis, mas converta para milissegundos na apresentação.

RecordableHistogram com timerify

Um histograma pode ser associado a uma função temporizada:

const histogram = createHistogram();
const timed = performance.timerify(executarOperacao, { histogram });

await timed();

console.log(histogram.percentile(99) / 1e6);

Isso evita processar cada entrada manualmente e permite obter percentis locais.

Monitorando event loop delay

const delay = monitorEventLoopDelay({ resolution: 20 });
delay.enable();

setInterval(() => {
  console.log({
    minMs: delay.min / 1e6,
    maxMs: delay.max / 1e6,
    meanMs: delay.mean / 1e6,
    p95Ms: delay.percentile(95) / 1e6,
    p99Ms: delay.percentile(99) / 1e6,
  });

  delay.reset();
}, 10_000).unref();

Quanto menor a resolução, maior o detalhe e potencialmente o custo. Meça overhead no ambiente real.

Event Loop Utilization

let previous = performance.eventLoopUtilization();

setInterval(() => {
  const delta = performance.eventLoopUtilization(previous);
  previous = performance.eventLoopUtilization();

  console.log({ utilization: delta.utilization });
}, 10_000).unref();

ELU mostra a proporção de atividade do event loop. Correlacione com delay, CPU, latência e throughput.

PerformanceResourceTiming

Aplicações e bibliotecas podem criar entradas de recursos para representar operações externas. Entretanto, em backends, traces distribuídos normalmente oferecem contexto mais completo. Use a timeline quando precisar de uma API local padronizada e de baixo acoplamento.

Medindo uma requisição HTTP

import { randomUUID } from 'node:crypto';

async function handleRequest(req, res) {
  const id = randomUUID();
  const startMark = `request:${id}:start`;
  const endMark = `request:${id}:end`;
  const measureName = `request:${id}`;

  performance.mark(startMark);
  try {
    await route(req, res);
  } finally {
    performance.mark(endMark);
    performance.measure(measureName, startMark, endMark);
    performance.clearMarks(startMark);
    performance.clearMarks(endMark);
    performance.clearMeasures(measureName);
  }
}

O exemplo demonstra limpeza, mas nomes únicos podem gerar alta cardinalidade se forem exportados diretamente. Para métricas, agregue por rota normalizada, método e status, nunca por ID.

Integração com logs

Registre somente operações lentas:

async function measureSlowOperation(name, operation) {
  const start = performance.now();
  try {
    return await operation();
  } finally {
    const durationMs = performance.now() - start;
    if (durationMs > 250) {
      logger.warn({ name, durationMs }, 'Operação lenta');
    }
  }
}

Não registre payloads, tokens ou dados pessoais. Inclua IDs de correlação pequenos e controlados.

Integração com métricas

Converta medidas para histogramas do Prometheus ou OpenTelemetry:

const start = performance.now();
try {
  return await operation();
} finally {
  requestDuration.observe(
    { route: '/users/:id', method: 'GET' },
    (performance.now() - start) / 1000,
  );
}

Confirme a unidade esperada pela biblioteca. Muitas métricas HTTP usam segundos.

PerformanceObserver e erros

O callback do observador deve ser simples. Trabalho pesado dentro dele pode perturbar a própria aplicação. Envie valores agregados para uma fila local ou atualize métricas em memória.

Benchmark de microfunções

perf_hooks ajuda a medir pequenos trechos, mas microbenchmarks exigem aquecimento, repetição e prevenção de otimizações irreais.

for (let i = 0; i < 10_000; i += 1) {
  funcao();
}

const start = performance.now();
for (let i = 0; i < 1_000_000; i += 1) {
  funcao();
}
console.log(performance.now() - start);

Teste o comportamento completo sob carga antes de escolher uma implementação baseada apenas em nanossegundos locais.

Testes de regressão

Evite limites rígidos em runners compartilhados. Uma estratégia mais robusta é executar várias rodadas, usar mediana e bloquear somente regressões grandes.

if (medianMs > baselineMs * 1.25) {
  throw new Error('Regressão de desempenho acima de 25%');
}

Reserve benchmarks precisos para máquinas dedicadas e mantenha smoke tests no CI comum.

Overhead

Toda instrumentação possui custo. Marcas, observadores, histogramas, logs e exportadores podem aumentar CPU e memória. Meça com e sem instrumentação e use amostragem quando necessário.

Erros comuns

  • usar Date.now() para durações críticas;
  • não limpar marcas e medidas;
  • criar nomes únicos exportados como labels;
  • confundir milissegundos e nanossegundos;
  • medir somente média;
  • executar trabalho pesado no observador;
  • usar microbenchmark como prova de capacidade real;
  • ignorar aquecimento do V8;
  • adicionar instrumentação sem medir overhead.

Fluxo recomendado

Use performance.now() para durações, marcas para etapas, histogramas para percentis e monitoramento do event loop para saúde do runtime. Agregue métricas por operações estáveis e valide sob carga. Combine com Event Loop Utilization, profiling em –prof, carga com Autocannon e observabilidade em Prometheus.

Consulte a documentação oficial de perf_hooks e a referência oficial de PerformanceObserver.

10 melhores cursos de programação em 2026

Descubra os melhores cursos de programação. Aprenda a escolher o curso ideal para iniciar ou avançar na carreira de desenvolvedor

POSTS RELACIONADOS

Ver todos

Seta para a direita