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