В эти выходные я немного поцарапал долгий срок и реализовал базовую поддержку спецификации Performance Timeline для ядра Node.js. Надеюсь, он скоро приземлится.

Определенно есть некоторые острые углы и, конечно, еще немало проблем, которые нужно решить, но вот как все работает в настоящее время. Все описанное ниже может быть изменено.

требуется ("perf_hooks")

Новый API через require('perf_hooks').

const {
  performance,
  PerformanceObserver
} = require('perf_hooks');

Свойство performance возвращает объект Performance, который примерно соответствует спецификации Время высокого разрешения.

Цель объекта Performance - предоставить доступ к «Временной шкале производительности», логической серии контрольных точек и показателей производительности, поддерживаемых как процессом Node.js, так и пользовательским кодом.

Внутренне временная шкала представляет собой не что иное, как связанный список из PerformanceEntry объектов. У каждого PerformanceEntry есть имя, тип, начальная отметка времени и продолжительность. В любой момент времени на временной шкале может существовать несколько разных PerformanceEntry типов. Реализация в настоящее время поддерживает четыре конкретных типа: 'node', 'frame', 'mark', 'measure', 'gc' и 'function'. Все они описаны ниже.

Вехи производительности Node.js

Первый PerformanceEntry экземпляр, добавленный на временную шкалу производительности, представляет собой специальную запись, в которой записываются временные метки, в которые происходят важные вехи при запуске процесса Node.js. Есть несколько способов получить доступ к этой специальной записи, но самый быстрый - использовать perf_hooks.performance.nodeTime.

> perf_hooks.performance.nodeTiming
PerformanceNodeTiming {
  duration: 4512.380027,
  startTime: 158745518.63114,
  entryType: 'node',
  name: 'node',
  arguments: 158745518.756349,
  initialize: 158745519.09161,
  inspectorStart: 158745522.408488,
  loopStart: 158745613.442409,
  loopExit: 0,
  loopFrame: 158749857.025862,
  bootstrapComplete: 158745613.439273,
  third_party_main_start: 0,
  third_party_main_end: 0,
  cluster_setup_start: 0,
  cluster_setup_end: 0,
  module_load_start: 158745583.850295,
  module_load_end: 158745583.851643,
  preload_modules_load_start: 158745583.852331,
  preload_modules_load_end: 158745583.879369 }

В настоящее время поддерживаются следующие свойства:

  • duration: количество миллисекунд, в течение которых процесс был активен.
  • arguments: отметка времени, когда завершилась обработка аргументов командной строки.
  • initialize: отметка времени, когда платформа Node.js завершила инициализацию.
  • inspectorStart: отметка времени, когда завершился запуск инспектора Node.js.
  • loopStart: отметка времени, когда начался цикл событий Node.js.
  • loopExit: отметка времени, когда завершился цикл обработки событий Node.js.
  • loopFrame: отметка времени, с которой началась текущая итерация цикла событий Node.js.
  • bootstrapComplete: отметка времени, когда завершился процесс начальной загрузки Node.js.
  • third_party_main_start: отметка времени, когда началась сторонняя основная обработка.
  • third_party_main_end: отметка времени, когда завершилась основная сторонняя обработка.
  • cluster_setup_start: отметка времени, когда началась установка дочернего кластера.
  • cluster_setup_end: отметка времени, когда закончилась установка дочернего кластера.
  • module_load_start: отметка времени, когда началась загрузка основного модуля.
  • module_load_end: отметка времени, когда закончилась загрузка основного модуля.
  • preload_modules_load_start: отметка времени, когда началась загрузка модуля предварительной загрузки.
  • preload_modules_load_end: отметка времени, когда загрузка модуля предварительной загрузки закончилась.

Время цикла событий Node.js

Второй экземпляр PerformanceEntry, добавленный на шкалу времени, представляет собой специальную запись, используемую для отслеживания времени цикла событий Node.js. Доступ к этой записи можно получить с помощью perf_hooks.performance.nodeFrame.

> perf_hooks.performance.nodeFrame
PerformanceFrame {
  countPerSecond: 9.91151849696801,
  count: 68,
  prior: 0.124875,
  entryType: 'frame',
  name: 'frame',
  duration: 128.827398,
  startTime: 32623025.740256 }
>

Поддерживаемые свойства включают:

  • countsPerSecond: количество итераций цикла событий в секунду.
  • count: общее количество итераций цикла событий.
  • startTime: метка времени, с которой началась текущая итерация цикла событий.
  • duration: текущая продолжительность текущей итерации цикла событий.
  • prior: общее количество миллисекунд, затраченных на предыдущую итерацию цикла событий.

Знаки и меры

Реализация поддерживает расширение API User Timing для Timeline производительности. Это позволяет пользователям устанавливать именованные маркеры на шкале времени и измерять общее количество миллисекунд между этими маркерами.

Например:

