[vm, service] Add requestHeapSnapshot.

This replaces _requestHeapSnapshot and _getObjectByAddresses, which relied on the invalid assumption that objects don't move and the heap hasn't mutated since the initial snapshot.

Updates Observatory to the new format. Adds treemap visualization of the dominator tree. Adds the ability to save and load heap snapshots to files. Removes analysis grouping references by source and target class. Removes ability to navigate from a pixel in the fragmentation view to inspector. Removes ability to query the top retaining instance from an ordinary class browser.

Change-Id: Ia7781c05d43bf3ec149f8b4ecab803b37c3ee981
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/112181
Commit-Queue: Ryan Macnak <rmacnak@google.com>
Reviewed-by: Ben Konyi <bkonyi@google.com>
This commit is contained in:
Ryan Macnak 2019-08-19 19:31:42 +00:00 committed by commit-bot@chromium.org
parent 8a9448138a
commit 6e987b6eb5
53 changed files with 2075 additions and 6808 deletions

View file

@ -49,8 +49,6 @@ export 'package:observatory/src/elements/local_var_descriptors_ref.dart';
export 'package:observatory/src/elements/logging.dart';
export 'package:observatory/src/elements/megamorphiccache_ref.dart';
export 'package:observatory/src/elements/megamorphiccache_view.dart';
export 'package:observatory/src/elements/memory/dashboard.dart';
export 'package:observatory/src/elements/memory/graph.dart';
export 'package:observatory/src/elements/metric/details.dart';
export 'package:observatory/src/elements/metric/graph.dart';
export 'package:observatory/src/elements/metrics.dart';

View file

@ -5,6 +5,7 @@
library heap_snapshot;
import 'dart:async';
import 'dart:typed_data';
import 'package:observatory/models.dart' as M;
import 'package:observatory/object_graph.dart';
import 'package:observatory/service.dart' as S;

View file

@ -5,6 +5,8 @@
library models;
import 'dart:async';
import 'dart:typed_data';
import 'package:observatory/object_graph.dart';
part 'src/models/exceptions.dart';
@ -91,7 +93,6 @@ part 'src/models/repositories/strongly_reachable_instances.dart';
part 'src/models/repositories/subtype_test_cache.dart';
part 'src/models/repositories/target.dart';
part 'src/models/repositories/timeline.dart';
part 'src/models/repositories/top_retaining_instances.dart';
part 'src/models/repositories/type_arguments.dart';
part 'src/models/repositories/unlinked_call.dart';
part 'src/models/repositories/vm.dart';

File diff suppressed because it is too large Load diff

View file

@ -50,7 +50,6 @@ part 'src/repositories/strongly_reachable_instances.dart';
part 'src/repositories/subtype_test_cache.dart';
part 'src/repositories/target.dart';
part 'src/repositories/timeline.dart';
part 'src/repositories/top_retaining_instances.dart';
part 'src/repositories/type_arguments.dart';
part 'src/repositories/unlinked_call.dart';
part 'src/repositories/vm.dart';

View file

