Перейти к содержанию

V8

latest

Модуль node:v8 предоставляет API, специфичные для версии V8, встроенной в бинарник Node.js. Обратиться к нему можно так:

1
import v8 from 'node:v8';
1
const v8 = require('node:v8');

v8.cachedDataVersionTag()

Возвращает целое число — тег версии, выведенный из версии V8, флагов командной строки и возможностей CPU. Позволяет проверить, совместим ли буфер cachedData у vm.Script с текущим экземпляром V8.

1
2
3
4
5
console.log(v8.cachedDataVersionTag()); // 3947234607
// Значение v8.cachedDataVersionTag() зависит от версии V8, флагов CLI и CPU.
// Убедитесь, что оно меняется при переключении флагов.
v8.setFlagsFromString('--allow_natives_syntax');
console.log(v8.cachedDataVersionTag()); // 183726201

v8.getHeapCodeStatistics()

Статистика по коду и метаданным в куче; см. API V8 GetHeapCodeAndMetadataStatistics. Объект со свойствами:

1
2
3
4
5
6
{
  code_and_metadata_size: 212208,
  bytecode_and_metadata_size: 161368,
  external_script_source_size: 1410794,
  cpu_profiler_metadata_size: 0,
}

v8.getHeapSnapshot([options])

Добавлено в: v11.13.0

  • options <Object>
  • exposeInternals <boolean> Если true, включать внутренности в снимок кучи. По умолчанию: false.
  • exposeNumericValues <boolean> Если true, показывать числа в искусственных полях. По умолчанию: false.

  • Возвращает: <stream.Readable> Поток Readable со снимком кучи V8.

Строит снимок текущей кучи V8 и возвращает Readable для чтения JSON-представления. Формат рассчитан на инструменты вроде Chrome DevTools. Схема JSON недокументирована и зависит от V8 и может меняться между версиями.

Памяти нужно примерно вдвое больше размера кучи на момент снимка; возможно завершение процесса OOM killer.

Операция синхронная и блокирует цикл событий на время, зависящее от размера кучи.

1
2
3
4
5
// Вывести снимок кучи в консоль
import { getHeapSnapshot } from 'node:v8';
import process from 'node:process';
const stream = getHeapSnapshot();
stream.pipe(process.stdout);
1
2
3
4
5
// Вывести снимок кучи в консоль
const v8 = require('node:v8');
const process = require('node:process');
const stream = v8.getHeapSnapshot();
stream.pipe(process.stdout);

v8.getHeapSpaceStatistics()

Добавлено в: v6.0.0

Статистика по пространствам кучи V8 (сегменты, из которых она состоит). Порядок пространств и их набор не гарантируются: данные приходят из GetHeapSpaceStatistics и могут меняться между версиями V8.

Массив объектов со свойствами:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
[
  {
    "space_name": "new_space",
    "space_size": 2063872,
    "space_used_size": 951112,
    "space_available_size": 80824,
    "physical_space_size": 2063872
  },
  {
    "space_name": "old_space",
    "space_size": 3090560,
    "space_used_size": 2493792,
    "space_available_size": 0,
    "physical_space_size": 3090560
  },
  {
    "space_name": "code_space",
    "space_size": 1260160,
    "space_used_size": 644256,
    "space_available_size": 960,
    "physical_space_size": 1260160
  },
  {
    "space_name": "map_space",
    "space_size": 1094160,
    "space_used_size": 201608,
    "space_available_size": 0,
    "physical_space_size": 1094160
  },
  {
    "space_name": "large_object_space",
    "space_size": 0,
    "space_used_size": 0,
    "space_available_size": 1490980608,
    "physical_space_size": 0
  }
]

v8.getHeapStatistics()

Добавлено в: v1.0.0

Объект со свойствами:

total_heap_size — байты, выделенные V8 под кучу; может расти при необходимости.

total_heap_size_executable — часть кучи с исполняемым кодом (JIT и т.п.), в байтах.

total_physical_size — физически занятая память кучи V8 (закоммиченная), в байтах.

total_available_size — сколько байт ещё доступно куче V8 до лимита.

used_heap_size — байты, занятые объектами JavaScript в куче (фактически используемые).

heap_size_limit — максимальный размер кучи V8 в байтах (по умолчанию, ресурсам системы или --max_old_space_size).

malloced_memory — байты, выделенные через malloc в V8.

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

does_zap_garbage — 0/1: включён ли --zap_code_space (затирать освобождённую память шаблоном; RSS растёт, страницы реже вытесняются ОС).

number_of_native_contexts — число активных контекстов верхнего уровня; рост во времени может указывать на утечку.

number_of_detached_contexts — отсоединённые контексты, ещё не собранные GC; ненулевое значение — возможная утечка.

total_global_handles_size — общий объём памяти глобальных дескрипторов V8.

used_global_handles_size — использованный объём глобальных дескрипторов.

external_memory — память под ArrayBuffer и внешние строки.