const {
  performance,
  PerformanceObserver
} = require('perf_hooks');
performance.mark('A');
setImmediate(() => {
  performance.mark('B');
  performance.measure('A to C', 'A', 'B');
  const measure = performance.getEntriesByName('A to C')[0];
  console.log(measure.duration);
});

Время выполнения функции измерения

Реализация поддерживает механизм измерения времени выполнения функций JavaScript.

const {
  performance,
  PerformanceObserver
} = require('perf_hooks');
function myFunction() {}
const fn = performance.timerify(myFunction);
const obs = new PerformanceObserver((list) => {
  console.log(list.getEntries()[0]);
  obs.disconnect();
  performance.clearFunctions();
});
obs.observe({ entryTypes: ['function'] });
fn();  // Call the timerified function

Пока существует PerformanceObserver, подписанный на 'function' типы записей, данные о времени будут добавляться на временную шкалу производительности каждый раз, когда вызывается обернутая функция.

Один особенно интересный способ использовать это - рассчитать время загрузки каждой зависимости в приложении Node.js:

'use strict';
const {
  performance,
  PerformanceObserver
} = require('perf_hooks');
const mod = require('module');
// Monkey patch the require function
mod.Module.prototype.require =
  performance.timerify(mod.Module.prototype.require);
require = performance.timerify(require);
// Activate the observer
const obs = new PerformanceObserver((list) => {
  const entries = list.getEntries();
  entries.forEach((entry) => {
    console.log(`require('${entry[0]}')`, entry.duration);
  });
  obs.disconnect();
  // Free memory
  performance.clearFunctions();
});
obs.observe({ entryTypes: ['function'], buffered: true });
require('some-module');

Измерение сбора мусора

Реализация поддерживает возможность использовать thePerformanceObserver для отслеживания событий сборки мусора.

const {
  performance,
  PerformanceObserver
} = require('perf_hooks');
const obs = new PerformanceObserver((list) => {
  console.log(list.getEntries());
  performance.clearGC();
});
obs.observe({ entryTypes: ['gc'] });

Каждый раз, когда начинается большая или второстепенная сборка мусора, когда PerformanceObserver подписан на 'gc' события, на шкалу времени производительности будет добавляться новая запись.

Запрос временной шкалы

Чтобы увидеть, какие PerformanceEntry элементы были добавлены на временную шкалу производительности, можно использовать методы GetEntries(), GetEntriesByName() и GetEntriesByType(). Они возвращают необязательно отфильтрованные массивы из PerformanceEntry экземпляров. Однако они не являются наиболее эффективным средством отслеживания сроков.

Класс PerformanceObserver обеспечивает способ получения уведомлений обратного вызова всякий раз, когда добавляется новый экземпляр PerformanceEntry. Уведомления могут быть синхронными:

const {
  performance,
  PerformanceObserver
} = require('perf_hooks');
function callback(list, observer) {
  console.log(list.getEntries());
  observer.disconnect();
}
const obs = new PerformanceObserver(callback);
obs.observe(( entryTypes: ['mark', 'measure'] });

Или асинхронный (с использованием опции buffered):

const {
  performance,
  PerformanceObserver
} = require('perf_hooks');
function callback(list, observer) {
  console.log(list.getEntries());
  observer.disconnect();
}
const obs = new PerformanceObserver(callback);
obs.observe(( entryTypes: ['mark', 'measure'], buffered: true });

Каждый раз, когда код пользователя вызывает perf_hooks.performance.mark() или perf_hooks.performance.measure(), пока PerformanceObserver подписан, будет вызвана функция обратного вызова, переданная конструктору выше.

Просто быстрый пример

Есть несколько различных способов использования нового API. В приведенном ниже примере используется комбинация API Async_hooks и Performance Timing для точного измерения того, сколько времени требуется дескриптору Timeout для завершения от Init до Destroy:

'use strict';
const async_hooks = require('async_hooks');
const {
  performance,
  PerformanceObserver
} = require('perf_hooks');
const set = new Set();
const hook = async_hooks.createHook({
  init(id, type) {
    if (type === 'Timeout') {
      performance.mark(`Timeout-${id}-Init`);
      set.add(id);
    }
  },
  destroy(id) {
    if (set.has(id)) {
      set.delete(id);
      performance.mark(`Timeout-${id}-Destroy`);
      performance.measure(`Timeout-${id}`,
                          `Timeout-${id}-Init`,
                          `Timeout-${id}-Destroy`);
    }
  }
});
hook.enable();
const obs = new PerformanceObserver((list, observer) => {
  console.log(list.getEntries()[0]);
  performance.clearMarks();
  performance.clearMeasures();
  observer.disconnect();
});
obs.observe({ entryTypes: ['measure'], buffered: true });
setTimeout(() => console.log('test'), 1000);

Детали могут измениться

Поскольку этот запрос на перенос является новым, конкретные детали могут измениться. Я постараюсь обновлять этот пост по мере продвижения.