blob: 6aa539c76350113c99d88788b5b55de955f7d3df [file] [log] [blame]
// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'dart:async';
import 'dart:typed_data';
import '_stream_helpers.dart';
import 'vm_service.dart';
/// A representation of a field captured in a memory snapshot.
class HeapSnapshotField {
/// An index into [HeapSnapshotObject.references].
int get index => _index;
/// The name of the field.
String get name => _name;
int _index = -1;
String _name = '';
HeapSnapshotField._read(ReadStream reader) {
// flags (reserved)
reader.readInteger();
_index = reader.readInteger();
_name = reader.readUtf8();
// reserved
reader.readUtf8();
}
void _write(WriteStream writer) {
// flags (reserved)
writer.writeInteger(0);
writer.writeInteger(_index);
writer.writeUtf8(_name);
// reserved
writer.writeUtf8('');
}
}
/// A representation of a class type captured in a memory snapshot.
class HeapSnapshotClass {
/// The class ID representing this type.
int get classId => _classId;
/// The simple (not qualified) name of the class.
String get name => _name;
/// The name of the class's library.
String get libraryName => _libraryName;
/// The [Uri] of the class's library.
Uri get libraryUri => _libraryUri;
/// The list of fields in the class.
List<HeapSnapshotField> get fields => _fields;
final int _classId;
String _name = '';
String _libraryName = '';
late final Uri _libraryUri;
final List<HeapSnapshotField> _fields = <HeapSnapshotField>[];
HeapSnapshotClass._read(this._classId, ReadStream reader) {
// flags (reserved).
reader.readInteger();
_name = reader.readUtf8();
_libraryName = reader.readUtf8();
_libraryUri = Uri.parse(reader.readUtf8());
// reserved
reader.readUtf8();
_readFields(reader);
}
void _write(WriteStream writer) {
// flags (reserved).
writer.writeInteger(0);
writer.writeUtf8(_name);
writer.writeUtf8(_libraryName);
writer.writeUtf8(_libraryUri.toString());
// something reserved
writer.writeInteger(0);
_writeFields(writer);
}
HeapSnapshotClass._root()
: _classId = 0,
_name = 'Root',
_libraryName = '',
_libraryUri = Uri();
HeapSnapshotClass._sentinel()
: _classId = 0,
_name = 'Sentinel',
_libraryName = '',
_libraryUri = Uri();
void _readFields(ReadStream reader) {
final fieldCount = reader.readInteger();
for (int i = 0; i < fieldCount; ++i) {
_fields.add(HeapSnapshotField._read(reader));
}
}
void _writeFields(WriteStream writer) {
writer.writeInteger(_fields.length);
for (int i = 0; i < _fields.length; ++i) {
_fields[i]._write(writer);
}
}
}
/// A representation of an object instance captured in a memory snapshot.
class HeapSnapshotObject {
/// The class ID representing the type of this object.
int get classId => _classId;
/// The class representing the type of this object.
HeapSnapshotClass get klass {
if (_classId <= 0) {
return HeapSnapshotClass._sentinel();
}
return _graph._classes[_classId];
}
/// The space used by this object in bytes.
int get shallowSize => _shallowSize;
/// Data associated with this object.
dynamic get data => _data;
/// A list of indices into [HeapSnapshotGraph.objects].
Uint32List get references => Uint32List.sublistView(_graph._successors,
_graph._firstSuccessors[_oid], _graph._firstSuccessors[_oid + 1]);
/// A list of indices into [HeapSnapshotGraph.objects].
Uint32List get referrers {
final referrers = _graph._referrers;
final firstReferrers = _graph._firstReferrers;
if (referrers == null || firstReferrers == null) {
throw StateError('Referrers are not available in this snapshot. Pass'
' `calculateReferrers: true` when taking snapshot to calculate referrers.');
}
return Uint32List.sublistView(
referrers, firstReferrers[_oid], firstReferrers[_oid + 1]);
}
/// The identity hash code of this object.
///
/// If `identityHashCode` is 0, either the snapshot did not contain the list
/// of identity hash codes or this object cannot be compared across
/// snapshots.
int get identityHashCode => _identityHashCode;
Iterable<HeapSnapshotObject> get successors sync* {
final startSuccessorIndex = _graph._firstSuccessors[_oid];
final limitSuccessorIndex = _graph._firstSuccessors[_oid + 1];
for (int nextSuccessorIndex = startSuccessorIndex;
nextSuccessorIndex < limitSuccessorIndex;
++nextSuccessorIndex) {
final successorId = _graph._successors[nextSuccessorIndex];
yield _graph.objects[successorId];
}
}
final HeapSnapshotGraph _graph;
final int _oid;
int _classId = 0;
int _shallowSize = -1;
int _identityHashCode = 0;
late final dynamic _data;
HeapSnapshotObject._sentinel(this._graph)
: _oid = 0,
_data = HeapSnapshotObjectNoData() {
_graph._firstSuccessors[_oid] = _graph._nextSuccessor;
}
HeapSnapshotObject._read(
this._graph,
this._oid,
ReadStream reader, {
required bool decodeObjectData,
}) {
_classId = reader.readInteger();
_shallowSize = reader.readInteger();
final data = _getNonReferenceData(reader);
_data = decodeObjectData ? data : HeapSnapshotObjectNoData();
_readReferences(reader);
}
void _write(WriteStream writer) {
writer.writeInteger(_classId);
writer.writeInteger(_shallowSize);
_writeNonReferenceData(writer, _data);
_writeReferences(writer);
}
void _readReferences(ReadStream reader) {
_graph._firstSuccessors[_oid] = _graph._nextSuccessor;
final referencesCount = reader.readInteger();
for (int i = 0; i < referencesCount; ++i) {
final currentOid = _graph._nextSuccessor++;
final childOid = reader.readInteger();
_graph._successors[currentOid] = childOid;
_graph._referrerCounts[childOid]++;
}
}
void _writeReferences(WriteStream writer) {
final refStart = _graph._firstSuccessors[_oid];
final refEnd = _oid + 1 < _graph._firstSuccessors.length
? _graph._firstSuccessors[_oid + 1]
: _graph._nextSuccessor;
final referencesCount = refEnd - refStart;
writer.writeInteger(referencesCount);
for (int i = refStart; i < refEnd; ++i) {
final childOid = _graph._successors[i];
writer.writeInteger(childOid);
}
}
}
/// A representation of an external property captured in a memory snapshot.
class HeapSnapshotExternalProperty {
/// An index into [HeapSnapshotGraph.objects].
final int object;
/// The amount of external memory used.
final int externalSize;
/// The name of the external property.
final String name;
HeapSnapshotExternalProperty._read(ReadStream reader)
: object = reader.readInteger(),
externalSize = reader.readInteger(),
name = reader.readUtf8();
void _write(WriteStream writer) {
writer.writeInteger(object);
writer.writeInteger(externalSize);
writer.writeUtf8(name);
}
}
/// A graph representation of a heap snapshot.
class HeapSnapshotGraph {
/// The name of the isolate represented by this heap snapshot.
String get name => _name;
int get flags => _flags;
/// The sum of shallow sizes of all objects in this graph in bytes.
int get shallowSize => _shallowSize;
/// The amount of memory reserved for this heap in bytes.
///
/// At least as large as [shallowSize].
int get capacity => _capacity;
/// The sum of sizes of all external properties in this graph in bytes.
int get externalSize => _externalSize;
/// The list of classes found in this snapshot.
List<HeapSnapshotClass> get classes => _classes;
/// At least as big as the sum of all [HeapSnapshotObject.references].
int get referenceCount => _referenceCount;
/// The list of objects found in this snapshot.
List<HeapSnapshotObject> get objects => _objects;
/// The list of external properties found in this snapshot.
List<HeapSnapshotExternalProperty> get externalProperties =>
_externalProperties;
String _name = '';
int _flags = -1;
int _shallowSize = -1;
int _capacity = -1;
int _externalSize = -1;
final List<HeapSnapshotClass> _classes = <HeapSnapshotClass>[];
int _referenceCount = -1;
final List<HeapSnapshotObject> _objects = <HeapSnapshotObject>[];
final List<HeapSnapshotExternalProperty> _externalProperties =
<HeapSnapshotExternalProperty>[];
/// Contains the successors (references) of each object.
///
/// The array consists of blocks of successors, one block for each object.
/// [_firstSuccessors] contains the start indexes of each block.
///
/// Cells, not used yet start at index [_nextSuccessor].
late final Uint32List _successors;
/// See [_successors].
late final Uint32List _firstSuccessors;
/// See [_successors].
int _nextSuccessor = 0;
late Uint32List _referrerCounts;
Uint32List? _firstReferrers;
Uint32List? _referrers;
/// Requests a heap snapshot for a given isolate and builds a
/// [HeapSnapshotGraph].
///
/// Note: this method calls [VmService.streamListen] and
/// [VmService.streamCancel] on [EventStreams.kHeapSnapshot].
///
/// Set flags to false to save processing time and memory footprint
/// by skipping decoding or calculation of certain data:
///
/// - [calculateReferrers] for [HeapSnapshotObject.referrers]
/// - [decodeObjectData] for [HeapSnapshotObject.data]
/// - [decodeExternalProperties] for [HeapSnapshotGraph.externalProperties]
/// - [decodeIdentityHashCodes] for [HeapSnapshotObject.identityHashCode]
static Future<HeapSnapshotGraph> getSnapshot(
VmService service,
IsolateRef isolate, {
bool calculateReferrers = true,
bool decodeObjectData = true,
bool decodeExternalProperties = true,
bool decodeIdentityHashCodes = true,
}) async {
await service.streamListen(EventStreams.kHeapSnapshot);
final completer = Completer<HeapSnapshotGraph>();
final chunks = <ByteData>[];
late StreamSubscription streamSubscription;
streamSubscription = service.onHeapSnapshotEvent.listen((e) async {
chunks.add(e.data!);
if (e.last!) {
await service.streamCancel(EventStreams.kHeapSnapshot);
await streamSubscription.cancel();
completer.complete(HeapSnapshotGraph.fromChunks(
chunks,
calculateReferrers: calculateReferrers,
decodeObjectData: decodeObjectData,
decodeExternalProperties: decodeExternalProperties,
decodeIdentityHashCodes: decodeIdentityHashCodes,
));
}
});
await service.requestHeapSnapshot(isolate.id!);
return completer.future;
}
static const _magicHeader = 'dartheap';
/// Populates the [HeapSnapshotGraph] by parsing the events from the
/// `HeapSnapshot` stream.
///
/// Set flags to false to save processing time and memory footprint
/// by skipping decoding or calculation of certain data:
///
/// - [calculateReferrers] for [HeapSnapshotObject.referrers]
/// - [decodeObjectData] for [HeapSnapshotObject.data]
/// - [decodeExternalProperties] for [HeapSnapshotGraph.externalProperties]
/// - [decodeIdentityHashCodes] for [HeapSnapshotObject.identityHashCode]
HeapSnapshotGraph.fromChunks(
List<ByteData> chunks, {
bool calculateReferrers = true,
bool decodeObjectData = true,
bool decodeExternalProperties = true,
bool decodeIdentityHashCodes = true,
}) {
final reader = ReadStream(chunks);
// Skip magic header
for (int i = 0; i < _magicHeader.length; ++i) {
reader.readByte();
}
_flags = reader.readInteger();
_name = reader.readUtf8();
_shallowSize = reader.readInteger();
_capacity = reader.readInteger();
_externalSize = reader.readInteger();
_readClasses(reader);
_readObjects(reader, decodeObjectData: decodeObjectData);
if (decodeExternalProperties || decodeIdentityHashCodes) {
_readExternalProperties(
reader,
decodeExternalProperties: decodeExternalProperties,
);
}
if (decodeIdentityHashCodes) {
_readIdentityHashCodes(reader);
}
if (calculateReferrers) _calculateReferrers();
}
List<ByteData> toChunks() {
final writer = WriteStream();
for (int i = 0; i < _magicHeader.length; ++i) {
writer.writeByte(_magicHeader.codeUnitAt(i));
}
writer.writeInteger(_flags);
writer.writeUtf8(_name);
writer.writeInteger(_shallowSize);
writer.writeInteger(_capacity);
writer.writeInteger(_externalSize);
_writeClasses(writer);
_writeObjects(writer);
_writeExternalProperties(writer);
_writeIdentityHashCodes(writer);
return writer.chunks;
}
void _readClasses(ReadStream reader) {
final classCount = reader.readInteger();
_classes.add(HeapSnapshotClass._root());
for (int i = 1; i <= classCount; ++i) {
final klass = HeapSnapshotClass._read(i, reader);
_classes.add(klass);
}
}
void _writeClasses(WriteStream writer) {
writer.writeInteger(_classes.length - 1);
for (int i = 1; i < _classes.length; ++i) {
_classes[i]._write(writer);
}
}
void _readObjects(ReadStream reader, {required bool decodeObjectData}) {
_referenceCount = reader.readInteger();
final objectCount = reader.readInteger();
_firstSuccessors = _newUint32Array(objectCount + 2);
_successors = _newUint32Array(_referenceCount);
_referrerCounts = _newUint32Array(objectCount + 2);
_objects.add(HeapSnapshotObject._sentinel(this));
for (int i = 1; i <= objectCount; ++i) {
_objects.add(HeapSnapshotObject._read(
this,
i,
reader,
decodeObjectData: decodeObjectData,
));
}
_firstSuccessors[objectCount + 1] = _nextSuccessor;
}
void _writeObjects(WriteStream writer) {
writer.writeInteger(_referenceCount);
writer.writeInteger(_objects.length - 1);
for (int i = 1; i < _objects.length; ++i) {
_objects[i]._write(writer);
}
}
void _calculateReferrers() {
final objectCount = _objects.length - 1;
_firstReferrers = _newUint32Array(objectCount + 2);
_referrers = _newUint32Array(_referenceCount);
_firstReferrers![objectCount + 1] = _nextSuccessor;
// We reuse the [_predecessorCounts] array and turn it into the
// write cursor array.
final predecessorCounts = _referrerCounts;
_referrerCounts = Uint32List(0);
int sum = 0;
int totalCount = _referenceCount;
for (int i = objectCount; i >= 0; --i) {
sum += predecessorCounts[i];
final firstPredecessor = totalCount - sum;
_firstReferrers![i] = predecessorCounts[i] = firstPredecessor;
}
final predecessorWriteCursor = predecessorCounts;
for (int i = 1; i <= objectCount; ++i) {
final from = _firstSuccessors[i];
final to = _firstSuccessors[i + 1];
for (int j = from; j < to; ++j) {
final cursor = predecessorWriteCursor[_successors[j]]++;
_referrers![cursor] = i;
}
}
}
void _readExternalProperties(
ReadStream reader, {
required bool decodeExternalProperties,
}) {
final propertiesCount = reader.readInteger();
for (int i = 0; i < propertiesCount; ++i) {
final property = HeapSnapshotExternalProperty._read(reader);
if (decodeExternalProperties) {
_externalProperties.add(property);
}
}
}
void _writeExternalProperties(WriteStream writer) {
writer.writeInteger(_externalProperties.length);
for (int i = 0; i < _externalProperties.length; ++i) {
_externalProperties[i]._write(writer);
}
}
void _readIdentityHashCodes(ReadStream reader) {
if (reader.atEnd) {
// Older VMs don't include identity hash codes.
return;
}
final objectCount = _objects.length;
for (int i = 1; i < objectCount; ++i) {
_objects[i]._identityHashCode = reader.readInteger();
}
}
void _writeIdentityHashCodes(WriteStream writer) {
for (int i = 1; i < _objects.length; ++i) {
writer.writeInteger(_objects[i]._identityHashCode);
}
}
Uint32List _newUint32Array(int size) {
try {
return Uint32List(size);
} on ArgumentError {
// JS throws a misleading invalid argument error. Convert to a more
// user-friendly message.
throw Exception(
'OutOfMemoryError: Not enough memory available to analyze the snapshot.',
);
}
}
}
const _kNoData = 0;
const _kNullData = 1;
const _kBoolData = 2;
const _kIntData = 3;
const _kDoubleData = 4;
const _kLatin1Data = 5;
const _kUtf16Data = 6;
const _kLengthData = 7;
const _kNameData = 8;
void _writeNonReferenceData(WriteStream writer, dynamic data) {
if (data is HeapSnapshotObjectNoData) {
writer.writeInteger(_kNoData);
return;
}
if (data is HeapSnapshotObjectNullData) {
writer.writeInteger(_kNullData);
return;
}
if (data is bool) {
writer.writeInteger(_kBoolData);
writer.writeByte(data ? 1 : 0);
return;
}
if (data is int) {
writer.writeInteger(_kIntData);
writer.writeInteger(data);
return;
}
if (data is double) {
writer.writeInteger(_kDoubleData);
writer.writeFloat64(data);
return;
}
if (data is HeapSnapshotObjectLengthData) {
writer.writeInteger(_kLengthData);
writer.writeInteger(data.length);
return;
}
if (data is String) {
// We use _kNameData for any string, because long strings are already cut with `...`.
writer.writeInteger(_kNameData);
writer.writeUtf8(data);
return;
}
throw 'Not expected type: ${data.runtimeType}';
}
dynamic _getNonReferenceData(ReadStream reader) {
final tag = reader.readInteger();
switch (tag) {
case _kNoData:
return const HeapSnapshotObjectNoData();
case _kNullData:
return const HeapSnapshotObjectNullData();
case _kBoolData:
return (reader.readByte() == 1);
case _kIntData:
return reader.readInteger();
case _kDoubleData:
return reader.readFloat64();
case _kLatin1Data:
final len = reader.readInteger();
final str = reader.readLatin1();
return (str.length < len) ? '$str...' : str;
case _kUtf16Data:
final len = reader.readInteger();
final str = reader.readUtf16();
return (str.length < len) ? '$str...' : str;
case _kLengthData:
return HeapSnapshotObjectLengthData(reader.readInteger());
case _kNameData:
return reader.readUtf8();
default:
throw 'Invalid tag: $tag';
}
}
/// Represents that no data is associated with an object.
class HeapSnapshotObjectNoData {
const HeapSnapshotObjectNoData();
}
/// Represents that the data associated with an object is null.
class HeapSnapshotObjectNullData {
const HeapSnapshotObjectNullData();
}
/// Represents the length of an object.
class HeapSnapshotObjectLengthData {
final int length;
HeapSnapshotObjectLengthData(this.length);
}