total_allocated_bytes — всего байт выделено с создания Isolate.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
{
  total_heap_size: 7326976,
  total_heap_size_executable: 4194304,
  total_physical_size: 7326976,
  total_available_size: 1152656,
  used_heap_size: 3476208,
  heap_size_limit: 1535115264,
  malloced_memory: 16384,
  peak_malloced_memory: 1127496,
  does_zap_garbage: 0,
  number_of_native_contexts: 1,
  number_of_detached_contexts: 0,
  total_global_handles_size: 8192,
  used_global_handles_size: 3296,
  external_memory: 318824
}

v8.getCppHeapStatistics([detailLevel])

Возвращает статистику CppHeap по памяти через V8 CollectStatistics(); формат может меняться между версиями V8.

  • detailLevel <string> | undefined: По умолчанию: 'detailed'. Уровень детализации:
  • 'brief' — только суммарные выделенная и используемая память по всей куче.
  • 'detailed' — разбивка по пространствам и страницам, freelist и гистограммы типов.

Структура близка к cppgc::HeapStatistics; подробности — в документации V8.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// Detailed
({
  committed_size_bytes: 131072,
  resident_size_bytes: 131072,
  used_size_bytes: 152,
  space_statistics: [
    {
      name: 'NormalPageSpace0',
      committed_size_bytes: 0,
      resident_size_bytes: 0,
      used_size_bytes: 0,
      page_stats: [{}],
      free_list_stats: {},
    },
    {
      name: 'NormalPageSpace1',
      committed_size_bytes: 131072,
      resident_size_bytes: 131072,
      used_size_bytes: 152,
      page_stats: [{}],
      free_list_stats: {},
    },
    {
      name: 'NormalPageSpace2',
      committed_size_bytes: 0,
      resident_size_bytes: 0,
      used_size_bytes: 0,
      page_stats: [{}],
      free_list_stats: {},
    },
    {
      name: 'NormalPageSpace3',
      committed_size_bytes: 0,
      resident_size_bytes: 0,
      used_size_bytes: 0,
      page_stats: [{}],
      free_list_stats: {},
    },
    {
      name: 'LargePageSpace',
      committed_size_bytes: 0,
      resident_size_bytes: 0,
      used_size_bytes: 0,
      page_stats: [{}],
      free_list_stats: {},
    },
  ],
  type_names: [],
  detail_level: 'detailed',
});
1
2
3
4
5
6
7
8
9
// Brief
({
  committed_size_bytes: 131072,
  resident_size_bytes: 131072,
  used_size_bytes: 128864,
  space_statistics: [],
  type_names: [],
  detail_level: 'brief',
});

v8.queryObjects(ctor[, options])

  • ctor <Function> Конструктор для поиска по цепочке прототипов среди объектов в куче.
  • options undefined | <Object>
  • format <string> 'count' — число найденных объектов; 'summary' — массив кратких строковых описаний.
  • Возвращает: <number> | <Array<string>>

Аналог queryObjects() console API в консоли Chromium DevTools. После полного GC ищет объекты с подходящим конструктором в цепочке прототипов — удобно для регрессионных тестов на утечки. Не вызывайте для конструкторов, реализацию которых вы не контролируете.

Сырые ссылки на объекты не возвращаются. По умолчанию — только счётчик; при options.format === 'summary' — массив кратких представлений. Видимость сопоставима со снимком кучи, но без полной сериализации.

Учитываются только объекты текущего контекста выполнения.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
const { queryObjects } = require('node:v8');
class A { foo = 'bar'; }
console.log(queryObjects(A)); // 0
const a = new A();
console.log(queryObjects(A)); // 1
// [ "A { foo: 'bar' }" ]
console.log(queryObjects(A, { format: 'summary' }));

class B extends A { bar = 'qux'; }
const b = new B();
console.log(queryObjects(B)); // 1
// [ "B { foo: 'bar', bar: 'qux' }" ]
console.log(queryObjects(B, { format: 'summary' }));

// Если есть дочерние классы, конструктор также в цепочке прототипа ребёнка,
// поэтому в результат попадут и прототипы дочерних классов.
console.log(queryObjects(A));  // 3
// [ "B { foo: 'bar', bar: 'qux' }", 'A {}', "A { foo: 'bar' }" ]
console.log(queryObjects(A, { format: 'summary' }));
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
import { queryObjects } from 'node:v8';
class A { foo = 'bar'; }
console.log(queryObjects(A)); // 0
const a = new A();
console.log(queryObjects(A)); // 1
// [ "A { foo: 'bar' }" ]
console.log(queryObjects(A, { format: 'summary' }));

class B extends A { bar = 'qux'; }
const b = new B();
console.log(queryObjects(B)); // 1
// [ "B { foo: 'bar', bar: 'qux' }" ]
console.log(queryObjects(B, { format: 'summary' }));

// Если есть дочерние классы, конструктор также в цепочке прототипа ребёнка,
// поэтому в результат попадут и прототипы дочерних классов.
console.log(queryObjects(A));  // 3
// [ "B { foo: 'bar', bar: 'qux' }", 'A {}', "A { foo: 'bar' }" ]
console.log(queryObjects(A, { format: 'summary' }));

