• 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  Error,
21  Float32Array,
22  Float64Array,
23  Int16Array,
24  Int32Array,
25  Int8Array,
26  Map,
27  ObjectPrototypeToString,
28  Uint16Array,
29  Uint32Array,
30  Uint8Array,
31  Uint8ClampedArray,
32} = primordials;
33
34const { Buffer } = require('buffer');
35const { validateString } = require('internal/validators');
36const {
37  Serializer: _Serializer,
38  Deserializer: _Deserializer
39} = internalBinding('serdes');
40const assert = require('internal/assert');
41const { copy } = internalBinding('buffer');
42const { inspect } = require('internal/util/inspect');
43const { FastBuffer } = require('internal/buffer');
44const { getValidatedPath } = require('internal/fs/utils');
45const { toNamespacedPath } = require('path');
46const {
47  createHeapSnapshotStream,
48  triggerHeapSnapshot
49} = internalBinding('heap_utils');
50const { HeapSnapshotStream } = require('internal/heap_utils');
51
52function writeHeapSnapshot(filename) {
53  if (filename !== undefined) {
54    filename = getValidatedPath(filename);
55    filename = toNamespacedPath(filename);
56  }
57  return triggerHeapSnapshot(filename);
58}
59
60function getHeapSnapshot() {
61  const handle = createHeapSnapshotStream();
62  assert(handle);
63  return new HeapSnapshotStream(handle);
64}
65
66// Calling exposed c++ functions directly throws exception as it expected to be
67// called with new operator and caused an assert to fire.
68// Creating JS wrapper so that it gets caught at JS layer.
69class Serializer extends _Serializer { }
70
71class Deserializer extends _Deserializer { }
72
73const {
74  cachedDataVersionTag,
75  setFlagsFromString: _setFlagsFromString,
76  heapStatisticsArrayBuffer,
77  heapSpaceStatisticsArrayBuffer,
78  heapCodeStatisticsArrayBuffer,
79  updateHeapStatisticsArrayBuffer,
80  updateHeapSpaceStatisticsArrayBuffer,
81  updateHeapCodeStatisticsArrayBuffer,
82
83  // Properties for heap statistics buffer extraction.
84  kTotalHeapSizeIndex,
85  kTotalHeapSizeExecutableIndex,
86  kTotalPhysicalSizeIndex,
87  kTotalAvailableSize,
88  kUsedHeapSizeIndex,
89  kHeapSizeLimitIndex,
90  kDoesZapGarbageIndex,
91  kMallocedMemoryIndex,
92  kPeakMallocedMemoryIndex,
93  kNumberOfNativeContextsIndex,
94  kNumberOfDetachedContextsIndex,
95
96  // Properties for heap spaces statistics buffer extraction.
97  kHeapSpaces,
98  kHeapSpaceStatisticsPropertiesCount,
99  kSpaceSizeIndex,
100  kSpaceUsedSizeIndex,
101  kSpaceAvailableSizeIndex,
102  kPhysicalSpaceSizeIndex,
103
104  // Properties for heap code statistics buffer extraction.
105  kCodeAndMetadataSizeIndex,
106  kBytecodeAndMetadataSizeIndex,
107  kExternalScriptSourceSizeIndex
108} = internalBinding('v8');
109
110const kNumberOfHeapSpaces = kHeapSpaces.length;
111
112const heapStatisticsBuffer =
113    new Float64Array(heapStatisticsArrayBuffer);
114
115const heapSpaceStatisticsBuffer =
116    new Float64Array(heapSpaceStatisticsArrayBuffer);
117
118const heapCodeStatisticsBuffer =
119    new Float64Array(heapCodeStatisticsArrayBuffer);
120
121function setFlagsFromString(flags) {
122  validateString(flags, 'flags');
123  _setFlagsFromString(flags);
124}
125
126function getHeapStatistics() {
127  const buffer = heapStatisticsBuffer;
128
129  updateHeapStatisticsArrayBuffer();
130
131  return {
132    'total_heap_size': buffer[kTotalHeapSizeIndex],
133    'total_heap_size_executable': buffer[kTotalHeapSizeExecutableIndex],
134    'total_physical_size': buffer[kTotalPhysicalSizeIndex],
135    'total_available_size': buffer[kTotalAvailableSize],
136    'used_heap_size': buffer[kUsedHeapSizeIndex],
137    'heap_size_limit': buffer[kHeapSizeLimitIndex],
138    'malloced_memory': buffer[kMallocedMemoryIndex],
139    'peak_malloced_memory': buffer[kPeakMallocedMemoryIndex],
140    'does_zap_garbage': buffer[kDoesZapGarbageIndex],
141    'number_of_native_contexts': buffer[kNumberOfNativeContextsIndex],
142    'number_of_detached_contexts': buffer[kNumberOfDetachedContextsIndex]
143  };
144}
145
146function getHeapSpaceStatistics() {
147  const heapSpaceStatistics = new Array(kNumberOfHeapSpaces);
148  const buffer = heapSpaceStatisticsBuffer;
149  updateHeapSpaceStatisticsArrayBuffer();
150
151  for (let i = 0; i < kNumberOfHeapSpaces; i++) {
152    const propertyOffset = i * kHeapSpaceStatisticsPropertiesCount;
153    heapSpaceStatistics[i] = {
154      space_name: kHeapSpaces[i],
155      space_size: buffer[propertyOffset + kSpaceSizeIndex],
156      space_used_size: buffer[propertyOffset + kSpaceUsedSizeIndex],
157      space_available_size: buffer[propertyOffset + kSpaceAvailableSizeIndex],
158      physical_space_size: buffer[propertyOffset + kPhysicalSpaceSizeIndex]
159    };
160  }
161
162  return heapSpaceStatistics;
163}
164
165function getHeapCodeStatistics() {
166  const buffer = heapCodeStatisticsBuffer;
167
168  updateHeapCodeStatisticsArrayBuffer();
169  return {
170    'code_and_metadata_size': buffer[kCodeAndMetadataSizeIndex],
171    'bytecode_and_metadata_size': buffer[kBytecodeAndMetadataSizeIndex],
172    'external_script_source_size': buffer[kExternalScriptSourceSizeIndex]
173  };
174}
175
176/* V8 serialization API */
177
178/* JS methods for the base objects */
179Serializer.prototype._getDataCloneError = Error;
180
181Deserializer.prototype.readRawBytes = function readRawBytes(length) {
182  const offset = this._readRawBytes(length);
183  // `this.buffer` can be a Buffer or a plain Uint8Array, so just calling
184  // `.slice()` doesn't work.
185  return new FastBuffer(this.buffer.buffer,
186                        this.buffer.byteOffset + offset,
187                        length);
188};
189
190/* Keep track of how to handle different ArrayBufferViews.
191 * The default Serializer for Node does not use the V8 methods for serializing
192 * those objects because Node's `Buffer` objects use pooled allocation in many
193 * cases, and their underlying `ArrayBuffer`s would show up in the
194 * serialization. Because a) those may contain sensitive data and the user
195 * may not be aware of that and b) they are often much larger than the `Buffer`
196 * itself, custom serialization is applied. */
197const arrayBufferViewTypes = [Int8Array, Uint8Array, Uint8ClampedArray,
198                              Int16Array, Uint16Array, Int32Array, Uint32Array,
199                              Float32Array, Float64Array, DataView];
200
201const arrayBufferViewTypeToIndex = new Map();
202
203{
204  const dummy = new ArrayBuffer();
205  for (const [i, ctor] of arrayBufferViewTypes.entries()) {
206    const tag = ObjectPrototypeToString(new ctor(dummy));
207    arrayBufferViewTypeToIndex.set(tag, i);
208  }
209}
210
211const bufferConstructorIndex = arrayBufferViewTypes.push(FastBuffer) - 1;
212
213class DefaultSerializer extends Serializer {
214  constructor() {
215    super();
216
217    this._setTreatArrayBufferViewsAsHostObjects(true);
218  }
219
220  _writeHostObject(abView) {
221    let i = 0;
222    if (abView.constructor === Buffer) {
223      i = bufferConstructorIndex;
224    } else {
225      const tag = ObjectPrototypeToString(abView);
226      i = arrayBufferViewTypeToIndex.get(tag);
227
228      if (i === undefined) {
229        throw new this._getDataCloneError(
230          `Unserializable host object: ${inspect(abView)}`);
231      }
232    }
233    this.writeUint32(i);
234    this.writeUint32(abView.byteLength);
235    this.writeRawBytes(new Uint8Array(abView.buffer,
236                                      abView.byteOffset,
237                                      abView.byteLength));
238  }
239}
240
241class DefaultDeserializer extends Deserializer {
242  _readHostObject() {
243    const typeIndex = this.readUint32();
244    const ctor = arrayBufferViewTypes[typeIndex];
245    const byteLength = this.readUint32();
246    const byteOffset = this._readRawBytes(byteLength);
247    const BYTES_PER_ELEMENT = ctor.BYTES_PER_ELEMENT || 1;
248
249    const offset = this.buffer.byteOffset + byteOffset;
250    if (offset % BYTES_PER_ELEMENT === 0) {
251      return new ctor(this.buffer.buffer,
252                      offset,
253                      byteLength / BYTES_PER_ELEMENT);
254    }
255    // Copy to an aligned buffer first.
256    const buffer_copy = Buffer.allocUnsafe(byteLength);
257    copy(this.buffer, buffer_copy, 0, byteOffset, byteOffset + byteLength);
258    return new ctor(buffer_copy.buffer,
259                    buffer_copy.byteOffset,
260                    byteLength / BYTES_PER_ELEMENT);
261  }
262}
263
264function serialize(value) {
265  const ser = new DefaultSerializer();
266  ser.writeHeader();
267  ser.writeValue(value);
268  return ser.releaseBuffer();
269}
270
271function deserialize(buffer) {
272  const der = new DefaultDeserializer(buffer);
273  der.readHeader();
274  return der.readValue();
275}
276
277module.exports = {
278  cachedDataVersionTag,
279  getHeapSnapshot,
280  getHeapStatistics,
281  getHeapSpaceStatistics,
282  getHeapCodeStatistics,
283  setFlagsFromString,
284  Serializer,
285  Deserializer,
286  DefaultSerializer,
287  DefaultDeserializer,
288  deserialize,
289  serialize,
290  writeHeapSnapshot,
291};
292