@ -199,18 +199,15 @@ abstract class CommonWebSocketVM extends VM {
void _onBinaryMessage(dynamic data) {
_webSocket.nonStringToByteData(data).then((ByteData bytes) {
// See format spec. in VMs Service::SendEvent.
int offset = 0;
// Dart2JS workaround (no getUint64). Limit to 4 GB metadata.
assert(bytes.getUint32(offset, Endian.big) == 0);
int metaSize = bytes.getUint32(offset + 4, Endian.big);
offset += 8;
var meta = _utf8Decoder.convert(new Uint8List.view(
bytes.buffer, bytes.offsetInBytes + offset, metaSize));
offset += metaSize;
var data = new ByteData.view(bytes.buffer, bytes.offsetInBytes + offset,
bytes.lengthInBytes - offset);
var map = _parseJSON(meta);
var metadataOffset = 4;
var dataOffset = bytes.getUint32(0, Endian.little);
var metadataLength = dataOffset - metadataOffset;
var dataLength = bytes.lengthInBytes - dataOffset;
var metadata = _utf8Decoder.convert(new Uint8List.view(
bytes.buffer, bytes.offsetInBytes + metadataOffset, metadataLength));
var data = new ByteData.view(
bytes.buffer, bytes.offsetInBytes + dataOffset, dataLength);
var map = _parseJSON(metadata);
if (map == null || map['method'] != 'streamNotify') {
return;
}

View file

@ -173,7 +173,6 @@ class ObservatoryApplication {
_pageRegistry.add(new PortsPage(this));
_pageRegistry.add(new LoggingPage(this));
_pageRegistry.add(new TimelinePage(this));
_pageRegistry.add(new MemoryDashboardPage(this));
_pageRegistry.add(new TimelineDashboardPage(this));
// Note that ErrorPage must be the last entry in the list as it is
// the catch all.

View file

@ -32,7 +32,6 @@ final _stronglyReachangleInstancesRepository =
new StronglyReachableInstancesRepository();
final _subtypeTestCacheRepository = new SubtypeTestCacheRepository();
final _timelineRepository = new TimelineRepository();
final _topRetainingInstancesRepository = new TopRetainingInstancesRepository();
final _typeArgumentsRepository = new TypeArgumentsRepository();
final _unlinkedCallRepository = new UnlinkedCallRepository();
final _vmrepository = new VMRepository();
@ -266,7 +265,6 @@ class InspectPage extends MatchingPage {
_objectRepository,
_evalRepository,
_stronglyReachangleInstancesRepository,
_topRetainingInstancesRepository,
_classSampleProfileRepository,
queue: app.queue)
.element
@ -719,60 +717,6 @@ class AllocationProfilerPage extends MatchingPage {
}
}
class MemoryDashboardPage extends MatchingPage {
MemoryDashboardPage(app) : super('memory-dashboard', app);
final DivElement container = new DivElement();
void _visit(Uri uri) {
super._visit(uri);
if (app.vm == null) {
Logger.root.severe('MemoryDashboard has no VM');
// Reroute to vm-connect.
app.locationManager.go(Uris.vmConnect());
return;
}
final editor = getEditor(uri);
app.vm.reload().then((serviceObject) async {
VM vm = serviceObject;
// Preload all isolates to avoid sorting problems.
await Future.wait(vm.isolates.map((i) => i.load()));
container.children = <Element>[
new MemoryDashboardElement(
vm,
_vmrepository,
new IsolateRepository(vm),
editor,
_allocationProfileRepository,
_heapSnapshotRepository,
_objectRepository,
app.events,
app.notifications,
queue: app.queue)
.element
];
}).catchError((e, stack) {
Logger.root.severe('MemoryDashboard visit error: $e');
// Reroute to vm-connect.
app.locationManager.go(Uris.vmConnect());
});
}
void onInstall() {
if (element == null) {
element = container;
}
app.startGCEventListener();
}
@override
void onUninstall() {
super.onUninstall();
app.stopGCEventListener();
container.children = const [];
}
}
class PortsPage extends MatchingPage {
PortsPage(app) : super('ports', app);

View file

@ -12,7 +12,6 @@ import 'package:observatory/src/elements/inbound_references.dart';
import 'package:observatory/src/elements/retaining_path.dart';
import 'package:observatory/src/elements/sentinel_value.dart';
import 'package:observatory/src/elements/strongly_reachable_instances.dart';
import 'package:observatory/src/elements/top_retaining_instances.dart';
import 'package:observatory/utils.dart';
class ClassInstancesElement extends CustomElement implements Renderable {
@ -21,7 +20,6 @@ class ClassInstancesElement extends CustomElement implements Renderable {
ClassRefElement.tag,
InboundReferencesElement.tag,
RetainingPathElement.tag,
TopRetainingInstancesElement.tag
]);
RenderingScheduler<ClassInstancesElement> _r;
@ -33,7 +31,6 @@ class ClassInstancesElement extends CustomElement implements Renderable {
M.RetainedSizeRepository _retainedSizes;
M.ReachableSizeRepository _reachableSizes;
M.StronglyReachableInstancesRepository _stronglyReachableInstances;
M.TopRetainingInstancesRepository _topRetainingInstances;
M.ObjectRepository _objects;
M.Guarded<M.Instance> _retainedSize = null;
bool _loadingRetainedBytes = false;
@ -49,7 +46,6 @@ class ClassInstancesElement extends CustomElement implements Renderable {
M.RetainedSizeRepository retainedSizes,
M.ReachableSizeRepository reachableSizes,
M.StronglyReachableInstancesRepository stronglyReachableInstances,
M.TopRetainingInstancesRepository topRetainingInstances,
M.ObjectRepository objects,
{RenderingQueue queue}) {
assert(isolate != null);
@ -57,7 +53,6 @@ class ClassInstancesElement extends CustomElement implements Renderable {
assert(retainedSizes != null);
assert(reachableSizes != null);
assert(stronglyReachableInstances != null);
assert(topRetainingInstances != null);
assert(objects != null);
ClassInstancesElement e = new ClassInstancesElement.created();
e._r = new RenderingScheduler<ClassInstancesElement>(e, queue: queue);
@ -66,7 +61,6 @@ class ClassInstancesElement extends CustomElement implements Renderable {
e._retainedSizes = retainedSizes;
e._reachableSizes = reachableSizes;
e._stronglyReachableInstances = stronglyReachableInstances;
e._topRetainingInstances = topRetainingInstances;
e._objects = objects;
return e;
}
@ -87,17 +81,12 @@ class ClassInstancesElement extends CustomElement implements Renderable {
}
StronglyReachableInstancesElement _strong;
TopRetainingInstancesElement _topRetainig;
void render() {
_strong = _strong ??
new StronglyReachableInstancesElement(
_isolate, _cls, _stronglyReachableInstances, _objects,
queue: _r.queue);
_topRetainig = _topRetainig ??
new TopRetainingInstancesElement(
_isolate, _cls, _topRetainingInstances, _objects,
queue: _r.queue);
final instanceCount =
_cls.newSpace.current.instances + _cls.oldSpace.current.instances;
final size = Utils.formatSize(
@ -150,16 +139,6 @@ class ClassInstancesElement extends CustomElement implements Renderable {
..classes = ['memberValue']
..children = _createRetainedSizeValue()
],
new DivElement()
..classes = ['memberItem']
..children = <Element>[
new DivElement()
..classes = ['memberName']
..text = 'toplist by retained memory ',
new DivElement()
..classes = ['memberValue']
..children = <Element>[_topRetainig.element]
]
]
];
}

View file

@ -71,7 +71,6 @@ class ClassViewElement extends CustomElement implements Renderable {
M.InboundReferencesRepository _references;
M.RetainingPathRepository _retainingPaths;
M.StronglyReachableInstancesRepository _stronglyReachableInstances;
M.TopRetainingInstancesRepository _topRetainedInstances;
M.FieldRepository _fields;
M.ScriptRepository _scripts;
M.ObjectRepository _objects;
@ -100,7 +99,6 @@ class ClassViewElement extends CustomElement implements Renderable {
M.ObjectRepository objects,
M.EvalRepository eval,
M.StronglyReachableInstancesRepository stronglyReachable,
M.TopRetainingInstancesRepository topRetained,
M.ClassSampleProfileRepository profiles,
{RenderingQueue queue}) {
assert(vm != null);
@ -118,7 +116,6 @@ class ClassViewElement extends CustomElement implements Renderable {
assert(objects != null);
assert(eval != null);
assert(stronglyReachable != null);
assert(topRetained != null);
assert(profiles != null);
ClassViewElement e = new ClassViewElement.created();
e._r = new RenderingScheduler<ClassViewElement>(e, queue: queue);
@ -137,7 +134,6 @@ class ClassViewElement extends CustomElement implements Renderable {
e._objects = objects;
e._eval = eval;
e._stronglyReachableInstances = stronglyReachable;
e._topRetainedInstances = topRetained;
e._profiles = profiles;
return e;
}
@ -168,14 +164,8 @@ class ClassViewElement extends CustomElement implements Renderable {
_references, _retainingPaths, _objects,
queue: _r.queue);
_classInstances = _classInstances ??
new ClassInstancesElement(
_isolate,
_cls,
_retainedSizes,
_reachableSizes,
_stronglyReachableInstances,
_topRetainedInstances,
_objects,
new ClassInstancesElement(_isolate, _cls, _retainedSizes,
_reachableSizes, _stronglyReachableInstances, _objects,
queue: _r.queue);
var header = '';
if (_cls.isAbstract) {

View file

@ -1321,20 +1321,38 @@ eval-box .historyDelete button:hover {
height: 30px;
padding-left: 5%;
padding-right: 5%;
display: flex;
flex-direction: row;
}
.heap-snapshot .tree-item > .size,
.heap-snapshot .tree-item > .percentage {
display: inline-block;
text-align: right;
width: 4em;
margin-left: 0.25em;
margin-right: 0.25em;
flex-basis: 5em;
flex-grow: 0;
flex-shrink: 0;
}
.heap-snapshot .tree-item > .edge {
margin-left: 0.25em;
margin-right: 0.25em;
flex-basis: 0;
flex-grow: 1;
flex-shrink: 1;
}
.heap-snapshot .tree-item > .name {
display: inline;
margin-left: 0.5em;
margin-right: 0.5em;
flex-basis: 0;
flex-grow: 4;
flex-shrink: 4;
}
.heap-snapshot .tree-item > .link {
margin-left: 0.25em;
margin-right: 0.25em;
flex-grow: 0;
flex-shrink: 0;
}
@ -2720,7 +2738,7 @@ li.nav-menu, .nav-menu_label {
.virtual-collection .header.attached > div,
.virtual-collection .buffer > div {
display: inline-block;
/* display: inline-block;*/
}
.virtual-collection .buffer > div {
@ -2819,3 +2837,14 @@ li.nav-menu, .nav-menu_label {
.vm-connect ul {
list-style-type: none;
}
.treemapTile {
position: absolute;
box-sizing: border-box;
border: solid 1px;
font-size: 10px;
text-align: center;
overflow: hidden;
white-space: nowrap;
cursor: default;
}

View file

@ -1793,7 +1793,7 @@ class ObservatoryDebugger extends Debugger {
break;
case S.ServiceEvent.kIsolateRunnable:
case S.ServiceEvent.kGraph:
case S.ServiceEvent.kHeapSnapshot:
case S.ServiceEvent.kGC:
case S.ServiceEvent.kInspect:
// Ignore.

View file

@ -95,8 +95,7 @@ class HeapMapElement extends CustomElement implements Renderable {
_canvas = new CanvasElement()
..width = 1
..height = 1
..onMouseMove.listen(_handleMouseMove)
..onMouseDown.listen(_handleClick);
..onMouseMove.listen(_handleMouseMove);
}
// Set hover text to describe the object under the cursor.
@ -222,16 +221,6 @@ class HeapMapElement extends CustomElement implements Renderable {
_r.dirty();
}
void _handleClick(MouseEvent event) {
final isolate = _isolate as S.Isolate;
final address = _objectAt(event.offset).address.toRadixString(16);
isolate.getObjectByAddress(address).then((result) {
if (result.type != 'Sentinel') {
new AnchorElement(href: Uris.inspect(_isolate, object: result)).click();
}
});
}
void _updateFragmentationData() {
if (_fragmentation == null || _canvas == null) {
return;

File diff suppressed because it is too large Load diff

View file

@ -1,276 +0,0 @@
// Copyright (c) 2017, 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.
/// This Element is part of MemoryDashboardElement.
///
/// The Element is stripped down version of AllocationProfileElement where
/// concepts like old and new space has been hidden away.
///
/// For each class in the system it is shown the Total number of instances
/// alive, the Total memory used by these instances, the number of instances
/// created since the last reset, the memory used by these instances.
import 'dart:async';
import 'dart:html';
import 'package:observatory/models.dart' as M;
import 'package:observatory/src/elements/class_ref.dart';
import 'package:observatory/src/elements/containers/virtual_collection.dart';
import 'package:observatory/src/elements/helpers/rendering_scheduler.dart';
import 'package:observatory/src/elements/helpers/tag.dart';
import 'package:observatory/utils.dart';
enum _SortingField {
accumulatedSize,
accumulatedInstances,
currentSize,
currentInstances,
className,
}
enum _SortingDirection { ascending, descending }
class MemoryAllocationsElement extends CustomElement implements Renderable {
static const tag = const Tag<MemoryAllocationsElement>('memory-allocations',
dependencies: const [ClassRefElement.tag, VirtualCollectionElement.tag]);
RenderingScheduler<MemoryAllocationsElement> _r;
Stream<RenderedEvent<MemoryAllocationsElement>> get onRendered =>
_r.onRendered;
M.IsolateRef _isolate;
M.AllocationProfileRepository _repository;
M.AllocationProfile _profile;
M.EditorRepository _editor;
_SortingField _sortingField = _SortingField.accumulatedInstances;
_SortingDirection _sortingDirection = _SortingDirection.descending;
M.IsolateRef get isolate => _isolate;
factory MemoryAllocationsElement(M.IsolateRef isolate,
M.EditorRepository editor, M.AllocationProfileRepository repository,
{RenderingQueue queue}) {
assert(isolate != null);
assert(editor != null);
assert(repository != null);
MemoryAllocationsElement e = new MemoryAllocationsElement.created();
e._r = new RenderingScheduler<MemoryAllocationsElement>(e, queue: queue);
e._isolate = isolate;
e._editor = editor;
e._repository = repository;
return e;
}
MemoryAllocationsElement.created() : super.created(tag);
@override
attached() {
super.attached();
_r.enable();
_refresh();
}
@override
detached() {
super.detached();
_r.disable(notify: true);
children = <Element>[];
}
Future reload({bool gc = false, bool reset = false}) async {
return _refresh(gc: gc, reset: reset);
}
void render() {
if (_profile == null) {
children = <Element>[
new DivElement()
..classes = ['content-centered-big']
..children = <Element>[new HeadingElement.h2()..text = 'Loading...']
];
} else {
children = <Element>[
new VirtualCollectionElement(
_createCollectionLine, _updateCollectionLine,
createHeader: _createCollectionHeader,
search: _search,
items: _profile.members
.where((member) =>
member.newSpace.accumulated.instances != 0 ||
member.newSpace.current.instances != 0 ||
member.oldSpace.accumulated.instances != 0 ||
member.oldSpace.current.instances != 0)
.toList()
..sort(_createSorter()),
queue: _r.queue)
.element
];
}
}
_createSorter() {
var getter;
switch (_sortingField) {
case _SortingField.accumulatedSize:
getter = _getAccumulatedSize;
break;
case _SortingField.accumulatedInstances:
getter = _getAccumulatedInstances;
break;
case _SortingField.currentSize:
getter = _getCurrentSize;
break;
case _SortingField.currentInstances:
getter = _getCurrentInstances;
break;
case _SortingField.className:
getter = (M.ClassHeapStats s) => s.clazz.name;
break;
}
switch (_sortingDirection) {
case _SortingDirection.ascending:
int sort(M.ClassHeapStats a, M.ClassHeapStats b) {
return getter(a).compareTo(getter(b));
}
return sort;
case _SortingDirection.descending:
int sort(M.ClassHeapStats a, M.ClassHeapStats b) {
return getter(b).compareTo(getter(a));
}
return sort;
}
}
static HtmlElement _createCollectionLine() => new DivElement()
..classes = ['collection-item']
..children = <Element>[
new SpanElement()
..classes = ['bytes']
..text = '0B',
new SpanElement()
..classes = ['instances']
..text = '0',
new SpanElement()
..classes = ['bytes']
..text = '0B',
new SpanElement()
..classes = ['instances']
..text = '0',
new SpanElement()..classes = ['name']
];
List<HtmlElement> _createCollectionHeader() {
final resetAccumulators = new ButtonElement();
return [
new DivElement()
..classes = ['collection-item']
..children = <Element>[
new SpanElement()
..classes = ['group']
..nodes = [
new Text('Since Last '),
resetAccumulators
..text = 'Reset'
..title = 'Reset'
..onClick.listen((_) async {
resetAccumulators.disabled = true;
await _refresh(reset: true);
resetAccumulators.disabled = false;
})
],
new SpanElement()
..classes = ['group']
..text = 'Current'
],
new DivElement()
..classes = ['collection-item']
..children = <Element>[
_createHeaderButton(const ['bytes'], 'Size',
_SortingField.accumulatedSize, _SortingDirection.descending),
_createHeaderButton(const ['instances'], 'Instances',
_SortingField.accumulatedInstances, _SortingDirection.descending),
_createHeaderButton(const ['bytes'], 'Size',
_SortingField.currentSize, _SortingDirection.descending),
_createHeaderButton(const ['instances'], 'Instances',
_SortingField.currentInstances, _SortingDirection.descending),
_createHeaderButton(const ['name'], 'Class', _SortingField.className,
_SortingDirection.ascending)
],
];
}
ButtonElement _createHeaderButton(List<String> classes, String text,
_SortingField field, _SortingDirection direction) =>
new ButtonElement()
..classes = classes
..text = _sortingField != field
? text
: _sortingDirection == _SortingDirection.ascending
? '$text'
: '$text'
..onClick.listen((_) => _setSorting(field, direction));
void _setSorting(_SortingField field, _SortingDirection defaultDirection) {
if (_sortingField == field) {
switch (_sortingDirection) {
case _SortingDirection.descending:
_sortingDirection = _SortingDirection.ascending;
break;
case _SortingDirection.ascending:
_sortingDirection = _SortingDirection.descending;
break;
}
} else {
_sortingDirection = defaultDirection;
_sortingField = field;
}
_r.dirty();
}
void _updateCollectionLine(Element e, itemDynamic, index) {
M.ClassHeapStats item = itemDynamic;
e.children[0].text = Utils.formatSize(_getAccumulatedSize(item));
e.children[1].text = '${_getAccumulatedInstances(item)}';
e.children[2].text = Utils.formatSize(_getCurrentSize(item));
e.children[3].text = '${_getCurrentInstances(item)}';
if (item.clazz == null) {
e.children[4] = new SpanElement()
..text = item.displayName
..classes = ['name'];
return;
}
e.children[4] = (new ClassRefElement(_isolate, item.clazz, queue: _r.queue)
..classes = ['name'])
.element;
Element.clickEvent.forTarget(e.children[4], useCapture: true).listen((e) {
if (_editor.isAvailable) {
e.preventDefault();
_editor.openClass(isolate, item.clazz);
}
});
}
bool _search(Pattern pattern, itemDynamic) {
M.ClassHeapStats item = itemDynamic;
final String value = item.clazz?.name ?? item.displayName;
return value.contains(pattern);
}
Future _refresh({bool gc: false, bool reset: false}) async {
_profile = null;
_r.dirty();
_profile =
await _repository.get(_isolate, gc: gc, reset: reset, combine: true);
_r.dirty();
}
static int _getAccumulatedSize(M.ClassHeapStats s) =>
s.newSpace.accumulated.bytes + s.oldSpace.accumulated.bytes;
static int _getAccumulatedInstances(M.ClassHeapStats s) =>
s.newSpace.accumulated.instances + s.oldSpace.accumulated.instances;
static int _getCurrentSize(M.ClassHeapStats s) =>
s.newSpace.current.bytes + s.oldSpace.current.bytes;
static int _getCurrentInstances(M.ClassHeapStats s) =>
s.newSpace.current.instances + s.oldSpace.current.instances;
}

View file

@ -1,142 +0,0 @@
// Copyright (c) 2017, 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.
/// This page is not directly reachable from the main Observatory ui.
/// It is mainly mented to be used from editors as an integrated tool.
///
/// This page mainly targeting developers and not VM experts, so concepts like
/// old and new heap are abstracted away.
///
/// The page comprises an overall memory usage of the VM from where it is
/// possible to select an isolate to deeply analyze.
/// See MemoryGraphElement
///
/// Once an isolate is selected it is possible to information specific to it.
/// See MemoryProfileElement
///
/// The logic in this Element is mainly mented to orchestrate the two
/// sub-components by means of positioning and message passing.
import 'dart:async';
import 'dart:html';
import 'package:observatory/models.dart' as M;
import 'package:observatory/src/elements/helpers/nav_bar.dart';
import 'package:observatory/src/elements/helpers/rendering_scheduler.dart';
import 'package:observatory/src/elements/helpers/tag.dart';
import 'package:observatory/src/elements/nav/notify.dart';
import 'package:observatory/src/elements/memory/graph.dart';
import 'package:observatory/src/elements/memory/profile.dart';
class MemoryDashboardElement extends CustomElement implements Renderable {
static const tag = const Tag<MemoryDashboardElement>('memory-dashboard',
dependencies: const [
NavNotifyElement.tag,
MemoryGraphElement.tag,
MemoryProfileElement.tag,
]);
RenderingScheduler<MemoryDashboardElement> _r;
Stream<RenderedEvent<MemoryDashboardElement>> get onRendered => _r.onRendered;
M.VMRef _vm;
M.VMRepository _vms;
M.IsolateRepository _isolates;
M.EditorRepository _editor;
M.AllocationProfileRepository _allocations;
M.HeapSnapshotRepository _snapshots;
M.ObjectRepository _objects;
M.EventRepository _events;
M.NotificationRepository _notifications;
M.VMRef get vm => _vm;
M.NotificationRepository get notifications => _notifications;
factory MemoryDashboardElement(
M.VMRef vm,
M.VMRepository vms,
M.IsolateRepository isolates,
M.EditorRepository editor,
M.AllocationProfileRepository allocations,
M.HeapSnapshotRepository snapshots,
M.ObjectRepository objects,
M.EventRepository events,
M.NotificationRepository notifications,
{RenderingQueue queue}) {
assert(vm != null);
assert(vms != null);
assert(isolates != null);
assert(editor != null);
assert(allocations != null);
assert(events != null);
assert(notifications != null);
MemoryDashboardElement e = new MemoryDashboardElement.created();
e._r = new RenderingScheduler<MemoryDashboardElement>(e, queue: queue);
e._vm = vm;
e._vms = vms;
e._isolates = isolates;
e._editor = editor;
e._allocations = allocations;
e._snapshots = snapshots;
e._objects = objects;
e._events = events;
e._notifications = notifications;
return e;
}
MemoryDashboardElement.created() : super.created(tag);
@override
attached() {
super.attached();
_r.enable();
}
@override
detached() {
super.detached();
_r.disable(notify: true);
children = <Element>[];
}
M.IsolateRef _isolate;
MemoryGraphElement _graph;
void render() {
if (_graph == null) {
_graph =
new MemoryGraphElement(vm, _vms, _isolates, _events, queue: _r.queue)
..onIsolateSelected.listen(_onIsolateSelected);
}
children = <Element>[
navBar(<Element>[
new NavNotifyElement(_notifications, queue: _r.queue).element
]),
new DivElement()
..classes = ['content-centered-big']
..children = <Element>[
new HeadingElement.h2()..text = 'Memory Dashboard',
new HRElement(),
_graph.element,
new HRElement(),
],
];
if (_isolate == null) {
children.add(new DivElement()
..classes = ['content-centered-big']
..children = <Element>[
new HeadingElement.h1()..text = "No isolate selected"
]);
} else {
children.add(new MemoryProfileElement(
_isolate, _editor, _allocations, _snapshots, _objects)
.element);
}
}
void _onIsolateSelected(IsolateSelectedEvent e) {
_isolate = _r.checkAndReact(_isolate, e.isolate);
}
}

View file

@ -1,462 +0,0 @@
// Copyright (c) 2017, 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.
/// This Element is part of MemoryDashboardElement.
///
/// The Element periodically interrogates the VM to log the memory usage of each
/// Isolate and of the Native Memory.
///
/// For each isolate it is shown the Used and Free heap (new and old are merged
/// together)
///
/// When a GC event is received an extra point is introduced in the graph to
/// make the representation as precise as possible.
///
/// When an Isolate is selected the event is bubbled up to the parent.
import 'dart:async';
import 'dart:html';
import 'package:observatory/models.dart' as M;
import 'package:charted/charted.dart';
import 'package:observatory/src/elements/helpers/rendering_scheduler.dart';
import 'package:observatory/src/elements/helpers/tag.dart';
import 'package:observatory/utils.dart';
class IsolateSelectedEvent {
final M.Isolate isolate;
const IsolateSelectedEvent([this.isolate]);
}
class MemoryGraphElement extends CustomElement implements Renderable {
static const tag = const Tag<MemoryGraphElement>('memory-graph');
RenderingScheduler<MemoryGraphElement> _r;
final StreamController<IsolateSelectedEvent> _onIsolateSelected =
new StreamController<IsolateSelectedEvent>();
Stream<RenderedEvent<MemoryGraphElement>> get onRendered => _r.onRendered;
Stream<IsolateSelectedEvent> get onIsolateSelected =>
_onIsolateSelected.stream;
M.VMRef _vm;
M.VMRepository _vms;
M.IsolateRepository _isolates;
M.EventRepository _events;
StreamSubscription _onGCSubscription;
StreamSubscription _onResizeSubscription;
StreamSubscription _onConnectionClosedSubscription;
Timer _onTimer;
M.VMRef get vm => _vm;
factory MemoryGraphElement(M.VMRef vm, M.VMRepository vms,
M.IsolateRepository isolates, M.EventRepository events,
{RenderingQueue queue}) {
assert(vm != null);
assert(vms != null);
assert(isolates != null);
assert(events != null);
MemoryGraphElement e = new MemoryGraphElement.created();
e._r = new RenderingScheduler<MemoryGraphElement>(e, queue: queue);
e._vm = vm;
e._vms = vms;
e._isolates = isolates;
e._events = events;
return e;
}
MemoryGraphElement.created() : super.created(tag) {
final now = new DateTime.now();
var sample = now.subtract(_window);
while (sample.isBefore(now)) {
_ts.add(sample);
_vmSamples.add(<int>[0, 0]);
_isolateUsedSamples.add([]);
_isolateFreeSamples.add([]);
sample = sample.add(_period);
}
_ts.add(now);
_vmSamples.add(<int>[0, 0]);
_isolateUsedSamples.add([]);
_isolateFreeSamples.add([]);
}
static const Duration _period = const Duration(seconds: 2);
static const Duration _window = const Duration(minutes: 2);
@override
attached() {
super.attached();
_r.enable();
_onGCSubscription =
_events.onGCEvent.listen((e) => _refresh(gcIsolate: e.isolate));
_onConnectionClosedSubscription =
_events.onConnectionClosed.listen((_) => _onTimer.cancel());
_onResizeSubscription = window.onResize.listen((_) => _r.dirty());
_onTimer = new Timer.periodic(_period, (_) => _refresh());
_refresh();
}
@override
detached() {
super.detached();
_r.disable(notify: true);
children = <Element>[];
_onGCSubscription.cancel();
_onConnectionClosedSubscription.cancel();
_onResizeSubscription.cancel();
_onTimer.cancel();
}
final List<DateTime> _ts = <DateTime>[];
final List<List<int>> _vmSamples = <List<int>>[];
final List<M.IsolateRef> _seenIsolates = <M.IsolateRef>[];
final List<List<int>> _isolateUsedSamples = <List<int>>[];
final List<List<int>> _isolateFreeSamples = <List<int>>[];
final Map<String, int> _isolateIndex = <String, int>{};
final Map<String, String> _isolateName = <String, String>{};
var _selected;
var _previewed;
var _hovered;
void render() {
if (_previewed != null || _hovered != null) return;
// cache data of hoverboards
final ts = new List<DateTime>.from(_ts);
final vmSamples = new List<List<int>>.from(_vmSamples);
final isolateFreeSamples = new List<List<int>>.from(_isolateFreeSamples);
final isolateUsedSamples = new List<List<int>>.from(_isolateUsedSamples);
final now = _ts.last;
final nativeComponents = 1;
final legend = new DivElement();
final host = new DivElement();
final theme = new MemoryChartTheme(1);
children = <Element>[theme.style, legend, host];
final rect = host.getBoundingClientRect();
final series =
new List<int>.generate(_isolateIndex.length * 2 + 1, (i) => i + 1);
// The stacked line chart sorts from top to bottom
final columns = [
new ChartColumnSpec(
formatter: _formatTimeAxis, type: ChartColumnSpec.TYPE_NUMBER),
new ChartColumnSpec(label: 'Native', formatter: Utils.formatSize)
]..addAll(_isolateName.keys.expand((id) => [
new ChartColumnSpec(formatter: Utils.formatSize),
new ChartColumnSpec(label: _label(id), formatter: Utils.formatSize)
]));
// The stacked line chart sorts from top to bottom
final rows = new List.generate(_ts.length, (sampleIndex) {
final free = isolateFreeSamples[sampleIndex];
final used = isolateUsedSamples[sampleIndex];
final isolates = _isolateIndex.keys.expand((key) {
final isolateIndex = _isolateIndex[key];
return <int>[free[isolateIndex], used[isolateIndex]];
});
return [
ts[sampleIndex].difference(now).inMicroseconds,
vmSamples[sampleIndex][1] ?? 1000000
]..addAll(isolates);
});
final scale = new LinearScale()..domain = [(-_window).inMicroseconds, 0];
final axisConfig = new ChartAxisConfig()..scale = scale;
final sMemory =
new ChartSeries('Memory', series, new StackedLineChartRenderer());
final config = new ChartConfig([sMemory], [0])
..legend = new ChartLegend(legend)
..registerDimensionAxis(0, axisConfig);
config.minimumSize = new Rect(rect.width, rect.height);
final data = new ChartData(columns, rows);
final state = new ChartState(isMultiSelect: true)
..changes.listen(_handleEvent);
final area = new CartesianArea(host, data, config, state: state)
..theme = theme;
area.addChartBehavior(new Hovercard(builder: (int column, int row) {
if (column == 1) {
final data = vmSamples[row];
return _formatNativeOvercard(data[0], data[1]);
}
final isolate = _seenIsolates[column - 2];
final index = _isolateIndex[isolate.id];
final free = isolateFreeSamples[row][index];
final used = isolateUsedSamples[row][index];
return _formatIsolateOvercard(isolate.name, free, used);
}));
area.draw();
if (_selected != null) {
state.select(_selected);
if (_selected > 1) {
state.select(_selected + 1);
}
}
}
String _formatTimeAxis(dynamic ms) =>
Utils.formatDuration(new Duration(microseconds: ms.toInt()),
precision: DurationComponent.Seconds);
bool _running = false;
Future _refresh({M.IsolateRef gcIsolate}) async {
if (_running) return;
_running = true;
final now = new DateTime.now();
final start = now.subtract(_window);
final vm = await _vms.get(_vm);
// The Service classes order isolates from the older to the newer
final isolates =
(await Future.wait(vm.isolates.map(_isolates.get))).reversed.toList();
while (_ts.first.isBefore(start)) {
_ts.removeAt(0);
_vmSamples.removeAt(0);
_isolateUsedSamples.removeAt(0);
_isolateFreeSamples.removeAt(0);
}
if (_ts.first.isAfter(start)) {
_ts.insert(0, start);
_vmSamples.insert(0, _vmSamples.first);
_isolateUsedSamples.insert(0, _isolateUsedSamples.first);
_isolateFreeSamples.insert(0, _isolateFreeSamples.first);
}
if (_isolateIndex.length == 0) {
_selected = isolates.length * 2;
_onIsolateSelected.add(new IsolateSelectedEvent(isolates.last));
}
isolates
.where((isolate) => !_isolateIndex.containsKey(isolate.id))
.forEach((isolate) {
_isolateIndex[isolate.id] = _isolateIndex.length;
_seenIsolates.addAll([isolate, isolate]);
});
if (_isolateIndex.length != _isolateName.length) {
final extra =
new List.filled(_isolateIndex.length - _isolateName.length, 0);
_isolateUsedSamples.forEach((sample) => sample.addAll(extra));
_isolateFreeSamples.forEach((sample) => sample.addAll(extra));
}
final length = _isolateIndex.length;
if (gcIsolate != null) {
// After GC we add an extra point to show the drop in a clear way
final List<int> isolateUsedSample = new List<int>.filled(length, 0);
final List<int> isolateFreeSample = new List<int>.filled(length, 0);
isolates.forEach((M.Isolate isolate) {
_isolateName[isolate.id] = isolate.name;
final index = _isolateIndex[isolate.id];
if (isolate.id == gcIsolate) {
isolateUsedSample[index] =
_isolateUsedSamples.last[index] + _isolateFreeSamples.last[index];
isolateFreeSample[index] = 0;
} else {
isolateUsedSample[index] = _used(isolate);
isolateFreeSample[index] = _free(isolate);
}
});
_isolateUsedSamples.add(isolateUsedSample);
_isolateFreeSamples.add(isolateFreeSample);
_vmSamples.add(<int>[vm.currentRSS, vm.heapAllocatedMemoryUsage]);
_ts.add(now);
}
final List<int> isolateUsedSample = new List<int>.filled(length, 0);
final List<int> isolateFreeSample = new List<int>.filled(length, 0);
isolates.forEach((M.Isolate isolate) {
_isolateName[isolate.id] = isolate.name;
final index = _isolateIndex[isolate.id];
isolateUsedSample[index] = _used(isolate);
isolateFreeSample[index] = _free(isolate);
});
_isolateUsedSamples.add(isolateUsedSample);
_isolateFreeSamples.add(isolateFreeSample);
_vmSamples.add(<int>[vm.currentRSS, vm.heapAllocatedMemoryUsage]);
_ts.add(now);
_r.dirty();
_running = false;
}
void _handleEvent(records) => records.forEach((record) {
if (record is ChartSelectionChangeRecord) {
var selected = record.add;
if (selected == null) {
if (selected != _selected) {
_onIsolateSelected.add(const IsolateSelectedEvent());
_r.dirty();
}
} else {
if (selected == 1) {
if (selected != _selected) {
_onIsolateSelected.add(const IsolateSelectedEvent());
_r.dirty();
}
} else {
selected -= selected % 2;
if (selected != _selected) {
_onIsolateSelected
.add(new IsolateSelectedEvent(_seenIsolates[selected - 2]));
_r.dirty();
}
}
}
_selected = selected;
_previewed = null;
_hovered = null;
} else if (record is ChartPreviewChangeRecord) {
_previewed = record.previewed;
} else if (record is ChartHoverChangeRecord) {
_hovered = record.hovered;
}
});
int _used(M.Isolate i) => i.newSpace.used + i.oldSpace.used;
int _capacity(M.Isolate i) => i.newSpace.capacity + i.oldSpace.capacity;
int _free(M.Isolate i) => _capacity(i) - _used(i);
String _label(String isolateId) {
final index = _isolateIndex[isolateId];
final name = _isolateName[isolateId];
final free = _isolateFreeSamples.last[index];
final used = _isolateUsedSamples.last[index];
final usedStr = Utils.formatSize(used);
final capacity = free + used;
final capacityStr = Utils.formatSize(capacity);
return '${name} ($usedStr / $capacityStr)';
}
static HtmlElement _formatNativeOvercard(int currentRSS, int heap) =>
new DivElement()
..children = <Element>[
new DivElement()
..classes = ['hovercard-title']
..text = 'Native',
new DivElement()
..classes = ['hovercard-measure', 'hovercard-multi']
..children = <Element>[
new DivElement()
..classes = ['hovercard-measure-label']
..text = 'Total Memory Usage',
new DivElement()
..classes = ['hovercard-measure-value']
..text = currentRSS != null
? Utils.formatSize(currentRSS)
: "unavailable",
],
new DivElement()
..classes = ['hovercard-measure', 'hovercard-multi']
..children = <Element>[
new DivElement()
..classes = ['hovercard-measure-label']
..text = 'Native Heap',
new DivElement()
..classes = ['hovercard-measure-value']
..text = heap != null ? Utils.formatSize(heap) : "unavailable",
]
];
static HtmlElement _formatIsolateOvercard(String name, int free, int used) {
final capacity = free + used;
return new DivElement()
..children = <Element>[
new DivElement()
..classes = ['hovercard-title']
..text = name,
new DivElement()
..classes = ['hovercard-measure', 'hovercard-multi']
..children = <Element>[
new DivElement()
..classes = ['hovercard-measure-label']
..text = 'Heap Capacity',
new DivElement()
..classes = ['hovercard-measure-value']
..text = Utils.formatSize(capacity),
],
new DivElement()
..classes = ['hovercard-measure', 'hovercard-multi']
..children = <Element>[
new DivElement()
..classes = ['hovercard-measure-label']
..text = 'Free Heap',
new DivElement()
..classes = ['hovercard-measure-value']
..text = Utils.formatSize(free),
],
new DivElement()
..classes = ['hovercard-measure', 'hovercard-multi']
..children = <Element>[
new DivElement()
..classes = ['hovercard-measure-label']
..text = 'Used Heap',
new DivElement()
..classes = ['hovercard-measure-value']
..text = Utils.formatSize(used),
]
];
}
}
class MemoryChartTheme extends QuantumChartTheme {
final int _offset;
MemoryChartTheme(int offset) : _offset = offset {
assert(offset != null);
assert(offset >= 0);
}
@override
String getColorForKey(key, [int state = 0]) {
key -= 1;
if (key > _offset) {
key = _offset + (key - _offset) ~/ 2;
}
key += 1;
return super.getColorForKey(key, state);
}
@override
String getFilterForState(int state) => state & ChartState.COL_PREVIEW != 0 ||
state & ChartState.VAL_HOVERED != 0 ||
state & ChartState.COL_SELECTED != 0 ||
state & ChartState.VAL_HIGHLIGHTED != 0
? 'url(#drop-shadow)'
: '';
@override
String get filters =>
'<defs>' +
super.filters +
'''
<filter id="stroke-grid" primitiveUnits="userSpaceOnUse">
<feFlood in="SourceGraphic" x="0" y="0" width="4" height="4"
flood-color="black" flood-opacity="0.2" result='Black'/>
<feFlood in="SourceGraphic" x="1" y="1" width="3" height="3"
flood-color="black" flood-opacity="0.8" result='White'/>
<feComposite in="Black" in2="White" operator="xor" x="0" y="0" width="4" height="4"/>
<feTile x="0" y="0" width="100%" height="100%" />
<feComposite in2="SourceAlpha" result="Pattern" operator="in" x="0" y="0" width="100%" height="100%"/>
<feComposite in="SourceGraphic" in2="Pattern" operator="atop" x="0" y="0" width="100%" height="100%"/>
</filter>
</defs>
''';
StyleElement get style => new StyleElement()
..text = '''
memory-graph svg .stacked-line-rdr-line:nth-child(2n+${_offset + 1})
path:nth-child(1) {
filter: url(#stroke-grid);
}''';
}

View file

@ -1,158 +0,0 @@
// Copyright (c) 2017, 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.
/// This Element is part of MemoryDashboardElement.
///
/// The Element is stripped down version of AllocationProfileElement where
/// concepts like old and new space has been hidden away.
///
/// For each class in the system it is shown the Total number of instances
/// alive, the Total memory used by these instances, the number of instances
/// created since the last reset, the memory used by these instances.
///
/// When a GC event is received the profile is reloaded.
import 'dart:async';
import 'dart:html';
import 'package:observatory/models.dart' as M;
import 'package:observatory/src/elements/helpers/rendering_scheduler.dart';
import 'package:observatory/src/elements/helpers/tag.dart';
import 'package:observatory/src/elements/memory/allocations.dart';
import 'package:observatory/src/elements/memory/snapshot.dart';
enum _Analysis { allocations, dominatorTree }
class MemoryProfileElement extends CustomElement implements Renderable {
static const tag = const Tag<MemoryProfileElement>('memory-profile',
dependencies: const [
MemoryAllocationsElement.tag,
MemorySnapshotElement.tag
]);
RenderingScheduler<MemoryProfileElement> _r;
Stream<RenderedEvent<MemoryProfileElement>> get onRendered => _r.onRendered;
M.IsolateRef _isolate;
M.AllocationProfileRepository _allocations;
M.EditorRepository _editor;
M.HeapSnapshotRepository _snapshots;
M.ObjectRepository _objects;
_Analysis _analysis = _Analysis.allocations;
M.IsolateRef get isolate => _isolate;
factory MemoryProfileElement(
M.IsolateRef isolate,
M.EditorRepository editor,
M.AllocationProfileRepository allocations,
M.HeapSnapshotRepository snapshots,
M.ObjectRepository objects,
{RenderingQueue queue}) {
assert(isolate != null);
assert(editor != null);
assert(allocations != null);
assert(snapshots != null);
assert(objects != null);
MemoryProfileElement e = new MemoryProfileElement.created();
e._r = new RenderingScheduler<MemoryProfileElement>(e, queue: queue);
e._isolate = isolate;
e._editor = editor;
e._allocations = allocations;
e._snapshots = snapshots;
e._objects = objects;
return e;
}
MemoryProfileElement.created() : super.created(tag);
@override
attached() {
super.attached();
_r.enable();
}
@override
detached() {
super.detached();
_r.disable(notify: true);
children = <Element>[];
}
void render() {
HtmlElement current;
var reload;
switch (_analysis) {
case _Analysis.allocations:
final MemoryAllocationsElement allocations =
new MemoryAllocationsElement(_isolate, _editor, _allocations);
current = allocations.element;
reload = ({bool gc: false}) => allocations.reload(gc: gc);
break;
case _Analysis.dominatorTree:
final MemorySnapshotElement snapshot =
new MemorySnapshotElement(_isolate, _editor, _snapshots, _objects);
current = snapshot.element;
reload = ({bool gc: false}) => snapshot.reload(gc: gc);
break;
}
assert(current != null);
final ButtonElement bReload = new ButtonElement();
final ButtonElement bGC = new ButtonElement();
children = <Element>[
new DivElement()
..classes = ['content-centered-big']
..children = <Element>[
new HeadingElement.h1()
..nodes = [
new Text(_isolate.name),
bReload
..classes = ['header_button']
..text = ' ↺ Refresh'
..title = 'Refresh'
..onClick.listen((e) async {
bReload.disabled = true;
bGC.disabled = true;
await reload();
bReload.disabled = false;
bGC.disabled = false;
}),
bGC
..classes = ['header_button']
..text = ' ♺ Collect Garbage'
..title = 'Collect Garbage'
..onClick.listen((e) async {
bGC.disabled = true;
bReload.disabled = true;
await reload(gc: true);
bGC.disabled = false;
bReload.disabled = false;
}),
new SpanElement()
..classes = ['tab_buttons']
..children = <Element>[
new ButtonElement()
..text = 'Allocations'
..disabled = _analysis == _Analysis.allocations
..onClick.listen((_) {
_analysis = _Analysis.allocations;
_r.dirty();
}),
new ButtonElement()
..text = 'Dominator Tree'
..disabled = _analysis == _Analysis.dominatorTree
..onClick.listen((_) {
_analysis = _Analysis.dominatorTree;
_r.dirty();
}),
]
],
],
current
];
}
}

View file

@ -1,268 +0,0 @@
// Copyright (c) 2017, 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:html';
import 'dart:math' as Math;
import 'package:observatory/models.dart' as M;
import 'package:observatory/src/elements/class_ref.dart';
import 'package:observatory/src/elements/containers/virtual_tree.dart';
import 'package:observatory/src/elements/helpers/any_ref.dart';
import 'package:observatory/src/elements/helpers/rendering_scheduler.dart';
import 'package:observatory/src/elements/helpers/tag.dart';
import 'package:observatory/src/elements/helpers/uris.dart';
import 'package:observatory/utils.dart';
class MemorySnapshotElement extends CustomElement implements Renderable {
static const tag =
const Tag<MemorySnapshotElement>('memory-snapshot', dependencies: const [
ClassRefElement.tag,
VirtualTreeElement.tag,
]);
RenderingScheduler<MemorySnapshotElement> _r;
Stream<RenderedEvent<MemorySnapshotElement>> get onRendered => _r.onRendered;
M.IsolateRef _isolate;
M.EditorRepository _editor;
M.HeapSnapshotRepository _snapshots;
M.ObjectRepository _objects;
M.HeapSnapshot _snapshot;
Stream<M.HeapSnapshotLoadingProgressEvent> _progressStream;
M.HeapSnapshotLoadingProgress _progress;
M.IsolateRef get isolate => _isolate;
factory MemorySnapshotElement(M.IsolateRef isolate, M.EditorRepository editor,
M.HeapSnapshotRepository snapshots, M.ObjectRepository objects,
{RenderingQueue queue}) {
assert(isolate != null);
assert(editor != null);
assert(snapshots != null);
assert(objects != null);
MemorySnapshotElement e = new MemorySnapshotElement.created();
e._r = new RenderingScheduler<MemorySnapshotElement>(e, queue: queue);
e._isolate = isolate;
e._editor = editor;
e._snapshots = snapshots;
e._objects = objects;
return e;
}
MemorySnapshotElement.created() : super.created(tag);
@override
attached() {
super.attached();
_r.enable();
_refresh();
}
@override
detached() {
super.detached();
_r.disable(notify: true);
children = <Element>[];
}
void render() {
if (_progress == null) {
children = const [];
return;
}
List<Element> content;
switch (_progress.status) {
case M.HeapSnapshotLoadingStatus.fetching:
content = _createStatusMessage('Fetching snapshot from VM...',
description: _progress.stepDescription,
progress: _progress.progress);
break;
case M.HeapSnapshotLoadingStatus.loading:
content = _createStatusMessage('Loading snapshot...',
description: _progress.stepDescription,
progress: _progress.progress);
break;
case M.HeapSnapshotLoadingStatus.loaded:
content = _createReport();
break;
}
children = content;
}
Future reload({bool gc: false}) => _refresh(gc: gc);
Future _refresh({bool gc: false}) async {
_progress = null;
_progressStream =
_snapshots.get(isolate, roots: M.HeapSnapshotRoots.user, gc: gc);
_r.dirty();
_progressStream.listen((e) {
_progress = e.progress;
_r.dirty();
});
_progress = (await _progressStream.first).progress;
_r.dirty();
if (M.isHeapSnapshotProgressRunning(_progress.status)) {
_progress = (await _progressStream.last).progress;
_snapshot = _progress.snapshot;
_r.dirty();
}
}
static List<Element> _createStatusMessage(String message,
{String description: '', double progress: 0.0}) {
return [
new DivElement()
..classes = ['content-centered-big']
..children = <Element>[
new DivElement()
..classes = ['statusBox', 'shadow', 'center']
..children = <Element>[
new DivElement()
..classes = ['statusMessage']
..text = message,
new DivElement()
..classes = ['statusDescription']
..text = description,
new DivElement()
..style.background = '#0489c3'
..style.width = '$progress%'
..style.height = '15px'
..style.borderRadius = '4px'
]
]
];
}
VirtualTreeElement _tree;
List<Element> _createReport() {
final List roots = _getChildrenDominator(_snapshot.dominatorTree).toList();
_tree = new VirtualTreeElement(
_createDominator, _updateDominator, _getChildrenDominator,
items: roots, queue: _r.queue);
if (roots.length == 1) {
_tree.expand(roots.first, autoExpandSingleChildNodes: true);
}
final text = 'In a heap dominator tree, an object X is a parent of '
'object Y if every path from the root to Y goes through '
'X. This allows you to find "choke points" that are '
'holding onto a lot of memory. If an object becomes '
'garbage, all its children in the dominator tree become '
'garbage as well. '
'The retained size of an object is the sum of the '
'retained sizes of its children in the dominator tree '
'plus its own shallow size, and is the amount of memory '
'that would be freed if the object became garbage.';
return <HtmlElement>[
new DivElement()
..classes = ['content-centered-big', 'explanation']
..text = text
..title = text,
_tree.element
];
}
static HtmlElement _createDominator(toggle) {
return new DivElement()
..classes = ['tree-item']
..children = <Element>[
new SpanElement()
..classes = ['size']
..title = 'retained size',
new SpanElement()..classes = ['lines'],
new ButtonElement()
..classes = ['expander']
..onClick.listen((_) => toggle(autoToggleSingleChildNodes: true)),
new SpanElement()
..classes = ['percentage']
..title = 'percentage of heap being retained',
new SpanElement()..classes = ['name']
];
}
static const int kMaxChildren = 100;
static const int kMinRetainedSize = 4096;
static Iterable _getChildrenDominator(nodeDynamic) {
M.HeapSnapshotDominatorNode node = nodeDynamic;
final list = node.children.toList();
list.sort((a, b) => b.retainedSize - a.retainedSize);
return list
.where((child) => child.retainedSize >= kMinRetainedSize)
.take(kMaxChildren);
}
void _updateDominator(HtmlElement element, nodeDynamic, int depth) {
M.HeapSnapshotDominatorNode node = nodeDynamic;
element.children[0].text = Utils.formatSize(node.retainedSize);
_updateLines(element.children[1].children, depth);
if (_getChildrenDominator(node).isNotEmpty) {
element.children[2].text = _tree.isExpanded(node) ? '' : '';
} else {
element.children[2].text = '';
}
element.children[3].text =
Utils.formatPercentNormalized(node.retainedSize * 1.0 / _snapshot.size);
final wrapper = new SpanElement()
..classes = ['name']
..text = 'Loading...';
element.children[4] = wrapper;
if (node.isStack) {
wrapper
..text = ''
..children = <Element>[
new AnchorElement(href: Uris.debugger(isolate))..text = 'stack frames'
];
} else {
node.object.then((object) {
wrapper
..text = ''
..children = <Element>[
anyRef(_isolate, object, _objects,
queue: _r.queue, expandable: false)
];
});
}
Element.clickEvent
.forTarget(element.children[4], useCapture: true)
.listen((e) {
if (_editor.isAvailable) {
e.preventDefault();
_sendNodeToEditor(node);
}
});
}
Future _sendNodeToEditor(M.HeapSnapshotDominatorNode node) async {
final object = await node.object;
if (node.isStack) {
// TODO (https://github.com/flutter/flutter-intellij/issues/1290)
// open debugger
return new Future.value();
}
_editor.openObject(_isolate, object);
}
static _updateLines(List<Element> lines, int n) {
n = Math.max(0, n);
while (lines.length > n) {
lines.removeLast();
}
while (lines.length < n) {
lines.add(new SpanElement());
}
}
static String rootsToString(M.HeapSnapshotRoots roots) {
switch (roots) {
case M.HeapSnapshotRoots.user:
return 'User';
case M.HeapSnapshotRoots.vm:
return 'VM';
}
throw new Exception('Unknown HeapSnapshotRoots');
}
}

View file

@ -1,115 +0,0 @@
// Copyright (c) 2016, 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:html';
import 'dart:async';
import 'package:observatory/models.dart' as M;
import 'package:observatory/src/elements/curly_block.dart';
import 'package:observatory/src/elements/instance_ref.dart';
import 'package:observatory/src/elements/helpers/any_ref.dart';
import 'package:observatory/src/elements/helpers/rendering_scheduler.dart';
import 'package:observatory/src/elements/helpers/tag.dart';
import 'package:observatory/utils.dart';
class TopRetainingInstancesElement extends CustomElement implements Renderable {
static const tag = const Tag<TopRetainingInstancesElement>(
'top-retainig-instances',
dependencies: const [CurlyBlockElement.tag, InstanceRefElement.tag]);
RenderingScheduler<TopRetainingInstancesElement> _r;
Stream<RenderedEvent<TopRetainingInstancesElement>> get onRendered =>
_r.onRendered;
M.IsolateRef _isolate;
M.ClassRef _cls;
M.TopRetainingInstancesRepository _topRetainingInstances;
M.ObjectRepository _objects;
Iterable<M.RetainingObject> _topRetaining;
bool _expanded = false;
M.IsolateRef get isolate => _isolate;
M.ClassRef get cls => _cls;
factory TopRetainingInstancesElement(
M.IsolateRef isolate,
M.ClassRef cls,
M.TopRetainingInstancesRepository topRetainingInstances,
M.ObjectRepository objects,
{RenderingQueue queue}) {
assert(isolate != null);
assert(cls != null);
assert(topRetainingInstances != null);
assert(objects != null);
TopRetainingInstancesElement e = new TopRetainingInstancesElement.created();
e._r =
new RenderingScheduler<TopRetainingInstancesElement>(e, queue: queue);
e._isolate = isolate;
e._cls = cls;
e._topRetainingInstances = topRetainingInstances;
e._objects = objects;
return e;
}
TopRetainingInstancesElement.created() : super.created(tag);
@override
void attached() {
super.attached();
_r.enable();
}
@override
void detached() {
super.detached();
children = <Element>[];
_r.disable(notify: true);
}
void render() {
children = <Element>[
(new CurlyBlockElement(expanded: _expanded, queue: _r.queue)
..content = <Element>[
new DivElement()
..classes = ['memberList']
..children = _createContent()
]
..onToggle.listen((e) async {
_expanded = e.control.expanded;
if (_expanded) {
e.control.disabled = true;
await _refresh();
e.control.disabled = false;
}
}))
.element
];
}
Future _refresh() async {
_topRetaining = null;
_topRetaining = await _topRetainingInstances.get(_isolate, _cls);
_r.dirty();
}
List<Element> _createContent() {
if (_topRetaining == null) {
return [new SpanElement()..text = 'Loading...'];
}
return _topRetaining
.map<Element>((r) => new DivElement()
..classes = ['memberItem']
..children = <Element>[
new DivElement()
..classes = ['memberName']
..text = '${Utils.formatSize(r.retainedSize)} ',
new DivElement()
..classes = ['memberValue']
..children = <Element>[
anyRef(_isolate, r.object, _objects, queue: _r.queue)
]
])
.toList();
}
}

View file

@ -4,171 +4,38 @@
part of heap_snapshot;
// TODO(observatory): The two levels of interface (SnapshotGraph + HeapSnapshot)
// probably aren't providing any value after the removal of class-based
// reference merging that only happen in the second layer. Consider flattening.
class HeapSnapshot implements M.HeapSnapshot {
ObjectGraph graph;
SnapshotGraph graph;
DateTime timestamp;
int get objects => graph.vertexCount;
int get references => graph.edgeCount;
int get size => graph.internalSize + graph.externalSize;
HeapSnapshotDominatorNode dominatorTree;
int get size => graph.shallowSize + graph.externalSize;
HeapSnapshotMergedDominatorNode mergedDominatorTree;
List<MergedVertex> classReferences;
List<HeapSnapshotOwnershipClass> ownershipClasses;
List<SnapshotClass> classes;
SnapshotObject get root => graph.root;
List<ByteData> chunks;
static Future sleep([Duration duration = const Duration(microseconds: 0)]) {
final Completer completer = new Completer();
new Timer(duration, () => completer.complete());
return completer.future;
}
Stream<List> loadProgress(S.Isolate isolate, S.RawHeapSnapshot raw) {
final progress = new StreamController<List>.broadcast();
progress.add(['', 0.0]);
graph = new ObjectGraph(raw.chunks, raw.count);
var signal = (String description, double p) {
progress.add([description, p]);
return sleep();
};
Stream<String> loadProgress(S.Isolate isolate, List<ByteData> chunks) {
final progress = new StreamController<String>.broadcast();
progress.add('Loading...');
this.chunks = chunks;
graph = new SnapshotGraph(chunks);
(() async {
timestamp = new DateTime.now();
final stream = graph.process();
stream.listen((status) {
status[1] /= 2.0;
progress.add(status);
});
await stream.last;
dominatorTree = new HeapSnapshotDominatorNode(isolate, graph.root);
mergedDominatorTree =
new HeapSnapshotMergedDominatorNode(isolate, graph.mergedRoot);
classReferences = await buildMergedVertices(isolate, graph, signal);
ownershipClasses = buildOwnershipClasses(isolate, graph);
new HeapSnapshotMergedDominatorNode(isolate, graph.mergedRoot, null);
classes = graph.classes.toList();
progress.close();
}());
return progress.stream;
}
Future<List<MergedVertex>> buildMergedVertices(
S.Isolate isolate, ObjectGraph graph, signal) async {
final cidToMergedVertex = <int, MergedVertex>{};
int count = 0;
final Stopwatch watch = new Stopwatch();
watch.start();
var needToUpdate = () {
count++;
if (((count % 256) == 0) && (watch.elapsedMilliseconds > 16)) {
watch.reset();
return true;
}
return false;
};
final length = graph.vertices.length;
for (final vertex in graph.vertices) {
if (vertex.vmCid == 0) {
continue;
}
if (needToUpdate()) {
await signal('', count * 50.0 / length + 50);
}
final cid = vertex.vmCid;
MergedVertex source = cidToMergedVertex[cid];
if (source == null) {
cidToMergedVertex[cid] = source = new MergedVertex(isolate, cid);
}
source.instances++;
source.shallowSize += vertex.shallowSize ?? 0;
for (final vertex2 in vertex.successors) {
if (vertex2.vmCid == 0) {
continue;
}
final cid2 = vertex2.vmCid;
MergedEdge edge = source.outgoingEdges[cid2];
if (edge == null) {
MergedVertex target = cidToMergedVertex[cid2];
if (target == null) {
cidToMergedVertex[cid2] = target = new MergedVertex(isolate, cid2);
}
edge = new MergedEdge(source, target);
source.outgoingEdges[cid2] = edge;
target.incomingEdges.add(edge);
}
edge.count++;
// An over-estimate if there are multiple references to the same object.
edge.shallowSize += vertex2.shallowSize ?? 0;
}
}
return cidToMergedVertex.values.toList();
}
buildOwnershipClasses(S.Isolate isolate, ObjectGraph graph) {
var numCids = graph.numCids;
var classes = new List<HeapSnapshotOwnershipClass>();
for (var cid = 0; cid < numCids; cid++) {
var size = graph.getOwnedByCid(cid);
if (size != 0) {
classes.add(new HeapSnapshotOwnershipClass(cid, isolate, size));
}
}
return classes;
}
List<Future<S.ServiceObject>> getMostRetained(S.Isolate isolate,
{int classId, int limit}) {
List<Future<S.ServiceObject>> result = <Future<S.ServiceObject>>[];
for (ObjectVertex v
in graph.getMostRetained(classId: classId, limit: limit)) {
result.add(
isolate.getObjectByAddress(v.address).then((S.ServiceObject obj) {
if (obj is S.HeapObject) {
obj.retainedSize = v.retainedSize;
} else {
print("${obj.runtimeType} should be a HeapObject");
}
return obj;
}));
}
return result;
}
}
class HeapSnapshotDominatorNode implements M.HeapSnapshotDominatorNode {
final ObjectVertex v;
final S.Isolate isolate;
S.ServiceObject _preloaded;
bool get isStack => v.isStack;
Future<S.ServiceObject> get object {
if (_preloaded != null) {
return new Future.value(_preloaded);
} else {
return isolate.getObjectByAddress(v.address).then((S.ServiceObject obj) {
return _preloaded = obj;
});
}
}
Iterable<HeapSnapshotDominatorNode> _children;
Iterable<HeapSnapshotDominatorNode> get children {
if (_children != null) {
return _children;
} else {
return _children =
new List.unmodifiable(v.dominatorTreeChildren().map((v) {
return new HeapSnapshotDominatorNode(isolate, v);
}));
}
}
int get retainedSize => v.retainedSize;
int get shallowSize => v.shallowSize;
int get externalSize => v.externalSize;
HeapSnapshotDominatorNode(S.Isolate isolate, ObjectVertex vertex)
: isolate = isolate,
v = vertex;
}
class HeapSnapshotMergedDominatorNode
@ -176,11 +43,10 @@ class HeapSnapshotMergedDominatorNode
final MergedObjectVertex v;
final S.Isolate isolate;
bool get isStack => v.isStack;
SnapshotClass get klass => v.klass;
Future<S.HeapObject> get klass {
return new Future.value(isolate.getClassByCid(v.vmCid));
}
final _parent;
HeapSnapshotMergedDominatorNode get parent => _parent ?? this;
Iterable<HeapSnapshotMergedDominatorNode> _children;
Iterable<HeapSnapshotMergedDominatorNode> get children {
@ -189,104 +55,22 @@ class HeapSnapshotMergedDominatorNode
} else {
return _children =
new List.unmodifiable(v.dominatorTreeChildren().map((v) {
return new HeapSnapshotMergedDominatorNode(isolate, v);
return new HeapSnapshotMergedDominatorNode(isolate, v, this);
}));
}
}
List<SnapshotObject> get objects => v.objects;
int get instanceCount => v.instanceCount;
int get retainedSize => v.retainedSize;
int get shallowSize => v.shallowSize;
int get externalSize => v.externalSize;
HeapSnapshotMergedDominatorNode(S.Isolate isolate, MergedObjectVertex vertex)
String get description => "$instanceCount instances of ${klass.name}";
HeapSnapshotMergedDominatorNode(
S.Isolate isolate, MergedObjectVertex vertex, this._parent)
: isolate = isolate,
v = vertex;
}
class MergedEdge {
final MergedVertex sourceVertex;
final MergedVertex targetVertex;
int count = 0;
int shallowSize = 0;
int retainedSize = 0;
MergedEdge(this.sourceVertex, this.targetVertex);
}
class MergedVertex implements M.HeapSnapshotClassReferences {
final int cid;
final S.Isolate isolate;
S.Class get clazz => isolate.getClassByCid(cid);
int instances = 0;
int shallowSize = 0;
int retainedSize = 0;
List<MergedEdge> incomingEdges = new List<MergedEdge>();
Map<int, MergedEdge> outgoingEdges = new Map<int, MergedEdge>();
Iterable<HeapSnapshotClassInbound> _inbounds;
Iterable<HeapSnapshotClassInbound> get inbounds {
if (_inbounds != null) {
return _inbounds;
} else {
// It is important to keep the template.
// https://github.com/dart-lang/sdk/issues/27144
return _inbounds = new List<HeapSnapshotClassInbound>.unmodifiable(
incomingEdges.map((edge) {
return new HeapSnapshotClassInbound(this, edge);
}));
}
}
Iterable<HeapSnapshotClassOutbound> _outbounds;
Iterable<HeapSnapshotClassOutbound> get outbounds {
if (_outbounds != null) {
return _outbounds;
} else {
// It is important to keep the template.
// https://github.com/dart-lang/sdk/issues/27144
return _outbounds = new List<HeapSnapshotClassOutbound>.unmodifiable(
outgoingEdges.values.map((edge) {
return new HeapSnapshotClassOutbound(this, edge);
}));
}
}
MergedVertex(this.isolate, this.cid);
}
class HeapSnapshotClassInbound implements M.HeapSnapshotClassInbound {
final MergedVertex vertex;
final MergedEdge edge;
S.Class get source => edge.sourceVertex != vertex
? edge.sourceVertex.clazz
: edge.targetVertex.clazz;
int get count => edge.count;
int get shallowSize => edge.shallowSize;
int get retainedSize => edge.retainedSize;
HeapSnapshotClassInbound(this.vertex, this.edge);
}
class HeapSnapshotClassOutbound implements M.HeapSnapshotClassOutbound {
final MergedVertex vertex;
final MergedEdge edge;
S.Class get target => edge.sourceVertex != vertex
? edge.sourceVertex.clazz
: edge.targetVertex.clazz;
int get count => edge.count;
int get shallowSize => edge.shallowSize;
int get retainedSize => edge.retainedSize;
HeapSnapshotClassOutbound(this.vertex, this.edge);
}
class HeapSnapshotOwnershipClass implements M.HeapSnapshotOwnershipClass {
final int cid;
final S.Isolate isolate;
S.Class get clazz => isolate.getClassByCid(cid);
final int size;
HeapSnapshotOwnershipClass(this.cid, this.isolate, this.size);
}

View file

@ -71,5 +71,3 @@ abstract class InstanceSet {
int get count;
Iterable<ObjectRef> get instances;
}
abstract class TopRetainedInstances {}

View file

@ -4,60 +4,19 @@
part of models;
enum HeapSnapshotRoots { user, vm }
abstract class HeapSnapshot {
DateTime get timestamp;
int get objects;
int get references;
int get size;
HeapSnapshotDominatorNode get dominatorTree;
SnapshotObject get root;
HeapSnapshotMergedDominatorNode get mergedDominatorTree;
Iterable<HeapSnapshotClassReferences> get classReferences;
Iterable<HeapSnapshotOwnershipClass> get ownershipClasses;
}
abstract class HeapSnapshotDominatorNode {
int get shallowSize;
int get retainedSize;
bool get isStack;
Future<ObjectRef> get object;
Iterable<HeapSnapshotDominatorNode> get children;
Iterable<SnapshotClass> get classes;
List<ByteData> get chunks;
}
abstract class HeapSnapshotMergedDominatorNode {
int get instanceCount;
int get shallowSize;
int get retainedSize;
bool get isStack;
Future<ObjectRef> get klass;
SnapshotClass get klass;
Iterable<HeapSnapshotMergedDominatorNode> get children;
}
abstract class HeapSnapshotClassReferences {
ClassRef get clazz;
int get instances;
int get shallowSize;
int get retainedSize;
Iterable<HeapSnapshotClassInbound> get inbounds;
Iterable<HeapSnapshotClassOutbound> get outbounds;
}
abstract class HeapSnapshotClassInbound {
ClassRef get source;
int get count;
int get shallowSize;
int get retainedSize;
}
abstract class HeapSnapshotClassOutbound {
ClassRef get target;
int get count;
int get shallowSize;
int get retainedSize;
}
abstract class HeapSnapshotOwnershipClass {
ClassRef get clazz;
int get size;
}

View file

@ -30,6 +30,5 @@ abstract class HeapSnapshotLoadingProgress {
}
abstract class HeapSnapshotRepository {
Stream<HeapSnapshotLoadingProgressEvent> get(IsolateRef isolate,
{HeapSnapshotRoots roots: HeapSnapshotRoots.vm, bool gc: false});
Stream<HeapSnapshotLoadingProgressEvent> get(IsolateRef isolate);
}

View file

@ -1,9 +0,0 @@
// Copyright (c) 2016, 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
part of models;
abstract class TopRetainingInstancesRepository {
Future<Iterable<RetainingObject>> get(IsolateRef isolate, ClassRef cls);
}

View file

@ -16,8 +16,6 @@ class HeapSnapshotLoadingProgress extends M.HeapSnapshotLoadingProgress {
Stream<HeapSnapshotLoadingProgressEvent> get onProgress => _onProgress.stream;
final S.Isolate isolate;
final M.HeapSnapshotRoots roots;
final bool gc;
M.HeapSnapshotLoadingStatus _status = M.HeapSnapshotLoadingStatus.fetching;
String _stepDescription = '';
@ -33,7 +31,7 @@ class HeapSnapshotLoadingProgress extends M.HeapSnapshotLoadingProgress {
Duration get loadingTime => _loadingTime.elapsed;
HeapSnapshot get snapshot => _snapshot;
HeapSnapshotLoadingProgress(this.isolate, this.roots, this.gc) {
HeapSnapshotLoadingProgress(this.isolate) {
_run();
}
@ -43,15 +41,12 @@ class HeapSnapshotLoadingProgress extends M.HeapSnapshotLoadingProgress {
_status = M.HeapSnapshotLoadingStatus.fetching;
_triggerOnProgress();
await isolate.getClassRefs();
final stream = isolate.fetchHeapSnapshot(roots, gc);
final stream = isolate.fetchHeapSnapshot();
stream.listen((status) {
if (status is List) {
_progress = status[0] * 100.0 / status[1];
_stepDescription = 'Receiving snapshot chunk ${status[0] + 1}'
' of ${status[1]}...';
if (status is List && status[0] is double) {
_progress = 0.5;
_stepDescription = 'Receiving snapshot chunk ${status[0] + 1}...';
_triggerOnProgress();
}
});
@ -66,10 +61,10 @@ class HeapSnapshotLoadingProgress extends M.HeapSnapshotLoadingProgress {
HeapSnapshot snapshot = new HeapSnapshot();
Stream<List> progress = snapshot.loadProgress(isolate, response);
Stream<String> progress = snapshot.loadProgress(isolate, response);
progress.listen((value) {
_stepDescription = value[0];
_progress = value[1];
_stepDescription = value;
_progress = 0.5;
_triggerOnProgress();
});
@ -101,11 +96,9 @@ class HeapSnapshotLoadingProgress extends M.HeapSnapshotLoadingProgress {
}
class HeapSnapshotRepository implements M.HeapSnapshotRepository {
Stream<HeapSnapshotLoadingProgressEvent> get(M.IsolateRef i,
{M.HeapSnapshotRoots roots: M.HeapSnapshotRoots.vm, bool gc: false}) {
Stream<HeapSnapshotLoadingProgressEvent> get(M.IsolateRef i) {
S.Isolate isolate = i as S.Isolate;
assert(isolate != null);
assert(gc != null);
return new HeapSnapshotLoadingProgress(isolate, roots, gc).onProgress;
return new HeapSnapshotLoadingProgress(isolate).onProgress;
}
}

View file

@ -1,22 +0,0 @@
// Copyright (c) 2016, 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
part of repositories;
class TopRetainingInstancesRepository
implements M.TopRetainingInstancesRepository {
Future<Iterable<M.RetainingObject>> get(M.IsolateRef i, M.ClassRef c) async {
S.Isolate isolate = i as S.Isolate;
S.Class cls = c as S.Class;
assert(isolate != null);
assert(cls != null);
final raw =
await isolate.fetchHeapSnapshot(M.HeapSnapshotRoots.vm, true).last;
final snapshot = new HeapSnapshot();
await snapshot.loadProgress(isolate, raw).last;
return (await Future.wait(
snapshot.getMostRetained(isolate, classId: cls.vmCid, limit: 10)))
.map((object) => new S.RetainingObject(object));
}
}

View file

@ -878,7 +878,7 @@ abstract class VM extends ServiceObjectOwner implements M.VM {
await listenEventStream(kVMStream, _dispatchEventToIsolate);
await listenEventStream(kIsolateStream, _dispatchEventToIsolate);
await listenEventStream(kDebugStream, _dispatchEventToIsolate);
await listenEventStream(_kGraphStream, _dispatchEventToIsolate);
await listenEventStream(kHeapSnapshotStream, _dispatchEventToIsolate);
await listenEventStream(kServiceStream, _updateService);
} on NetworkRpcException catch (_) {
// ignore network errors here.
@ -928,7 +928,7 @@ abstract class VM extends ServiceObjectOwner implements M.VM {
static const kGCStream = 'GC';
static const kStdoutStream = 'Stdout';
static const kStderrStream = 'Stderr';
static const _kGraphStream = '_Graph';
static const kHeapSnapshotStream = 'HeapSnapshot';
static const kServiceStream = 'Service';
/// Returns a single-subscription Stream object for a VM event stream.
@ -1219,12 +1219,6 @@ class HeapSpace implements M.HeapSpace {
}
}
class RawHeapSnapshot {
final chunks;
final count;
RawHeapSnapshot(this.chunks, this.count);
}
/// State for a running isolate.
class Isolate extends ServiceObjectOwner implements M.Isolate {
static const kLoggingStream = 'Logging';
@ -1355,20 +1349,6 @@ class Isolate extends ServiceObjectOwner implements M.Isolate {
return invokeRpc('_getPersistentHandles', {});
}
Future<List<Class>> getClassRefs() async {
ServiceMap classList = await invokeRpc('getClassList', {});
assert(classList.type == 'ClassList');
var classRefs = <Class>[];
for (var cls in classList['classes']) {
// Skip over non-class classes.
if (cls is Class) {
_classesByCid[cls.vmCid] = cls;
classRefs.add(cls);
}
}
return classRefs;
}
/// Given the class list, loads each class.
Future<List<Class>> _loadClasses(ServiceMap classList) {
assert(classList.type == 'ClassList');
@ -1376,7 +1356,6 @@ class Isolate extends ServiceObjectOwner implements M.Isolate {
for (var cls in classList['classes']) {
// Skip over non-class classes.
if (cls is Class) {
_classesByCid[cls.vmCid] = cls;
futureClasses.add(cls.load().then<Class>((_) => cls));
}
}
@ -1401,8 +1380,6 @@ class Isolate extends ServiceObjectOwner implements M.Isolate {
return new Future.value(objectClass);
}
Class getClassByCid(int cid) => _classesByCid[cid];
ServiceObject getFromMap(Map map) {
if (map == null) {
return null;
@ -1467,7 +1444,6 @@ class Isolate extends ServiceObjectOwner implements M.Isolate {
Class objectClass;
final rootClasses = <Class>[];
Map<int, Class> _classesByCid = new Map<int, Class>();
Library rootLibrary;
List<Library> libraries = <Library>[];
@ -1508,43 +1484,30 @@ class Isolate extends ServiceObjectOwner implements M.Isolate {
}
// Occasionally these actually arrive out of order.
var chunkIndex = event.chunkIndex;
var chunkCount = event.chunkCount;
if (_chunksInProgress == null) {
_chunksInProgress = new List(chunkCount);
_chunksInProgress = new List();
}
_chunksInProgress[chunkIndex] = event.data;
_snapshotFetch.add([chunkIndex, chunkCount]);
for (var i = 0; i < chunkCount; i++) {
if (_chunksInProgress[i] == null) return;
_chunksInProgress.add(event.data);
_snapshotFetch.add([_chunksInProgress.length, _chunksInProgress.length]);
if (!event.lastChunk) {
return;
}
var loadedChunks = _chunksInProgress;
_chunksInProgress = null;
if (_snapshotFetch != null) {
_snapshotFetch.add(new RawHeapSnapshot(loadedChunks, event.nodeCount));
_snapshotFetch.add(loadedChunks);
_snapshotFetch.close();
}
}
static String _rootsToString(M.HeapSnapshotRoots roots) {
switch (roots) {
case M.HeapSnapshotRoots.user:
return "User";
case M.HeapSnapshotRoots.vm:
return "VM";
}
return null;
}
Stream fetchHeapSnapshot(M.HeapSnapshotRoots roots, bool collectGarbage) {
Stream fetchHeapSnapshot() {
if (_snapshotFetch == null || _snapshotFetch.isClosed) {
_snapshotFetch = new StreamController.broadcast();
// isolate.vm.streamListen('_Graph');
isolate.invokeRpcNoUpgrade('_requestHeapSnapshot',
{'roots': _rootsToString(roots), 'collectGarbage': collectGarbage});
// isolate.vm.streamListen('HeapSnapshot');
isolate.invokeRpcNoUpgrade('requestHeapSnapshot', {});
}
return _snapshotFetch.stream;
}
@ -1719,7 +1682,7 @@ class Isolate extends ServiceObjectOwner implements M.Isolate {
_updateRunState();
break;
case ServiceEvent.kGraph:
case ServiceEvent.kHeapSnapshot:
_loadHeapSnapshot(event);
break;
@ -1915,15 +1878,6 @@ class Isolate extends ServiceObjectOwner implements M.Isolate {
return invokeRpc('getInstances', params);
}
Future<ServiceObject /*HeapObject*/ > getObjectByAddress(String address,
[bool ref = true]) {
Map params = {
'address': address,
'ref': ref,
};
return invokeRpc('_getObjectByAddress', params);
}
final Map<String, ServiceMetric> dartMetrics = <String, ServiceMetric>{};
final Map<String, ServiceMetric> nativeMetrics = <String, ServiceMetric>{};
@ -2108,7 +2062,7 @@ class ServiceEvent extends ServiceObject {
static const kBreakpointAdded = 'BreakpointAdded';
static const kBreakpointResolved = 'BreakpointResolved';
static const kBreakpointRemoved = 'BreakpointRemoved';
static const kGraph = '_Graph';
static const kHeapSnapshot = 'HeapSnapshot';
static const kGC = 'GC';
static const kInspect = 'Inspect';
static const kDebuggerSettingsUpdate = '_DebuggerSettingsUpdate';
@ -2154,7 +2108,7 @@ class ServiceEvent extends ServiceObject {
String service;
String alias;
int chunkIndex, chunkCount, nodeCount;
bool lastChunk;
bool get isPauseEvent {
return (kind == kPauseStart ||
@ -2203,15 +2157,7 @@ class ServiceEvent extends ServiceObject {
if (map['_data'] != null) {
data = map['_data'];
}
if (map['chunkIndex'] != null) {
chunkIndex = map['chunkIndex'];
}
if (map['chunkCount'] != null) {
chunkCount = map['chunkCount'];
}
if (map['nodeCount'] != null) {
nodeCount = map['nodeCount'];
}
lastChunk = map['last'] ?? false;
if (map['count'] != null) {
count = map['count'];
}
@ -2495,7 +2441,6 @@ class Class extends HeapObject implements M.Class {
SourceLocation location;
DartError error;
int vmCid;
final Allocations newSpace = new Allocations();
final Allocations oldSpace = new Allocations();
@ -2529,7 +2474,6 @@ class Class extends HeapObject implements M.Class {
}
var idPrefix = "classes/";
assert(id.startsWith(idPrefix));
vmCid = int.parse(id.substring(idPrefix.length));
if (mapIsRef) {
return;

View file

@ -93,11 +93,6 @@ observatory_sources = [
"lib/src/elements/logging_list.dart",
"lib/src/elements/megamorphiccache_ref.dart",
"lib/src/elements/megamorphiccache_view.dart",
"lib/src/elements/memory/allocations.dart",
"lib/src/elements/memory/dashboard.dart",
"lib/src/elements/memory/graph.dart",
"lib/src/elements/memory/profile.dart",
"lib/src/elements/memory/snapshot.dart",
"lib/src/elements/metric/details.dart",
"lib/src/elements/metric/graph.dart",
"lib/src/elements/metrics.dart",
@ -139,7 +134,6 @@ observatory_sources = [
"lib/src/elements/subtypetestcache_view.dart",
"lib/src/elements/timeline/dashboard.dart",
"lib/src/elements/timeline_page.dart",
"lib/src/elements/top_retaining_instances.dart",
"lib/src/elements/type_arguments_ref.dart",
"lib/src/elements/unknown_ref.dart",
"lib/src/elements/unlinkedcall_ref.dart",
@ -232,7 +226,6 @@ observatory_sources = [
"lib/src/models/repositories/subtype_test_cache.dart",
"lib/src/models/repositories/target.dart",
"lib/src/models/repositories/timeline.dart",
"lib/src/models/repositories/top_retaining_instances.dart",
"lib/src/models/repositories/type_arguments.dart",
"lib/src/models/repositories/unlinked_call.dart",
"lib/src/models/repositories/vm.dart",
@ -271,7 +264,6 @@ observatory_sources = [
"lib/src/repositories/subtype_test_cache.dart",
"lib/src/repositories/target.dart",
"lib/src/repositories/timeline.dart",
"lib/src/repositories/top_retaining_instances.dart",
"lib/src/repositories/type_arguments.dart",
"lib/src/repositories/unlinked_call.dart",
"lib/src/repositories/vm.dart",

View file

@ -1,54 +0,0 @@
// Copyright (c) 2015, 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 'package:observatory/object_graph.dart';
import 'package:unittest/unittest.dart';
dynamic confuse() {
if (true) {
return "5";
}
return 5;
}
main() {
var map = new AddressMapper(42);
expect(map.get(1, 2, 3), isNull);
expect(map.put(1, 2, 3, 4), equals(4));
expect(map.get(1, 2, 3), equals(4));
expect(map.get(2, 3, 1), isNull);
expect(map.get(3, 1, 2), isNull);
bool exceptionThrown = false;
try {
expect(exceptionThrown, isFalse);
map.put(1, 2, 3, 44);
expect(true, isFalse);
} catch (e) {
exceptionThrown = true;
}
expect(exceptionThrown, isTrue);
exceptionThrown = false;
try {
expect(exceptionThrown, isFalse);
map.put(5, 6, 7, 0);
expect(true, isFalse);
} catch (e) {
exceptionThrown = true;
}
expect(exceptionThrown, isTrue);
exceptionThrown = false;
try {
expect(exceptionThrown, isFalse);
map.put(confuse(), 6, 7, 0);
expect(true, isFalse);
} catch (e) {
exceptionThrown = true;
}
expect(exceptionThrown, isTrue);
}

View file

@ -1,146 +0,0 @@
// Copyright (c) 2015, 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.
// VMOptions=
// VMOptions=--use_compactor
// VMOptions=--use_compactor --force_evacuation
import 'package:observatory/heap_snapshot.dart';
import 'package:observatory/models.dart' as M;
import 'package:observatory/service_io.dart';
import 'package:unittest/unittest.dart';
import 'test_helper.dart';
// small example from [Lenguaer & Tarjan 1979]
class R {
var x;
var y;
var z;
}
class A {
var x;
}
class B {
var x;
var y;
var z;
}
class C {
var x;
var y;
}
class D {
var x;
}
class E {
var x;
}
class F {
var x;
}
class G {
var x;
var y;
}
class H {
var x;
var y;
}
class I {
var x;
}
class J {
var x;
}
class K {
var x;
var y;
}
class L {
var x;
}
var r;
buildGraph() {
r = new R();
var a = new A();
var b = new B();
var c = new C();
var d = new D();
var e = new E();
var f = new F();
var g = new G();
var h = new H();
var i = new I();
var j = new J();
var k = new K();
var l = new L();
r.x = a;
r.y = b;
r.z = c;
a.x = d;
b.x = a;
b.y = d;
b.z = e;
c.x = f;
c.y = g;
d.x = l;
e.x = h;
f.x = i;
g.x = i;
g.y = j;
h.x = e;
h.y = k;
i.x = k;
j.x = i;
k.x = i;
k.y = r;
l.x = h;
}
var tests = <IsolateTest>[
(Isolate isolate) async {
final Library rootLib = await isolate.rootLibrary.load();
final raw =
await isolate.fetchHeapSnapshot(M.HeapSnapshotRoots.user, false).last;
final snapshot = new HeapSnapshot();
await snapshot.loadProgress(isolate, raw).last;
node(String className) {
var cls = rootLib.classes.singleWhere((cls) => cls.name == className);
return snapshot.graph.vertices.singleWhere((v) => v.vmCid == cls.vmCid);
}
expect(node('I').dominator, equals(node('R')));
expect(node('K').dominator, equals(node('R')));
expect(node('C').dominator, equals(node('R')));
expect(node('H').dominator, equals(node('R')));
expect(node('E').dominator, equals(node('R')));
expect(node('A').dominator, equals(node('R')));
expect(node('D').dominator, equals(node('R')));
expect(node('B').dominator, equals(node('R')));
expect(node('F').dominator, equals(node('C')));
expect(node('G').dominator, equals(node('C')));
expect(node('J').dominator, equals(node('G')));
expect(node('L').dominator, equals(node('D')));
expect(node('R'), isNotNull); // The field.
},
];
main(args) => runIsolateTests(args, tests, testeeBefore: buildGraph);

View file

@ -114,30 +114,28 @@ buildGraph() {
var tests = <IsolateTest>[
(Isolate isolate) async {
final Library rootLib = await isolate.rootLibrary.load();
final raw =
await isolate.fetchHeapSnapshot(M.HeapSnapshotRoots.vm, false).last;
final raw = await isolate.fetchHeapSnapshot().last;
final snapshot = new HeapSnapshot();
await snapshot.loadProgress(isolate, raw).last;
node(String className) {
var cls = rootLib.classes.singleWhere((cls) => cls.name == className);
return snapshot.graph.vertices.singleWhere((v) => v.vmCid == cls.vmCid);
return snapshot.graph.objects
.singleWhere((v) => v.klass.name == className);
}
expect(node('I').dominator, equals(node('R')));
expect(node('K').dominator, equals(node('R')));
expect(node('C').dominator, equals(node('R')));
expect(node('H').dominator, equals(node('R')));
expect(node('E').dominator, equals(node('R')));
expect(node('A').dominator, equals(node('R')));
expect(node('D').dominator, equals(node('R')));
expect(node('B').dominator, equals(node('R')));
expect(node('I').parent, equals(node('R')));
expect(node('K').parent, equals(node('R')));
expect(node('C').parent, equals(node('R')));
expect(node('H').parent, equals(node('R')));
expect(node('E').parent, equals(node('R')));
expect(node('A').parent, equals(node('R')));
expect(node('D').parent, equals(node('R')));
expect(node('B').parent, equals(node('R')));
expect(node('F').dominator, equals(node('C')));
expect(node('G').dominator, equals(node('C')));
expect(node('J').dominator, equals(node('G')));
expect(node('L').dominator, equals(node('D')));
expect(node('F').parent, equals(node('C')));
expect(node('G').parent, equals(node('C')));
expect(node('J').parent, equals(node('G')));
expect(node('L').parent, equals(node('D')));
expect(node('R'), isNotNull); // The field.
},

View file

@ -12,7 +12,7 @@ var tests = <VMTest>[
var result = await vm.invokeRpcNoUpgrade('getVersion', {});
expect(result['type'], equals('Version'));
expect(result['major'], equals(3));
expect(result['minor'], equals(25));
expect(result['minor'], equals(26));
expect(result['_privateMajor'], equals(0));
expect(result['_privateMinor'], equals(0));
},

View file

@ -1,60 +0,0 @@
// Copyright (c) 2016, 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:developer';
import 'test_helper.dart';
import 'service_test_common.dart';
import 'package:observatory/heap_snapshot.dart';
import 'package:observatory/models.dart' as M;
import 'package:observatory/object_graph.dart';
import 'package:observatory/service_io.dart';
import 'package:unittest/unittest.dart';
int arrayLength = 1024 * 1024;
int minArraySize = arrayLength * 4;
void script() {
var stackSlot = new List(arrayLength);
debugger();
print(stackSlot); // Prevent optimizing away the stack slot.
}
Future checkForStackReferent(Isolate isolate) async {
Library corelib =
isolate.libraries.singleWhere((lib) => lib.uri == 'dart:core');
await corelib.load();
Class _List =
corelib.classes.singleWhere((cls) => cls.vmName.startsWith('_List'));
int kArrayCid = _List.vmCid;
RawHeapSnapshot raw =
await isolate.fetchHeapSnapshot(M.HeapSnapshotRoots.user, false).last;
HeapSnapshot snapshot = new HeapSnapshot();
await snapshot.loadProgress(isolate, raw).last;
ObjectGraph graph = snapshot.graph;
var root = graph.root;
var stack =
graph.root.dominatorTreeChildren().singleWhere((child) => child.isStack);
expect(stack.retainedSize, greaterThanOrEqualTo(minArraySize));
bool foundBigArray = false;
for (var stackReferent in stack.dominatorTreeChildren()) {
if (stackReferent.vmCid == kArrayCid &&
stackReferent.shallowSize >= minArraySize) {
foundBigArray = true;
}
}
}
var tests = <IsolateTest>[
hasStoppedAtBreakpoint,
checkForStackReferent,
resumeIsolate,
];
main(args) => runIsolateTests(args, tests, testeeConcurrent: script);

View file

@ -1,97 +0,0 @@
// Copyright (c) 2014, 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 'package:observatory/heap_snapshot.dart';
import 'package:observatory/models.dart' as M;
import 'package:observatory/object_graph.dart';
import 'package:observatory/service_io.dart';
import 'package:unittest/unittest.dart';
import 'test_helper.dart';
class Foo {
dynamic left;
dynamic right;
}
Foo r;
List lst;
void script() {
// Create 3 instances of Foo, with out-degrees
// 0 (for b), 1 (for a), and 2 (for staticFoo).
r = new Foo();
var a = new Foo();
var b = new Foo();
r.left = a;
r.right = b;
a.left = b;
lst = new List(2);
lst[0] = lst; // Self-loop.
// Larger than any other fixed-size list in a fresh heap.
lst[1] = new List(123456);
}
int fooId;
var tests = <IsolateTest>[
(Isolate isolate) async {
Library lib = await isolate.rootLibrary.load();
expect(lib.classes.length, equals(1));
Class fooClass = lib.classes.first;
fooId = fooClass.vmCid;
RawHeapSnapshot raw =
await isolate.fetchHeapSnapshot(M.HeapSnapshotRoots.user, false).last;
HeapSnapshot snapshot = new HeapSnapshot();
await snapshot.loadProgress(isolate, raw).last;
ObjectGraph graph = snapshot.graph;
expect(fooId, isNotNull);
Iterable<ObjectVertex> foos =
graph.vertices.where((ObjectVertex obj) => obj.vmCid == fooId);
expect(foos.length, equals(3));
expect(foos.where((obj) => obj.successors.length == 0).length, equals(1));
expect(foos.where((obj) => obj.successors.length == 1).length, equals(1));
expect(foos.where((obj) => obj.successors.length == 2).length, equals(1));
ObjectVertex bVertex =
foos.where((ObjectVertex obj) => obj.successors.length == 0).first;
ObjectVertex aVertex =
foos.where((ObjectVertex obj) => obj.successors.length == 1).first;
ObjectVertex rVertex =
foos.where((ObjectVertex obj) => obj.successors.length == 2).first;
// TODO(koda): Check actual byte sizes.
expect(aVertex.retainedSize, equals(aVertex.shallowSize));
expect(bVertex.retainedSize, equals(bVertex.shallowSize));
expect(
rVertex.retainedSize,
equals(
aVertex.shallowSize + bVertex.shallowSize + rVertex.shallowSize));
Library corelib =
isolate.libraries.singleWhere((lib) => lib.uri == 'dart:core');
await corelib.load();
Class _List =
corelib.classes.singleWhere((cls) => cls.vmName.startsWith('_List'));
int kArrayCid = _List.vmCid;
// startsWith to ignore the private mangling
List<ObjectVertex> lists = new List.from(
graph.vertices.where((ObjectVertex obj) => obj.vmCid == kArrayCid));
expect(lists.length >= 2, isTrue);
// Order by decreasing retained size.
lists.sort((u, v) => v.retainedSize - u.retainedSize);
ObjectVertex first = lists[0];
ObjectVertex second = lists[1];
// Check that the short list retains more than the long list inside.
expect(first.successors.length, equals(2 + second.successors.length));
// ... and specifically, that it retains exactly itself + the long one.
expect(first.retainedSize, equals(first.shallowSize + second.shallowSize));
},
];
main(args) => runIsolateTests(args, tests, testeeBefore: script);

View file

@ -34,35 +34,32 @@ void script() {
lst[1] = new List(1234569);
}
int fooId;
var tests = <IsolateTest>[
(Isolate isolate) async {
Library lib = await isolate.rootLibrary.load();
expect(lib.classes.length, equals(1));
Class fooClass = lib.classes.first;
fooId = fooClass.vmCid;
RawHeapSnapshot raw =
await isolate.fetchHeapSnapshot(M.HeapSnapshotRoots.vm, false).last;
var raw = await isolate.fetchHeapSnapshot().last;
HeapSnapshot snapshot = new HeapSnapshot();
await snapshot.loadProgress(isolate, raw).last;
ObjectGraph graph = snapshot.graph;
var graph = snapshot.graph;
expect(fooId, isNotNull);
Iterable<ObjectVertex> foos =
graph.vertices.where((ObjectVertex obj) => obj.vmCid == fooId);
Iterable<SnapshotObject> foos =
graph.objects.where((SnapshotObject obj) => obj.klass.name == "Foo");
expect(foos.length, equals(3));
expect(foos.where((obj) => obj.successors.length == 0).length, equals(1));
expect(foos.where((obj) => obj.successors.length == 1).length, equals(1));
expect(foos.where((obj) => obj.successors.length == 2).length, equals(1));
ObjectVertex bVertex =
foos.where((ObjectVertex obj) => obj.successors.length == 0).first;
ObjectVertex aVertex =
foos.where((ObjectVertex obj) => obj.successors.length == 1).first;
ObjectVertex rVertex =
foos.where((ObjectVertex obj) => obj.successors.length == 2).first;
SnapshotObject bVertex = foos.singleWhere((SnapshotObject obj) {
List<SnapshotObject> successors = obj.successors.toList();
return successors[0].klass.name == "Null" &&
successors[1].klass.name == "Null";
});
SnapshotObject aVertex = foos.singleWhere((SnapshotObject obj) {
List<SnapshotObject> successors = obj.successors.toList();
return successors[0].klass.name == "Foo" &&
successors[1].klass.name == "Null";
});
SnapshotObject rVertex = foos.singleWhere((SnapshotObject obj) {
List<SnapshotObject> successors = obj.successors.toList();
return successors[0].klass.name == "Foo" &&
successors[1].klass.name == "Foo";
});
// TODO(koda): Check actual byte sizes.
@ -73,23 +70,17 @@ var tests = <IsolateTest>[
equals(
aVertex.shallowSize + bVertex.shallowSize + rVertex.shallowSize));
Library corelib =
isolate.libraries.singleWhere((lib) => lib.uri == 'dart:core');
await corelib.load();
Class _List =
corelib.classes.singleWhere((cls) => cls.vmName.startsWith('_List'));
int kArrayCid = _List.vmCid;
// startsWith to ignore the private mangling
List<ObjectVertex> lists = new List.from(
graph.vertices.where((ObjectVertex obj) => obj.vmCid == kArrayCid));
List<SnapshotObject> lists = new List.from(
graph.objects.where((SnapshotObject obj) => obj.klass.name == '_List'));
expect(lists.length >= 2, isTrue);
// Order by decreasing retained size.
lists.sort((u, v) => v.retainedSize - u.retainedSize);
ObjectVertex first = lists[0];
ObjectVertex second = lists[1];
SnapshotObject first = lists[0];
expect(first.successors.length, greaterThanOrEqualTo(2));
SnapshotObject second = lists[1];
expect(second.successors.length, greaterThanOrEqualTo(1234569));
// Check that the short list retains more than the long list inside.
expect(first.successors.length, equals(2 + second.successors.length));
// ... and specifically, that it retains exactly itself + the long one.
// and specifically, that it retains exactly itself + the long one.
expect(first.retainedSize, equals(first.shallowSize + second.shallowSize));
},
];

View file

@ -1,54 +0,0 @@
// Copyright (c) 2015, 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 'package:observatory/object_graph.dart';
import 'package:unittest/unittest.dart';
import 'dart:typed_data';
testRoundTrip(final int n) {
var bytes = [];
var remaining = n;
while (remaining > 127) {
bytes.add(remaining & 127);
remaining = remaining >> 7;
}
bytes.add(remaining + 128);
print("Encoded $n as $bytes");
var typedBytes = new ByteData.view(new Uint8List.fromList(bytes).buffer);
var stream = new ReadStream([typedBytes]);
stream.readUnsigned();
expect(stream.isZero, equals(n == 0));
expect(stream.low, equals((n >> 0) & 0xFFFFFFF));
expect(stream.mid, equals((n >> 28) & 0xFFFFFFF));
expect(stream.high, equals((n >> 56) & 0xFFFFFFF));
const kMaxUint32 = (1 << 32) - 1;
if (n > kMaxUint32) {
expect(stream.clampedUint32, equals(kMaxUint32));
} else {
expect(stream.clampedUint32, equals(n));
}
expect(stream.position, equals(bytes.length));
}
main() {
const kMaxUint64 = (1 << 64) - 1;
var n = 3;
while (n < kMaxUint64) {
testRoundTrip(n);
n <<= 1;
}
n = 5;
while (n < kMaxUint64) {
testRoundTrip(n);
n <<= 1;
}
}

View file

@ -165,10 +165,6 @@ enum ClassId {
// Illegal class id.
kIllegalCid = 0,
// A sentinel used by the vm service's heap snapshots to represent references
// from the stack.
kStackCid = 1,
// The following entries describes classes for pseudo-objects in the heap
// that should never be reachable from live objects. Free list elements
// maintain the free list for old space, and forwarding corpses are used to

View file

@ -1219,6 +1219,7 @@ class Isolate : public BaseIsolate, public IntrusiveDListEntry<Isolate> {
friend class GCMarker; // VisitObjectPointers
friend class SafepointHandler;
friend class ObjectGraph; // VisitObjectPointers
friend class HeapSnapshotWriter; // VisitObjectPointers
friend class Scavenger; // VisitObjectPointers
friend class HeapIterationScope; // VisitObjectPointers
friend class ServiceIsolate;

View file

@ -7821,6 +7821,7 @@ class String : public Instance {
friend class ExternalTwoByteString;
friend class RawOneByteString;
friend class RODataSerializationCluster; // SetHash
friend class Pass2Visitor; // Stack "handle"
};
class OneByteString : public AllStatic {

View file

@ -8,14 +8,18 @@
#include "vm/dart_api_state.h"
#include "vm/growable_array.h"
#include "vm/isolate.h"
#include "vm/native_symbol.h"
#include "vm/object.h"
#include "vm/object_store.h"
#include "vm/raw_object.h"
#include "vm/raw_object_fields.h"
#include "vm/reusable_handles.h"
#include "vm/visitor.h"
namespace dart {
#if !defined(PRODUCT)
static bool IsUserClass(intptr_t cid) {
if (cid == kContextCid) return true;
if (cid == kTypeArgumentsCid) return false;
@ -514,101 +518,107 @@ intptr_t ObjectGraph::InboundReferences(Object* obj, const Array& references) {
return visitor.length();
}
static void WritePtr(RawObject* raw, WriteStream* stream) {
ASSERT(raw->IsHeapObject());
ASSERT(raw->IsOldObject());
uword addr = RawObject::ToAddr(raw);
ASSERT(Utils::IsAligned(addr, kObjectAlignment));
// Using units of kObjectAlignment makes the ids fit into Smis when parsed
// in the Dart code of the Observatory.
// TODO(koda): Use delta-encoding/back-references to further compress this.
stream->WriteUnsigned(addr / kObjectAlignment);
void HeapSnapshotWriter::EnsureAvailable(intptr_t needed) {
intptr_t available = capacity_ - size_;
if (available >= needed) {
return;
}
if (buffer_ != nullptr) {
Flush();
}
ASSERT(buffer_ == nullptr);
intptr_t chunk_size = kPreferredChunkSize;
if (chunk_size < needed + kMetadataReservation) {
chunk_size = needed + kMetadataReservation;
}
buffer_ = reinterpret_cast<uint8_t*>(malloc(chunk_size));
size_ = kMetadataReservation;
capacity_ = chunk_size;
}
class WritePointerVisitor : public ObjectPointerVisitor {
public:
WritePointerVisitor(Isolate* isolate,
WriteStream* stream,
bool only_instances)
: ObjectPointerVisitor(isolate),
stream_(stream),
only_instances_(only_instances),
count_(0) {}
virtual void VisitPointers(RawObject** first, RawObject** last) {
for (RawObject** current = first; current <= last; ++current) {
RawObject* object = *current;
if (!object->IsHeapObject() || object->InVMIsolateHeap()) {
// Ignore smis and objects in the VM isolate for now.
// TODO(koda): To track which field each pointer corresponds to,
// we'll need to encode which fields were omitted here.
continue;
void HeapSnapshotWriter::Flush(bool last) {
if (size_ == 0 && !last) {
return;
}
JSONStream js;
{
JSONObject jsobj(&js);
jsobj.AddProperty("jsonrpc", "2.0");
jsobj.AddProperty("method", "streamNotify");
{
JSONObject params(&jsobj, "params");
params.AddProperty("streamId", Service::heapsnapshot_stream.id());
{
JSONObject event(&params, "event");
event.AddProperty("type", "Event");
event.AddProperty("kind", "HeapSnapshot");
event.AddProperty("isolate", thread()->isolate());
event.AddPropertyTimeMillis("timestamp", OS::GetCurrentTimeMillis());
event.AddProperty("last", last);
}
if (only_instances_ && !IsUserClass(object->GetClassId())) {
continue;
}
WritePtr(object, stream_);
++count_;
}
}
intptr_t count() const { return count_; }
private:
WriteStream* stream_;
bool only_instances_;
intptr_t count_;
};
static void WriteHeader(RawObject* raw,
intptr_t size,
intptr_t cid,
WriteStream* stream) {
WritePtr(raw, stream);
ASSERT(Utils::IsAligned(size, kObjectAlignment));
stream->WriteUnsigned(size);
stream->WriteUnsigned(cid);
Service::SendEventWithData(Service::heapsnapshot_stream.id(), "HeapSnapshot",
kMetadataReservation, js.buffer()->buf(),
js.buffer()->length(), buffer_, size_);
buffer_ = nullptr;
size_ = 0;
capacity_ = 0;
}
class WriteGraphVisitor : public ObjectGraph::Visitor {
public:
WriteGraphVisitor(Isolate* isolate,
WriteStream* stream,
ObjectGraph::SnapshotRoots roots)
: stream_(stream),
ptr_writer_(isolate, stream, roots == ObjectGraph::kUser),
roots_(roots),
count_(0) {}
void HeapSnapshotWriter::AssignObjectId(RawObject* obj) {
// TODO(rmacnak): We're assigning IDs in iteration order, so we can use the
// compator's trick of using a finger table with bit counting to make the
// mapping much smaller.
ASSERT(obj->IsHeapObject());
thread()->heap()->SetObjectId(obj, ++object_count_);
}
virtual Direction VisitObject(ObjectGraph::StackIterator* it) {
RawObject* raw_obj = it->Get();
Thread* thread = Thread::Current();
REUSABLE_OBJECT_HANDLESCOPE(thread);
Object& obj = thread->ObjectHandle();
obj = raw_obj;
if ((roots_ == ObjectGraph::kVM) || obj.IsField() || obj.IsInstance() ||
obj.IsContext()) {
// Each object is a header + a zero-terminated list of its neighbors.
WriteHeader(raw_obj, raw_obj->HeapSize(), obj.GetClassId(), stream_);
raw_obj->VisitPointers(&ptr_writer_);
stream_->WriteUnsigned(0);
++count_;
}
return kProceed;
intptr_t HeapSnapshotWriter::GetObjectId(RawObject* obj) {
if (!obj->IsHeapObject()) {
return 0;
}
return thread()->heap()->GetObjectId(obj);
}
void HeapSnapshotWriter::ClearObjectIds() {
thread()->heap()->ResetObjectIdTable();
}
void HeapSnapshotWriter::CountReferences(intptr_t count) {
reference_count_ += count;
}
void HeapSnapshotWriter::CountExternalProperty() {
external_property_count_ += 1;
}
class Pass1Visitor : public ObjectVisitor,
public ObjectPointerVisitor,
public HandleVisitor {
public:
explicit Pass1Visitor(HeapSnapshotWriter* writer)
: ObjectVisitor(),
ObjectPointerVisitor(Isolate::Current()),
HandleVisitor(Thread::Current()),
writer_(writer) {}
void VisitObject(RawObject* obj) {
if (obj->IsPseudoObject()) return;
writer_->AssignObjectId(obj);
obj->VisitPointers(this);
}
intptr_t count() const { return count_; }
private:
WriteStream* stream_;
WritePointerVisitor ptr_writer_;
ObjectGraph::SnapshotRoots roots_;
intptr_t count_;
};
class WriteGraphExternalSizesVisitor : public HandleVisitor {
public:
WriteGraphExternalSizesVisitor(Thread* thread, WriteStream* stream)
: HandleVisitor(thread), stream_(stream) {}
void VisitPointers(RawObject** from, RawObject** to) {
intptr_t count = to - from + 1;
ASSERT(count >= 0);
writer_->CountReferences(count);
}
void VisitHandle(uword addr) {
FinalizablePersistentHandle* weak_persistent_handle =
@ -617,76 +627,389 @@ class WriteGraphExternalSizesVisitor : public HandleVisitor {
return; // Free handle.
}
WritePtr(weak_persistent_handle->raw(), stream_);
stream_->WriteUnsigned(weak_persistent_handle->external_size());
writer_->CountExternalProperty();
}
private:
WriteStream* stream_;
HeapSnapshotWriter* const writer_;
DISALLOW_COPY_AND_ASSIGN(Pass1Visitor);
};
intptr_t ObjectGraph::Serialize(WriteStream* stream,
SnapshotRoots roots,
bool collect_garbage) {
if (collect_garbage) {
isolate()->heap()->CollectAllGarbage();
enum NonReferenceDataTags {
kNoData = 0,
kNullData,
kBoolData,
kIntData,
kDoubleData,
kLatin1Data,
kUTF16Data,
kLengthData,
kNameData,
};
static const intptr_t kMaxStringElements = 128;
class Pass2Visitor : public ObjectVisitor,
public ObjectPointerVisitor,
public HandleVisitor {
public:
explicit Pass2Visitor(HeapSnapshotWriter* writer)
: ObjectVisitor(),
ObjectPointerVisitor(Isolate::Current()),
HandleVisitor(Thread::Current()),
writer_(writer) {}
void VisitObject(RawObject* obj) {
if (obj->IsPseudoObject()) return;
intptr_t cid = obj->GetClassId();
writer_->WriteUnsigned(cid);
writer_->WriteUnsigned(discount_sizes_ ? 0 : obj->HeapSize());
if (cid == kNullCid) {
writer_->WriteUnsigned(kNullData);
} else if (cid == kBoolCid) {
writer_->WriteUnsigned(kBoolData);
writer_->WriteUnsigned(static_cast<RawBool*>(obj)->ptr()->value_);
} else if (cid == kSmiCid) {
UNREACHABLE();
} else if (cid == kMintCid) {
writer_->WriteUnsigned(kIntData);
writer_->WriteSigned(static_cast<RawMint*>(obj)->ptr()->value_);
} else if (cid == kDoubleCid) {
writer_->WriteUnsigned(kDoubleData);
writer_->WriteBytes(&(static_cast<RawDouble*>(obj)->ptr()->value_),
sizeof(double));
} else if (cid == kOneByteStringCid) {
RawOneByteString* str = static_cast<RawOneByteString*>(obj);
intptr_t len = Smi::Value(str->ptr()->length_);
intptr_t trunc_len = Utils::Minimum(len, kMaxStringElements);
writer_->WriteUnsigned(kLatin1Data);
writer_->WriteUnsigned(len);
writer_->WriteUnsigned(trunc_len);
writer_->WriteBytes(&str->ptr()->data()[0], trunc_len);
} else if (cid == kExternalOneByteStringCid) {
RawExternalOneByteString* str =
static_cast<RawExternalOneByteString*>(obj);
intptr_t len = Smi::Value(str->ptr()->length_);
intptr_t trunc_len = Utils::Minimum(len, kMaxStringElements);
writer_->WriteUnsigned(kLatin1Data);
writer_->WriteUnsigned(len);
writer_->WriteUnsigned(trunc_len);
writer_->WriteBytes(&str->ptr()->external_data_[0], trunc_len);
} else if (cid == kTwoByteStringCid) {
RawTwoByteString* str = static_cast<RawTwoByteString*>(obj);
intptr_t len = Smi::Value(str->ptr()->length_);
intptr_t trunc_len = Utils::Minimum(len, kMaxStringElements);
writer_->WriteUnsigned(kUTF16Data);
writer_->WriteUnsigned(len);
writer_->WriteUnsigned(trunc_len);
writer_->WriteBytes(&str->ptr()->data()[0], trunc_len * 2);
} else if (cid == kExternalTwoByteStringCid) {
RawExternalTwoByteString* str =
static_cast<RawExternalTwoByteString*>(obj);
intptr_t len = Smi::Value(str->ptr()->length_);
intptr_t trunc_len = Utils::Minimum(len, kMaxStringElements);
writer_->WriteUnsigned(kUTF16Data);
writer_->WriteUnsigned(len);
writer_->WriteUnsigned(trunc_len);
writer_->WriteBytes(&str->ptr()->external_data_[0], trunc_len * 2);
} else if (cid == kArrayCid || cid == kImmutableArrayCid) {
writer_->WriteUnsigned(kLengthData);
writer_->WriteUnsigned(
Smi::Value(static_cast<RawArray*>(obj)->ptr()->length_));
} else if (cid == kGrowableObjectArrayCid) {
writer_->WriteUnsigned(kLengthData);
writer_->WriteUnsigned(Smi::Value(
static_cast<RawGrowableObjectArray*>(obj)->ptr()->length_));
} else if (cid == kLinkedHashMapCid) {
writer_->WriteUnsigned(kLengthData);
writer_->WriteUnsigned(
Smi::Value(static_cast<RawLinkedHashMap*>(obj)->ptr()->used_data_));
} else if (cid == kObjectPoolCid) {
writer_->WriteUnsigned(kLengthData);
writer_->WriteUnsigned(static_cast<RawObjectPool*>(obj)->ptr()->length_);
} else if (RawObject::IsTypedDataClassId(cid)) {
writer_->WriteUnsigned(kLengthData);
writer_->WriteUnsigned(
Smi::Value(static_cast<RawTypedData*>(obj)->ptr()->length_));
} else if (RawObject::IsExternalTypedDataClassId(cid)) {
writer_->WriteUnsigned(kLengthData);
writer_->WriteUnsigned(
Smi::Value(static_cast<RawExternalTypedData*>(obj)->ptr()->length_));
} else if (cid == kFunctionCid) {
writer_->WriteUnsigned(kNameData);
ScrubAndWriteUtf8(static_cast<RawFunction*>(obj)->ptr()->name_);
} else if (cid == kCodeCid) {
RawObject* owner = static_cast<RawCode*>(obj)->ptr()->owner_;
if (owner->IsFunction()) {
writer_->WriteUnsigned(kNameData);
ScrubAndWriteUtf8(static_cast<RawFunction*>(owner)->ptr()->name_);
} else if (owner->IsClass()) {
writer_->WriteUnsigned(kNameData);
ScrubAndWriteUtf8(static_cast<RawClass*>(owner)->ptr()->name_);
} else {
writer_->WriteUnsigned(kNoData);
}
} else if (cid == kFieldCid) {
writer_->WriteUnsigned(kNameData);
ScrubAndWriteUtf8(static_cast<RawField*>(obj)->ptr()->name_);
} else if (cid == kClassCid) {
writer_->WriteUnsigned(kNameData);
ScrubAndWriteUtf8(static_cast<RawClass*>(obj)->ptr()->name_);
} else if (cid == kLibraryCid) {
writer_->WriteUnsigned(kNameData);
ScrubAndWriteUtf8(static_cast<RawLibrary*>(obj)->ptr()->url_);
} else if (cid == kScriptCid) {
writer_->WriteUnsigned(kNameData);
ScrubAndWriteUtf8(static_cast<RawScript*>(obj)->ptr()->url_);
} else {
writer_->WriteUnsigned(kNoData);
}
DoCount();
obj->VisitPointersPrecise(this);
DoWrite();
obj->VisitPointersPrecise(this);
}
// Current encoding assumes objects do not move, so promote everything to old.
isolate()->heap()->new_space()->Evacuate();
HeapIterationScope iteration_scope(Thread::Current(), true);
RawObject* kRootAddress = reinterpret_cast<RawObject*>(kHeapObjectTag);
const intptr_t kRootCid = kIllegalCid;
RawObject* kStackAddress =
reinterpret_cast<RawObject*>(kObjectAlignment + kHeapObjectTag);
void ScrubAndWriteUtf8(RawString* str) {
if (str == String::null()) {
writer_->WriteUtf8("null");
} else {
String handle;
handle = str;
char* value = handle.ToMallocCString();
writer_->ScrubAndWriteUtf8(value);
free(value);
}
}
stream->WriteUnsigned(kObjectAlignment);
stream->WriteUnsigned(kStackCid);
stream->WriteUnsigned(kFieldCid);
stream->WriteUnsigned(isolate()->class_table()->NumCids());
void set_discount_sizes(bool value) { discount_sizes_ = value; }
if (roots == kVM) {
// Write root "object".
WriteHeader(kRootAddress, 0, kRootCid, stream);
WritePointerVisitor ptr_writer(isolate(), stream, false);
isolate()->VisitObjectPointers(&ptr_writer,
void DoCount() {
writing_ = false;
counted_ = 0;
written_ = 0;
}
void DoWrite() {
writing_ = true;
writer_->WriteUnsigned(counted_);
}
void VisitPointers(RawObject** from, RawObject** to) {
if (writing_) {
for (RawObject** ptr = from; ptr <= to; ptr++) {
RawObject* target = *ptr;
written_++;
total_++;
writer_->WriteUnsigned(writer_->GetObjectId(target));
}
} else {
intptr_t count = to - from + 1;
ASSERT(count >= 0);
counted_ += count;
}
}
void VisitHandle(uword addr) {
FinalizablePersistentHandle* weak_persistent_handle =
reinterpret_cast<FinalizablePersistentHandle*>(addr);
if (!weak_persistent_handle->raw()->IsHeapObject()) {
return; // Free handle.
}
writer_->WriteUnsigned(writer_->GetObjectId(weak_persistent_handle->raw()));
writer_->WriteUnsigned(weak_persistent_handle->external_size());
// Attempt to include a native symbol name.
char* name = NativeSymbolResolver::LookupSymbolName(
reinterpret_cast<uintptr_t>(weak_persistent_handle->callback()), NULL);
writer_->WriteUtf8((name == NULL) ? "Unknown native function" : name);
if (name != NULL) {
NativeSymbolResolver::FreeSymbolName(name);
}
}
private:
HeapSnapshotWriter* const writer_;
bool writing_ = false;
intptr_t counted_ = 0;
intptr_t written_ = 0;
intptr_t total_ = 0;
bool discount_sizes_ = false;
DISALLOW_COPY_AND_ASSIGN(Pass2Visitor);
};
void HeapSnapshotWriter::Write() {
HeapIterationScope iteration(thread());
WriteBytes("dartheap", 8); // Magic value.
WriteUnsigned(0); // Flags.
WriteUtf8(isolate()->name());
Heap* H = thread()->heap();
WriteUnsigned(
(H->new_space()->UsedInWords() + H->old_space()->UsedInWords()) *
kWordSize);
WriteUnsigned(
(H->new_space()->CapacityInWords() + H->old_space()->CapacityInWords()) *
kWordSize);
WriteUnsigned(
(H->new_space()->ExternalInWords() + H->old_space()->ExternalInWords()) *
kWordSize);
{
HANDLESCOPE(thread());
ClassTable* class_table = isolate()->class_table();
class_count_ = class_table->NumCids() - 1;
Class& cls = Class::Handle();
Library& lib = Library::Handle();
String& str = String::Handle();
Array& fields = Array::Handle();
Field& field = Field::Handle();
WriteUnsigned(class_count_);
for (intptr_t cid = 1; cid <= class_count_; cid++) {
if (!class_table->HasValidClassAt(cid)) {
WriteUnsigned(0); // Flags
WriteUtf8(""); // Name
WriteUtf8(""); // Library name
WriteUtf8(""); // Library uri
WriteUtf8(""); // Reserved
WriteUnsigned(0); // Field count
} else {
cls = class_table->At(cid);
WriteUnsigned(0); // Flags
str = cls.Name();
ScrubAndWriteUtf8(const_cast<char*>(str.ToCString()));
lib = cls.library();
if (lib.IsNull()) {
WriteUtf8("");
WriteUtf8("");
} else {
str = lib.name();
ScrubAndWriteUtf8(const_cast<char*>(str.ToCString()));
str = lib.url();
ScrubAndWriteUtf8(const_cast<char*>(str.ToCString()));
}
WriteUtf8(""); // Reserved
intptr_t field_count = 0;
intptr_t min_offset = kIntptrMax;
for (intptr_t j = 0; OffsetsTable::offsets_table[j].class_id != -1;
j++) {
if (OffsetsTable::offsets_table[j].class_id == cid) {
field_count++;
intptr_t offset = OffsetsTable::offsets_table[j].offset;
min_offset = Utils::Minimum(min_offset, offset);
}
}
if (cls.is_finalized()) {
do {
fields = cls.fields();
if (!fields.IsNull()) {
for (intptr_t i = 0; i < fields.Length(); i++) {
field ^= fields.At(i);
if (field.is_instance()) {
field_count++;
}
}
}
cls = cls.SuperClass();
} while (!cls.IsNull());
cls = class_table->At(cid);
}
WriteUnsigned(field_count);
for (intptr_t j = 0; OffsetsTable::offsets_table[j].class_id != -1;
j++) {
if (OffsetsTable::offsets_table[j].class_id == cid) {
intptr_t flags = 1; // Strong.
WriteUnsigned(flags);
intptr_t offset = OffsetsTable::offsets_table[j].offset;
intptr_t index = (offset - min_offset) / kWordSize;
ASSERT(index >= 0);
WriteUnsigned(index);
WriteUtf8(OffsetsTable::offsets_table[j].field_name);
WriteUtf8(""); // Reserved
}
}
if (cls.is_finalized()) {
do {
fields = cls.fields();
if (!fields.IsNull()) {
for (intptr_t i = 0; i < fields.Length(); i++) {
field ^= fields.At(i);
if (field.is_instance()) {
intptr_t flags = 1; // Strong.
WriteUnsigned(flags);
intptr_t index = field.Offset() / kWordSize - 1;
ASSERT(index >= 0);
WriteUnsigned(index);
str = field.name();
ScrubAndWriteUtf8(const_cast<char*>(str.ToCString()));
WriteUtf8(""); // Reserved
}
}
}
cls = cls.SuperClass();
} while (!cls.IsNull());
cls = class_table->At(cid);
}
}
}
}
{
Pass1Visitor visitor(this);
// Root "object".
++object_count_;
isolate()->VisitObjectPointers(&visitor,
ValidationPolicy::kDontValidateFrames);
stream->WriteUnsigned(0);
} else {
{
// Write root "object".
WriteHeader(kRootAddress, 0, kRootCid, stream);
WritePointerVisitor ptr_writer(isolate(), stream, false);
IterateUserFields(&ptr_writer);
WritePtr(kStackAddress, stream);
stream->WriteUnsigned(0);
}
{
// Write stack "object".
WriteHeader(kStackAddress, 0, kStackCid, stream);
WritePointerVisitor ptr_writer(isolate(), stream, true);
isolate()->VisitStackPointers(&ptr_writer,
ValidationPolicy::kDontValidateFrames);
stream->WriteUnsigned(0);
}
// Heap objects.
iteration.IterateVMIsolateObjects(&visitor);
iteration.IterateObjects(&visitor);
// External properties.
isolate()->VisitWeakPersistentHandles(&visitor);
}
WriteGraphVisitor visitor(isolate(), stream, roots);
IterateObjects(&visitor);
stream->WriteUnsigned(0);
{
Pass2Visitor visitor(this);
WriteGraphExternalSizesVisitor external_visitor(Thread::Current(), stream);
isolate()->VisitWeakPersistentHandles(&external_visitor);
stream->WriteUnsigned(0);
WriteUnsigned(reference_count_);
WriteUnsigned(object_count_);
intptr_t object_count = visitor.count();
if (roots == kVM) {
object_count += 1; // root
} else {
object_count += 2; // root and stack
// Root "object".
WriteUnsigned(0); // cid
WriteUnsigned(0); // shallowSize
WriteUnsigned(kNoData);
visitor.DoCount();
isolate()->VisitObjectPointers(&visitor,
ValidationPolicy::kDontValidateFrames);
visitor.DoWrite();
isolate()->VisitObjectPointers(&visitor,
ValidationPolicy::kDontValidateFrames);
// Heap objects.
visitor.set_discount_sizes(true);
iteration.IterateVMIsolateObjects(&visitor);
visitor.set_discount_sizes(false);
iteration.IterateObjects(&visitor);
// External properties.
WriteUnsigned(external_property_count_);
isolate()->VisitWeakPersistentHandles(&visitor);
}
return object_count;
ClearObjectIds();
Flush(true);
}
#endif // !defined(PRODUCT)
} // namespace dart

View file

@ -11,10 +11,10 @@
namespace dart {
class Array;
class Isolate;
class Object;
class RawObject;
class WriteStream;
#if !defined(PRODUCT)
// Utility to traverse the object graph in an ordered fashion.
// Example uses:
@ -108,21 +108,100 @@ class ObjectGraph : public ThreadStackResource {
// be live due to references from the stack or embedder handles.
intptr_t InboundReferences(Object* obj, const Array& references);
enum SnapshotRoots { kVM, kUser };
// Write the isolate's object graph to 'stream'. Smis and nulls are omitted.
// Returns the number of nodes in the stream, including the root.
// If collect_garbage is false, the graph will include weakly-reachable
// objects.
// TODO(koda): Document format; support streaming/chunking.
intptr_t Serialize(WriteStream* stream,
SnapshotRoots roots,
bool collect_garbage);
private:
DISALLOW_IMPLICIT_CONSTRUCTORS(ObjectGraph);
};
// Generates a dump of the heap, whose format is described in
// runtime/vm/service/heap_snapshot.md.
class HeapSnapshotWriter : public ThreadStackResource {
public:
explicit HeapSnapshotWriter(Thread* thread) : ThreadStackResource(thread) {}
void WriteSigned(int64_t value) {
EnsureAvailable((sizeof(value) * kBitsPerByte) / 7 + 1);
bool is_last_part = false;
while (!is_last_part) {
uint8_t part = value & 0x7F;
value >>= 7;
if ((value == 0 && (part & 0x40) == 0) ||
(value == static_cast<intptr_t>(-1) && (part & 0x40) != 0)) {
is_last_part = true;
} else {
part |= 0x80;
}
buffer_[size_++] = part;
}
}
void WriteUnsigned(uintptr_t value) {
EnsureAvailable((sizeof(value) * kBitsPerByte) / 7 + 1);
bool is_last_part = false;
while (!is_last_part) {
uint8_t part = value & 0x7F;
value >>= 7;
if (value == 0) {
is_last_part = true;
} else {
part |= 0x80;
}
buffer_[size_++] = part;
}
}
void WriteBytes(const void* bytes, intptr_t len) {
EnsureAvailable(len);
memmove(&buffer_[size_], bytes, len);
size_ += len;
}
void ScrubAndWriteUtf8(char* value) {
intptr_t len = strlen(value);
for (intptr_t i = len - 1; i >= 0; i--) {
if (value[i] == '@') {
value[i] = '\0';
}
}
WriteUtf8(value);
}
void WriteUtf8(const char* value) {
intptr_t len = strlen(value);
WriteUnsigned(len);
WriteBytes(value, len);
}
void AssignObjectId(RawObject* obj);
intptr_t GetObjectId(RawObject* obj);
void ClearObjectIds();
void CountReferences(intptr_t count);
void CountExternalProperty();
void Write();
private:
static const intptr_t kMetadataReservation = 512;
static const intptr_t kPreferredChunkSize = MB;
void EnsureAvailable(intptr_t needed);
void Flush(bool last = false);
uint8_t* buffer_ = nullptr;
intptr_t size_ = 0;
intptr_t capacity_ = 0;
intptr_t class_count_ = 0;
intptr_t object_count_ = 0;
intptr_t reference_count_ = 0;
intptr_t external_property_count_ = 0;
DISALLOW_COPY_AND_ASSIGN(HeapSnapshotWriter);
};
#endif // !defined(PRODUCT)
} // namespace dart
#endif // RUNTIME_VM_OBJECT_GRAPH_H_

View file

@ -8,6 +8,8 @@
namespace dart {
#if !defined(PRODUCT)
class CounterVisitor : public ObjectGraph::Visitor {
public:
// Records the number of objects and total size visited, excluding 'skip'
@ -192,4 +194,6 @@ ISOLATE_UNIT_TEST_CASE(RetainingPathGCRoot) {
EXPECT_STREQ(result.gc_root_type, "local handle");
}
#endif // !defined(PRODUCT)
} // namespace dart

View file

@ -347,6 +347,27 @@ intptr_t RawObject::VisitPointersPredefined(ObjectPointerVisitor* visitor,
#endif
}
void RawObject::VisitPointersPrecise(ObjectPointerVisitor* visitor) {
intptr_t class_id = GetClassId();
if (class_id < kNumPredefinedCids) {
VisitPointersPredefined(visitor, class_id);
return;
}
// N.B.: Not using the heap size!
uword next_field_offset = visitor->isolate()
->GetClassForHeapWalkAt(class_id)
->ptr()
->next_field_offset_in_words_
<< kWordSizeLog2;
ASSERT(next_field_offset > 0);
uword obj_addr = RawObject::ToAddr(this);
uword from = obj_addr + sizeof(RawObject);
uword to = obj_addr + next_field_offset - kWordSize;
visitor->VisitPointers(reinterpret_cast<RawObject**>(from),
reinterpret_cast<RawObject**>(to));
}
bool RawObject::FindObject(FindObjectVisitor* visitor) {
ASSERT(visitor != NULL);
return visitor->FindObject(this);

View file

@ -106,7 +106,8 @@ enum class MemoryOrder {
friend class object##SerializationCluster; \
friend class object##DeserializationCluster; \
friend class Serializer; \
friend class Deserializer;
friend class Deserializer; \
friend class Pass2Visitor;
// RawObject is the base class of all raw objects; even though it carries the
// tags_ field not all raw objects are allocated in the heap and thus cannot
@ -444,6 +445,10 @@ class RawObject {
return instance_size;
}
// This variant ensures that we do not visit the extra slot created from
// rounding up instance sizes up to the allocation unit.
void VisitPointersPrecise(ObjectPointerVisitor* visitor);
static RawObject* FromAddr(uword addr) {
// We expect the untagged address here.
ASSERT((addr & kSmiTagMask) != kHeapObjectTag);

View file

@ -6,9 +6,9 @@
namespace dart {
#if defined(DART_PRECOMPILER)
#if defined(DART_PRECOMPILER) || !defined(DART_PRODUCT)
#define RAW_CLASSES_AND_FIELDS(F) \
#define COMMON_CLASSES_AND_FIELDS(F) \
F(Class, name_) \
F(Class, user_name_) \
F(Class, functions_) \
@ -55,7 +55,6 @@ namespace dart {
F(Field, type_) \
F(Field, guarded_list_length_) \
F(Field, dependent_code_) \
F(Field, type_test_cache_) \
F(Field, initializer_function_) \
F(Script, url_) \
F(Script, resolved_url_) \
@ -205,6 +204,18 @@ namespace dart {
F(TypedDataView, typed_data_) \
F(TypedDataView, offset_in_bytes_)
#define AOT_CLASSES_AND_FIELDS(F)
#define JIT_CLASSES_AND_FIELDS(F) \
F(Code, active_instructions_) \
F(Code, deopt_info_array_) \
F(Code, static_calls_target_table_) \
F(ICData, receivers_static_type_) \
F(Function, bytecode_) \
F(Function, unoptimized_code_) \
F(Field, type_test_cache_) \
F(Field, saved_initial_value_)
OffsetsTable::OffsetsTable(Zone* zone) : cached_offsets_(zone) {
for (intptr_t i = 0; offsets_table[i].class_id != -1; ++i) {
OffsetsTableEntry entry = offsets_table[i];
@ -222,7 +233,12 @@ const char* OffsetsTable::FieldNameForOffset(intptr_t class_id,
// clang-format off
OffsetsTable::OffsetsTableEntry OffsetsTable::offsets_table[] = {
RAW_CLASSES_AND_FIELDS(DEFINE_OFFSETS_TABLE_ENTRY)
COMMON_CLASSES_AND_FIELDS(DEFINE_OFFSETS_TABLE_ENTRY)
#if defined(DART_PRECOMPILED_RUNTIME)
AOT_CLASSES_AND_FIELDS(DEFINE_OFFSETS_TABLE_ENTRY)
#else
JIT_CLASSES_AND_FIELDS(DEFINE_OFFSETS_TABLE_ENTRY)
#endif
{-1, nullptr, -1}
};
// clang-format on

View file

@ -19,7 +19,7 @@
namespace dart {
#if defined(DART_PRECOMPILER)
#if defined(DART_PRECOMPILER) || !defined(DART_PRODUCT)
class OffsetsTable : public ZoneAllocated {
public:
@ -29,7 +29,6 @@ class OffsetsTable : public ZoneAllocated {
// Otherwise, the returned string is allocated in global static memory.
const char* FieldNameForOffset(intptr_t cid, intptr_t offset);
private:
struct OffsetsTableEntry {
const intptr_t class_id;
const char* field_name;
@ -38,6 +37,7 @@ class OffsetsTable : public ZoneAllocated {
static OffsetsTableEntry offsets_table[];
private:
struct IntAndIntToStringMapTraits {
typedef std::pair<intptr_t, intptr_t> Key;
typedef const char* Value;

View file

@ -120,7 +120,7 @@ StreamInfo Service::isolate_stream("Isolate");
StreamInfo Service::debug_stream("Debug");
StreamInfo Service::gc_stream("GC");
StreamInfo Service::echo_stream("_Echo");
StreamInfo Service::graph_stream("_Graph");
StreamInfo Service::heapsnapshot_stream("HeapSnapshot");
StreamInfo Service::logging_stream("Logging");
StreamInfo Service::extension_stream("Extension");
StreamInfo Service::timeline_stream("Timeline");
@ -131,7 +131,7 @@ intptr_t Service::dart_library_kernel_len_ = 0;
static StreamInfo* streams_[] = {
&Service::vm_stream, &Service::isolate_stream,
&Service::debug_stream, &Service::gc_stream,
&Service::echo_stream, &Service::graph_stream,
&Service::echo_stream, &Service::heapsnapshot_stream,
&Service::logging_stream, &Service::extension_stream,
&Service::timeline_stream};
@ -213,14 +213,6 @@ RawObject* Service::RequestAssets() {
return object.raw();
}
static uint8_t* allocator(uint8_t* ptr, intptr_t old_size, intptr_t new_size) {
void* new_ptr = realloc(reinterpret_cast<void*>(ptr), new_size);
if (new_ptr == NULL) {
OUT_OF_MEMORY();
}
return reinterpret_cast<uint8_t*>(new_ptr);
}
static void PrintMissingParamError(JSONStream* js, const char* param) {
js->PrintError(kInvalidParams, "%s expects the '%s' parameter", js->method(),
param);
@ -972,8 +964,6 @@ void Service::SendEvent(const char* stream_id,
bool result;
{
TransitionVMToNative transition(thread);
Dart_CObject cbytes;
cbytes.type = Dart_CObject_kExternalTypedData;
cbytes.value.as_external_typed_data.type = Dart_TypedData_kUint8;
@ -994,7 +984,14 @@ void Service::SendEvent(const char* stream_id,
message.value.as_array.length = 2;
message.value.as_array.values = elements;
result = Dart_PostCObject(ServiceIsolate::Port(), &message);
ApiMessageWriter writer;
std::unique_ptr<Message> msg = writer.WriteCMessage(
&message, ServiceIsolate::Port(), Message::kNormalPriority);
if (msg == nullptr) {
result = false;
} else {
result = PortMap::PostMessage(std::move(msg));
}
}
if (!result) {
@ -1004,34 +1001,20 @@ void Service::SendEvent(const char* stream_id,
void Service::SendEventWithData(const char* stream_id,
const char* event_type,
intptr_t reservation,
const char* metadata,
intptr_t metadata_size,
const uint8_t* data,
uint8_t* data,
intptr_t data_size) {
// Bitstream: [metadata size (big-endian 64 bit)] [metadata (UTF-8)] [data]
const intptr_t total_bytes = sizeof(uint64_t) + metadata_size + data_size;
uint8_t* message = static_cast<uint8_t*>(malloc(total_bytes));
if (message == NULL) {
OUT_OF_MEMORY();
}
intptr_t offset = 0;
// Metadata size.
reinterpret_cast<uint64_t*>(message)[0] =
Utils::HostToBigEndian64(metadata_size);
offset += sizeof(uint64_t);
// Metadata.
memmove(&message[offset], metadata, metadata_size);
offset += metadata_size;
// Data.
memmove(&message[offset], data, data_size);
offset += data_size;
ASSERT(offset == total_bytes);
SendEvent(stream_id, event_type, message, total_bytes);
ASSERT(kInt32Size + metadata_size <= reservation);
// Using a SPACE creates valid JSON. Our goal here is to prevent the memory
// overhead of copying to concatenate metadata and payload together by
// over-allocating to underlying buffer before we know how long the metadata
// will be.
memset(data, ' ', reservation);
reinterpret_cast<uint32_t*>(data)[0] = reservation;
memmove(&(reinterpret_cast<uint32_t*>(data)[1]), metadata, metadata_size);
Service::SendEvent(stream_id, event_type, data, data_size);
}
static void ReportPauseOnConsole(ServiceEvent* event) {
@ -1586,9 +1569,15 @@ void Service::SendEchoEvent(Isolate* isolate, const char* text) {
}
}
}
uint8_t data[] = {0, 128, 255};
SendEventWithData(echo_stream.id(), "_Echo", js.buffer()->buf(),
js.buffer()->length(), data, sizeof(data));
intptr_t reservation = js.buffer()->length() + sizeof(int32_t);
intptr_t data_size = reservation + 3;
uint8_t* data = reinterpret_cast<uint8_t*>(malloc(data_size));
data[reservation + 0] = 0;
data[reservation + 1] = 128;
data[reservation + 2] = 255;
SendEventWithData(echo_stream.id(), "_Echo", reservation, js.buffer()->buf(),
js.buffer()->length(), data, data_size);
}
static bool TriggerEchoEvent(Thread* thread, JSONStream* js) {
@ -4073,82 +4062,20 @@ static bool GetHeapMap(Thread* thread, JSONStream* js) {
return true;
}
static const char* snapshot_roots_names[] = {
"User", "VM", NULL,
};
static ObjectGraph::SnapshotRoots snapshot_roots_values[] = {
ObjectGraph::kUser, ObjectGraph::kVM,
};
static const MethodParameter* request_heap_snapshot_params[] = {
RUNNABLE_ISOLATE_PARAMETER,
new EnumParameter("roots", false /* not required */, snapshot_roots_names),
new BoolParameter("collectGarbage", false /* not required */), NULL,
};
static bool RequestHeapSnapshot(Thread* thread, JSONStream* js) {
ObjectGraph::SnapshotRoots roots = ObjectGraph::kVM;
const char* roots_arg = js->LookupParam("roots");
if (roots_arg != NULL) {
roots = EnumMapper(roots_arg, snapshot_roots_names, snapshot_roots_values);
}
const bool collect_garbage =
BoolParameter::Parse(js->LookupParam("collectGarbage"), true);
if (Service::graph_stream.enabled()) {
Service::SendGraphEvent(thread, roots, collect_garbage);
if (Service::heapsnapshot_stream.enabled()) {
HeapSnapshotWriter writer(thread);
writer.Write();
}
// TODO(koda): Provide some id that ties this request to async response(s).
PrintSuccess(js);
return true;
}
void Service::SendGraphEvent(Thread* thread,
ObjectGraph::SnapshotRoots roots,
bool collect_garbage) {
uint8_t* buffer = NULL;
WriteStream stream(&buffer, &allocator, 1 * MB);
ObjectGraph graph(thread);
intptr_t node_count = graph.Serialize(&stream, roots, collect_garbage);
// Chrome crashes receiving a single tens-of-megabytes blob, so send the
// snapshot in megabyte-sized chunks instead.
const intptr_t kChunkSize = 1 * MB;
intptr_t num_chunks =
(stream.bytes_written() + (kChunkSize - 1)) / kChunkSize;
for (intptr_t i = 0; i < num_chunks; i++) {
JSONStream js;
{
JSONObject jsobj(&js);
jsobj.AddProperty("jsonrpc", "2.0");
jsobj.AddProperty("method", "streamNotify");
{
JSONObject params(&jsobj, "params");
params.AddProperty("streamId", graph_stream.id());
{
JSONObject event(&params, "event");
event.AddProperty("type", "Event");
event.AddProperty("kind", "_Graph");
event.AddProperty("isolate", thread->isolate());
event.AddPropertyTimeMillis("timestamp", OS::GetCurrentTimeMillis());
event.AddProperty("chunkIndex", i);
event.AddProperty("chunkCount", num_chunks);
event.AddProperty("nodeCount", node_count);
}
}
}
uint8_t* chunk_start = buffer + (i * kChunkSize);
intptr_t chunk_size = (i + 1 == num_chunks)
? stream.bytes_written() - (i * kChunkSize)
: kChunkSize;
SendEventWithData(graph_stream.id(), "_Graph", js.buffer()->buf(),
js.buffer()->length(), chunk_start, chunk_size);
}
}
void Service::SendInspectEvent(Isolate* isolate, const Object& inspectee) {
if (!Service::debug_stream.enabled()) {
return;
@ -4213,78 +4140,6 @@ void Service::SendExtensionEvent(Isolate* isolate,
Service::HandleEvent(&event);
}
class ContainsAddressVisitor : public FindObjectVisitor {
public:
explicit ContainsAddressVisitor(uword addr) : addr_(addr) {}
virtual ~ContainsAddressVisitor() {}
virtual uword filter_addr() const { return addr_; }
virtual bool FindObject(RawObject* obj) const {
if (obj->IsPseudoObject()) {
return false;
}
uword obj_begin = RawObject::ToAddr(obj);
uword obj_end = obj_begin + obj->HeapSize();
return obj_begin <= addr_ && addr_ < obj_end;
}
private:
uword addr_;
};
static const MethodParameter* get_object_by_address_params[] = {
RUNNABLE_ISOLATE_PARAMETER, NULL,
};
static RawObject* GetObjectHelper(Thread* thread, uword addr) {
HeapIterationScope iteration(thread);
Object& object = Object::Handle(thread->zone());
{
NoSafepointScope no_safepoint;
Isolate* isolate = thread->isolate();
ContainsAddressVisitor visitor(addr);
object = isolate->heap()->FindObject(&visitor);
}
if (!object.IsNull()) {
return object.raw();
}
{
NoSafepointScope no_safepoint;
ContainsAddressVisitor visitor(addr);
object = Dart::vm_isolate()->heap()->FindObject(&visitor);
}
return object.raw();
}
static bool GetObjectByAddress(Thread* thread, JSONStream* js) {
const char* addr_str = js->LookupParam("address");
if (addr_str == NULL) {
PrintMissingParamError(js, "address");
return true;
}
// Handle heap objects.
uword addr = 0;
if (!GetUnsignedIntegerId(addr_str, &addr, 16)) {
PrintInvalidParamError(js, "address");
return true;
}
bool ref = js->HasParam("ref") && js->ParamIs("ref", "true");
const Object& obj =
Object::Handle(thread->zone(), GetObjectHelper(thread, addr));
if (obj.IsNull()) {
PrintSentinel(js, kFreeSentinel);
} else {
obj.PrintJSON(js, ref);
}
return true;
}
static const MethodParameter* get_persistent_handles_params[] = {
ISOLATE_PARAMETER, NULL,
};
@ -4969,8 +4824,6 @@ static const ServiceMethodDescriptor service_methods_[] = {
get_object_params },
{ "_getObjectStore", GetObjectStore,
get_object_store_params },
{ "_getObjectByAddress", GetObjectByAddress,
get_object_by_address_params },
{ "_getPersistentHandles", GetPersistentHandles,
get_persistent_handles_params, },
{ "_getPorts", GetPorts,
@ -5019,7 +4872,7 @@ static const ServiceMethodDescriptor service_methods_[] = {
reload_sources_params },
{ "resume", Resume,
resume_params },
{ "_requestHeapSnapshot", RequestHeapSnapshot,
{ "requestHeapSnapshot", RequestHeapSnapshot,
request_heap_snapshot_params },
{ "_evaluateCompiledExpression", EvaluateCompiledExpression,
evaluate_compiled_expression_params },

View file

@ -15,7 +15,7 @@
namespace dart {
#define SERVICE_PROTOCOL_MAJOR_VERSION 3
#define SERVICE_PROTOCOL_MINOR_VERSION 25
#define SERVICE_PROTOCOL_MINOR_VERSION 26
class Array;
class EmbedderServiceHandler;
@ -121,9 +121,6 @@ class Service : public AllStatic {
Dart_GetVMServiceAssetsArchive get_service_assets);
static void SendEchoEvent(Isolate* isolate, const char* text);
static void SendGraphEvent(Thread* thread,
ObjectGraph::SnapshotRoots roots,
bool collect_garbage);
static void SendInspectEvent(Isolate* isolate, const Object& inspectee);
static void SendEmbedderEvent(Isolate* isolate,
@ -146,6 +143,15 @@ class Service : public AllStatic {
const String& event_kind,
const String& event_data);
// Takes ownership of 'data'.
static void SendEventWithData(const char* stream_id,
const char* event_type,
intptr_t reservation,
const char* metadata,
intptr_t metadata_size,
uint8_t* data,
intptr_t data_size);
static void PostError(const String& method_name,
const Array& parameter_keys,
const Array& parameter_values,
@ -159,7 +165,7 @@ class Service : public AllStatic {
static StreamInfo debug_stream;
static StreamInfo gc_stream;
static StreamInfo echo_stream;
static StreamInfo graph_stream;
static StreamInfo heapsnapshot_stream;
static StreamInfo logging_stream;
static StreamInfo extension_stream;
static StreamInfo timeline_stream;
@ -212,20 +218,13 @@ class Service : public AllStatic {
const Array& parameter_values,
const Instance& reply_port,
const Instance& id);
// Takes ownership of 'bytes'.
static void SendEvent(const char* stream_id,
const char* event_type,
uint8_t* bytes,
intptr_t bytes_length);
// Does not take ownership of 'data'.
static void SendEventWithData(const char* stream_id,
const char* event_type,
const char* metadata,
intptr_t metadata_size,
const uint8_t* data,
intptr_t data_size);
static void PostEvent(Isolate* isolate,
const char* stream_id,
const char* kind,

View file

@ -0,0 +1,183 @@
# Dart VM Service Heap Snapshot
A snapshot of a heap in the Dart VM that allows for arbitrary analysis of memory usage.
## Object IDs
An object id is a 1-origin index into SnapshotGraph.objects.
Object id 0 is a sentinel value. It indicates target of a reference has been omitted from the snapshot.
The root object is has object id 1.
This notion of id is unrelated to the id used by the rest of the VM service and cannot, for example, be used as a argument to getObject.
## Class IDs
A class id is a 1-origin index into SnapshotGraph.classes.
Class id 0 is a sentinel value.
This notion of id is unrelated to the id used by the rest of the VM service and cannot, for example, be used as a argument to getObject.
## Graph properties
The graph may contain unreachable objects.
The graph may references without a corresponding SnapshotField.
## Format
```
type SnapshotGraph {
magic : uint8[8] = "dartheap",
flags : uleb128,
name : Utf8String,
// The sum of shallow sizes of all objects in this graph.
shallowSize : uleb128,
// The amount of memory reserved for this heap. At least as large as |shallowSize|.
capacity : uleb128,
// The sum of sizes of all external properites in this graph.
externalSize : uleb128,
classCount : uleb128,
classes : SnapshotClass[classCount],
// At least as big as the sum of SnapshotObject.referenceCount.
referenceCount : uleb128,
objectCount : uleb128,
objects : SnapshotObject[objectCount],
externalPropertyCount : uleb128,
externalProperties : SnapshotExternalProperty[externalPropertyCount],
}
```
```
type SnapshotClass {
// Reserved.
flags : uleb128,
// The simple (not qualified) name of the class.
name : Utf8String,
// The name of the class's library.
libraryName : Utf8String,
// The URI of the class's library.
libraryUri : Utf8String,
reserved : Utf8String,
fieldCount : uleb128,
fields : SnapshotField[fieldCount],
}
```
```
type SnapshotField {
// Reserved.
flags : uleb128,
// A 0-origin index into SnapshotObject.references.
index : uleb128,
name : Utf8String,
reserved : Utf8String,
}
```
```
type SnapshotObject {
// A 1-origin index into SnapshotGraph.classes.
classId : uleb128,
// The space used by this object in bytes.
shallowSize : uleb128,
data : NonReferenceData,
referenceCount : uleb128,
// A list of 1-origin indicies into SnapshotGraph.objects
references : uleb128[referenceCount],
}
```
```
type NonReferenceData {
tag : uleb128,
}
type NoData extends NonReferenceData {
tag : uleb128 = 0,
}
type NullData extends NonReferenceData {
tag : uleb128 = 1,
}
type BoolData extends NonReferenceData {
tag : uleb128 = 2,
value : uleb128,
}
type IntegerData extends NonReferenceData {
tag : uleb128 = 3,
value : uleb128,
}
type DoubleData extends NonReferenceData {
tag: uleb128 = 4,
value: float64,
}
type Latin1StringData extends NonReferenceData {
tag : uleb128 = 5,
length : uleb128,
truncatedLength : uleb128,
codeUnits : uint8[truncatedLength],
}
type Utf16StringData extends NonReferenceData {
tag : uleb128 = 6,
length : uleb128,
truncatedLength : uleb128,
codeUnits : uint16[truncatedLength],
}
// Indicates the object is variable length, such as a known implementation
// of List, Map or Set.
type LengthData extends NonReferenceData {
tag : uleb128 = 7,
length : uleb128,
}
// Indicates the object has some name, such as Function, Field, Class or Library.
type NameData extends NonReferenceData {
tag : uleb128 = 7,
name : Utf8String,
}
```
```
type SnapshotExternalProperty {
// A 1-origin index into SnapshotGraph.objects.
object : uleb128,
externalSize : uleb128,
name : Utf8String,
}
```
```
type Utf8String {
length : uleb128,
codeUnits : uint8[length],
}
```

View file

@ -1,4 +1,4 @@
# Dart VM Service Protocol 3.25
# Dart VM Service Protocol 3.26
> Please post feedback to the [observatory-discuss group][discuss-list]
@ -261,7 +261,20 @@ _streamNotify_, and the _params_ will have _streamId_ and _event_ properties:
It is considered a _backwards compatible_ change to add a new type of event to an existing stream.
Clients should be written to handle this gracefully.
### Binary Events
Some events are associated with bulk binary data. These events are delivered as
WebSocket binary frames instead of text frames. A binary event's metadata
should be interpreted as UTF-8 encoded JSON, with the same properties as
described above for ordinary events.
```
type BinaryEvent {
dataOffset : uint32,
metadata : uint8[dataOffset-4],
data : uint8[],
}
```
## Types
@ -1003,6 +1016,20 @@ Note that breakpoints are added and removed on a per-isolate basis.
See [Success](#success).
### requestHeapSnapshot
```
Success requestHeapSnapshot(string isolateId)
```
Requests a dump of the Dart heap of the given isolate.
This method immediately returns success. The VM will then begin delivering
binary events on the `HeapSnapshot` event stream. The binary data in these
events, when concatenated together, conforms to the SnapshotGraph type. The
splitting of the SnapshotGraph into events can happen at any byte offset,
including the middle of scalar fields.
### resume
```
@ -1160,6 +1187,7 @@ Extension | Extension
Timeline | TimelineEvents
Logging | Logging
Service | ServiceRegistered, ServiceUnregistered
HeapSnapshot | HeapSnapshot
Additionally, some embedders provide the _Stdout_ and _Stderr_
streams. These streams allow the client to subscribe to writes to
@ -3294,5 +3322,6 @@ version | comments
3.23 | Add `VMFlagUpdate` event kind to the `VM` stream.
3.24 | Add `operatingSystem` property to `VM` object.
3.25 | Add 'getInboundReferences', 'getRetainingPath' RPCs, and 'InboundReferences', 'InboundReference', 'RetainingPath', and 'RetainingObject' objects.
3.26 | Add 'requestHeapSnapshot'.
[discuss-list]: https://groups.google.com/a/dartlang.org/forum/#!forum/observatory-discuss

File diff suppressed because it is too large Load diff

View file

@ -2,6 +2,8 @@
// 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.
#include <memory>
#include "platform/globals.h"
#include "include/dart_tools_api.h"
@ -560,68 +562,6 @@ ISOLATE_UNIT_TEST_CASE(Service_PersistentHandles) {
EXPECT_NOTSUBSTRING("\"externalSize\":\"128\"", handler.msg());
}
ISOLATE_UNIT_TEST_CASE(Service_Address) {
const char* kScript =
"var port;\n" // Set to our mock port by C++.
"\n"
"main() {\n"
"}";
Isolate* isolate = thread->isolate();
isolate->set_is_runnable(true);
Dart_Handle lib;
{
TransitionVMToNative transition(thread);
lib = TestCase::LoadTestScript(kScript, NULL);
EXPECT_VALID(lib);
}
// Build a mock message handler and wrap it in a dart port.
ServiceTestMessageHandler handler;
Dart_Port port_id = PortMap::CreatePort(&handler);
Dart_Handle port = Api::NewHandle(thread, SendPort::New(port_id));
{
TransitionVMToNative transition(thread);
EXPECT_VALID(port);
EXPECT_VALID(Dart_SetField(lib, NewString("port"), port));
}
const String& str = String::Handle(String::New("foobar", Heap::kOld));
Array& service_msg = Array::Handle();
// Note: If we ever introduce old space compaction, this test might fail.
uword start_addr = RawObject::ToAddr(str.raw());
// Expect to find 'str', also from internal addresses.
for (int offset = 0; offset < kObjectAlignment; ++offset) {
uword addr = start_addr + offset;
char buf[1024];
bool ref = offset % 2 == 0;
Utils::SNPrint(buf, sizeof(buf),
(ref ? "[0, port, '0', '_getObjectByAddress', "
"['address', 'ref'], ['%" Px "', 'true']]"
: "[0, port, '0', '_getObjectByAddress', "
"['address'], ['%" Px "']]"),
addr);
service_msg = Eval(lib, buf);
HandleIsolateMessage(isolate, service_msg);
EXPECT_EQ(MessageHandler::kOK, handler.HandleNextMessage());
EXPECT_SUBSTRING(ref ? "\"type\":\"@Instance\"" : "\"type\":\"Instance\"",
handler.msg());
EXPECT_SUBSTRING("\"kind\":\"String\"", handler.msg());
EXPECT_SUBSTRING("foobar", handler.msg());
}
// Expect null when no object is found.
service_msg = Eval(lib,
"[0, port, '0', '_getObjectByAddress', "
"['address'], ['7']]");
HandleIsolateMessage(isolate, service_msg);
EXPECT_EQ(MessageHandler::kOK, handler.HandleNextMessage());
// TODO(turnidge): Should this be a ServiceException instead?
EXPECT_SUBSTRING(
"{\"type\":\"Sentinel\",\"kind\":\"Free\","
"\"valueAsString\":\"<free>\"",
handler.msg());
}
static bool alpha_callback(const char* name,
const char** option_keys,
const char** option_values,