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