v8.setFlagsFromString(flags)

v8.setFlagsFromString() задаёт флаги V8 из программы. Менять настройки после старта VM опасно: возможны сбои и потеря данных или отсутствие эффекта.

Список опций V8 для вашей сборки: node --v8-options.

Пример:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import { setFlagsFromString } from 'node:v8';
import { setInterval } from 'node:timers';

// setFlagsFromString to trace garbage collection events
setFlagsFromString('--trace-gc');

// Trigger GC events by using some memory
let arrays = [];
const interval = setInterval(() => {
  for (let i = 0; i < 500; i++) {
    arrays.push(new Array(10000).fill(Math.random()));
  }

  if (arrays.length > 5000) {
    arrays = arrays.slice(-1000);
  }

  console.log(`\n* Created ${arrays.length} arrays\n`);
}, 100);

// setFlagsFromString to stop tracing GC events after 1.5 seconds
setTimeout(() => {
  setFlagsFromString('--notrace-gc');
  console.log('\nStopped tracing!\n');
}, 1500);

// Stop triggering GC events altogether after 2.5 seconds
setTimeout(() => {
  clearInterval(interval);
}, 2500);
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
const { setFlagsFromString } = require('node:v8');
const { setInterval } = require('node:timers');

// setFlagsFromString to trace garbage collection events
setFlagsFromString('--trace-gc');

// Trigger GC events by using some memory
let arrays = [];
const interval = setInterval(() => {
  for (let i = 0; i < 500; i++) {
    arrays.push(new Array(10000).fill(Math.random()));
  }

  if (arrays.length > 5000) {
    arrays = arrays.slice(-1000);
  }

  console.log(`\n* Created ${arrays.length} arrays\n`);
}, 100);

// setFlagsFromString to stop tracing GC events after 1.5 seconds
setTimeout(() => {
  console.log('\nStopped tracing!\n');
  setFlagsFromString('--notrace-gc');
}, 1500);

// Stop triggering GC events altogether after 2.5 seconds
setTimeout(() => {
  clearInterval(interval);
}, 2500);

v8.stopCoverage()

v8.stopCoverage() останавливает сбор покрытия, запущенный NODE_V8_COVERAGE, чтобы V8 освободил счётчики и мог оптимизировать код. Сочетается с v8.takeCoverage() при сборе покрытия по запросу.

v8.takeCoverage()

v8.takeCoverage() записывает на диск покрытие, начатое NODE_V8_COVERAGE, по запросу. Метод можно вызывать многократно: каждый раз счётчик сбрасывается и пишется новый отчёт в каталог из NODE_V8_COVERAGE.

При завершении процесса последний отчёт всё равно запишется, если до выхода не вызвали v8.stopCoverage().

v8.writeHeapSnapshot([filename[,options]])

Добавлено в: v11.13.0

  • filename <string> Путь к файлу для сохранения снимка кучи V8. Если не задан, имя генерируется по шаблону 'Heap-${yyyymmdd}-${hhmmss}-${pid}-${thread_id}.heapsnapshot', где {pid} — PID процесса Node.js, {thread_id}0 для главного потока или id воркера.
  • options <Object>
  • exposeInternals <boolean> Если true, включать внутренности в снимок. По умолчанию: false.
  • exposeNumericValues <boolean> Если true, показывать числа в искусственных полях. По умолчанию: false.
  • Возвращает: <string> Путь к сохранённому файлу.

Строит снимок текущей кучи V8 и записывает JSON для инструментов вроде Chrome DevTools. Схема недокументирована и зависит от версии V8.

Снимок относится к одному isolate. При worker threads снимок с главного потока не включает воркеры и наоборот.

Нужно примерно вдвое больше памяти, чем размер кучи; возможен OOM killer.

Операция синхронная и блокирует цикл событий.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import { writeHeapSnapshot } from 'node:v8';
import { Worker, isMainThread, parentPort } from 'node:worker_threads';
import { fileURLToPath } from 'node:url';

