• 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  ArrayBuffer,
20  ArrayPrototypeForEach,
21  ArrayPrototypePush,
22  DataView,
23  Error,
24  Float32Array,
25  Float64Array,
26  Int16Array,
27  Int32Array,
28  Int8Array,
29  ObjectPrototypeToString,
30  SafeMap,
31  Uint16Array,
32  Uint32Array,
33  Uint8Array,
34  Uint8ClampedArray,
35} = primordials;
36
37const { Buffer } = require('buffer');
38const { validateString } = require('internal/validators');
39const {
40  Serializer,
41  Deserializer
42} = internalBinding('serdes');
43
44let profiler = {};
45if (internalBinding('config').hasInspector) {
46  profiler = internalBinding('profiler');
47}
48
49const assert = require('internal/assert');
50const { copy } = internalBinding('buffer');
51const { inspect } = require('internal/util/inspect');
52const { FastBuffer } = require('internal/buffer');
53const { getValidatedPath } = require('internal/fs/utils');
54const { toNamespacedPath } = require('path');
55const {
56  createHeapSnapshotStream,
57  triggerHeapSnapshot
58} = internalBinding('heap_utils');
59const { HeapSnapshotStream } = require('internal/heap_utils');
60
61/**
62 * Generates a snapshot of the current V8 heap
63 * and writes it to a JSON file.
64 * @param {string} [filename]
65 * @returns {string}
66 */
67function writeHeapSnapshot(filename) {
68  if (filename !== undefined) {
69    filename = getValidatedPath(filename);
70    filename = toNamespacedPath(filename);
71  }
72  return triggerHeapSnapshot(filename);
73}
74
75/**
76 * Generates a snapshot of the current V8 heap
77 * and returns a Readable Stream.
78 * @returns {import('./stream.js').Readable}
79 */
80function getHeapSnapshot() {
81  const handle = createHeapSnapshotStream();
82  assert(handle);
83  return new HeapSnapshotStream(handle);
84}
85
86const {
87  cachedDataVersionTag,
88  setFlagsFromString: _setFlagsFromString,
89  heapStatisticsBuffer,
90  heapSpaceStatisticsBuffer,
91  heapCodeStatisticsBuffer,
92  updateHeapStatisticsBuffer,
93  updateHeapSpaceStatisticsBuffer,
94  updateHeapCodeStatisticsBuffer,
95
96  // Properties for heap statistics buffer extraction.
97  kTotalHeapSizeIndex,
98  kTotalHeapSizeExecutableIndex,
99  kTotalPhysicalSizeIndex,
100  kTotalAvailableSize,
101  kUsedHeapSizeIndex,
102  kHeapSizeLimitIndex,
103  kDoesZapGarbageIndex,
104  kMallocedMemoryIndex,
105  kPeakMallocedMemoryIndex,
106  kNumberOfNativeContextsIndex,
107  kNumberOfDetachedContextsIndex,
108
109  // Properties for heap spaces statistics buffer extraction.
110  kHeapSpaces,
111  kSpaceSizeIndex,
112  kSpaceUsedSizeIndex,
113  kSpaceAvailableSizeIndex,
114  kPhysicalSpaceSizeIndex,
115
116  // Properties for heap code statistics buffer extraction.
117  kCodeAndMetadataSizeIndex,
118  kBytecodeAndMetadataSizeIndex,
119  kExternalScriptSourceSizeIndex
120} = internalBinding('v8');
121
122const kNumberOfHeapSpaces = kHeapSpaces.length;
123
124/**
125 * Sets V8 command-line flags.
126 * @param {string} flags
127 * @returns {void}
128 */
129function setFlagsFromString(flags) {
130  validateString(flags, 'flags');
131  _setFlagsFromString(flags);
132}
133
134/**
135 * Gets the current V8 heap statistics.
136 * @returns {{
137 *   total_heap_size: number;
138 *   total_heap_size_executable: number;
139 *   total_physical_size: number;
140 *   total_available_size: number;
141 *   used_heap_size: number;
142 *   heap_size_limit: number;
143 *   malloced_memory: number;
144 *   peak_malloced_memory: number;
145 *   does_zap_garbage: number;
146 *   number_of_native_contexts: number;
147 *   number_of_detached_contexts: number;
148 *   }}
149 */
150function getHeapStatistics() {
151  const buffer = heapStatisticsBuffer;
152
153  updateHeapStatisticsBuffer();
154
155  return {
156    'total_heap_size': buffer[kTotalHeapSizeIndex],
157    'total_heap_size_executable': buffer[kTotalHeapSizeExecutableIndex],
158    'total_physical_size': buffer[kTotalPhysicalSizeIndex],
159    'total_available_size': buffer[kTotalAvailableSize],
160    'used_heap_size': buffer[kUsedHeapSizeIndex],
161    'heap_size_limit': buffer[kHeapSizeLimitIndex],
162    'malloced_memory': buffer[kMallocedMemoryIndex],
163    'peak_malloced_memory': buffer[kPeakMallocedMemoryIndex],
164    'does_zap_garbage': buffer[kDoesZapGarbageIndex],
165    'number_of_native_contexts': buffer[kNumberOfNativeContextsIndex],
166    'number_of_detached_contexts': buffer[kNumberOfDetachedContextsIndex]
167  };
168}
169
170/**
171 * Gets the current V8 heap space statistics.
172 * @returns {{
173 *   space_name: string;
174 *   space_size: number;
175 *   space_used_size: number;
176 *   space_available_size: number;
177 *   physical_space_size: number;
178 *   }[]}
179 */
180function getHeapSpaceStatistics() {
181  const heapSpaceStatistics = new Array(kNumberOfHeapSpaces);
182  const buffer = heapSpaceStatisticsBuffer;
183
184  for (let i = 0; i < kNumberOfHeapSpaces; i++) {
185    updateHeapSpaceStatisticsBuffer(i);
186    heapSpaceStatistics[i] = {
187      space_name: kHeapSpaces[i],
188      space_size: buffer[kSpaceSizeIndex],
189      space_used_size: buffer[kSpaceUsedSizeIndex],
190      space_available_size: buffer[kSpaceAvailableSizeIndex],
191      physical_space_size: buffer[kPhysicalSpaceSizeIndex]
192    };
193  }
194
195  return heapSpaceStatistics;
196}
197
198/**
199 * Gets the current V8 heap code statistics.
200 * @returns {{
201 *   code_and_metadata_size: number;
202 *   bytecode_and_metadata_size: number;
203 *   external_script_source_size: number;
204 *   }}
205 */
206function getHeapCodeStatistics() {
207  const buffer = heapCodeStatisticsBuffer;
208
209  updateHeapCodeStatisticsBuffer();
210  return {
211    'code_and_metadata_size': buffer[kCodeAndMetadataSizeIndex],
212    'bytecode_and_metadata_size': buffer[kBytecodeAndMetadataSizeIndex],
213    'external_script_source_size': buffer[kExternalScriptSourceSizeIndex]
214  };
215}
216
217/* V8 serialization API */
218
219/* JS methods for the base objects */
220Serializer.prototype._getDataCloneError = Error;
221
222/**
223 * Reads raw bytes from the deserializer's internal buffer.
224 * @param {number} length
225 * @returns {Buffer}
226 */
227Deserializer.prototype.readRawBytes = function readRawBytes(length) {
228  const offset = this._readRawBytes(length);
229  // `this.buffer` can be a Buffer or a plain Uint8Array, so just calling
230  // `.slice()` doesn't work.
231  return new FastBuffer(this.buffer.buffer,
232                        this.buffer.byteOffset + offset,
233                        length);
234};
235
236/* Keep track of how to handle different ArrayBufferViews.
237 * The default Serializer for Node does not use the V8 methods for serializing
238 * those objects because Node's `Buffer` objects use pooled allocation in many
239 * cases, and their underlying `ArrayBuffer`s would show up in the
240 * serialization. Because a) those may contain sensitive data and the user
241 * may not be aware of that and b) they are often much larger than the `Buffer`
242 * itself, custom serialization is applied. */
243const arrayBufferViewTypes = [Int8Array, Uint8Array, Uint8ClampedArray,
244                              Int16Array, Uint16Array, Int32Array, Uint32Array,
245                              Float32Array, Float64Array, DataView];
246
247const arrayBufferViewTypeToIndex = new SafeMap();
248
249{
250  const dummy = new ArrayBuffer();
251  ArrayPrototypeForEach(arrayBufferViewTypes, (ctor, i) => {
252    const tag = ObjectPrototypeToString(new ctor(dummy));
253    arrayBufferViewTypeToIndex.set(tag, i);
254  });
255}
256
257const bufferConstructorIndex =
258  ArrayPrototypePush(arrayBufferViewTypes, FastBuffer) - 1;
259
260class DefaultSerializer extends Serializer {
261  constructor() {
262    super();
263
264    this._setTreatArrayBufferViewsAsHostObjects(true);
265  }
266
267  /**
268   * Used to write some kind of host object, i.e. an
269   * object that is created by native C++ bindings.
270   * @param {Object} abView
271   * @returns {void}
272   */
273  _writeHostObject(abView) {
274    let i = 0;
275    if (abView.constructor === Buffer) {
276      i = bufferConstructorIndex;
277    } else {
278      const tag = ObjectPrototypeToString(abView);
279      i = arrayBufferViewTypeToIndex.get(tag);
280
281      if (i === undefined) {
282        throw new this._getDataCloneError(
283          `Unserializable host object: ${inspect(abView)}`);
284      }
285    }
286    this.writeUint32(i);
287    this.writeUint32(abView.byteLength);
288    this.writeRawBytes(new Uint8Array(abView.buffer,
289                                      abView.byteOffset,
290                                      abView.byteLength));
291  }
292}
293
294class DefaultDeserializer extends Deserializer {
295  /**
296   * Used to read some kind of host object, i.e. an
297   * object that is created by native C++ bindings.
298   * @returns {any}
299   */
300  _readHostObject() {
301    const typeIndex = this.readUint32();
302    const ctor = arrayBufferViewTypes[typeIndex];
303    const byteLength = this.readUint32();
304    const byteOffset = this._readRawBytes(byteLength);
305    const BYTES_PER_ELEMENT = ctor.BYTES_PER_ELEMENT || 1;
306
307    const offset = this.buffer.byteOffset + byteOffset;
308    if (offset % BYTES_PER_ELEMENT === 0) {
309      return new ctor(this.buffer.buffer,
310                      offset,
311                      byteLength / BYTES_PER_ELEMENT);
312    }
313    // Copy to an aligned buffer first.
314    const buffer_copy = Buffer.allocUnsafe(byteLength);
315    copy(this.buffer, buffer_copy, 0, byteOffset, byteOffset + byteLength);
316    return new ctor(buffer_copy.buffer,
317                    buffer_copy.byteOffset,
318                    byteLength / BYTES_PER_ELEMENT);
319  }
320}
321
322/**
323 * Uses a `DefaultSerializer` to serialize `value`
324 * into a buffer.
325 * @param {any} value
326 * @returns {Buffer}
327 */
328function serialize(value) {
329  const ser = new DefaultSerializer();
330  ser.writeHeader();
331  ser.writeValue(value);
332  return ser.releaseBuffer();
333}
334
335/**
336 * Uses a `DefaultDeserializer` with default options
337 * to read a JavaScript value from a buffer.
338 * @param {Buffer | TypedArray | DataView} buffer
339 * @returns {any}
340 */
341function deserialize(buffer) {
342  const der = new DefaultDeserializer(buffer);
343  der.readHeader();
344  return der.readValue();
345}
346
347module.exports = {
348  cachedDataVersionTag,
349  getHeapSnapshot,
350  getHeapStatistics,
351  getHeapSpaceStatistics,
352  getHeapCodeStatistics,
353  setFlagsFromString,
354  Serializer,
355  Deserializer,
356  DefaultSerializer,
357  DefaultDeserializer,
358  deserialize,
359  takeCoverage: profiler.takeCoverage,
360  stopCoverage: profiler.stopCoverage,
361  serialize,
362  writeHeapSnapshot,
363};
364