Chromium Code Reviews| 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..fe5a841e8b20efb847c5d6e0c5e284cdcdd8b8c3 100644 |
| --- a/runtime/observatory/lib/src/elements/heap_snapshot.dart |
| +++ b/runtime/observatory/lib/src/elements/heap_snapshot.dart |
| @@ -2,469 +2,433 @@ |
| // 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(); |
| + HeapSnapshotElement.created() : super.created(); |
| - 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); |
| - } |
| - } |
| - } |
| + @override |
| + attached() { |
| + super.attached(); |
| + _r.enable(); |
| + _refresh(); |
| } |
| -} |
| - |
| -class MergedVertexRow extends TableTreeRow { |
| - final Isolate isolate; |
| - final MergedVertex vertex; |
| - MergedVertexRow(TableTree tree, |
| - TableTreeRow parent, |
| - this.isolate, |
| - this.vertex) |
| - : super(tree, parent) { |
| + @override |
| + detached() { |
| + super.detached(); |
| + _r.disable(notify: true); |
| + children = []; |
| } |
| - bool hasChildren() { |
| - return vertex.outgoingEdges.length > 0 || |
| - vertex.incomingEdges.length > 0; |
| + void render() { |
| + var 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; |
| + } |
| + switch (_progress.status) { |
|
rmacnak
2016/08/23 16:57:09
Because computing the dominator tree can take quit
cbernaschina
2016/08/23 17:28:28
Done.
|
| + case M.HeapSnapshotLoadingStatus.fetching : |
| + content.addAll(_createStatusMessage('Fetching profile from VM...')); |
|
rmacnak
2016/08/23 16:57:09
profile -> snapshot
cbernaschina
2016/08/23 17:28:28
Done.
|
| + break; |
| + case M.HeapSnapshotLoadingStatus.loading : |
| + content.addAll(_createStatusMessage('Loading profile...', |
| + progress: _progress.progress)); |
| + break; |
| + case M.HeapSnapshotLoadingStatus.disabled : |
| + content.addAll(_createDisabledMessage()); |
| + break; |
| + case M.HeapSnapshotLoadingStatus.loaded: |
| + content.addAll(_createReport()); |
| + break; |
| + } |
| + children = content; |
| } |
| - 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)); |
| + Future _refresh() async { |
| + _progress = null; |
| + _progressStream = _snapshots.get(isolate); |
| + _r.dirty(); |
| + _progressStream.listen((_) => _r.dirty()); |
| + _progress = (await _progressStream.first).progress; |
| + _r.dirty(); |
| + if (M.isHeapSnapshotProgressRunning(_progress.status)) { |
| + _progress = (await _progressStream.last).progress; |
| + _snapshot = _progress.snapshot; |
| + _r.dirty(); |
| } |
| - |
| - |
| - 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); |
| } |
| -} |
| - |
| -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) { |
| + static List<Element> _createStatusMessage(String message, |
| + {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()..style.background = '#0489c3' |
| + ..style.width = '$progress%' |
| + ..style.height = '15px' |
| + ..style.borderRadius = '4px' |
| + ] |
| + ] |
| + ]; |
| } |
| - bool hasChildren() { |
| - return outgoing |
| - ? vertex.outgoingEdges.length > 0 |
| - : vertex.incomingEdges.length > 0; |
| + static List<Element> _createDisabledMessage() { |
|
rmacnak
2016/08/23 16:57:09
The profile flag has no effect on the heap snapsho
cbernaschina
2016/08/23 17:28:28
Done
|
| + return [ |
| + new DivElement()..classes = ['content-centered-big'] |
| + ..children = [ |
| + new DivElement()..classes = ['statusBox' 'shadow' 'center'] |
| + ..children = [ |
| + new DivElement() |
| + ..children = [ |
| + new HeadingElement.h1() |
| + ..text = 'Profiling is disabled', |
| + new BRElement(), |
| + new DivElement() |
| + ..innerHtml = 'Perhaps the <b>profile</b> ' |
| + 'flag has been disabled for this VM.', |
| + new BRElement(), |
| + new SpanElement()..text = 'See all', |
| + new AnchorElement(href: Uris.flags())..text = 'vm flags' |
| + ] |
| + ] |
| + ] |
| + ]; |
| } |
| - 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 memory. If an object becomes garbage, ' |
|
rmacnak
2016/08/23 16:57:09
a lot of memory
cbernaschina
2016/08/23 17:28:28
Done.
|
| + '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.shallowSize); |
|
rmacnak
2016/08/23 17:01:02
retainedSize
cbernaschina
2016/08/23 17:28:28
Done.
|
| + _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(); |
| + }) |
| + ]; |
| } |
| } |