"use strict"; Object.defineProperty(exports, "__esModule", { value: true, }); exports.ChromeHeapSnapshotProcessor = void 0; var _invariant = _interopRequireDefault(require("invariant")); function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } const CHILDREN_FIELD_TYPE = "__CHILDREN__"; class ChromeHeapSnapshotProcessor { constructor(snapshotData) { this._snapshotData = snapshotData; this._globalStringTable = new ChromeHeapSnapshotStringTable( this._snapshotData.strings, ); } traceFunctionInfos() { return new ChromeHeapSnapshotRecordIterator( this._snapshotData.trace_function_infos, this._snapshotData.snapshot.meta.trace_function_info_fields, { name: "string", script_name: "string", }, this._globalStringTable, undefined, ); } locations() { return new ChromeHeapSnapshotRecordIterator( this._snapshotData.locations, this._snapshotData.snapshot.meta.location_fields, null, this._globalStringTable, undefined, ); } nodes() { return new ChromeHeapSnapshotRecordIterator( this._snapshotData.nodes, this._snapshotData.snapshot.meta.node_fields, this._snapshotData.snapshot.meta.node_types, this._globalStringTable, undefined, ); } edges() { return new ChromeHeapSnapshotRecordIterator( this._snapshotData.edges, this._snapshotData.snapshot.meta.edge_fields, this._snapshotData.snapshot.meta.edge_types, this._globalStringTable, undefined, ); } traceTree() { return new ChromeHeapSnapshotRecordIterator( this._snapshotData.trace_tree, this._snapshotData.snapshot.meta.trace_node_fields, { children: CHILDREN_FIELD_TYPE, }, this._globalStringTable, undefined, ); } } exports.ChromeHeapSnapshotProcessor = ChromeHeapSnapshotProcessor; class ChromeHeapSnapshotStringTable { constructor(strings) { this._strings = strings; this._indexCache = new Map(); } add(value) { this._syncIndexCache(); let index = this._indexCache.get(value); if (index != null) { return index; } index = this._strings.length; this._strings.push(value); this._indexCache.set(value, index); return index; } get(index) { (0, _invariant.default)( index >= 0 && index < this._strings.length, "index out of string table range", ); return this._strings[index]; } _syncIndexCache() { if (this._strings.length > this._indexCache.size) { for (let i = this._indexCache.size; i < this._strings.length; ++i) { this._indexCache.set(this._strings[i], i); } } } } class ChromeHeapSnapshotRecordAccessor { constructor( buffer, recordFields, recordTypes, globalStringTable, position, parent, ) { if (parent) { this._recordSize = parent._recordSize; this._fieldToOffset = parent._fieldToOffset; this._fieldToType = parent._fieldToType; } else { this._recordSize = recordFields.length; this._fieldToOffset = new Map( Object.entries(recordFields).map(([offsetStr, name]) => [ String(name), Number(offsetStr), ]), ); if (Array.isArray(recordTypes)) { this._fieldToType = new Map( Object.entries(recordTypes).map(([offsetStr, type]) => [ recordFields[Number(offsetStr)], type, ]), ); } else { this._fieldToType = new Map(Object.entries(recordTypes || {})); } } this._buffer = buffer; this._position = position; (0, _invariant.default)( this._position % this._recordSize === 0, "Record accessor constructed at invalid offset", ); (0, _invariant.default)( this._buffer.length % this._recordSize === 0, "Record accessor constructed with wrong size buffer", ); this._globalStringTable = globalStringTable; } getString(field) { const dynamicValue = this._getScalar(field); if (typeof dynamicValue === "string") { return dynamicValue; } throw new Error("Not a string or enum field: " + field); } getNumber(field) { const dynamicValue = this._getScalar(field); if (typeof dynamicValue === "number") { return dynamicValue; } throw new Error("Not a number field: " + field); } getChildren(field) { const fieldType = this._fieldToType.get(field); if (fieldType !== CHILDREN_FIELD_TYPE) { throw new Error("Not a children field: " + field); } const childrenBuffer = this._getRaw(field); (0, _invariant.default)( Array.isArray(childrenBuffer), "Expected array in children-typed field", ); return new ChromeHeapSnapshotRecordIterator( childrenBuffer, [], null, this._globalStringTable, -this._fieldToOffset.size, this, ); } setString(field, value) { this._setRaw(field, this._encodeString(field, value)); } setNumber(field, value) { const fieldType = this._fieldToType.get(field); if ( Array.isArray(fieldType) || fieldType === "string" || fieldType === CHILDREN_FIELD_TYPE ) { throw new Error("Not a number field: " + field); } this._setRaw(field, value); } moveToRecord(recordIndex) { this._moveToPosition(recordIndex * this._recordSize); } append(record) { const savedPosition = this._position; try { return this.moveAndInsert(this._buffer.length / this._recordSize, record); } finally { this._position = savedPosition; } } moveAndInsert(recordIndex, record) { this._moveToPosition(recordIndex * this._recordSize, true); let didResizeBuffer = false; try { for (const field of this._fieldToOffset.keys()) { if (!Object.prototype.hasOwnProperty.call(record, field)) { throw new Error("Missing value for field: " + field); } } this._buffer.splice(this._position, 0, ...new Array(this._recordSize)); didResizeBuffer = true; for (const field of Object.keys(record)) { this._set(field, record[field]); } return this._position / this._recordSize; } catch (e) { if (didResizeBuffer) { this._buffer.splice(this._position, this._recordSize); } throw e; } } protectedHasNext() { if (this._position < 0) { return this._buffer.length > 0; } return this._position < this._buffer.length; } protectedTryMoveNext() { if (this.protectedHasNext()) { this._moveToPosition(this._position + this._recordSize, true); } } _getRaw(field) { this._validatePosition(); const offset = this._fieldToOffset.get(field); if (offset == null) { throw new Error("Unknown field: " + field); } return this._buffer[this._position + offset]; } _getScalar(field) { const rawValue = this._getRaw(field); if (Array.isArray(rawValue)) { throw new Error("Not a scalar field: " + field); } const fieldType = this._fieldToType.get(field); if (Array.isArray(fieldType)) { (0, _invariant.default)( rawValue >= 0 && rawValue < fieldType.length, "raw value does not match field enum type", ); return fieldType[rawValue]; } if (fieldType === "string") { return this._globalStringTable.get(rawValue); } return rawValue; } _setRaw(field, rawValue) { this._validatePosition(); const offset = this._fieldToOffset.get(field); if (offset == null) { throw new Error("Unknown field: " + field); } this._buffer[this._position + offset] = rawValue; } _set(field, value) { if (typeof value === "string") { this.setString(field, value); } else if (typeof value === "number") { this.setNumber(field, value); } else if (Array.isArray(value)) { this._setChildren(field, value); } else { throw new Error("Unsupported value for field: " + field); } } _setChildren(field, value) { const fieldType = this._fieldToType.get(field); if (fieldType !== CHILDREN_FIELD_TYPE) { throw new Error("Not a children field: " + field); } this._setRaw(field, []); const childIt = this.getChildren(field); for (const child of value) { childIt.append(child); } } _encodeString(field, value) { const fieldType = this._fieldToType.get(field); if (Array.isArray(fieldType)) { const index = fieldType.indexOf(value); (0, _invariant.default)( index >= 0, "Cannot define new values in enum field", ); return index; } if (fieldType === "string") { return this._globalStringTable.add(value); } throw new Error("Not a string or enum field: " + field); } _validatePosition(allowEnd = false, position = this._position) { if (!Number.isInteger(position)) { throw new Error(`Position ${position} is not an integer`); } if (position % this._recordSize !== 0) { throw new Error( `Position ${position} is not a multiple of record size ${this._recordSize}`, ); } if (position < 0) { throw new Error(`Position ${position} is out of range`); } const maxPosition = allowEnd ? this._buffer.length : this._buffer.length - 1; if (position > maxPosition) { throw new Error(`Position ${position} is out of range`); } if (this._buffer.length - position < this._recordSize) { if (!(allowEnd && this._buffer.length === position)) { throw new Error( `Record at position ${position} is truncated: expected ${this._recordSize} fields but found ${this._buffer.length - position}`, ); } } } _moveToPosition(nextPosition, allowEnd = false) { this._validatePosition(allowEnd, nextPosition); this._position = nextPosition; } } class ChromeHeapSnapshotRecordIterator extends ChromeHeapSnapshotRecordAccessor { constructor( buffer, recordFields, recordTypes, globalStringTable, position = -recordFields.length, parent, ) { super( buffer, recordFields, recordTypes, globalStringTable, position, parent, ); } next() { this.protectedTryMoveNext(); const done = !this.protectedHasNext(); return done ? { done, } : { done, value: this, }; } [Symbol.iterator]() { return this; } }