• 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 
17 const {
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 
37 const { Buffer } = require('buffer');
38 const { validateString } = require('internal/validators');
39 const {
40   Serializer,
41   Deserializer
42 } = internalBinding('serdes');
43 
44 let profiler = {};
45 if (internalBinding('config').hasInspector) {
46   profiler = internalBinding('profiler');
47 }
48 
49 const assert = require('internal/assert');
50 const { copy } = internalBinding('buffer');
51 const { inspect } = require('internal/util/inspect');
52 const { FastBuffer } = require('internal/buffer');
53 const { getValidatedPath } = require('internal/fs/utils');
54 const { toNamespacedPath } = require('path');
55 const {
56   createHeapSnapshotStream,
57   triggerHeapSnapshot
58 } = internalBinding('heap_utils');
59 const { 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  */
67 function 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  */
80 function getHeapSnapshot() {
81   const handle = createHeapSnapshotStream();
82   assert(handle);
83   return new HeapSnapshotStream(handle);
84 }
85 
86 const {
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 
122 const kNumberOfHeapSpaces = kHeapSpaces.length;
123 
124 /**
125  * Sets V8 command-line flags.
126  * @param {string} flags
127  * @returns {void}
128  */
129 function 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  */
150 function 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  */
180 function 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  */
206 function 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 */
220 Serializer.prototype._getDataCloneError = Error;
221 
222 /**
223  * Reads raw bytes from the deserializer's internal buffer.
224  * @param {number} length
225  * @returns {Buffer}
226  */
227 Deserializer.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. */
243 const arrayBufferViewTypes = [Int8Array, Uint8Array, Uint8ClampedArray,
244                               Int16Array, Uint16Array, Int32Array, Uint32Array,
245                               Float32Array, Float64Array, DataView];
246 
247 const 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 
257 const bufferConstructorIndex =
258   ArrayPrototypePush(arrayBufferViewTypes, FastBuffer) - 1;
259 
260 class 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 
294 class 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  */
328 function 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  */
341 function deserialize(buffer) {
342   const der = new DefaultDeserializer(buffer);
343   der.readHeader();
344   return der.readValue();
345 }
346 
347 module.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