• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright (c) 2014, StrongLoop Inc.
2//
3// Permission to use, copy, modify, and/or distribute this software for any
4// purpose with or without fee is hereby granted, provided that the above
5// copyright notice and this permission notice appear in all copies.
6//
7// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
8// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
9// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
10// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
11// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
12// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
13// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
14
15'use strict';
16
17const {
18  Array,
19  BigInt64Array,
20  BigUint64Array,
21  DataView,
22  Error,
23  Float32Array,
24  Float64Array,
25  Int16Array,
26  Int32Array,
27  Int8Array,
28  ObjectPrototypeToString,
29  Uint16Array,
30  Uint32Array,
31  Uint8Array,
32  Uint8ClampedArray,
33} = primordials;
34
35const { Buffer } = require('buffer');
36const { validateString, validateUint32 } = require('internal/validators');
37const {
38  Serializer,
39  Deserializer,
40} = internalBinding('serdes');
41const {
42  namespace: startupSnapshot,
43} = require('internal/v8/startup_snapshot');
44
45let profiler = {};
46if (internalBinding('config').hasInspector) {
47  profiler = internalBinding('profiler');
48}
49
50const assert = require('internal/assert');
51const { copy } = internalBinding('buffer');
52const { inspect } = require('internal/util/inspect');
53const { FastBuffer } = require('internal/buffer');
54const { getValidatedPath } = require('internal/fs/utils');
55const { toNamespacedPath } = require('path');
56const {
57  createHeapSnapshotStream,
58  triggerHeapSnapshot,
59} = internalBinding('heap_utils');
60const { HeapSnapshotStream } = require('internal/heap_utils');
61const promiseHooks = require('internal/promise_hooks');
62const { getOptionValue } = require('internal/options');
63const { JSONParse } = primordials;
64/**
65 * Generates a snapshot of the current V8 heap
66 * and writes it to a JSON file.
67 * @param {string} [filename]
68 * @returns {string}
69 */
70function writeHeapSnapshot(filename) {
71  if (filename !== undefined) {
72    filename = getValidatedPath(filename);
73    filename = toNamespacedPath(filename);
74  }
75  return triggerHeapSnapshot(filename);
76}
77
78/**
79 * Generates a snapshot of the current V8 heap
80 * and returns a Readable Stream.
81 * @returns {import('./stream.js').Readable}
82 */
83function getHeapSnapshot() {
84  const handle = createHeapSnapshotStream();
85  assert(handle);
86  return new HeapSnapshotStream(handle);
87}
88
89// We need to get the buffer from the binding at the callsite since
90// it's re-initialized after deserialization.
91const binding = internalBinding('v8');
92
93const {
94  cachedDataVersionTag,
95  setFlagsFromString: _setFlagsFromString,
96  updateHeapStatisticsBuffer,
97  updateHeapSpaceStatisticsBuffer,
98  updateHeapCodeStatisticsBuffer,
99  setHeapSnapshotNearHeapLimit: _setHeapSnapshotNearHeapLimit,
100
101  // Properties for heap statistics buffer extraction.
102  kTotalHeapSizeIndex,
103  kTotalHeapSizeExecutableIndex,
104  kTotalPhysicalSizeIndex,
105  kTotalAvailableSize,
106  kUsedHeapSizeIndex,
107  kHeapSizeLimitIndex,
108  kDoesZapGarbageIndex,
109  kMallocedMemoryIndex,
110  kPeakMallocedMemoryIndex,
111  kNumberOfNativeContextsIndex,
112  kNumberOfDetachedContextsIndex,
113  kTotalGlobalHandlesSizeIndex,
114  kUsedGlobalHandlesSizeIndex,
115  kExternalMemoryIndex,
116
117  // Properties for heap spaces statistics buffer extraction.
118  kHeapSpaces,
119  kSpaceSizeIndex,
120  kSpaceUsedSizeIndex,
121  kSpaceAvailableSizeIndex,
122  kPhysicalSpaceSizeIndex,
123
124  // Properties for heap code statistics buffer extraction.
125  kCodeAndMetadataSizeIndex,
126  kBytecodeAndMetadataSizeIndex,
127  kExternalScriptSourceSizeIndex,
128  kCPUProfilerMetaDataSizeIndex,
129} = binding;
130
131const kNumberOfHeapSpaces = kHeapSpaces.length;
132
133/**
134 * Sets V8 command-line flags.
135 * @param {string} flags
136 * @returns {void}
137 */
138function setFlagsFromString(flags) {
139  validateString(flags, 'flags');
140  _setFlagsFromString(flags);
141}
142
143/**
144 * Gets the current V8 heap statistics.
145 * @returns {{
146 *   total_heap_size: number;
147 *   total_heap_size_executable: number;
148 *   total_physical_size: number;
149 *   total_available_size: number;
150 *   used_heap_size: number;
151 *   heap_size_limit: number;
152 *   malloced_memory: number;
153 *   peak_malloced_memory: number;
154 *   does_zap_garbage: number;
155 *   number_of_native_contexts: number;
156 *   number_of_detached_contexts: number;
157 *   }}
158 */
159function getHeapStatistics() {
160  const buffer = binding.heapStatisticsBuffer;
161
162  updateHeapStatisticsBuffer();
163
164  return {
165    total_heap_size: buffer[kTotalHeapSizeIndex],
166    total_heap_size_executable: buffer[kTotalHeapSizeExecutableIndex],
167    total_physical_size: buffer[kTotalPhysicalSizeIndex],
168    total_available_size: buffer[kTotalAvailableSize],
169    used_heap_size: buffer[kUsedHeapSizeIndex],
170    heap_size_limit: buffer[kHeapSizeLimitIndex],
171    malloced_memory: buffer[kMallocedMemoryIndex],
172    peak_malloced_memory: buffer[kPeakMallocedMemoryIndex],
173    does_zap_garbage: buffer[kDoesZapGarbageIndex],
174    number_of_native_contexts: buffer[kNumberOfNativeContextsIndex],
175    number_of_detached_contexts: buffer[kNumberOfDetachedContextsIndex],
176    total_global_handles_size: buffer[kTotalGlobalHandlesSizeIndex],
177    used_global_handles_size: buffer[kUsedGlobalHandlesSizeIndex],
178    external_memory: buffer[kExternalMemoryIndex],
179  };
180}
181
182/**
183 * Gets the current V8 heap space statistics.
184 * @returns {{
185 *   space_name: string;
186 *   space_size: number;
187 *   space_used_size: number;
188 *   space_available_size: number;
189 *   physical_space_size: number;
190 *   }[]}
191 */
192function getHeapSpaceStatistics() {
193  const heapSpaceStatistics = new Array(kNumberOfHeapSpaces);
194  const buffer = binding.heapSpaceStatisticsBuffer;
195
196  for (let i = 0; i < kNumberOfHeapSpaces; i++) {
197    updateHeapSpaceStatisticsBuffer(i);
198    heapSpaceStatistics[i] = {
199      space_name: kHeapSpaces[i],
200      space_size: buffer[kSpaceSizeIndex],
201      space_used_size: buffer[kSpaceUsedSizeIndex],
202      space_available_size: buffer[kSpaceAvailableSizeIndex],
203      physical_space_size: buffer[kPhysicalSpaceSizeIndex],
204    };
205  }
206
207  return heapSpaceStatistics;
208}
209
210/**
211 * Gets the current V8 heap code statistics.
212 * @returns {{
213 *   code_and_metadata_size: number;
214 *   bytecode_and_metadata_size: number;
215 *   external_script_source_size: number;
216 *   cpu_profiler_metadata_size: number;
217 *   }}
218 */
219function getHeapCodeStatistics() {
220  const buffer = binding.heapCodeStatisticsBuffer;
221
222  updateHeapCodeStatisticsBuffer();
223  return {
224    code_and_metadata_size: buffer[kCodeAndMetadataSizeIndex],
225    bytecode_and_metadata_size: buffer[kBytecodeAndMetadataSizeIndex],
226    external_script_source_size: buffer[kExternalScriptSourceSizeIndex],
227    cpu_profiler_metadata_size: buffer[kCPUProfilerMetaDataSizeIndex],
228  };
229}
230
231let heapSnapshotNearHeapLimitCallbackAdded = false;
232function setHeapSnapshotNearHeapLimit(limit) {
233  validateUint32(limit, 'limit', 1);
234  if (heapSnapshotNearHeapLimitCallbackAdded ||
235      getOptionValue('--heapsnapshot-near-heap-limit') > 0
236  ) {
237    return;
238  }
239  heapSnapshotNearHeapLimitCallbackAdded = true;
240  _setHeapSnapshotNearHeapLimit(limit);
241}
242
243/* V8 serialization API */
244
245/* JS methods for the base objects */
246Serializer.prototype._getDataCloneError = Error;
247
248/**
249 * Reads raw bytes from the deserializer's internal buffer.
250 * @param {number} length
251 * @returns {Buffer}
252 */
253Deserializer.prototype.readRawBytes = function readRawBytes(length) {
254  const offset = this._readRawBytes(length);
255  // `this.buffer` can be a Buffer or a plain Uint8Array, so just calling
256  // `.slice()` doesn't work.
257  return new FastBuffer(this.buffer.buffer,
258                        this.buffer.byteOffset + offset,
259                        length);
260};
261
262function arrayBufferViewTypeToIndex(abView) {
263  const type = ObjectPrototypeToString(abView);
264  if (type === '[object Int8Array]') return 0;
265  if (type === '[object Uint8Array]') return 1;
266  if (type === '[object Uint8ClampedArray]') return 2;
267  if (type === '[object Int16Array]') return 3;
268  if (type === '[object Uint16Array]') return 4;
269  if (type === '[object Int32Array]') return 5;
270  if (type === '[object Uint32Array]') return 6;
271  if (type === '[object Float32Array]') return 7;
272  if (type === '[object Float64Array]') return 8;
273  if (type === '[object DataView]') return 9;
274  // Index 10 is FastBuffer.
275  if (type === '[object BigInt64Array]') return 11;
276  if (type === '[object BigUint64Array]') return 12;
277  return -1;
278}
279
280function arrayBufferViewIndexToType(index) {
281  if (index === 0) return Int8Array;
282  if (index === 1) return Uint8Array;
283  if (index === 2) return Uint8ClampedArray;
284  if (index === 3) return Int16Array;
285  if (index === 4) return Uint16Array;
286  if (index === 5) return Int32Array;
287  if (index === 6) return Uint32Array;
288  if (index === 7) return Float32Array;
289  if (index === 8) return Float64Array;
290  if (index === 9) return DataView;
291  if (index === 10) return FastBuffer;
292  if (index === 11) return BigInt64Array;
293  if (index === 12) return BigUint64Array;
294  return undefined;
295}
296
297class DefaultSerializer extends Serializer {
298  constructor() {
299    super();
300
301    this._setTreatArrayBufferViewsAsHostObjects(true);
302  }
303
304  /**
305   * Used to write some kind of host object, i.e. an
306   * object that is created by native C++ bindings.
307   * @param {object} abView
308   * @returns {void}
309   */
310  _writeHostObject(abView) {
311    // Keep track of how to handle different ArrayBufferViews. The default
312    // Serializer for Node does not use the V8 methods for serializing those
313    // objects because Node's `Buffer` objects use pooled allocation in many
314    // cases, and their underlying `ArrayBuffer`s would show up in the
315    // serialization. Because a) those may contain sensitive data and the user
316    // may not be aware of that and b) they are often much larger than the
317    // `Buffer` itself, custom serialization is applied.
318    let i = 10;  // FastBuffer
319    if (abView.constructor !== Buffer) {
320      i = arrayBufferViewTypeToIndex(abView);
321      if (i === -1) {
322        throw new this._getDataCloneError(
323          `Unserializable host object: ${inspect(abView)}`);
324      }
325    }
326    this.writeUint32(i);
327    this.writeUint32(abView.byteLength);
328    this.writeRawBytes(new Uint8Array(abView.buffer,
329                                      abView.byteOffset,
330                                      abView.byteLength));
331  }
332}
333
334class DefaultDeserializer extends Deserializer {
335  /**
336   * Used to read some kind of host object, i.e. an
337   * object that is created by native C++ bindings.
338   * @returns {any}
339   */
340  _readHostObject() {
341    const typeIndex = this.readUint32();
342    const ctor = arrayBufferViewIndexToType(typeIndex);
343    const byteLength = this.readUint32();
344    const byteOffset = this._readRawBytes(byteLength);
345    const BYTES_PER_ELEMENT = ctor.BYTES_PER_ELEMENT || 1;
346
347    const offset = this.buffer.byteOffset + byteOffset;
348    if (offset % BYTES_PER_ELEMENT === 0) {
349      return new ctor(this.buffer.buffer,
350                      offset,
351                      byteLength / BYTES_PER_ELEMENT);
352    }
353    // Copy to an aligned buffer first.
354    const buffer_copy = Buffer.allocUnsafe(byteLength);
355    copy(this.buffer, buffer_copy, 0, byteOffset, byteOffset + byteLength);
356    return new ctor(buffer_copy.buffer,
357                    buffer_copy.byteOffset,
358                    byteLength / BYTES_PER_ELEMENT);
359  }
360}
361
362/**
363 * Uses a `DefaultSerializer` to serialize `value`
364 * into a buffer.
365 * @param {any} value
366 * @returns {Buffer}
367 */
368function serialize(value) {
369  const ser = new DefaultSerializer();
370  ser.writeHeader();
371  ser.writeValue(value);
372  return ser.releaseBuffer();
373}
374
375/**
376 * Uses a `DefaultDeserializer` with default options
377 * to read a JavaScript value from a buffer.
378 * @param {Buffer | TypedArray | DataView} buffer
379 * @returns {any}
380 */
381function deserialize(buffer) {
382  const der = new DefaultDeserializer(buffer);
383  der.readHeader();
384  return der.readValue();
385}
386
387class GCProfiler {
388  #profiler = null;
389
390  start() {
391    if (!this.#profiler) {
392      this.#profiler = new binding.GCProfiler();
393      this.#profiler.start();
394    }
395  }
396
397  stop() {
398    if (this.#profiler) {
399      const data = this.#profiler.stop();
400      this.#profiler = null;
401      return JSONParse(data);
402    }
403  }
404}
405
406module.exports = {
407  cachedDataVersionTag,
408  getHeapSnapshot,
409  getHeapStatistics,
410  getHeapSpaceStatistics,
411  getHeapCodeStatistics,
412  setFlagsFromString,
413  Serializer,
414  Deserializer,
415  DefaultSerializer,
416  DefaultDeserializer,
417  deserialize,
418  takeCoverage: profiler.takeCoverage,
419  stopCoverage: profiler.stopCoverage,
420  serialize,
421  writeHeapSnapshot,
422  promiseHooks,
423  startupSnapshot,
424  setHeapSnapshotNearHeapLimit,
425  GCProfiler,
426};
427