if (isMainThread) {
  const __filename = fileURLToPath(import.meta.url);
  const worker = new Worker(__filename);

  worker.once('message', (filename) => {
    console.log(`worker heapdump: ${filename}`);
    // Now get a heapdump for the main thread.
    console.log(`main thread heapdump: ${writeHeapSnapshot()}`);
  });

  // Tell the worker to create a heapdump.
  worker.postMessage('heapdump');
} else {
  parentPort.once('message', (message) => {
    if (message === 'heapdump') {
      // Generate a heapdump for the worker
      // and return the filename to the parent.
      parentPort.postMessage(writeHeapSnapshot());
    }
  });
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const { writeHeapSnapshot } = require('node:v8');
const { Worker, isMainThread, parentPort } = require('node:worker_threads');

if (isMainThread) {
  const worker = new Worker(__filename);

  worker.once('message', (filename) => {
    console.log(`worker heapdump: ${filename}`);
    // Now get a heapdump for the main thread.
    console.log(`main thread heapdump: ${writeHeapSnapshot()}`);
  });

  // Tell the worker to create a heapdump.
  worker.postMessage('heapdump');
} else {
  parentPort.once('message', (message) => {
    if (message === 'heapdump') {
      // Generate a heapdump for the worker
      // and return the filename to the parent.
      parentPort.postMessage(writeHeapSnapshot());
    }
  });
}

v8.setHeapSnapshotNearHeapLimit(limit)

Ничего не делает, если --heapsnapshot-near-heap-limit уже задан в CLI или API вызван повторно. limit — положительное целое. Подробнее: --heapsnapshot-near-heap-limit.

API сериализации

Сериализация значений JavaScript, совместимая с HTML structured clone algorithm.

Формат обратно совместим (пригоден для записи на диск). Равные значения JS могут дать разный поток байт.

v8.serialize(value)

Использует DefaultSerializer для сериализации value в буфер.

ERR_BUFFER_TOO_LARGE, если объект слишком велик для буфера больше buffer.constants.MAX_LENGTH.

v8.deserialize(buffer)

Читает значение JS через DefaultDeserializer с настройками по умолчанию.

Класс: v8.Serializer

new Serializer()

Создаёт Serializer.

serializer.writeHeader()

Записывает заголовок с версией формата сериализации.

serializer.writeValue(value)

Сериализует значение и добавляет байты во внутренний буфер.

При невозможности сериализации — ошибка.

serializer.releaseBuffer()

Возвращает внутренний буфер; после вызова Serializer использовать нельзя. Доопределённое поведение при ошибке предыдущей записи.

serializer.transferArrayBuffer(id, arrayBuffer)

  • id <integer> 32-битное беззнаковое целое.
  • arrayBuffer <ArrayBuffer> Экземпляр ArrayBuffer.

Помечает ArrayBuffer для передачи вне потока. В контексте десериализации передайте ArrayBuffer в deserializer.transferArrayBuffer().

serializer.writeUint32(value)

Сырые 32 бита без знака. Для кастомного serializer._writeHostObject().

serializer.writeUint64(hi, lo)

Сырые 64 бита (старшие и младшие 32 бита). Для serializer._writeHostObject().

serializer.writeDouble(value)

Значение JS number. Для serializer._writeHostObject().

serializer.writeRawBytes(buffer)

Сырые байты во внутренний буфер; при десериализации нужна согласованная длина. Для serializer._writeHostObject().

serializer._writeHostObject(object)

Вызывается для «хостового» объекта из нативных привязок C++. Если сериализовать нельзя — подходящее исключение.

В базовом классе отсутствует; можно переопределить в подклассе.

serializer._getDataCloneError(message)

Формирует объект ошибки при невозможности клонирования.

По умолчанию Error; можно переопределить в подклассе.

serializer._getSharedArrayBufferId(sharedArrayBuffer)

Вызывается при сериализации SharedArrayBuffer. Должен вернуть 32-битный беззнаковый id; один и тот же id для уже сериализованного буфера. При десериализации id уйдёт в deserializer.transferArrayBuffer().

Если нельзя сериализовать — исключение.

В базовом классе отсутствует; можно задать в подклассе.

serializer._setTreatArrayBufferViewsAsHostObjects(flag)

  • flag <boolean> По умолчанию: false

Считать TypedArray и DataView хостовыми объектами и передавать в serializer._writeHostObject().

Класс: v8.Deserializer

new Deserializer(buffer)

Создаёт Deserializer.

deserializer.readHeader()

Читает и проверяет заголовок (версию формата). Неподдерживаемый формат — Error.

deserializer.readValue()

Десериализует значение из буфера.

deserializer.transferArrayBuffer(id, arrayBuffer)

Помечает ArrayBuffer для передачи вне потока. В контексте сериализации используйте serializer.transferArrayBuffer() или id из serializer._getSharedArrayBufferId() для SharedArrayBuffer.

deserializer.getWireFormatVersion()

Версия формата wire. Полезно для старого кода. Не вызывать до .readHeader().

deserializer.readUint32()

Сырые 32 бита без знака. Для deserializer._readHostObject().

deserializer.readUint64()

Сырые 64 бита как [hi, lo]. Для deserializer._readHostObject().

deserializer.readDouble()

Значение JS number. Для deserializer._readHostObject().

deserializer.readRawBytes(length)

Сырые байты; length должен совпадать с serializer.writeRawBytes(). Для deserializer._readHostObject().

deserializer._readHostObject()

Читает хостовый объект из нативных привязок. При ошибке — исключение.

В базовом классе отсутствует; задаётся в подклассе.

Класс: v8.DefaultSerializer

Подкласс Serializer: сериализует TypedArray (в т.ч. Buffer) и DataView как хостовые объекты и сохраняет только используемый фрагмент ArrayBuffer.

Класс: v8.DefaultDeserializer

Подкласс Deserializer для формата DefaultSerializer.

Хуки промисов

Интерфейс promiseHooks отслеживает жизненный цикл промисов. Для всей асинхронной активности см. async_hooks (внутри использует этот модуль). Для контекста запросов см. AsyncLocalStorage.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
import { promiseHooks } from 'node:v8';

// There are four lifecycle events produced by promises:

// The `init` event represents the creation of a promise. This could be a
// direct creation such as with `new Promise(...)` or a continuation such
// as `then()` or `catch()`. It also happens whenever an async function is
// called or does an `await`. If a continuation promise is created, the
// `parent` will be the promise it is a continuation from.
function init(promise, parent) {
  console.log('a promise was created', { promise, parent });
}

// The `settled` event happens when a promise receives a resolution or
// rejection value. This may happen synchronously such as when using
// `Promise.resolve()` on non-promise input.
function settled(promise) {
  console.log('a promise resolved or rejected', { promise });
}

// The `before` event runs immediately before a `then()` or `catch()` handler
// runs or an `await` resumes execution.
function before(promise) {
  console.log('a promise is about to call a then handler', { promise });
}

// The `after` event runs immediately after a `then()` handler runs or when
// an `await` begins after resuming from another.
function after(promise) {
  console.log('a promise is done calling a then handler', { promise });
}

// Lifecycle hooks may be started and stopped individually
const stopWatchingInits = promiseHooks.onInit(init);
const stopWatchingSettleds = promiseHooks.onSettled(settled);
const stopWatchingBefores = promiseHooks.onBefore(before);
const stopWatchingAfters = promiseHooks.onAfter(after);

// Or they may be started and stopped in groups
const stopHookSet = promiseHooks.createHook({
  init,
  settled,
  before,
  after,
});

// Trigger the hooks by using promises
const promiseLog = (word) => Promise.resolve(word).then(console.log);
promiseLog('Hello');
promiseLog('World');

// To stop a hook, call the function returned at its creation.
stopWatchingInits();
stopWatchingSettleds();
stopWatchingBefores();
stopWatchingAfters();
stopHookSet();
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
const { promiseHooks } = require('node:v8');

// There are four lifecycle events produced by promises:

// The `init` event represents the creation of a promise. This could be a
// direct creation such as with `new Promise(...)` or a continuation such
// as `then()` or `catch()`. It also happens whenever an async function is
// called or does an `await`. If a continuation promise is created, the
// `parent` will be the promise it is a continuation from.
function init(promise, parent) {
  console.log('a promise was created', { promise, parent });
}

// The `settled` event happens when a promise receives a resolution or
// rejection value. This may happen synchronously such as when using
// `Promise.resolve()` on non-promise input.
function settled(promise) {
  console.log('a promise resolved or rejected', { promise });
}

// The `before` event runs immediately before a `then()` or `catch()` handler
// runs or an `await` resumes execution.
function before(promise) {
  console.log('a promise is about to call a then handler', { promise });
}

// The `after` event runs immediately after a `then()` handler runs or when
// an `await` begins after resuming from another.
function after(promise) {
  console.log('a promise is done calling a then handler', { promise });
}

// Lifecycle hooks may be started and stopped individually
const stopWatchingInits = promiseHooks.onInit(init);
const stopWatchingSettleds = promiseHooks.onSettled(settled);
const stopWatchingBefores = promiseHooks.onBefore(before);
const stopWatchingAfters = promiseHooks.onAfter(after);

// Or they may be started and stopped in groups
const stopHookSet = promiseHooks.createHook({
  init,
  settled,
  before,
  after,
});

// Trigger the hooks by using promises
const promisePrint = (word) => Promise.resolve(word).then(console.log);
promisePrint('Hello');
promisePrint('World');

// To stop a hook, call the function returned at its creation.
stopWatchingInits();
stopWatchingSettleds();
stopWatchingBefores();
stopWatchingAfters();
stopHookSet();

promiseHooks.onInit(init)

  • init <Function> Колбэк init callback при создании промиса.
  • Возвращает: <Function> Функция для отключения хука.

init должен быть обычной функцией; async нельзя — бесконечный цикл микрозадач.

1
2
3
import { promiseHooks } from 'node:v8';

const stop = promiseHooks.onInit((promise, parent) => {});
1
2
3
const { promiseHooks } = require('node:v8');

const stop = promiseHooks.onInit((promise, parent) => {});

promiseHooks.onSettled(settled)

  • settled <Function> Колбэк settled callback при выполнении или отклонении.
  • Возвращает: <Function> Функция для отключения хука.

settled должен быть обычной функцией; async нельзя — бесконечный цикл микрозадач.

1
2
3
import { promiseHooks } from 'node:v8';

const stop = promiseHooks.onSettled((promise) => {});
1
2
3
const { promiseHooks } = require('node:v8');

const stop = promiseHooks.onSettled((promise) => {});

promiseHooks.onBefore(before)

  • before <Function> Колбэк before callback до выполнения продолжения промиса.
  • Возвращает: <Function> Функция для отключения хука.

before должен быть обычной функцией; async нельзя — бесконечный цикл микрозадач.

1
2
3
import { promiseHooks } from 'node:v8';

const stop = promiseHooks.onBefore((promise) => {});
1
2
3
const { promiseHooks } = require('node:v8');

const stop = promiseHooks.onBefore((promise) => {});

promiseHooks.onAfter(after)

  • after <Function> Колбэк after callback после выполнения продолжения промиса.
  • Возвращает: <Function> Функция для отключения хука.

after должен быть обычной функцией; async нельзя — бесконечный цикл микрозадач.

1
2
3
import { promiseHooks } from 'node:v8';

const stop = promiseHooks.onAfter((promise) => {});
1
2
3
const { promiseHooks } = require('node:v8');

const stop = promiseHooks.onAfter((promise) => {});

promiseHooks.createHook(callbacks)

Все колбэки — обычные функции; async запрещён (бесконечный цикл микрозадач).

Регистрирует обработчики событий жизненного цикла промисов.

Колбэки init/before/after/settled вызываются для соответствующих этапов.

Все поля необязательны: например, для отслеживания только создания достаточно init. Подробности — в разделе Колбэки хуков.

1
2
3
4
5
import { promiseHooks } from 'node:v8';

const stopAll = promiseHooks.createHook({
  init(promise, parent) {},
});
1
2
3
4
5
const { promiseHooks } = require('node:v8');

const stopAll = promiseHooks.createHook({
  init(promise, parent) {},
});

Колбэки хуков

События жизни промиса делятся на четыре группы: создание, до/после продолжения или вокруг await, завершение (resolve/reject).

В отличие от async_hooks здесь нет destroy: у сокетов есть «закрыто», у промисов — пока на них ссылается код. Для модели async_hooks используется отслеживание через GC, оно дорогое и сборка может не произойти.

Колбэки init/before/after/settled не должны быть async — иначе бесконечный цикл промисов.

Порядок событий относительно async_hooks не определён.

init(promise, parent)

  • promise <Promise> Создаваемый промис.
  • parent <Promise> Промис-родитель, если есть.

Вызывается при создании промиса. Не гарантирует последующие before/after — только возможность; так бывает, если продолжений не было.

before(promise)

Перед выполнением продолжения: then/catch/finally или возобновление await.

before может быть от 0 до N раз: 0, если продолжений не было; много раз при нескольких цепочках от одного промиса.

after(promise)

Сразу после продолжения: обработчик then/catch/finally или перед следующим await.

settled(promise)

Когда получено значение или отказ; может быть синхронно у Promise.resolve()/reject().

API снимка запуска

Интерфейс v8.startupSnapshot задаёт хуки сериализации/десериализации для пользовательских снимков запуска.

1
2
3
$ node --snapshot-blob snapshot.blob --build-snapshot entry.js
# Запуск процесса со снимком
$ node --snapshot-blob snapshot.blob

В entry.js методы v8.startupSnapshot описывают, как сохранять данные пользовательских объектов в снимке и как восстанавливать их при загрузке. Например, скрипт:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
'use strict';

const fs = require('node:fs');
const zlib = require('node:zlib');
const path = require('node:path');
const assert = require('node:assert');

const v8 = require('node:v8');

class BookShelf {
  storage = new Map();

  // Reading a series of files from directory and store them into storage.
  constructor(directory, books) {
    for (const book of books) {
      this.storage.set(book, fs.readFileSync(path.join(directory, book)));
    }
  }

  static compressAll(shelf) {
    for (const [ book, content ] of shelf.storage) {
      shelf.storage.set(book, zlib.gzipSync(content));
    }
  }

  static decompressAll(shelf) {
    for (const [ book, content ] of shelf.storage) {
      shelf.storage.set(book, zlib.gunzipSync(content));
    }
  }
}

// __dirname here is where the snapshot script is placed
// during snapshot building time.
const shelf = new BookShelf(__dirname, [
  'book1.en_US.txt',
  'book1.es_ES.txt',
  'book2.zh_CN.txt',
]);

assert(v8.startupSnapshot.isBuildingSnapshot());
// On snapshot serialization, compress the books to reduce size.
v8.startupSnapshot.addSerializeCallback(BookShelf.compressAll, shelf);
// On snapshot deserialization, decompress the books.
v8.startupSnapshot.addDeserializeCallback(BookShelf.decompressAll, shelf);
v8.startupSnapshot.setDeserializeMainFunction((shelf) => {
  // process.env and process.argv are refreshed during snapshot
  // deserialization.
  const lang = process.env.BOOK_LANG || 'en_US';
  const book = process.argv[1];
  const name = `${book}.${lang}.txt`;
  console.log(shelf.storage.get(name));
}, shelf);

Получившийся бинарник при запуске выведет данные, десериализованные из снимка, используя обновлённые process.env и process.argv запущенного процесса:

1
2
$ BOOK_LANG=es_ES node --snapshot-blob snapshot.blob book1
# Prints content of book1.es_ES.txt deserialized from the snapshot.

Сейчас приложение, десериализованное из пользовательского снимка, нельзя снова сериализовать в снимок, поэтому эти API доступны только приложениям, которые не были десериализованы из пользовательского снимка.

v8.startupSnapshot.addSerializeCallback(callback[, data])

  • callback <Function> Колбэк, вызываемый перед сериализацией.
  • data <any> Необязательные данные, передаваемые в callback при вызове.

Добавляет колбэк, который будет вызван, когда экземпляр Node.js собирается сериализоваться в снимок и завершиться. Можно использовать для освобождения ресурсов, которые не следует или нельзя сериализовать, либо для приведения пользовательских данных к виду, удобному для сериализации.

Колбэки выполняются в порядке добавления.

v8.startupSnapshot.addDeserializeCallback(callback[, data])

  • callback <Function> Колбэк, вызываемый после десериализации снимка.
  • data <any> Необязательные данные, передаваемые в callback при вызове.

Добавляет колбэк, который будет вызван, когда экземпляр Node.js десериализуется из снимка. callback и data (если заданы) попадут в снимок; их можно использовать для повторной инициализации состояния приложения или повторного получения ресурсов, нужных при перезапуске из снимка.

Колбэки выполняются в порядке добавления.

v8.startupSnapshot.setDeserializeMainFunction(callback[, data])

  • callback <Function> Колбэк точки входа после десериализации снимка.
  • data <any> Необязательные данные, передаваемые в callback при вызове.

Задаёт точку входа приложения Node.js при десериализации из снимка. Можно вызвать только один раз в скрипте сборки снимка. Если вызван, десериализованному приложению не нужен отдельный скрипт точки входа — будет вызван колбэк вместе с десериализованными данными (если заданы); иначе десериализованному приложению по-прежнему нужно передать скрипт точки входа.

v8.startupSnapshot.isBuildingSnapshot()

Возвращает true, если экземпляр Node.js запущен для сборки снимка.

Класс: v8.GCProfiler

Этот API собирает данные о GC в текущем потоке.

new v8.GCProfiler()

Создаёт новый экземпляр класса v8.GCProfiler. Этот API поддерживает синтаксис using.

profiler.start()

Начинает сбор данных о GC.

profiler.stop()

Останавливает сбор данных о GC и возвращает объект. Содержимое объекта приведено ниже.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
{
  "version": 1,
  "startTime": 1674059033862,
  "statistics": [
    {
      "gcType": "Scavenge",
      "beforeGC": {
        "heapStatistics": {
          "totalHeapSize": 5005312,
          "totalHeapSizeExecutable": 524288,
          "totalPhysicalSize": 5226496,
          "totalAvailableSize": 4341325216,
          "totalGlobalHandlesSize": 8192,
          "usedGlobalHandlesSize": 2112,
          "usedHeapSize": 4883840,
          "heapSizeLimit": 4345298944,
          "mallocedMemory": 254128,
          "externalMemory": 225138,
          "peakMallocedMemory": 181760
        },
        "heapSpaceStatistics": [
          {
            "spaceName": "read_only_space",
            "spaceSize": 0,
            "spaceUsedSize": 0,
            "spaceAvailableSize": 0,
            "physicalSpaceSize": 0
          }
        ]
      },
      "cost": 1574.14,
      "afterGC": {
        "heapStatistics": {
          "totalHeapSize": 6053888,
          "totalHeapSizeExecutable": 524288,
          "totalPhysicalSize": 5500928,
          "totalAvailableSize": 4341101384,
          "totalGlobalHandlesSize": 8192,
          "usedGlobalHandlesSize": 2112,
          "usedHeapSize": 4059096,
          "heapSizeLimit": 4345298944,
          "mallocedMemory": 254128,
          "externalMemory": 225138,
          "peakMallocedMemory": 181760
        },
        "heapSpaceStatistics": [
          {
            "spaceName": "read_only_space",
            "spaceSize": 0,
            "spaceUsedSize": 0,
            "spaceAvailableSize": 0,
            "physicalSpaceSize": 0
          }
        ]
      }
    }
  ],
  "endTime": 1674059036865
}

Пример:

1
2
3
4
5
6
import { GCProfiler } from 'node:v8';
const profiler = new GCProfiler();
profiler.start();
setTimeout(() => {
  console.log(profiler.stop());
}, 1000);
1
2
3
4
5
6
const { GCProfiler } = require('node:v8');
const profiler = new GCProfiler();
profiler.start();
setTimeout(() => {
  console.log(profiler.stop());
}, 1000);

profiler[Symbol.dispose]()

Останавливает сбор данных о GC и отбрасывает профиль.

Класс: SyncCPUProfileHandle

syncCpuProfileHandle.stop()

Останавливает сбор профиля и возвращает данные профиля.

syncCpuProfileHandle[Symbol.dispose]()

Останавливает сбор профиля; профиль будет отброшен.

Класс: SyncHeapProfileHandle

Стабильность: 1 – Экспериментальная

syncHeapProfileHandle.stop()

Останавливает сбор профиля и возвращает данные профиля.

syncHeapProfileHandle[Symbol.dispose]()

Останавливает сбор профиля; профиль будет отброшен.

Класс: CPUProfileHandle

cpuProfileHandle.stop()

Останавливает сбор профиля и возвращает Promise, который разрешается данными профиля или отклоняется с ошибкой.

cpuProfileHandle[Symbol.asyncDispose]()

Останавливает сбор профиля; профиль будет отброшен.

Класс: HeapProfileHandle

heapProfileHandle.stop()

Останавливает сбор профиля и возвращает Promise, который разрешается данными профиля или отклоняется с ошибкой.

heapProfileHandle[Symbol.asyncDispose]()

Останавливает сбор профиля; профиль будет отброшен.

v8.isStringOneByteRepresentation(content)

V8 поддерживает только Latin-1/ISO-8859-1 и UTF16 как внутреннее представление строки. Если у content внутреннее представление — Latin-1/ISO-8859-1, функция вернёт true; иначе — false.

Если метод возвращает false, это не значит, что в строке есть символы вне Latin-1/ISO-8859-1. Иногда строка Latin-1 может храниться как UTF16.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import { isStringOneByteRepresentation } from 'node:v8';
import { Buffer } from 'node:buffer';

const Encoding = {
  latin1: 1,
  utf16le: 2,
};
const buffer = Buffer.alloc(100);
function writeString(input) {
  if (isStringOneByteRepresentation(input)) {
    console.log(`input: '${input}'`);
    buffer.writeUint8(Encoding.latin1);
    buffer.writeUint32LE(input.length, 1);
    buffer.write(input, 5, 'latin1');
    console.log(`decoded: '${buffer.toString('latin1', 5, 5 + input.length)}'\n`);
  } else {
    console.log(`input: '${input}'`);
    buffer.writeUint8(Encoding.utf16le);
    buffer.writeUint32LE(input.length * 2, 1);
    buffer.write(input, 5, 'utf16le');
    console.log(`decoded: '${buffer.toString('utf16le', 5, 5 + input.length * 2)}'`);
  }
}
writeString('hello');
writeString('你好');
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
const { isStringOneByteRepresentation } = require('node:v8');
const { Buffer } = require('node:buffer');

const Encoding = {
  latin1: 1,
  utf16le: 2,
};
const buffer = Buffer.alloc(100);
function writeString(input) {
  if (isStringOneByteRepresentation(input)) {
    console.log(`input: '${input}'`);
    buffer.writeUint8(Encoding.latin1);
    buffer.writeUint32LE(input.length, 1);
    buffer.write(input, 5, 'latin1');
    console.log(`decoded: '${buffer.toString('latin1', 5, 5 + input.length)}'\n`);
  } else {
    console.log(`input: '${input}'`);
    buffer.writeUint8(Encoding.utf16le);
    buffer.writeUint32LE(input.length * 2, 1);
    buffer.write(input, 5, 'utf16le');
    console.log(`decoded: '${buffer.toString('utf16le', 5, 5 + input.length * 2)}'`);
  }
}
writeString('hello');
writeString('你好');

v8.startCpuProfile()

Запускает профилирование CPU и возвращает объект SyncCPUProfileHandle. Этот API поддерживает синтаксис using.

1
2
3
const handle = v8.startCpuProfile();
const profile = handle.stop();
console.log(profile);

v8.startHeapProfile([options])

  • options <Object>
  • sampleInterval <number> Средний интервал выборки в байтах. По умолчанию: 524288 (512 KiB).
  • stackDepth <integer> Максимальная глубина стека для выборок. По умолчанию: 16.
  • forceGC <boolean> Принудительно запустить сборку мусора перед снятием профиля. По умолчанию: false.
  • includeObjectsCollectedByMajorGC <boolean> Включать объекты, собранные major GC. По умолчанию: false.
  • includeObjectsCollectedByMinorGC <boolean> Включать объекты, собранные minor GC. По умолчанию: false.
  • Возвращает: <SyncHeapProfileHandle>

Запускает профилирование кучи и возвращает объект SyncHeapProfileHandle. Этот API поддерживает синтаксис using.

1
2
3
4
5
const v8 = require('node:v8');

const handle = v8.startHeapProfile();
const profile = handle.stop();
console.log(profile);
1
2
3
4
5
import v8 from 'node:v8';

const handle = v8.startHeapProfile();
const profile = handle.stop();
console.log(profile);

С пользовательскими параметрами:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
const v8 = require('node:v8');

const handle = v8.startHeapProfile({
  sampleInterval: 1024,
  stackDepth: 8,
  forceGC: true,
  includeObjectsCollectedByMajorGC: true,
});
const profile = handle.stop();
console.log(profile);
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
import v8 from 'node:v8';

const handle = v8.startHeapProfile({
  sampleInterval: 1024,
  stackDepth: 8,
  forceGC: true,
  includeObjectsCollectedByMajorGC: true,
});
const profile = handle.stop();
console.log(profile);

Комментарии