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(); |
+ }) |
+ ]; |
} |
} |