Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(664)

Unified Diff: runtime/observatory/lib/src/elements/heap_snapshot.dart

Issue 1124153006: Heap snapshot visualizations (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: sync Created 5 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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
new file mode 100644
index 0000000000000000000000000000000000000000..4285692af0a07f57dbc5603864a0d380ab206c5e
--- /dev/null
+++ b/runtime/observatory/lib/src/elements/heap_snapshot.dart
@@ -0,0 +1,463 @@
+// 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.
+
+library heap_snapshot_element;
+
+import 'dart:async';
+import 'dart:html';
+import 'observatory_element.dart';
+import 'heap_profile.dart';
+import 'package:observatory/app.dart';
+import 'package:observatory/service.dart';
+import 'package:observatory/elements.dart';
+import 'package:observatory/object_graph.dart';
+import 'package:logging/logging.dart';
+import 'package:polymer/polymer.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 = "Retaining x of y.";
+ 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");
+ String hexAddress = vertex.address.toRadixString(16);
+ snapshot.isolate.getObjectByAddress(hexAddress).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);
+ }
+}
+
+
+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;
+ }
+
+ 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;
+
+ MergedVertexRow(TableTree tree,
+ TableTreeRow parent,
+ this.isolate,
+ this.vertex)
+ : super(tree, parent) {
+ }
+
+ bool hasChildren() {
+ return vertex.outgoingEdges.length > 0 ||
+ vertex.incomingEdges.length > 0;
+ }
+
+ 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));
+ }
+
+
+ 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);
+
+ ClassRefElement 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) {
+ }
+
+ bool hasChildren() {
+ return outgoing
+ ? vertex.outgoingEdges.length > 0
+ : vertex.incomingEdges.length > 0;
+ }
+
+ 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);
+ }
+ }
+ }
+ }
+
+ 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);
+ }
+}
+
+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) {
+ }
+
+ 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 {
+ ClassRefElement 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);
+ }
+}
+
+
+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 {
+ 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);
+ }
+
+ source.instances++;
+ source.shallowSize += (vertex.shallowSize == null ? 0 : vertex.shallowSize);
+
+ 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);
+ }
+ edge.count++;
+ // An over-estimate if there are multiple references to the same object.
+ edge.shallowSize += vertex2.shallowSize == null ? 0 : vertex2.shallowSize;
+ }
+ }
+
+ 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();
+ } else {
+ snapshot = isolate.latestSnapshot;
+ state = 'Loaded';
+ _update();
+ }
+ }
+
+ Future refresh() {
+ return _getHeapSnapshot();
+ }
+
+ Future _getHeapSnapshot() {
+ var completer = new Completer();
+ state = "Requesting heap snapshot...";
+ isolate.getClassRefs();
+ var stopwatch = new Stopwatch()..start();
+ isolate.fetchHeapSnapshot().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();
+ } else {
+ throw "Unexpected event $event";
+ }
+ });
+ return completer.future;
+ }
+
+ void _update() {
+ if (snapshot == null) {
+ return;
+ }
+
+ switch(analysisSelector) {
+ case 'DominatorTree':
+ _buildDominatorTree();
+ break;
+ case 'MergeByClass':
+ _buildMergedVertices();
+ break;
+ }
+ }
+
+ 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;
+ }
+
+ 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);
+ });
+ });
+ }
+}
« no previous file with comments | « runtime/observatory/lib/src/elements/heap_profile.dart ('k') | runtime/observatory/lib/src/elements/heap_snapshot.html » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698