NNODE LOOP LABruntime observatoryNNEON · Статьи на 90 языках
CONNECTING
v24.18.0linux/x64
Metrics → time series → расследование
10

Prometheus и Grafana

Свяжите process memory, Event Loop delay и лабораторную утечку с настоящим Prometheus endpoint и готовым Grafana dashboard.

PROCESS IDтекущий сервер
UPTIMEпосле запуска
LOOP DELAY P95perf_hooks
UTILIZATIONevent loop
HTTP ROUNDTRIPbrowser → server
LIVE TRACE

Временная шкала

ГОТОВ
0 ms
События появятся здесьЗапустите выбранный сценарий
#ВРЕМЯИСТОЧНИКСОБЫТИЕ
Ожидаю запуск эксперимента…
ГЛАВА 10
ПОДРОБНЫЙ РАЗБОР · ОТ БАЗЫ К КОДУ

Разбираем: Prometheus и Grafana

Этот раздел можно читать до запуска опыта. После теории вернитесь к live trace и сопоставьте каждый шаг с реальным событием.

СНАЧАЛА ПРОСТЫМИ СЛОВАМИ

Метрика — это регулярно измеряемое число с метками и временем. Приложение публикует текущие значения, Prometheus периодически их забирает и хранит как временные ряды, а Grafana строит панели и помогает увидеть тренд. Ни Grafana, ни Prometheus сами по себе не исправляют утечку.

ТЕХНИЧЕСКАЯ ОСНОВА

Лаборатория отдаёт Prometheus text exposition на `/api/metrics`: память главного процесса и memory child, Event Loop utilization/delay, активные запуски и ошибки. В optional Docker Compose Prometheus scrape-ит endpoint каждые пять секунд, а Grafana автоматически получает datasource и готовый dashboard.

Зачем это знатьHeap snapshot отвечает «что удерживает память сейчас», а мониторинг — «когда начался рост, при какой нагрузке и исчез ли он после релиза». Production-диагностика требует обоих уровней вместе с логами и traces.
ГДЕ ВЫПОЛНЯЕТСЯ РАБОТА
01ВАШ JS-КОДfunctions · callbacks
02NODE APIsfs · crypto · timers
03V8 + LIBUVheap · loop · pool
04ОПЕРАЦИОННАЯ СИСТЕМАI/O · threads · memory
01 · СЛОВАРЬ

Термины этого эксперимента

Сначала поймите слова — затем порядок выполнения.

01

Metric

Числовое измерение во времени; gauge меняется в обе стороны, counter обычно только растёт.

02

Time series

Последовательность samples одного metric name и уникального набора labels.

03

Scrape

HTTP-запрос Prometheus к metrics endpoint для получения текущих samples.

04

Cardinality

Число уникальных label combinations. userId/requestId в labels способны взорвать стоимость хранения.

05

SLI

Измеримый показатель качества сервиса, например доля успешных запросов или latency.

06

Alert

Правило над временным рядом, которое срабатывает при устойчивом условии, а не обязательно на одиночном sample.

02 · МЕХАНИКА

Что происходит по шагам

Каждый шаг соответствует наблюдаемому состоянию runtime.

  1. 01
    Приложение измеряет

    process.memoryUsage и perf_hooks дают runtime signals, а бизнес-код увеличивает counters.

  2. 02
    Endpoint публикует

    `/api/metrics` возвращает HELP/TYPE и samples в Prometheus text format.

  3. 03
    Prometheus scrape-ит

    Через равные интервалы он сохраняет значения с timestamp в локальную TSDB.

  4. 04
    PromQL вычисляет

    rate(counter[window]), max_over_time и сравнения превращают samples в диагностические сигналы.

  5. 05
    Grafana визуализирует

    Provisioned dashboard показывает память main/child, Event Loop delay, ELU и частоту запусков.

  6. 06
    Alert ведёт к расследованию

    Устойчивый рост открывает runbook: workload, logs, snapshot безопасной реплики, retainer path, исправление.

03 · КОНТЕКСТ

Где результат требует оговорки

Эти детали объясняют, почему похожий код иногда даёт другой trace.

01

Heap slope важнее одного числа

Высокий стабильный heap может быть нормой; подозрителен новый растущий baseline после сопоставимых циклов нагрузки и GC.

02

Event Loop delay и CPU не взаимозаменяемы

Высокий delay может дать синхронный I/O или пауза GC, а CPU процесса включает работу вне главного Event Loop. Коррелируйте сигналы.

03

Counter читают через rate/increase

Абсолютное число запусков растёт с uptime. Для текущей интенсивности используйте rate на окне; restart counter учитывается Prometheus.

04

Labels должны быть ограниченными

mode или outcome имеют малую cardinality. URL с id, email, stack trace и requestId оставляйте логам/traces.

05

Локальный dashboard — не вся production-система

Для реального сервиса добавьте HTTP RED metrics, DB/queue pool saturation, cgroup memory, restarts/OOMKill, alert routing и retention policy.

