blob: 14e5818cb58907c1c3d2762eaa915ee73a551dc0 [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:convert';
import 'dart:typed_data';
import '../vm_service.dart';
class _ReadStream {
final List<ByteData> _chunks;
int _chunkIndex = 0;
int _byteIndex = 0;
_ReadStream(this._chunks);
bool get atEnd => ((_byteIndex >= _chunks[_chunkIndex].lengthInBytes) &&
(_chunkIndex + 1 >= _chunks.length));
int readByte() {
while (_byteIndex >= _chunks[_chunkIndex].lengthInBytes) {
_chunkIndex++;
_byteIndex = 0;
}
return _chunks[_chunkIndex].getUint8(_byteIndex++);
}
/// Read one ULEB128 number.
int readUnsigned() {
int result = 0;
int shift = 0;
for (;;) {
int part = readByte();
result |= (part & 0x7F) << shift;
if ((part & 0x80) == 0) {
break;
}
shift += 7;
}
return result;
}
/// Read one SLEB128 number.
int readSigned() {
int result = 0;
int shift = 0;
for (;;) {
int part = readByte();
result |= (part & 0x7F) << shift;
shift += 7;
if ((part & 0x80) == 0) {
if ((part & 0x40) != 0) {
result |= (-1 << shift);
}
break;
}
}
return result;
}
double readFloat64() {
final bytes = Uint8List(8);
for (int i = 0; i < 8; i++) {
bytes[i] = readByte();
}
return Float64List.view(bytes.buffer)[0];
}
String readUtf8() {
int len = readUnsigned();
final bytes = Uint8List(len);
for (int i = 0; i < len; i++) {
bytes[i] = readByte();
}
return Utf8Codec(allowMalformed: true).decode(bytes);
}
String readLatin1() {
int len = readUnsigned();
final codeUnits = Uint8List(len);
for (int i = 0; i < len; i++) {
codeUnits[i] = readByte();
}
return String.fromCharCodes(codeUnits);
}
String readUtf16() {
int len = readUnsigned();
final codeUnits = Uint16List(len);
for (int i = 0; i < len; i++) {
codeUnits[i] = readByte() | (readByte() << 8);
}
return String.fromCharCodes(codeUnits);
}
}
/// 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.readUnsigned();
_index = reader.readUnsigned();
_name = reader.readUtf8();
// reserved
reader.readUtf8();
}
}
/// 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.readUnsigned();
_name = reader.readUtf8();
_libraryName = reader.readUtf8();
_libraryUri = Uri.parse(reader.readUtf8());
// reserved
reader.readUtf8();
_populateFields(reader);
}
HeapSnapshotClass._root()
: _classId = 0,
_name = 'Root',
_libraryName = '',
_libraryUri = Uri();
HeapSnapshotClass._sentinel()
: _classId = 0,
_name = 'Sentinel',
_libraryName = '',
_libraryUri = Uri();
void _populateFields(_ReadStream reader) {
final fieldCount = reader.readUnsigned();
for (int i = 0; i < fieldCount; ++i) {
_fields.add(HeapSnapshotField._read(reader));
}
}
}
/// 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].
List<int> get references => _references;
/// 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;
final List<int> _references = <int>[];
HeapSnapshotObject._sentinel(this._graph)
: _oid = 0,
_data = HeapSnapshotObjectNoData() {
_graph._firstSuccessors[_oid] = _graph._eid;
}
HeapSnapshotObject._read(this._graph, this._oid, _ReadStream reader) {
_classId = reader.readUnsigned();
_shallowSize = reader.readUnsigned();
_data = _getNonReferenceData(reader);
_graph._firstSuccessors[_oid] = _graph._eid;
_populateReferences(reader);
}
void _populateReferences(_ReadStream reader) {
final referencesCount = reader.readUnsigned();
for (int i = 0; i < referencesCount; ++i) {
int childOid = reader.readUnsigned();
_references.add(childOid);
_graph._successors[_graph._eid] = childOid;
_graph._eid++;
}
}
}
/// 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.readUnsigned(),
externalSize = reader.readUnsigned(),
name = reader.readUtf8();
}
/// 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.referenceCount].
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>[];
late Uint32List _firstSuccessors;
late Uint32List _successors;
int _eid = 0;
/// Requests a heap snapshot for a given isolate and builds a
/// [HeapSnapshotGraph].
///
/// Note: this method calls [VmService.streamListen] and
/// [VmService.streamCancel] on [EventStreams.kHeapSnapshot].
static Future<HeapSnapshotGraph> getSnapshot(
VmService service, IsolateRef isolate) 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));
}
});
await service.requestHeapSnapshot(isolate.id!);
return completer.future;
}
/// Populates the [HeapSnapshotGraph] by parsing the events from the
/// `HeapSnapshot` stream.
HeapSnapshotGraph.fromChunks(List<ByteData> chunks) {
final reader = _ReadStream(chunks);
// Skip magic header
for (int i = 0; i < 8; ++i) {
reader.readByte();
}
_flags = reader.readUnsigned();
_name = reader.readUtf8();
_shallowSize = reader.readUnsigned();
_capacity = reader.readUnsigned();
_externalSize = reader.readUnsigned();
_populateClasses(reader);
_populateObjects(reader);
_populateExternalProperties(reader);
_populateIdentityHashCodes(reader);
}
void _populateClasses(_ReadStream reader) {
final classCount = reader.readUnsigned();
_classes.add(HeapSnapshotClass._root());
for (int i = 1; i <= classCount; ++i) {
final klass = HeapSnapshotClass._read(i, reader);
_classes.add(klass);
}
}
void _populateObjects(_ReadStream reader) {
_referenceCount = reader.readUnsigned();
final objectCount = reader.readUnsigned();
_firstSuccessors = _newUint32Array(objectCount + 2);
_successors = _newUint32Array(_referenceCount);
_objects.add(HeapSnapshotObject._sentinel(this));
for (int i = 1; i <= objectCount; ++i) {
_objects.add(HeapSnapshotObject._read(this, i, reader));
}
_firstSuccessors[objectCount + 1] = _eid;
}
void _populateExternalProperties(_ReadStream reader) {
final propertiesCount = reader.readUnsigned();
for (int i = 0; i < propertiesCount; ++i) {
_externalProperties.add(HeapSnapshotExternalProperty._read(reader));
}
}
void _populateIdentityHashCodes(_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.readUnsigned();
}
}
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;
dynamic _getNonReferenceData(_ReadStream reader) {
final tag = reader.readUnsigned();
switch (tag) {
case _kNoData:
return const HeapSnapshotObjectNoData();
case _kNullData:
return const HeapSnapshotObjectNullData();
case _kBoolData:
return (reader.readByte() == 1);
case _kIntData:
return reader.readUnsigned();
case _kDoubleData:
return reader.readFloat64();
case _kLatin1Data:
final len = reader.readUnsigned();
final str = reader.readLatin1();
return (str.length < len) ? '$str...' : str;
case _kUtf16Data:
final len = reader.readUnsigned();
final str = reader.readUtf16();
return (str.length < len) ? '$str...' : str;
case _kLengthData:
return HeapSnapshotObjectLengthData(reader.readUnsigned());
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);
}