| Index: runtime/observatory/lib/src/elements/heap_snapshot.dart
|
| diff --git a/runtime/observatory/lib/src/elements/heap_snapshot.dart b/runtime/observatory/lib/src/elements/heap_snapshot.dart
|
| index fbfb9eb4c56647f572b0ca2a604d2843db96c996..0b1ffcfdc2c236bff55ae2417b9645451a73136e 100644
|
| --- a/runtime/observatory/lib/src/elements/heap_snapshot.dart
|
| +++ b/runtime/observatory/lib/src/elements/heap_snapshot.dart
|
| @@ -2,469 +2,416 @@
|
| // 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.
|
|
|
| -library heap_snapshot_element;
|
| +// Copyright (c) 2013, 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 'class_ref_wrapper.dart';
|
| -import 'observatory_element.dart';
|
| -import 'package:observatory/app.dart';
|
| -import 'package:observatory/service.dart';
|
| -import 'package:observatory/elements.dart';
|
| -import 'package:observatory/object_graph.dart';
|
| -import 'package:polymer/polymer.dart';
|
| -import 'package:logging/logging.dart';
|
| -
|
| -class DominatorTreeRow extends TableTreeRow {
|
| - final ObjectVertex vertex;
|
| - final HeapSnapshot snapshot;
|
| -
|
| - var _domTreeChildren;
|
| - get domTreeChildren {
|
| - if (_domTreeChildren == null) {
|
| - _domTreeChildren = vertex.dominatorTreeChildren();
|
| - }
|
| - return _domTreeChildren;
|
| - }
|
| -
|
| - DominatorTreeRow(TableTree tree,
|
| - TableTreeRow parent,
|
| - this.vertex,
|
| - this.snapshot)
|
| - : super(tree, parent) {
|
| - }
|
| -
|
| - bool hasChildren() {
|
| - return domTreeChildren.length > 0;
|
| - }
|
| -
|
| - static const int kMaxChildren = 100;
|
| - static const int kMinRetainedSize = 4096;
|
| -
|
| - void onShow() {
|
| - super.onShow();
|
| - if (children.length == 0) {
|
| - domTreeChildren.sort((a, b) => b.retainedSize - a.retainedSize);
|
| - int includedChildren = 0;
|
| - for (var childVertex in domTreeChildren) {
|
| - if (childVertex.retainedSize >= kMinRetainedSize) {
|
| - if (++includedChildren <= kMaxChildren) {
|
| - var row = new DominatorTreeRow(tree, this, childVertex, snapshot);
|
| - children.add(row);
|
| - }
|
| - }
|
| - }
|
| - }
|
| -
|
| - var firstColumn = flexColumns[0];
|
| - firstColumn.style.justifyContent = 'flex-start';
|
| - firstColumn.style.position = 'relative';
|
| - firstColumn.style.alignItems = 'center';
|
| - firstColumn.style.setProperty('overflow-x', 'hidden');
|
| -
|
| - var percentRetained = vertex.retainedSize / snapshot.graph.size;
|
| - var percentNode = new SpanElement();
|
| - percentNode.text = Utils.formatPercentNormalized(percentRetained);
|
| - percentNode.style.minWidth = '5em';
|
| - percentNode.style.textAlign = 'right';
|
| - percentNode.title = "Percent of heap being retained";
|
| - percentNode.style.display = 'inline-block';
|
| - firstColumn.children.add(percentNode);
|
| -
|
| - var gap = new SpanElement();
|
| - gap.style.minWidth = '1em';
|
| - gap.style.display = 'inline-block';
|
| - firstColumn.children.add(gap);
|
| -
|
| - AnyServiceRefElement objectRef = new Element.tag("any-service-ref");
|
| - snapshot.isolate.getObjectByAddress(vertex.address).then((obj) {
|
| - objectRef.ref = obj;
|
| - });
|
| - objectRef.style.alignSelf = 'center';
|
| - firstColumn.children.add(objectRef);
|
| -
|
| - var secondColumn = flexColumns[1];
|
| - secondColumn.style.justifyContent = 'flex-end';
|
| - secondColumn.style.position = 'relative';
|
| - secondColumn.style.alignItems = 'center';
|
| - secondColumn.style.paddingRight = '0.5em';
|
| - secondColumn.text = Utils.formatSize(vertex.retainedSize);
|
| - }
|
| +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/src/elements/nav/bar.dart';
|
| +import 'package:observatory/src/elements/nav/isolate_menu.dart';
|
| +import 'package:observatory/src/elements/nav/menu.dart';
|
| +import 'package:observatory/src/elements/nav/notify.dart';
|
| +import 'package:observatory/src/elements/nav/refresh.dart';
|
| +import 'package:observatory/src/elements/nav/top_menu.dart';
|
| +import 'package:observatory/src/elements/nav/vm_menu.dart';
|
| +import 'package:observatory/utils.dart';
|
| +
|
| +enum HeapSnapshotTreeMode {
|
| + dominatorTree,
|
| + groupByClass
|
| }
|
|
|
| -
|
| -class MergedVerticesRow extends TableTreeRow {
|
| - final Isolate isolate;
|
| - final List<MergedVertex> mergedVertices;
|
| -
|
| - MergedVerticesRow(TableTree tree,
|
| - TableTreeRow parent,
|
| - this.isolate,
|
| - this.mergedVertices)
|
| - : super(tree, parent) {
|
| - }
|
| -
|
| - bool hasChildren() {
|
| - return mergedVertices.length > 0;
|
| +class HeapSnapshotElement extends HtmlElement implements Renderable {
|
| + static const tag = const Tag<HeapSnapshotElement>('heap-snapshot',
|
| + dependencies: const [
|
| + ClassRefElement.tag,
|
| + NavBarElement.tag,
|
| + NavTopMenuElement.tag,
|
| + NavVMMenuElement.tag,
|
| + NavIsolateMenuElement.tag,
|
| + NavMenuElement.tag,
|
| + NavRefreshElement.tag,
|
| + NavNotifyElement.tag,
|
| + VirtualTreeElement.tag,
|
| + ]);
|
| +
|
| + RenderingScheduler<HeapSnapshotElement> _r;
|
| +
|
| + Stream<RenderedEvent<HeapSnapshotElement>> get onRendered => _r.onRendered;
|
| +
|
| + M.VM _vm;
|
| + M.IsolateRef _isolate;
|
| + M.EventRepository _events;
|
| + M.NotificationRepository _notifications;
|
| + M.HeapSnapshotRepository _snapshots;
|
| + M.InstanceRepository _instances;
|
| + M.HeapSnapshot _snapshot;
|
| + Stream<M.HeapSnapshotLoadingProgressEvent> _progressStream;
|
| + M.HeapSnapshotLoadingProgress _progress;
|
| + HeapSnapshotTreeMode _mode = HeapSnapshotTreeMode.dominatorTree;
|
| +
|
| +
|
| + M.IsolateRef get isolate => _isolate;
|
| + M.NotificationRepository get notifications => _notifications;
|
| + M.HeapSnapshotRepository get profiles => _snapshots;
|
| + M.VMRef get vm => _vm;
|
| +
|
| + factory HeapSnapshotElement(M.VM vm, M.IsolateRef isolate,
|
| + M.EventRepository events,
|
| + M.NotificationRepository notifications,
|
| + M.HeapSnapshotRepository snapshots,
|
| + M.InstanceRepository instances,
|
| + {RenderingQueue queue}) {
|
| + assert(vm != null);
|
| + assert(isolate != null);
|
| + assert(events != null);
|
| + assert(notifications != null);
|
| + assert(snapshots != null);
|
| + assert(instances != null);
|
| + HeapSnapshotElement e = document.createElement(tag.name);
|
| + e._r = new RenderingScheduler(e, queue: queue);
|
| + e._vm = vm;
|
| + e._isolate = isolate;
|
| + e._events = events;
|
| + e._notifications = notifications;
|
| + e._snapshots = snapshots;
|
| + e._instances = instances;
|
| + return e;
|
| }
|
|
|
| - void onShow() {
|
| - super.onShow();
|
| -
|
| - if (children.length == 0) {
|
| - mergedVertices.sort((a, b) => b.shallowSize - a.shallowSize);
|
| - for (var mergedVertex in mergedVertices) {
|
| - if (mergedVertex.instances > 0) {
|
| - var row = new MergedVertexRow(tree, this, isolate, mergedVertex);
|
| - children.add(row);
|
| - }
|
| - }
|
| - }
|
| - }
|
| -}
|
| -
|
| -class MergedVertexRow extends TableTreeRow {
|
| - final Isolate isolate;
|
| - final MergedVertex vertex;
|
| + HeapSnapshotElement.created() : super.created();
|
|
|
| - MergedVertexRow(TableTree tree,
|
| - TableTreeRow parent,
|
| - this.isolate,
|
| - this.vertex)
|
| - : super(tree, parent) {
|
| + @override
|
| + attached() {
|
| + super.attached();
|
| + _r.enable();
|
| + _refresh();
|
| }
|
|
|
| - bool hasChildren() {
|
| - return vertex.outgoingEdges.length > 0 ||
|
| - vertex.incomingEdges.length > 0;
|
| + @override
|
| + detached() {
|
| + super.detached();
|
| + _r.disable(notify: true);
|
| + children = [];
|
| }
|
|
|
| - void onShow() {
|
| - super.onShow();
|
| - if (children.length == 0) {
|
| - children.add(new MergedEdgesRow(tree, this, isolate, vertex, true));
|
| - children.add(new MergedEdgesRow(tree, this, isolate, vertex, false));
|
| + void render() {
|
| + final content = [
|
| + new NavBarElement(queue: _r.queue)
|
| + ..children = [
|
| + new NavTopMenuElement(queue: _r.queue),
|
| + new NavVMMenuElement(_vm, _events, queue: _r.queue),
|
| + new NavIsolateMenuElement(_isolate, _events, queue: _r.queue),
|
| + new NavMenuElement('heap snapshot', link: Uris.profiler(_isolate),
|
| + last: true, queue: _r.queue),
|
| + new NavRefreshElement(queue: _r.queue)
|
| + ..disabled = M.isHeapSnapshotProgressRunning(_progress?.status)
|
| + ..onRefresh.listen((e) {
|
| + _refresh();
|
| + }),
|
| + new NavNotifyElement(_notifications, queue: _r.queue)
|
| + ],
|
| + ];
|
| + if (_progress == null) {
|
| + children = content;
|
| + return;
|
| }
|
| -
|
| -
|
| - var firstColumn = flexColumns[0];
|
| - firstColumn.style.justifyContent = 'flex-start';
|
| - firstColumn.style.position = 'relative';
|
| - firstColumn.style.alignItems = 'center';
|
| -
|
| - var percentNode = new SpanElement();
|
| - percentNode.text = "${vertex.instances} instances of";
|
| - percentNode.style.minWidth = '5em';
|
| - percentNode.style.textAlign = 'right';
|
| - firstColumn.children.add(percentNode);
|
| -
|
| - var gap = new SpanElement();
|
| - gap.style.minWidth = '1em';
|
| - gap.style.display = 'inline-block';
|
| - firstColumn.children.add(gap);
|
| -
|
| - ClassRefElementWrapper classRef = new Element.tag("class-ref");
|
| - classRef.ref = isolate.getClassByCid(vertex.cid);
|
| - classRef.style.alignSelf = 'center';
|
| - firstColumn.children.add(classRef);
|
| -
|
| - var secondColumn = flexColumns[1];
|
| - secondColumn.style.justifyContent = 'flex-end';
|
| - secondColumn.style.position = 'relative';
|
| - secondColumn.style.alignItems = 'center';
|
| - secondColumn.style.paddingRight = '0.5em';
|
| - secondColumn.text = Utils.formatSize(vertex.shallowSize);
|
| + switch (_progress.status) {
|
| + case M.HeapSnapshotLoadingStatus.fetching :
|
| + content.addAll(_createStatusMessage('Fetching snapshot from VM...',
|
| + description: _progress.stepDescription,
|
| + progress: _progress.progress));
|
| + break;
|
| + case M.HeapSnapshotLoadingStatus.loading :
|
| + content.addAll(_createStatusMessage('Loading snapshot...',
|
| + description: _progress.stepDescription,
|
| + progress: _progress.progress));
|
| + break;
|
| + case M.HeapSnapshotLoadingStatus.loaded:
|
| + content.addAll(_createReport());
|
| + break;
|
| + }
|
| + children = content;
|
| }
|
| -}
|
|
|
| -class MergedEdgesRow extends TableTreeRow {
|
| - final Isolate isolate;
|
| - final MergedVertex vertex;
|
| - final bool outgoing;
|
| -
|
| - MergedEdgesRow(TableTree tree,
|
| - TableTreeRow parent,
|
| - this.isolate,
|
| - this.vertex,
|
| - this.outgoing)
|
| - : super(tree, parent) {
|
| + Future _refresh() async {
|
| + _progress = null;
|
| + _progressStream = _snapshots.get(isolate);
|
| + _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();
|
| + }
|
| }
|
|
|
| - bool hasChildren() {
|
| - return outgoing
|
| - ? vertex.outgoingEdges.length > 0
|
| - : vertex.incomingEdges.length > 0;
|
| + static List<Element> _createStatusMessage(String message,
|
| + {String description: '', double progress: 0.0}) {
|
| + return [
|
| + new DivElement()..classes = ['content-centered-big']
|
| + ..children = [
|
| + new DivElement()..classes = ['statusBox', 'shadow', 'center']
|
| + ..children = [
|
| + 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'
|
| + ]
|
| + ]
|
| + ];
|
| }
|
|
|
| - void onShow() {
|
| - super.onShow();
|
| - if (children.length == 0) {
|
| - if (outgoing) {
|
| - var outgoingEdges = vertex.outgoingEdges.values.toList();
|
| - outgoingEdges.sort((a, b) => b.shallowSize - a.shallowSize);
|
| - for (var edge in outgoingEdges) {
|
| - if (edge.count > 0) {
|
| - var row = new MergedEdgeRow(tree, this, isolate, edge, true);
|
| - children.add(row);
|
| - }
|
| - }
|
| - } else {
|
| - vertex.incomingEdges.sort((a, b) => b.shallowSize - a.shallowSize);
|
| - for (var edge in vertex.incomingEdges) {
|
| - if (edge.count > 0) {
|
| - var row = new MergedEdgeRow(tree, this, isolate, edge, false);
|
| - children.add(row);
|
| - }
|
| - }
|
| - }
|
| + VirtualTreeElement _tree;
|
| +
|
| + List<Element> _createReport() {
|
| + var report = [
|
| + new DivElement()..classes = ['content-centered-big']
|
| + ..children = [
|
| + new DivElement()..classes = ['memberList']
|
| + ..children = [
|
| + new DivElement()..classes = ['memberItem']
|
| + ..children = [
|
| + new DivElement()..classes = ['memberName']
|
| + ..text = 'Refreshed ',
|
| + new DivElement()..classes = ['memberName']
|
| + ..text = Utils.formatDateTime(_snapshot.timestamp)
|
| + ],
|
| + new DivElement()..classes = ['memberItem']
|
| + ..children = [
|
| + new DivElement()..classes = ['memberName']
|
| + ..text = 'Objects ',
|
| + new DivElement()..classes = ['memberName']
|
| + ..text = '${_snapshot.objects}'
|
| + ],
|
| + new DivElement()..classes = ['memberItem']
|
| + ..children = [
|
| + new DivElement()..classes = ['memberName']
|
| + ..text = 'References ',
|
| + new DivElement()..classes = ['memberName']
|
| + ..text = '${_snapshot.references}'
|
| + ],
|
| + new DivElement()..classes = ['memberItem']
|
| + ..children = [
|
| + new DivElement()..classes = ['memberName']
|
| + ..text = 'Size ',
|
| + new DivElement()..classes = ['memberName']
|
| + ..text = Utils.formatSize(_snapshot.size)
|
| + ],
|
| + new DivElement()..classes = ['memberItem']
|
| + ..children = [
|
| + new DivElement()..classes = ['memberName']
|
| + ..text = 'Analysis ',
|
| + new DivElement()..classes = ['memberName']
|
| + ..children = _createModeSelect()
|
| + ]
|
| + ]
|
| + ],
|
| + ];
|
| + switch (_mode) {
|
| + case HeapSnapshotTreeMode.dominatorTree:
|
| + _tree = new VirtualTreeElement(_createDominator, _updateDominator,
|
| + _getChildrenDominator,
|
| + items: _getChildrenDominator(_snapshot.dominatorTree),
|
| + queue: _r.queue);
|
| + _tree.expand(_snapshot.dominatorTree);
|
| + 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.';
|
| + report.addAll([
|
| + new DivElement()..classes = ['content-centered-big', 'explanation']
|
| + ..text = text
|
| + ..title = text,
|
| + _tree
|
| + ]);
|
| + break;
|
| + case HeapSnapshotTreeMode.groupByClass:
|
| + final items = _snapshot.classReferences.toList();
|
| + items.sort((a, b) => b.shallowSize - a.shallowSize);
|
| + _tree = new VirtualTreeElement(_createGroup, _updateGroup,
|
| + _getChildrenGroup, items: items, queue: _r.queue);
|
| + _tree.expand(_snapshot.dominatorTree);
|
| + report.add(_tree);
|
| + break;
|
| + default:
|
| + break;
|
| }
|
| -
|
| - var count = 0;
|
| - var shallowSize = 0;
|
| - var edges = outgoing ? vertex.outgoingEdges.values : vertex.incomingEdges;
|
| - for (var edge in edges) {
|
| - count += edge.count;
|
| - shallowSize += edge.shallowSize;
|
| - }
|
| -
|
| - var firstColumn = flexColumns[0];
|
| - firstColumn.style.justifyContent = 'flex-start';
|
| - firstColumn.style.position = 'relative';
|
| - firstColumn.style.alignItems = 'center';
|
| -
|
| - var countNode = new SpanElement();
|
| - countNode.text = "$count";
|
| - countNode.style.minWidth = '5em';
|
| - countNode.style.textAlign = 'right';
|
| - firstColumn.children.add(countNode);
|
| -
|
| - var gap = new SpanElement();
|
| - gap.style.minWidth = '1em';
|
| - gap.style.display = 'inline-block';
|
| - firstColumn.children.add(gap);
|
| -
|
| - var labelNode = new SpanElement();
|
| - labelNode.text = outgoing ? "Outgoing references" : "Incoming references";
|
| - firstColumn.children.add(labelNode);
|
| -
|
| - var secondColumn = flexColumns[1];
|
| - secondColumn.style.justifyContent = 'flex-end';
|
| - secondColumn.style.position = 'relative';
|
| - secondColumn.style.alignItems = 'center';
|
| - secondColumn.style.paddingRight = '0.5em';
|
| - secondColumn.text = Utils.formatSize(shallowSize);
|
| + return report;
|
| }
|
| -}
|
|
|
| -class MergedEdgeRow extends TableTreeRow {
|
| - final Isolate isolate;
|
| - final MergedEdge edge;
|
| - final bool outgoing;
|
| -
|
| - MergedEdgeRow(TableTree tree,
|
| - TableTreeRow parent,
|
| - this.isolate,
|
| - this.edge,
|
| - this.outgoing)
|
| - : super(tree, parent) {
|
| + static Element _createDominator(toggle) {
|
| + return new DivElement()
|
| + ..classes = const ['tree-item']
|
| + ..children = [
|
| + new SpanElement()..classes = const ['size']
|
| + ..title = 'retained size',
|
| + new SpanElement()..classes = const ['lines'],
|
| + new ButtonElement()..classes = const ['expander']
|
| + ..onClick.listen((_) => toggle(autoToggleSingleChildNodes: true)),
|
| + new SpanElement()..classes = const ['percentage']
|
| + ..title = 'percentage of heap being retained',
|
| + new SpanElement()..classes = const ['name']
|
| + ];
|
| }
|
|
|
| - bool hasChildren() => false;
|
| -
|
| - void onShow() {
|
| - super.onShow();
|
| -
|
| - var firstColumn = flexColumns[0];
|
| - firstColumn.style.justifyContent = 'flex-start';
|
| - firstColumn.style.position = 'relative';
|
| - firstColumn.style.alignItems = 'center';
|
| -
|
| - var percentNode = new SpanElement();
|
| - var preposition = outgoing ? "to" : "from";
|
| - percentNode.text = "${edge.count} references $preposition instances of";
|
| - percentNode.style.minWidth = '5em';
|
| - percentNode.style.textAlign = 'right';
|
| - firstColumn.children.add(percentNode);
|
| -
|
| - var gap = new SpanElement();
|
| - gap.style.minWidth = '1em';
|
| - gap.style.display = 'inline-block';
|
| - firstColumn.children.add(gap);
|
| -
|
| - MergedVertex v = outgoing ? edge.target : edge.source;
|
| - if (v.cid == 0) {
|
| - var rootName = new SpanElement();
|
| - rootName.text = '<root>';
|
| - firstColumn.children.add(rootName);
|
| - } else {
|
| - ClassRefElementWrapper classRef = new Element.tag("class-ref");
|
| - classRef.ref = isolate.getClassByCid(v.cid);
|
| - classRef.style.alignSelf = 'center';
|
| - firstColumn.children.add(classRef);
|
| - }
|
| -
|
| - var secondColumn = flexColumns[1];
|
| - secondColumn.style.justifyContent = 'flex-end';
|
| - secondColumn.style.position = 'relative';
|
| - secondColumn.style.alignItems = 'center';
|
| - secondColumn.style.paddingRight = '0.5em';
|
| - secondColumn.text = Utils.formatSize(edge.shallowSize);
|
| + static Element _createGroup(toggle) {
|
| + return new DivElement()
|
| + ..classes = const ['tree-item']
|
| + ..children = [
|
| + new SpanElement()..classes = const ['size']
|
| + ..title = 'shallow size',
|
| + new SpanElement()..classes = const ['lines'],
|
| + new ButtonElement()..classes = const ['expander']
|
| + ..onClick.listen((_) => toggle(autoToggleSingleChildNodes: true)),
|
| + new SpanElement()..classes = const ['count']
|
| + ..title = 'shallow size',
|
| + new SpanElement()..classes = const ['name']
|
| + ];
|
| }
|
| -}
|
| -
|
| -
|
| -class MergedEdge {
|
| - final MergedVertex source;
|
| - final MergedVertex target;
|
| - int count = 0;
|
| - int shallowSize = 0;
|
| - int retainedSize = 0;
|
|
|
| - MergedEdge(this.source, this.target);
|
| -}
|
| -
|
| -class MergedVertex {
|
| - final int cid;
|
| - int instances = 0;
|
| - int shallowSize = 0;
|
| - int retainedSize = 0;
|
| -
|
| - List<MergedEdge> incomingEdges = new List<MergedEdge>();
|
| - Map<int, MergedEdge> outgoingEdges = new Map<int, MergedEdge>();
|
| -
|
| - MergedVertex(this.cid);
|
| -}
|
| -
|
| -
|
| -Future<List<MergedVertex>> buildMergedVertices(ObjectGraph graph) async {
|
| - Logger.root.info("Start merge vertices");
|
| -
|
| - var cidToMergedVertex = {};
|
| -
|
| - for (var vertex in graph.vertices) {
|
| - var cid = vertex.vmCid;
|
| - MergedVertex source = cidToMergedVertex[cid];
|
| - if (source == null) {
|
| - cidToMergedVertex[cid] = source = new MergedVertex(cid);
|
| - }
|
| + static const int kMaxChildren = 100;
|
| + static const int kMinRetainedSize = 4096;
|
|
|
| - source.instances++;
|
| - source.shallowSize += (vertex.shallowSize == null ? 0 : vertex.shallowSize);
|
| + static _getChildrenDominator(M.HeapSnapshotDominatorNode node) {
|
| + final list = node.children.toList();
|
| + list.sort((a, b) => b.retainedSize - a.retainedSize);
|
| + return list.where((child) => child.retainedSize >= kMinRetainedSize)
|
| + .take(kMaxChildren);
|
| + }
|
|
|
| - for (var vertex2 in vertex.successors) {
|
| - var cid2 = vertex2.vmCid;
|
| - MergedEdge edge = source.outgoingEdges[cid2];
|
| - if (edge == null) {
|
| - MergedVertex target = cidToMergedVertex[cid2];
|
| - if (target == null) {
|
| - cidToMergedVertex[cid2] = target = new MergedVertex(cid2);
|
| - }
|
| - edge = new MergedEdge(source, target);
|
| - source.outgoingEdges[cid2] = edge;
|
| - target.incomingEdges.add(edge);
|
| + static _getChildrenGroup(item) {
|
| + if (item is M.HeapSnapshotClassReferences) {
|
| + if (item.inbounds.isNotEmpty || item.outbounds.isNotEmpty) {
|
| + return [item.inbounds, item.outbounds];
|
| }
|
| - edge.count++;
|
| - // An over-estimate if there are multiple references to the same object.
|
| - edge.shallowSize += vertex2.shallowSize == null ? 0 : vertex2.shallowSize;
|
| + } else if (item is Iterable) {
|
| + return item.toList()..sort((a, b) => b.shallowSize - a.shallowSize);
|
| }
|
| + return const [];
|
| }
|
|
|
| - Logger.root.info("End merge vertices");
|
| -
|
| - return cidToMergedVertex.values.toList();
|
| -}
|
| -
|
| -@CustomTag('heap-snapshot')
|
| -class HeapSnapshotElement extends ObservatoryElement {
|
| - @published Isolate isolate;
|
| - @observable HeapSnapshot snapshot;
|
| -
|
| - @published String state = 'Requested';
|
| - @published String analysisSelector = 'DominatorTree';
|
| -
|
| - HeapSnapshotElement.created() : super.created();
|
| -
|
| - void analysisSelectorChanged(oldValue) {
|
| - _update();
|
| - }
|
| -
|
| - void isolateChanged(oldValue) {
|
| - if (isolate == null) return;
|
| -
|
| - if (isolate.latestSnapshot == null) {
|
| - _getHeapSnapshot();
|
| + void _updateDominator(HtmlElement element, M.HeapSnapshotDominatorNode node,
|
| + int depth) {
|
| + 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 {
|
| - snapshot = isolate.latestSnapshot;
|
| - state = 'Loaded';
|
| - _update();
|
| + element.children[2].text = '';
|
| }
|
| + element.children[3].text = Utils.formatPercentNormalized(
|
| + node.retainedSize * 1.0 / _snapshot.size);
|
| + final wrapper = new SpanElement()..classes = const ['name']
|
| + ..text = 'Loading...';
|
| + element.children[4] = wrapper;
|
| + node.object.then((object) {
|
| + wrapper..text = ''
|
| + ..children = [anyRef(_isolate, object, _instances, queue: _r.queue)];
|
| + });
|
| }
|
|
|
| - Future refresh() {
|
| - return _getHeapSnapshot();
|
| - }
|
| -
|
| - Future _getHeapSnapshot() {
|
| - var completer = new Completer();
|
| - state = "Requesting heap snapshot...";
|
| - isolate.getClassRefs();
|
| -
|
| - bool collectGarbage =
|
| - app.locationManager.getBoolParameter('collectGarbage', true);
|
| -
|
| - var stopwatch = new Stopwatch()..start();
|
| - isolate.fetchHeapSnapshot(collectGarbage).listen((event) {
|
| - if (event is String) {
|
| - print("${stopwatch.elapsedMilliseconds} $event");
|
| - state = event;
|
| - } else if (event is HeapSnapshot) {
|
| - snapshot = event;
|
| - state = 'Loaded';
|
| - completer.complete(snapshot);
|
| - _update();
|
| + void _updateGroup(HtmlElement element, item, int depth) {
|
| + _updateLines(element.children[1].children, depth);
|
| + if (item is M.HeapSnapshotClassReferences) {
|
| + element.children[0].text = Utils.formatSize(item.shallowSize);
|
| + element.children[2].text = _tree.isExpanded(item) ? '▼' : '►';
|
| + element.children[3].text = '${item.instances} instances of ';
|
| + element.children[4] = new ClassRefElement(_isolate, item.clazz,
|
| + queue: _r.queue)..classes = const ['name'];
|
| + } else if (item is Iterable) {
|
| + element.children[0].text = '';
|
| + if (item.isNotEmpty) {
|
| + element.children[2].text = _tree.isExpanded(item) ? '▼' : '►';
|
| } else {
|
| - throw "Unexpected event $event";
|
| + element.children[2].text = '';
|
| }
|
| - });
|
| - return completer.future;
|
| + element.children[3].text = '';
|
| + if (item is Iterable<M.HeapSnapshotClassInbound>) {
|
| + element.children[4] = new SpanElement()..classes = const ['name']
|
| + ..text = '${item.length} Ingoing references';
|
| + } else {
|
| + element.children[4] = new SpanElement()..classes = const ['name']
|
| + ..text = '${item.length} Outgoing references';
|
| + }
|
| + } else {
|
| + element.children[0].text = '';
|
| + element.children[2].text = '';
|
| + element.children[3].text = '';
|
| + element.children[4] = new SpanElement()..classes = const ['name'];
|
| + if (item is M.HeapSnapshotClassInbound){
|
| + element.children[3].text =
|
| + '${item.count} references from instances of ';
|
| + element.children[4].children = [
|
| + new ClassRefElement(_isolate, item.source,
|
| + queue: _r.queue)
|
| + ];
|
| + } else if (item is M.HeapSnapshotClassOutbound){
|
| + element.children[3]..text = '${item.count} references to instances of ';
|
| + element.children[4].children = [
|
| + new ClassRefElement(_isolate, item.target,
|
| + queue: _r.queue)
|
| + ];
|
| + }
|
| + }
|
| }
|
|
|
| - void _update() {
|
| - if (snapshot == null) {
|
| - return;
|
| + static _updateLines(List<Element> lines, int n) {
|
| + n = Math.max(0, n);
|
| + while (lines.length > n) {
|
| + lines.removeLast();
|
| }
|
| -
|
| - switch(analysisSelector) {
|
| - case 'DominatorTree':
|
| - _buildDominatorTree();
|
| - break;
|
| - case 'MergeByClass':
|
| - _buildMergedVertices();
|
| - break;
|
| + while (lines.length < n) {
|
| + lines.add(new SpanElement());
|
| }
|
| }
|
|
|
| - void _buildDominatorTree() {
|
| - var tableBody = shadowRoot.querySelector('#treeBody');
|
| - var tree = new TableTree(tableBody, 2);
|
| - var rootRow =
|
| - new DominatorTreeRow(tree, null, snapshot.graph.root, snapshot);
|
| - tree.initialize(rootRow);
|
| - return;
|
| + static String modeToString(HeapSnapshotTreeMode mode) {
|
| + switch (mode) {
|
| + case HeapSnapshotTreeMode.dominatorTree: return 'Dominator tree';
|
| + case HeapSnapshotTreeMode.groupByClass: return 'Group by class';
|
| + }
|
| + throw new Exception('Unknown ProfileTreeMode');
|
| }
|
|
|
| - void _buildMergedVertices() {
|
| - state = 'Grouping...';
|
| - var tableBody = shadowRoot.querySelector('#treeBody');
|
| - var tree = new TableTree(tableBody, 2);
|
| - tableBody.children.clear();
|
| -
|
| - new Future.delayed(const Duration(milliseconds: 500), () {
|
| - buildMergedVertices(snapshot.graph).then((vertices) {
|
| - state = 'Loaded';
|
| - var rootRow = new MergedVerticesRow(tree, null, isolate, vertices);
|
| - tree.initialize(rootRow);
|
| - });
|
| - });
|
| + List<Element> _createModeSelect() {
|
| + var s;
|
| + return [
|
| + s = new SelectElement()..classes = ['analysis-select']
|
| + ..value = modeToString(_mode)
|
| + ..children = HeapSnapshotTreeMode.values.map((mode) {
|
| + return new OptionElement(value: modeToString(mode),
|
| + selected: _mode == mode)
|
| + ..text = modeToString(mode);
|
| + }).toList(growable: false)
|
| + ..onChange.listen((_) {
|
| + _mode = HeapSnapshotTreeMode.values[s.selectedIndex];
|
| + _r.dirty();
|
| + })
|
| + ];
|
| }
|
| }
|
|
|