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