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

Unified Diff: runtime/observatory/lib/object_graph.dart

Issue 1124153006: Heap snapshot visualizations (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: 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
« no previous file with comments | « runtime/observatory/lib/elements.dart ('k') | runtime/observatory/lib/src/app/application.dart » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: runtime/observatory/lib/object_graph.dart
diff --git a/runtime/observatory/lib/object_graph.dart b/runtime/observatory/lib/object_graph.dart
index 9c9ad34aa4e1bfe018c8fca7b3d560664573df26..ac030a6659da753ed89e72d13f1b2cf7a967590a 100644
--- a/runtime/observatory/lib/object_graph.dart
+++ b/runtime/observatory/lib/object_graph.dart
@@ -4,29 +4,45 @@
library object_graph;
+import 'dart:async';
import 'dart:typed_data';
+import 'dart:collection';
koda 2015/05/21 02:05:10 Sort imports.
-import 'dominator_tree.dart';
+import 'package:logging/logging.dart';
// Port of dart::ReadStream from vm/datastream.h.
-class ReadStream {
- int _cur = 0;
- final ByteData _data;
-
- ReadStream(this._data);
-
- int get pendingBytes => _data.lengthInBytes - _cur;
-
+class _ReadStream {
+ int position = 0;
+ int _size = 0;
+ final List<ByteData> _chunks;
+
+ _ReadStream(this._chunks) {
+ int n = _chunks.length;
+ for (var i = 0; i < n; i++) {
+ var chunk = _chunks[i];
+ if (i + 1 != n) {
+ assert(chunk.lengthInBytes == (1 << 20));
+ }
+ _size += chunk.lengthInBytes;
+ }
+ }
+
+ int get pendingBytes => _size - position;
+
+ int getUint8(i) {
+ return _chunks[i >> 20].getUint8(i & 0xFFFFF);
+ }
+
int readUnsigned() {
int result = 0;
int shift = 0;
- while (_data.getUint8(_cur) <= maxUnsignedDataPerByte) {
- result |= _data.getUint8(_cur) << shift;
+ while (getUint8(position) <= maxUnsignedDataPerByte) {
+ result |= getUint8(position) << shift;
shift += dataBitsPerByte;
- ++_cur;
+ ++position;
}
- result |= (_data.getUint8(_cur) & byteMask) << shift;
- ++_cur;
+ result |= (getUint8(position) & byteMask) << shift;
+ ++position;
return result;
}
@@ -36,59 +52,137 @@ class ReadStream {
}
class ObjectVertex {
- // Never null. The isolate root has id 0.
+ // 0 represents invalid/uninitialized, 1 is the root.
final int _id;
- bool get isRoot => _id == 0;
- // TODO(koda): Include units in object graph metadata.
- int addressForWordSize(int bytesPerWord) => _id * 2 * bytesPerWord;
- // null for VM-heap objects.
- int _shallowSize;
- int get shallowSize => _shallowSize;
- int _retainedSize;
- int get retainedSize => _retainedSize;
- // null for VM-heap objects.
- int _classId;
- int get classId => _classId;
- final List<ObjectVertex> succ = new List<ObjectVertex>();
- ObjectVertex(this._id) : _retainedSize = 0;
- String toString() => '$_id,$_shallowSize,$succ';
-}
+ final ObjectGraph _graph;
-// See implementation of ObjectGraph::Serialize for format.
-class ObjectGraph {
- final Map<int, ObjectVertex> _idToVertex = new Map<int, ObjectVertex>();
+ ObjectVertex._(this._id, this._graph);
+
+ bool operator ==(other) => _id == other._id && _graph == other._graph;
+ int get hashCode => _id;
+
+ int get retainedSize => _graph._retainedSizes[_id];
+ ObjectVertex get dominator => new ObjectVertex._(_graph._doms[_id], _graph);
+
+ int get shallowSize {
+ var stream = new _ReadStream(_graph._chunks);
+ stream.position = _graph._positions[_id];
+ stream.readUnsigned(); // addr
+ return stream.readUnsigned(); // shallowSize
+ }
- ObjectVertex _asVertex(int id) {
- return _idToVertex.putIfAbsent(id, () => new ObjectVertex(id));
+ int get vmCid {
+ var stream = new _ReadStream(_graph._chunks);
+ stream.position = _graph._positions[_id];
+ stream.readUnsigned(); // addr
+ stream.readUnsigned(); // shallowSize
+ return stream.readUnsigned(); // cid
}
- void _addFrom(ReadStream stream) {
- ObjectVertex obj = _asVertex(stream.readUnsigned());
- obj._shallowSize = stream.readUnsigned();
- obj._classId = stream.readUnsigned();
- int last = stream.readUnsigned();
- while (last != 0) {
- obj.succ.add(_asVertex(last));
- last = stream.readUnsigned();
+ get successors => new _SuccessorsIterable(_graph, _id);
+
+ int get address {
+ // Note that everywhere else in this file, "address" really means an address
+ // scaled down by kObjectAlignment. They were scaled down so they would fit
+ // into Smis on client.
+ var stream = new _ReadStream(_graph._chunks);
+ stream.position = _graph._positions[_id];
+ var scaledAddr = stream.readUnsigned();
+ return scaledAddr * _graph._kObjectAlignment;
+ }
+
+ List<ObjectVertex> dominatorTreeChildren() {
+ var N = _graph._N;
+ var doms = _graph._doms;
+
+ var parentId = _id;
+ var domChildren = [];
+
+ for (var childId = 1; childId <= N; childId++) {
+ if (doms[childId] == parentId) {
+ domChildren.add(new ObjectVertex._(childId, _graph));
+ }
}
+
+ return domChildren;
+ }
+}
+
+class _SuccessorsIterable extends IterableBase<ObjectVertex> {
+ final ObjectGraph _graph;
+ final int _id;
+
+ _SuccessorsIterable(this._graph, this._id);
+
+ Iterator<ObjectVertex> get iterator => new _SuccessorsIterator(_graph, _id);
+}
+
+class _SuccessorsIterator implements Iterator<ObjectVertex> {
+ final ObjectGraph _graph;
+ _ReadStream _stream;
+
+ ObjectVertex current;
+
+ _SuccessorsIterator(this._graph, int id) {
+ _stream = new _ReadStream(this._graph._chunks);
+ _stream.position = _graph._positions[id];
+ _stream.readUnsigned(); // addr
+ _stream.readUnsigned(); // shallowSize
+ _stream.readUnsigned(); // cid
koda 2015/05/21 02:05:09 Consider having a sanity check on 'cid' here to ca
}
- ObjectGraph(ReadStream reader) {
- while (reader.pendingBytes > 0) {
- _addFrom(reader);
+ bool moveNext() {
+ while (true) {
+ var nextAddr = _stream.readUnsigned();
+ if (nextAddr == 0) return false;
+ var nextId = _graph._addrToId[nextAddr];
+ if (nextId == null) continue; // Reference to VM isolate's heap.
+ current = new ObjectVertex._(nextId, _graph);
+ return true;
}
- _computeRetainedSizes();
- _mostRetained = new List<ObjectVertex>.from(
- vertices.where((u) => !u.isRoot));
- _mostRetained.sort((u, v) => v.retainedSize - u.retainedSize);
}
+}
+
+class _VerticesIterable extends IterableBase<ObjectVertex> {
+ final ObjectGraph _graph;
+
+ _VerticesIterable(this._graph);
+
+ Iterator<ObjectVertex> get iterator => new _VerticesIterator(_graph);
+}
+
+class _VerticesIterator implements Iterator<ObjectVertex> {
+ final ObjectGraph _graph;
+
+ int _nextId = 0;
+ ObjectVertex current;
+
+ _VerticesIterator(this._graph);
+
+ bool moveNext() {
+ if (_nextId == _graph._N) return false;
+ current = new ObjectVertex._(_nextId++, _graph);
+ return true;
+ }
+}
+
+class ObjectGraph {
+ ObjectGraph(List<ByteData> chunks, int nodeCount)
+ : this._chunks = chunks
+ , this._N = nodeCount;
+
+ int get size => _size;
+ int get vertexCount => _N;
+ int get edgeCount => _E;
- Iterable<ObjectVertex> get vertices => _idToVertex.values;
- List<ObjectVertex> _mostRetained;
+ ObjectVertex get root => new ObjectVertex._(1, this);
+ Iterable<ObjectVertex> get vertices => new _VerticesIterable(this);
- ObjectVertex get root => _asVertex(0);
-
Iterable<ObjectVertex> getMostRetained({int classId, int limit}) {
+ List<ObjectVertex> _mostRetained =
+ new List<ObjectVertex>.from(vertices.where((u) => !u.isRoot));
+ _mostRetained.sort((u, v) => v.retainedSize - u.retainedSize);
+
var result = _mostRetained;
if (classId != null) {
result = result.where((u) => u.classId == classId);
@@ -98,41 +192,326 @@ class ObjectGraph {
}
return result;
}
-
- void _computeRetainedSizes() {
- // The retained size for an object is the sum of the shallow sizes of
- // all its descendants in the dominator tree (including itself).
- var d = new Dominator();
- for (ObjectVertex u in vertices) {
- if (u.shallowSize != null) {
- u._retainedSize = u.shallowSize;
- d.addEdges(u, u.succ.where((ObjectVertex v) => v.shallowSize != null));
+
+ Future process(statusReporter) async {
+ // We build futures here instead of marking the steps as async to avoid the
+ // heavy lifting being inside a tranformed method.
koda 2015/05/21 02:05:09 tranformed -> transformed
+
+ statusReporter.add("Finding node positions...");
+ await new Future(() => _buildPositions());
+
+ statusReporter.add("Finding post order...");
+ await new Future(() => _buildPostOrder());
+
+ statusReporter.add("Finding predecessors...");
+ await new Future(() => _buildPredecessors());
+
+ statusReporter.add("Finding dominators...");
+ await new Future(() => _buildDominators());
+
+ _firstPreds = null;
+ _preds = null;
+ _postOrderIndices = null;
+
+ statusReporter.add("Finding retained sizes...");
+ await new Future(() => _calculateRetainedSizes());
+
+ _postOrderOrdinals = null;
+
+ statusReporter.add("Done");
+ return this;
+ }
+
+ final List<ByteData> _chunks;
+
+ int _kObjectAlignment;
+ int _N;
+ int _E;
+ int _size;
+
+ Map<int, int> _addrToId = new Map<int, int>();
+
+ // Indexed by node id, with id 0 representing invalid/uninitialized.
+ Uint32List _positions; // Position of the node in the snapshot.
+ Uint32List _postOrderOrdinals; // post-order index -> id
+ Uint32List _postOrderIndices; // id -> post-order index
+ Uint32List _firstPreds; // Offset into preds.
+ Uint32List _preds;
+ Uint32List _doms;
+ Uint32List _retainedSizes;
+
+ void _buildPositions() {
+ var N = _N;
+ var addrToId = _addrToId;
koda 2015/05/21 02:05:10 Can you make the field final and get rid of this m
+
+ var positions = new Uint32List(N + 1);
+
+ var stream = new _ReadStream(_chunks);
+ _kObjectAlignment = stream.readUnsigned();
+
+ var id = 1;
+ while (stream.pendingBytes > 0) {
+ positions[id] = stream.position;
+ var addr = stream.readUnsigned();
+ var shallowSize = stream.readUnsigned();
+ var cid = stream.readUnsigned();
+ addrToId[addr] = id;
+
+ var succAddr = stream.readUnsigned();
+ while (succAddr != 0) {
+ succAddr = stream.readUnsigned();
+ }
+ id++;
+ }
+ assert(id == (N + 1));
+
+ var root = addrToId[0];
+ assert(root == 1);
+
+ _positions = positions;
+ }
+
+ void _buildPostOrder() {
+ var N = _N;
+ var E = 0;
+ var addrToId = _addrToId;
koda 2015/05/21 02:05:09 Ditto.
+ var positions = _positions;
+
+ var postOrderOrdinals = new Uint32List(N);
+ var postOrderIndices = new Uint32List(N + 1);
+ var stackNodes = new Uint32List(N);
+ var stackCurrentEdgePos = new Uint32List(N);
+
+ var visited = new Uint8List(N + 1);
+ var postOrderIndex = 0;
+ var stackTop = 0;
+ var root = 1;
+
+ stackNodes[0] = root;
+
+ var stream = new _ReadStream(_chunks);
+ stream.position = positions[root];
+ stream.readUnsigned(); // addr
+ stream.readUnsigned(); // shallowSize
+ stream.readUnsigned(); // cid
+ stackCurrentEdgePos[0] = stream.position;
+ visited[root] = 1;
+
+ while (stackTop >= 0) {
+ var n = stackNodes[stackTop];
+ var edgePos = stackCurrentEdgePos[stackTop];
+
+ stream.position = edgePos;
+ var childAddr = stream.readUnsigned();
+ if (childAddr != 0) {
+ stackCurrentEdgePos[stackTop] = stream.position;
+ var childId = addrToId[childAddr];
+ if (childId == null) continue; // Reference to VM isolate's heap.
+ ++E;
+ if (visited[childId] == 1) continue;
+
+ ++stackTop;
+ stackNodes[stackTop] = childId;
+
+ stream.position = positions[childId];
+ stream.readUnsigned(); // addr
+ stream.readUnsigned(); // shallowSize
+ stream.readUnsigned(); // cid
+ stackCurrentEdgePos[stackTop] = stream.position; // i.e., first edge
+ visited[childId] = 1;
+ } else {
+ // Done with all children.
+ postOrderIndices[n] = postOrderIndex;
+ postOrderOrdinals[postOrderIndex++] = n;
+ --stackTop;
}
}
- d.computeDominatorTree(root);
- // Compute all retained sizes "bottom up", starting from the leaves.
- // Keep track of number of remaining children of each vertex.
- var degree = new Map<ObjectVertex, int>();
- for (ObjectVertex u in vertices) {
- var v = d.dominator(u);
- if (v != null) {
- degree[v] = 1 + degree.putIfAbsent(v, () => 0);
+
+ assert(postOrderIndex == N);
+ assert(postOrderOrdinals[N - 1] == root);
+
+ _postOrderOrdinals = postOrderOrdinals;
+ _postOrderIndices = postOrderIndices;
+ _E = E;
+ }
+
+ void _buildPredecessors() {
+ var N = _N;
+ var E = _E;
+ var addrToId = _addrToId;
+ var positions = _positions;
+
+ // This is first filled with the predecessor counts, then reused to hold the
+ // offset to the first predecessor (see alias below).
+ // + 1 because 0 is a sentinel
+ // + 1 so the number of predecessors can be found from the difference with
+ // the next node's offset.
+ var numPreds = new Uint32List(N + 2);
+ var preds = new Uint32List(E);
+
+ // Count predecessors of each node.
+ var stream = new _ReadStream(_chunks);
+ for (var i = 1; i <= N; i++) {
+ stream.position = positions[i];
+ stream.readUnsigned(); // addr
+ stream.readUnsigned(); // shallowSize
+ stream.readUnsigned(); // cid
+ var succAddr = stream.readUnsigned();
+ while (succAddr != 0) {
+ var succId = addrToId[succAddr];
+ if (succId != null) {
+ numPreds[succId]++;
+ } else {
+ // Reference to VM isolate's heap.
+ }
+ succAddr = stream.readUnsigned();
}
}
- var leaves = new List<ObjectVertex>();
- for (ObjectVertex u in vertices) {
- if (!degree.containsKey(u)) {
- leaves.add(u);
+
+ // Assign indices into predecessors array.
+ var firstPreds = numPreds; // Alias.
+ var nextPreds = new Uint32List(N + 1);
+ var predIndex = 0;
+ for (var i = 1; i <= N; i++) {
+ var thisPredIndex = predIndex;
+ predIndex += numPreds[i];
+ firstPreds[i] = thisPredIndex;
+ nextPreds[i] = thisPredIndex;
+ }
+ assert(predIndex == E);
+ firstPreds[N + 1] = E; // Extra entry for cheap boundry detection.
koda 2015/05/21 02:05:09 boundry -> boundary
+
+ // Fill predecessors array.
+ for (var i = 1; i <= N; i++) {
+ stream.position = positions[i];
+ stream.readUnsigned(); // addr
+ stream.readUnsigned(); // shallowSize
+ stream.readUnsigned(); // cid
+ var succAddr = stream.readUnsigned();
+ while (succAddr != 0) {
+ var succId = addrToId[succAddr];
+ if (succId != null) {
+ var predIndex = nextPreds[succId]++;
+ preds[predIndex] = i;
+ } else {
+ // Reference to VM isolate's heap.
+ }
+ succAddr = stream.readUnsigned();
}
}
- while (!leaves.isEmpty) {
- var v = leaves.removeLast();
- var u = d.dominator(v);
- if (u == null) continue;
- u._retainedSize += v._retainedSize;
- if (--degree[u] == 0) {
- leaves.add(u);
+
+ _firstPreds = firstPreds;
+ _preds = preds;
+ }
+
+ // "A Simple, Fast Dominance Algorithm"
+ // Keith D. Cooper, Timothy J. Harvey, and Ken Kennedy
+ void _buildDominators() {
+ var N = _N;
+ var E = _E;
+ var addrToId = _addrToId;
+ var postOrder = _postOrderOrdinals;
+ var postOrderIndex = _postOrderIndices;
+ var firstPreds = _firstPreds;
+ var preds = _preds;
+
+ var root = 1;
+ var rootPostOrderIndex = postOrderIndex[root];
+ var domByPOI = new Uint32List(N + 1);
+
+ domByPOI[rootPostOrderIndex] = rootPostOrderIndex;
+
+ var iteration = 0;
+ var changed = true;
+ while (changed) {
+ changed = false;
+ Logger.root.info("Find dominators iteration $iteration");
+ iteration++; // dart2js heaps typically converge in 10 iterations.
+
+ // Visit the nodes, except the root, in reverse post order (top down).
+ for (var curPostOrderIndex = rootPostOrderIndex - 1;
+ curPostOrderIndex > 1;
+ --curPostOrderIndex) {
koda 2015/05/21 02:05:10 Be consistent w.r.t. pre/post-increment.
+ if (domByPOI[curPostOrderIndex] == rootPostOrderIndex)
+ continue;
+
+ var nodeOrdinal = postOrder[curPostOrderIndex];
+ var newDomIndex = 0; // 0 = undefined
+
+ // Intersect the DOM sets of the node's precedessors.
+ var beginPredIndex = firstPreds[nodeOrdinal];
+ var endPredIndex = firstPreds[nodeOrdinal + 1];
+ for (var predIndex = beginPredIndex;
+ predIndex < endPredIndex;
+ predIndex++) {
+ var predOrdinal = preds[predIndex];
+ var predPostOrderIndex = postOrderIndex[predOrdinal];
+ if (domByPOI[predPostOrderIndex] != 0) {
+ if (newDomIndex == 0) {
+ newDomIndex = predPostOrderIndex;
+ } else {
+ // Note this two finger algorithm to find the DOM intersection
+ // relies on comparing nodes by their post order index.
+ while (predPostOrderIndex != newDomIndex) {
+ while(predPostOrderIndex < newDomIndex)
+ predPostOrderIndex = domByPOI[predPostOrderIndex];
+ while (newDomIndex < predPostOrderIndex)
+ newDomIndex = domByPOI[newDomIndex];
+ }
+ }
+ if (newDomIndex == rootPostOrderIndex) {
+ break;
+ }
+ }
+ }
+ if (newDomIndex != 0 && domByPOI[curPostOrderIndex] != newDomIndex) {
+ domByPOI[curPostOrderIndex] = newDomIndex;
+ changed = true;
+ }
}
}
+
+ // Reindex doms by id instead of post order index so we can throw away
+ // the post order arrays.
+ var domById = new Uint32List(N + 1);
+ for (var id = 1; id <= N; id++) {
+ domById[id] = postOrder[domByPOI[postOrderIndex[id]]];
+ }
+
+ domById[root] = 0;
+
+ _doms = domById;
+ }
+
+ void _calculateRetainedSizes() {
+ final N = _N;
koda 2015/05/21 02:05:09 Be consistent in whether you make locals final.
+ final E = _E;
+
+ var size = 0;
+ var positions = _positions;
+ var postOrderOrdinals = _postOrderOrdinals;
+ var doms = _doms;
+ var retainedSizes = new Uint32List(N + 1);
+
+ // Start with retained size as shallow size.
+ var reader = new _ReadStream(_chunks);
+ for (var i = 1; i <= N; i++) {
+ reader.position = positions[i];
+ reader.readUnsigned(); // addr
+ var shallowSize = reader.readUnsigned();
+ retainedSizes[i] = shallowSize;
+ size += shallowSize;
+ }
+
+ // In post order (bottom up), add retained size to dominator's retained
+ // size, skipping root.
+ for (var o = 0; o < (N - 1); o++) {
+ var i = postOrderOrdinals[o];
+ assert(i != 1);
+ retainedSizes[doms[i]] += retainedSizes[i];
+ }
+
+ _retainedSizes = retainedSizes;
+ _size = size;
}
-}
+}
« no previous file with comments | « runtime/observatory/lib/elements.dart ('k') | runtime/observatory/lib/src/app/application.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698