01
Теория

Сначала разберитесь, какие части Node участвуют в выполнении.

02
Упрощённый код

Затем уберите служебные детали и рассмотрите только главную идею.

03
Runtime-код

После этого сопоставьте модель с кодом, который создаёт live trace.

04 · Упрощённый код

Минимальная модель без служебного кода

src/demos.js · учебный фрагментJavaScript
import { monitorEventLoopDelay } from 'node:perf_hooks';

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

// Prometheus scrape:
// GET /api/metrics
// node_loop_lab_process_resident_memory_bytes 123456789
// node_loop_lab_event_loop_delay_p95_seconds 0.012

console.log(delay.percentile(95) / 1e6, 'ms');
05 · Runtime-код

Полный код, который выполняет сценарий

Это не альтернативный пример: ниже показаны функции и файлы, используемые кнопкой запуска.

ФАКТИЧЕСКИЙ SOURCE

Код сформирован из реальной серверной функции. Для сценариев с отдельным процессом или Worker показаны все участвующие файлы.

src/demos.js
сценарий69 строк
import {
  monitorEventLoopDelay,
  performance,
} from 'node:perf_hooks';

const sleep = (ms) =>
  new Promise((resolve) => setTimeout(resolve, ms));

function blockMainThread(durationMs) {
  const startedAt = performance.now();
  let iterations = 0;

  // Намеренная блокировка для учебного сценария. В production так делать нельзя.
  while (performance.now() - startedAt < durationMs) {
    iterations += Math.sqrt((iterations % 10_000) + 1);
  }

  return Math.round(iterations);
}

async function observabilitySignals(emit) {
  const histogram = monitorEventLoopDelay({ resolution: 10 });
  histogram.enable();
  const eluBefore = performance.eventLoopUtilization();
  const memoryBefore = process.memoryUsage();

  emit(
    'metrics',
    'sample',
    `До нагрузки: RSS=${Math.round(memoryBefore.rss / 1024 / 1024)} MB, heapUsed=${Math.round(
      memoryBefore.heapUsed / 1024 / 1024,
    )} MB`,
  );
  emit(
    'prometheus',
    'info',
    'Prometheus получает эти process/runtime/memory-lab ряды через GET /api/metrics',
  );

  // Даём monitorEventLoopDelay установить свой sampling timer до блокировки.
  await sleep(35);
  emit(
    'event-loop',
    'warning',
    'Создаём короткую контролируемую блокировку, чтобы метрика delay получила сигнал',
  );
  blockMainThread(140);
  await sleep(35);

  const elu = performance.eventLoopUtilization(eluBefore);
  const p95Ms = Number.isFinite(histogram.percentile(95))
    ? histogram.percentile(95) / 1e6
    : 0;
  const maxMs = Number.isFinite(histogram.max) ? histogram.max / 1e6 : 0;
  histogram.disable();

  emit(
    'metrics',
    'result',
    `Event Loop: p95 delay=${p95Ms.toFixed(1)} мс, max=${maxMs.toFixed(
      1,
    )} мс, ELU=${(elu.utilization * 100).toFixed(1)}%`,
  );
  emit(
    'grafana',
    'result',
    'Grafana не измеряет процесс сама: она строит панели по временным рядам, которые собрал Prometheus',
  );
}

Именно вызовы emit(...) превращаются в строки live trace. await и Promise удерживают HTTP-поток открытым до завершения сценария.

06 · НЕ ПЕРЕПУТАЙТЕ

Популярные заблуждения

Миф слева, корректная модель справа.

МИФ

Grafana собирает метрики приложения.

НА САМОМ ДЕЛЕ

В этой схеме приложение публикует, Prometheus собирает и хранит, Grafana запрашивает и визуализирует.

МИФ

Alert должен сработать при первом высоком sample.

НА САМОМ ДЕЛЕ

Одиночный spike часто нормален; обычно задают окно, порог, duration и runbook.

МИФ

RSS можно сложить с heapUsed и external.

НА САМОМ ДЕЛЕ

RSS уже является общей резидентной картиной, а компоненты частично перекрываются.

МИФ

В label полезно положить как можно больше контекста.

НА САМОМ ДЕЛЕ

Неограниченные значения создают новые time series и могут перегрузить monitoring раньше приложения.

МИФ

Красивый dashboard доказывает отсутствие утечки.

НА САМОМ ДЕЛЕ

Он помогает заметить симптом. Причину подтверждают контролируемой нагрузкой, profiles/snapshots и retainer path.

07 · САМОПРОВЕРКА

Ответьте своими словами

Если ответ получается объяснить без терминов из документации, ментальная модель уже начала складываться.

  1. Как отличить полезно выросший bounded cache от memory leak по временным рядам?
  2. Почему requestId нельзя использовать как Prometheus label?
  3. Какой runbook должен открываться при росте heap и Event Loop delay?
  4. Каких HTTP-метрик пока не хватает этой учебной лаборатории для настоящего SLO?